Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.imashnake.animite.anime

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.imashnake.animite.api.anilist.AnilistMediaRepository
Expand All @@ -19,6 +18,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
Expand All @@ -29,10 +29,9 @@ import javax.inject.Inject
@OptIn(ExperimentalCoroutinesApi::class)
class AnimeViewModel @Inject constructor(
private val mediaListRepository: AnilistMediaRepository,
savedStateHandle: SavedStateHandle,
) : ViewModel() {

private val now = savedStateHandle.getStateFlow(NOW, LocalDate.now())
private val now = flowOf(LocalDate.now())
private val refreshTrigger = MutableSharedFlow<Unit>()

var useNetwork = false
Expand Down Expand Up @@ -123,8 +122,4 @@ class AnimeViewModel @Inject constructor(
useNetwork = false
setIsRefreshing(false)
}

companion object {
const val NOW = "now"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.imashnake.animite.anime.navigation.di

import androidx.compose.animation.SharedTransitionScope
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.ui.LocalNavAnimatedContentScope
import com.imashnake.animite.anime.AnimeScreen
import com.imashnake.animite.navigation.AnimeRoute
import com.imashnake.animite.navigation.Navigator
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.multibindings.IntoSet

@Module
@InstallIn(ActivityRetainedComponent::class)
object NavigationModule {

Check warning

Code scanning / detekt

Public classes, interfaces and objects require documentation. Warning

NavigationModule is missing required documentation.

@Provides
@IntoSet
fun provideNavEntry(

Check warning

Code scanning / detekt

Public functions require documentation. Warning

The function provideNavEntry is missing documentation.
navigator: Navigator,
): EntryProviderScope<NavKey>.(SharedTransitionScope) -> Unit = { sharedScope ->
entry<AnimeRoute> {
AnimeScreen(
onNavigateToMediaItem = navigator::navigate,
sharedTransitionScope = sharedScope,
animatedVisibilityScope = LocalNavAnimatedContentScope.current,
)
}
}
}
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.lifecycleRuntimeKtx)
implementation(libs.androidx.navigationCompose)

// Compose
implementation(libs.bundles.compose)
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/kotlin/com/imashnake/animite/ApplicationModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.imashnake.animite

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@Module
@InstallIn(SingletonComponent::class)
object ApplicationModule {

Check warning

Code scanning / detekt

Public classes, interfaces and objects require documentation. Warning

ApplicationModule is missing required documentation.

@Provides
fun provideVersionName() = BuildConfig.VERSION_NAME

Check warning

Code scanning / detekt

Public functions require documentation. Warning

The function provideVersionName is missing documentation.
}
179 changes: 72 additions & 107 deletions app/src/main/kotlin/com/imashnake/animite/features/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
package com.imashnake.animite.features

import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.RoundedCorner
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.SizeTransform

Check warning

Code scanning / detekt

Unused Imports are dead code and should be removed. Warning

The import 'androidx.compose.animation.SizeTransform' is unused.
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutHorizontally
Expand All @@ -27,49 +26,50 @@
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.navigation.NavDestination.Companion.hasRoute
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navDeepLink
import com.imashnake.animite.BuildConfig
import com.imashnake.animite.anime.AnimeScreen
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.EntryProviderScope
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.imashnake.animite.api.anilist.sanitize.media.MediaList
import com.imashnake.animite.core.ui.LocalPaddings
import com.imashnake.animite.features.searchbar.SearchFrontDrop
import com.imashnake.animite.features.theme.AnimiteTheme
import com.imashnake.animite.features.theme.manropeFontFamily
import com.imashnake.animite.manga.MangaScreen
import com.imashnake.animite.media.MediaPage
import com.imashnake.animite.navigation.AnimeRoute
import com.imashnake.animite.navigation.MangaRoute
import com.imashnake.animite.navigation.NavigationBar
import com.imashnake.animite.navigation.NavigationBarPaths
import com.imashnake.animite.navigation.NavigationRail
import com.imashnake.animite.navigation.ProfileRoute
import com.imashnake.animite.navigation.SocialRoute
import com.imashnake.animite.profile.ProfileScreen
import com.imashnake.animite.profile.ProfileViewModel
import com.imashnake.animite.profile.dev.internal.ANILIST_AUTH_DEEPLINK
import com.imashnake.animite.navigation.Navigator
import com.imashnake.animite.profile.AvatarViewModel
import com.imashnake.animite.settings.SettingsPage
import com.imashnake.animite.settings.SettingsViewModel
import com.imashnake.animite.settings.Theme
import com.imashnake.animite.social.SocialScreen
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filterNotNull
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

@Inject
internal lateinit var entryProviders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.(SharedTransitionScope) -> Unit>

Check warning

Code scanning / detekt

Line detected, which is longer than the defined maximum line length in the code style. Warning

Line detected, which is longer than the defined maximum line length in the code style.

@Inject
internal lateinit var navigator: Navigator

private val settingsViewModel: SettingsViewModel by viewModels()
private val profileViewModel: ProfileViewModel by viewModels()
private val avatarViewModel: AvatarViewModel by viewModels()
Comment thread
boswelja marked this conversation as resolved.

var showSplashScreen = true

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -88,24 +88,24 @@

val useSystemColorScheme by settingsViewModel.useSystemColorScheme.collectAsState(initial = false)

val useDarkTheme = when(Theme.valueOf(theme)) {
val useDarkTheme = when (Theme.valueOf(theme)) {
Theme.DARK -> true
Theme.LIGHT -> false
Theme.DEVICE_THEME -> isSystemInDarkTheme()
}

val dayHour by settingsViewModel.dayHour.collectAsState(initial = null)

val avatar by profileViewModel.viewerAvatar.collectAsState(initial = null)
val avatar by avatarViewModel.viewerAvatar.collectAsState(initial = null)

AnimiteTheme(
useDarkTheme = useDarkTheme,
useSystemColorScheme = useSystemColorScheme,
dayHour = dayHour
) {
MainScreen(
deviceScreenCornerRadius = getDeviceScreenCornerRadius(),
useDarkTheme = useDarkTheme,
navigator = navigator,
navEntries = entryProviders,
avatar = avatar,
modifier = Modifier
.fillMaxSize()
Expand All @@ -114,37 +114,20 @@
}
}
}

private fun getDeviceScreenCornerRadius(): Int {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
return windowManager
.currentWindowMetrics
.windowInsets
.getRoundedCorner(RoundedCorner.POSITION_TOP_RIGHT)
?.radius
?: 0
}
return 0
}
}

@OptIn(ExperimentalLayoutApi::class)
@Composable
fun MainScreen(
deviceScreenCornerRadius: Int,
useDarkTheme: Boolean,
navigator: Navigator,
navEntries: Set<EntryProviderScope<NavKey>.(SharedTransitionScope) -> Unit>,
avatar: String?,
modifier: Modifier = Modifier,
) {
val navController = rememberNavController()

val currentBackStackEntry by navController.currentBackStackEntryAsState()
val isNavBarVisible = rememberSaveable(currentBackStackEntry) {
if (currentBackStackEntry != null) {
NavigationBarPaths.entries.any {
it.matchesDestination(currentBackStackEntry!!)
}
} else false
val isNavBarVisible by remember(navigator.backStack.size) {
derivedStateOf {
NavigationBarPaths.entries.any { it.route == navigator.backStack.lastOrNull() }
}
}

// TODO: Refactor to use Scaffold once AnimatedVisibility issues are fixed;
Expand All @@ -154,89 +137,71 @@
LocalContentColor provides MaterialTheme.colorScheme.onBackground
) {
SharedTransitionLayout {
NavHost(navController = navController, startDestination = AnimeRoute) {
composable<AnimeRoute> {
AnimeScreen(
onNavigateToMediaItem = navController::navigate,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this,
)
}
composable<MangaRoute> {
MangaScreen(
onNavigateToMediaItem = navController::navigate,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this,
)
}
composable<MediaPage> {
MediaPage(
onBack = navController::navigateUp,
onNavigateToMediaItem = navController::navigate,
useDarkTheme = useDarkTheme,
deviceScreenCornerRadius = deviceScreenCornerRadius,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this,
)
}
composable<ProfileRoute>(
deepLinks = listOf(
navDeepLink {
uriPattern = ANILIST_AUTH_DEEPLINK
action = Intent.ACTION_VIEW
}
)
) {
ProfileScreen(
onNavigateToMediaItem = navController::navigate,
onNavigateToSettings = navController::navigate,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this,
)
}
composable<SettingsPage> {
SettingsPage(versionName = BuildConfig.VERSION_NAME)
}
composable<SocialRoute> {
SocialScreen()
}
}
val backstack = navigator.backStack
if (backstack.isEmpty()) return@SharedTransitionLayout

NavDisplay(
onBack = navigator::onBackPressed,
entryDecorators = listOf(
rememberSaveableStateHolderNavEntryDecorator(),
rememberViewModelStoreNavEntryDecorator()
),
backStack = backstack,
entryProvider = entryProvider {
Comment thread
Iannnr marked this conversation as resolved.
navEntries.forEach { builder ->
builder(this@SharedTransitionLayout)
}
},
sharedTransitionScope = this,
)
}
}

when(LocalConfiguration.current.orientation) {
when (LocalConfiguration.current.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
AnimatedVisibility(
visible = isNavBarVisible,
modifier = Modifier.align(Alignment.CenterStart),
enter = slideInHorizontally { -it },
exit = slideOutHorizontally { -it }
) { NavigationRail(navController, avatar) }
) {
NavigationRail(
backStack = navigator.backStack,
avatar = avatar,
onNavigate = navigator::navigate,
)
}
}

else -> {
AnimatedVisibility(
visible = isNavBarVisible,
modifier = Modifier.align(Alignment.BottomCenter),
enter = slideInVertically { it },
exit = slideOutVertically { it }
) { NavigationBar(navController, avatar) }
) {
NavigationBar(
backStack = navigator.backStack,
avatar = avatar,
onNavigate = navigator::navigate,
)
}
}
}

SearchFrontDrop(
hasExtraPadding = isNavBarVisible &&
LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT,
onItemClick = { id, mediaType, title ->
navController.navigate(
MediaPage(
id = id,
source = MediaList.Type.SEARCH.name,
mediaType = mediaType.rawValue,
title = title,
)
val route = MediaPage(
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decided that we should rename these to _Route classes, maybe another day

id = id,
source = MediaList.Type.SEARCH.name,
mediaType = mediaType.rawValue,
title = title,
)
navigator.navigate(route)
},
isFabVisible = currentBackStackEntry?.destination?.hasRoute<SettingsPage>() == false,
isFabVisible = !navigator.backStack.contains(SettingsPage),
Comment thread
Iannnr marked this conversation as resolved.
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(
Expand Down
Loading
Loading