diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cf36b13d..fde94c62 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ android { defaultConfig { applicationId = "com.joeloewi.croissant" - versionCode = 49 + versionCode = 50 versionName = "1.2.2" targetSdk = 34 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 785e5a98..b7aa9b91 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -72,9 +72,6 @@ - diff --git a/app/src/main/kotlin/com/joeloewi/croissant/CroissantApplication.kt b/app/src/main/kotlin/com/joeloewi/croissant/CroissantApplication.kt index 99cce0e1..0285181e 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/CroissantApplication.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/CroissantApplication.kt @@ -1,7 +1,13 @@ package com.joeloewi.croissant import android.app.Application +import com.google.android.material.color.DynamicColors import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class CroissantApplication : Application() \ No newline at end of file +class CroissantApplication : Application() { + override fun onCreate() { + super.onCreate() + DynamicColors.applyToActivitiesIfAvailable(this) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt b/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt index 27477bd6..5cf3b29d 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt @@ -45,6 +45,7 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.util.fastForEach import androidx.compose.ui.window.DialogProperties import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen @@ -62,7 +63,6 @@ import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import androidx.navigation.navDeepLink -import com.google.android.material.color.DynamicColors import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics @@ -108,10 +108,9 @@ class MainActivity : AppCompatActivity() { @SuppressLint("HardwareIds") override fun onCreate(savedInstanceState: Bundle?) { installSplashScreen() - enableEdgeToEdge() super.onCreate(savedInstanceState) - DynamicColors.applyToActivityIfAvailable(this) + enableEdgeToEdge() lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -125,12 +124,14 @@ class MainActivity : AppCompatActivity() { } } - Firebase.analytics.setUserId( - Settings.Secure.getString( - contentResolver, - Settings.Secure.ANDROID_ID + lifecycleScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { + Firebase.analytics.setUserId( + Settings.Secure.getString( + contentResolver, + Settings.Secure.ANDROID_ID + ) ) - ) + } setContent { CroissantTheme { @@ -155,7 +156,6 @@ class MainActivity : AppCompatActivity() { } } -@SuppressLint("RestrictedApi") @OptIn(ExperimentalMaterial3WindowSizeClassApi::class) @Composable fun CroissantApp( @@ -179,7 +179,6 @@ fun CroissantApp( GlobalDestination.EmptyScreen.route ).toImmutableList() } - val currentBackStack by navController.currentBackStack.collectAsStateWithLifecycle() val windowSizeClass = calculateWindowSizeClass(activity = activity) val croissantNavigations = remember { listOf( @@ -206,9 +205,9 @@ fun CroissantApp( Scaffold( contentWindowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal), bottomBar = { - val currentBackStackEntry by navController.currentBackStackEntryFlow.collectAsStateWithLifecycle( - initialValue = null, - ) + val currentBackStackEntry by remember(navController) { + navController.currentBackStackEntryFlow + }.collectAsStateWithLifecycle(initialValue = null) val isBottomNavigationBarVisible by remember { derivedStateOf { !fullScreenDestinations.any { route -> @@ -226,26 +225,7 @@ fun CroissantApp( currentBackStackEntry = { currentBackStackEntry }, onClickNavigationButton = { route -> navController.navigate(route) { - val startDestination = - navController.graph.findStartDestination() - - val popUpToDestination = - if (currentBackStack.any { - it.destination == startDestination - }) { - - startDestination.route - ?: activity::class.java.simpleName - } else if (currentBackStack.any { - it.destination.route == AttendancesDestination.AttendancesScreen.route - }) { - - AttendancesDestination.AttendancesScreen.route - } else { - activity::class.java.simpleName - } - - popUpTo(popUpToDestination) { + popUpTo(navController.graph.findStartDestination().id) { saveState = true } launchSingleTop = true @@ -372,7 +352,7 @@ fun CroissantNavHost( }, onClickAttendance = { navController.value.navigate( - AttendancesDestination.AttendanceDetailScreen().generateRoute(it.id) + AttendancesDestination.AttendanceDetailScreen.generateRoute(it.id) ) } ) @@ -390,7 +370,7 @@ fun CroissantNavHost( }, onNavigateToAttendanceDetailScreen = { navController.value.navigate( - AttendancesDestination.AttendanceDetailScreen().generateRoute(it) + AttendancesDestination.AttendanceDetailScreen.generateRoute(it) ) { popUpTo(AttendancesDestination.CreateAttendanceScreen.route) { inclusive = true @@ -420,32 +400,54 @@ fun CroissantNavHost( } composable( - route = AttendancesDestination.AttendanceDetailScreen().route, - arguments = AttendancesDestination.AttendanceDetailScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - } + route = AttendancesDestination.AttendanceDetailScreen.route, + arguments = AttendancesDestination.AttendanceDetailScreen.arguments.map { argument -> + navArgument(argument.first, argument.second) }, deepLinks = listOf( navDeepLink { uriPattern = - "$deepLinkUri/${AttendancesDestination.AttendanceDetailScreen().route}" + "$deepLinkUri/${AttendancesDestination.AttendanceDetailScreen.route}" } ) - ) { + ) { navBackStackEntry -> + val fromDeeplink = remember(navBackStackEntry.arguments) { + navBackStackEntry.arguments?.getBoolean(AttendancesDestination.AttendanceDetailScreen.FROM_DEEPLINK) + ?: false + } val newCookie by remember { - it.savedStateHandle.getStateFlow(COOKIE, "") + navBackStackEntry.savedStateHandle.getStateFlow(COOKIE, "") }.collectAsStateWithLifecycle() + LaunchedEffect(fromDeeplink) { + if (fromDeeplink) { + with(navController.value.graph) { + findNode(CroissantNavigation.Attendances.route)?.id?.let { + setStartDestination( + it + ) + } + } + } + } + AttendanceDetailScreen( newCookie = { newCookie }, - onNavigateUp = { navController.value.navigateUp() }, + onNavigateUp = { + if (fromDeeplink) { + with(navController.value) { + popBackStack(graph.findStartDestination().id, inclusive = true) + } + } else { + navController.value.navigateUp() + } + }, onClickRefreshSession = { navController.value.navigate(AttendancesDestination.LoginHoYoLabScreen.route) }, onClickLogSummary = { attendanceId, loggableWorker -> navController.value.navigate( - AttendancesDestination.AttendanceLogsCalendarScreen().generateRoute( + AttendancesDestination.AttendanceLogsCalendarScreen.generateRoute( attendanceId, loggableWorker ) @@ -455,18 +457,16 @@ fun CroissantNavHost( } composable( - route = AttendancesDestination.AttendanceLogsCalendarScreen().route, - arguments = AttendancesDestination.AttendanceLogsCalendarScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - } + route = AttendancesDestination.AttendanceLogsCalendarScreen.route, + arguments = AttendancesDestination.AttendanceLogsCalendarScreen.arguments.map { argument -> + navArgument(argument.first, argument.second) } ) { AttendanceLogsCalendarScreen( onNavigateUp = { navController.value.navigateUp() }, onClickDay = { attendanceId, loggableWorker, localDate -> navController.value.navigate( - AttendancesDestination.AttendanceLogsDayScreen().generateRoute( + AttendancesDestination.AttendanceLogsDayScreen.generateRoute( attendanceId = attendanceId, loggableWorker = loggableWorker, localDate = localDate, @@ -477,11 +477,9 @@ fun CroissantNavHost( } composable( - route = AttendancesDestination.AttendanceLogsDayScreen().route, - arguments = AttendancesDestination.AttendanceLogsDayScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - } + route = AttendancesDestination.AttendanceLogsDayScreen.route, + arguments = AttendancesDestination.AttendanceLogsDayScreen.arguments.map { argument -> + navArgument(argument.first, argument.second) } ) { AttendanceLogsDayScreen( @@ -534,6 +532,13 @@ fun CroissantNavHost( } }, onShowDefaultScreen = { + with(navController.value.graph) { + findNode(CroissantNavigation.Attendances.route)?.id?.let { + setStartDestination( + it + ) + } + } navController.value.navigate(AttendancesDestination.AttendancesScreen.route) { popUpTo(activity::class.java.simpleName) { inclusive = true @@ -546,6 +551,13 @@ fun CroissantNavHost( composable(route = GlobalDestination.FirstLaunchScreen.route) { FirstLaunchScreen( onNavigateToAttendances = { + with(navController.value.graph) { + findNode(CroissantNavigation.Attendances.route)?.id?.let { + setStartDestination( + it + ) + } + } navController.value.navigate(AttendancesDestination.AttendancesScreen.route) { popUpTo(activity::class.java.simpleName) { inclusive = true @@ -615,7 +627,7 @@ private fun CroissantBottomNavigationBar( NavigationBar( modifier = modifier ) { - croissantNavigations.forEach { croissantNavigation -> + croissantNavigations.fastForEach { croissantNavigation -> key(croissantNavigation.route) { val isSelected by remember(croissantNavigation.route) { derivedStateOf { currentBackStackEntry()?.destination?.hierarchy?.any { it.route == croissantNavigation.route } == true } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ResinStatusWidgetConfigurationActivity.kt b/app/src/main/kotlin/com/joeloewi/croissant/ResinStatusWidgetConfigurationActivity.kt index 733e957e..1a9011e0 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ResinStatusWidgetConfigurationActivity.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ResinStatusWidgetConfigurationActivity.kt @@ -31,7 +31,6 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.navigation import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument -import com.google.android.material.color.DynamicColors import com.google.firebase.Firebase import com.google.firebase.analytics.FirebaseAnalytics import com.google.firebase.analytics.analytics @@ -61,7 +60,6 @@ class ResinStatusWidgetConfigurationActivity : AppCompatActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() - DynamicColors.applyToActivityIfAvailable(this) lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { @@ -143,10 +141,7 @@ fun ResinStatusWidgetConfigurationApp() { composable( route = ResinStatusWidgetConfigurationDestination.LoadingScreen().route, arguments = ResinStatusWidgetConfigurationDestination.LoadingScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - defaultValue = AppWidgetManager.INVALID_APPWIDGET_ID - } + navArgument(argument.first, argument.second) } ) { val loadingViewModel: LoadingViewModel = hiltViewModel() @@ -160,9 +155,7 @@ fun ResinStatusWidgetConfigurationApp() { composable( route = ResinStatusWidgetConfigurationDestination.CreateResinStatusWidgetScreen().route, arguments = ResinStatusWidgetConfigurationDestination.CreateResinStatusWidgetScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - } + navArgument(argument.first, argument.second) }, ) { val newCookie by remember { @@ -196,9 +189,7 @@ fun ResinStatusWidgetConfigurationApp() { composable( route = ResinStatusWidgetConfigurationDestination.ResinStatusWidgetDetailScreen().route, arguments = ResinStatusWidgetConfigurationDestination.ResinStatusWidgetDetailScreen().arguments.map { argument -> - navArgument(argument.first) { - type = argument.second - } + navArgument(argument.first, argument.second) } ) { ResinStatusWidgetDetailScreen() diff --git a/app/src/main/kotlin/com/joeloewi/croissant/di/EntryPoints.kt b/app/src/main/kotlin/com/joeloewi/croissant/di/EntryPoints.kt index 4fc6b087..46b525f0 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/di/EntryPoints.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/di/EntryPoints.kt @@ -16,7 +16,6 @@ package com.joeloewi.croissant.di -import android.app.Application import android.content.Context import androidx.hilt.work.HiltWorkerFactory import androidx.work.RunnableScheduler @@ -35,7 +34,6 @@ import kotlin.reflect.KClass interface InitializerEntryPoint { fun imageLoader(): ImageLoader fun hiltWorkerFactory(): HiltWorkerFactory - fun application(): Application @DefaultDispatcherExecutor fun executor(): Executor diff --git a/app/src/main/kotlin/com/joeloewi/croissant/di/UtilModule.kt b/app/src/main/kotlin/com/joeloewi/croissant/di/UtilModule.kt index 9e4fabef..7b57d574 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/di/UtilModule.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/di/UtilModule.kt @@ -51,6 +51,7 @@ object UtilModule { @ApplicationContext context: Context ): TextToSpeechFactory = TextToSpeechFactory(context) + @Singleton @Provides fun provideNotificationGenerator( @ApplicationContext context: Context diff --git a/app/src/main/kotlin/com/joeloewi/croissant/initializer/DynamicColorInitializer.kt b/app/src/main/kotlin/com/joeloewi/croissant/initializer/DynamicColorInitializer.kt deleted file mode 100644 index 63eb8e84..00000000 --- a/app/src/main/kotlin/com/joeloewi/croissant/initializer/DynamicColorInitializer.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.joeloewi.croissant.initializer - -import android.content.Context -import androidx.startup.Initializer -import com.google.android.material.color.DynamicColors -import com.joeloewi.croissant.di.InitializerEntryPoint -import com.joeloewi.croissant.di.entryPoints - -class DynamicColorInitializer : Initializer { - - override fun create(context: Context) { - val initializerEntryPoint: InitializerEntryPoint by context.entryPoints() - val application = initializerEntryPoint.application() - - DynamicColors.applyToActivitiesIfAvailable(application) - } - - override fun dependencies(): MutableList>> = mutableListOf() -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt b/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt index f8e27d71..490bdfa6 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt @@ -31,13 +31,13 @@ import javax.inject.Inject @AndroidEntryPoint class AlarmReceiver : BroadcastReceiver() { + private val _processLifecycleScope = ProcessLifecycleOwner.get().lifecycleScope private val _coroutineContext = Dispatchers.IO + CoroutineExceptionHandler { _, throwable -> Firebase.crashlytics.apply { log(this@AlarmReceiver.javaClass.simpleName) recordException(throwable) } } - private val _processLifecycleScope by lazy { ProcessLifecycleOwner.get().lifecycleScope } @Inject lateinit var application: Application @@ -58,9 +58,9 @@ class AlarmReceiver : BroadcastReceiver() { lateinit var alarmScheduler: AlarmScheduler override fun onReceive(p0: Context, p1: Intent) { - when (p1.action) { - Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED, AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED -> { - _processLifecycleScope.launch(_coroutineContext) { + _processLifecycleScope.launch(_coroutineContext) { + when (p1.action) { + Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED, AlarmManager.ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED -> { getAllOneShotAttendanceUseCase().map { attendance -> async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { attendance.runCatching { @@ -72,12 +72,9 @@ class AlarmReceiver : BroadcastReceiver() { } }.awaitAll() } - } - - RECEIVE_ATTEND_CHECK_IN_ALARM -> { - val attendanceId = p1.getLongExtra(ATTENDANCE_ID, Long.MIN_VALUE) - _processLifecycleScope.launch(_coroutineContext) { + RECEIVE_ATTEND_CHECK_IN_ALARM -> { + val attendanceId = p1.getLongExtra(ATTENDANCE_ID, Long.MIN_VALUE) val attendanceWithGames = getOneAttendanceUseCase(attendanceId) val attendance = attendanceWithGames.attendance val oneTimeWork = OneTimeWorkRequestBuilder() @@ -101,10 +98,10 @@ class AlarmReceiver : BroadcastReceiver() { scheduleForTomorrow = true ) } - } - else -> { + else -> { + } } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt b/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt index a1377c32..c3b1605a 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt @@ -22,13 +22,13 @@ import javax.inject.Inject @AndroidEntryPoint class MigrationHelper : BroadcastReceiver() { + private val _processLifecycleScope = ProcessLifecycleOwner.get().lifecycleScope private val _coroutineContext = Dispatchers.IO + CoroutineExceptionHandler { _, throwable -> Firebase.crashlytics.apply { log(this@MigrationHelper.javaClass.simpleName) recordException(throwable) } } - private val _processLifecycleScope by lazy { ProcessLifecycleOwner.get().lifecycleScope } @Inject lateinit var application: Application @@ -46,9 +46,9 @@ class MigrationHelper : BroadcastReceiver() { lateinit var workManager: WorkManager override fun onReceive(p0: Context, p1: Intent) { - when (p1.action) { - Intent.ACTION_MY_PACKAGE_REPLACED -> { - _processLifecycleScope.launch(_coroutineContext) { + _processLifecycleScope.launch(_coroutineContext) { + when (p1.action) { + Intent.ACTION_MY_PACKAGE_REPLACED -> { //because work manager's job can be deferred, cancel check in event worker //instead of work manager, use alarm manager getAllOneShotAttendanceUseCase().map { attendance -> @@ -57,10 +57,8 @@ class MigrationHelper : BroadcastReceiver() { } }.awaitAll() } - } - - else -> { + else -> {} } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt b/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt index 825fe9eb..d55b519f 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt @@ -30,7 +30,7 @@ import javax.inject.Inject @AndroidEntryPoint class ResinStatusWidgetProvider : AppWidgetProvider() { - private val _processLifecycleOwner by lazy { ProcessLifecycleOwner.get() } + private val _processLifecycleScope = ProcessLifecycleOwner.get().lifecycleScope private val _coroutineContext = Dispatchers.IO + CoroutineExceptionHandler { _, throwable -> Firebase.crashlytics.apply { log(this@ResinStatusWidgetProvider.javaClass.simpleName) @@ -55,10 +55,8 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { appWidgetManager: AppWidgetManager, appWidgetIds: IntArray ) { - super.onUpdate(context, appWidgetManager, appWidgetIds) //this method also called when user put widget on home screen - - _processLifecycleOwner.lifecycleScope.launch(_coroutineContext) { + _processLifecycleScope.launch(_coroutineContext) { appWidgetIds.map { appWidgetId -> async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { if (powerManager.isPowerSaveMode && !powerManager.isIgnoringBatteryOptimizationsCompat( @@ -99,12 +97,12 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { } }.awaitAll() } + + super.onUpdate(context, appWidgetManager, appWidgetIds) } override fun onDeleted(context: Context, appWidgetIds: IntArray) { - super.onDeleted(context, appWidgetIds) - - _processLifecycleOwner.lifecycleScope.launch(_coroutineContext) { + _processLifecycleScope.launch(_coroutineContext) { appWidgetIds.run { map { appWidgetId -> async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { @@ -119,5 +117,7 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { deleteByAppWidgetIdResinStatusWidgetUseCase(*this) } } + + super.onDeleted(context, appWidgetIds) } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt b/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt index 0775a157..9a008ba6 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt @@ -18,21 +18,21 @@ import javax.inject.Inject @AndroidEntryPoint class TimeZoneChangedReceiver @Inject constructor( ) : BroadcastReceiver() { + private val _processLifecycleScope = ProcessLifecycleOwner.get().lifecycleScope private val _coroutineContext = Dispatchers.IO + CoroutineExceptionHandler { _, throwable -> Firebase.crashlytics.apply { log(this@TimeZoneChangedReceiver.javaClass.simpleName) recordException(throwable) } } - private val _processLifecycleScope by lazy { ProcessLifecycleOwner.get().lifecycleScope } @Inject lateinit var notificationGenerator: NotificationGenerator override fun onReceive(context: Context, intent: Intent) { - when (intent.action) { - Intent.ACTION_TIMEZONE_CHANGED -> { - _processLifecycleScope.launch(_coroutineContext) { + _processLifecycleScope.launch(_coroutineContext) { + when (intent.action) { + Intent.ACTION_TIMEZONE_CHANGED -> { with(notificationGenerator) { safeNotify( UUID.randomUUID().toString(), @@ -41,10 +41,10 @@ class TimeZoneChangedReceiver @Inject constructor( ) } } - } - else -> { + else -> { + } } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/CroissantNavigation.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/CroissantNavigation.kt index d05f57f5..7478f8d9 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/CroissantNavigation.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/CroissantNavigation.kt @@ -8,9 +8,11 @@ import androidx.compose.material.icons.filled.TaskAlt import androidx.compose.material.icons.outlined.Redeem import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.TaskAlt +import androidx.compose.runtime.Immutable import androidx.compose.ui.graphics.vector.ImageVector import com.joeloewi.croissant.R +@Immutable sealed class CroissantNavigation( val route: String, val filledIcon: ImageVector, diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/AttendancesDestination.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/AttendancesDestination.kt index 35370b2b..8c490ee5 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/AttendancesDestination.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/AttendancesDestination.kt @@ -1,12 +1,18 @@ package com.joeloewi.croissant.ui.navigation.main.attendances +import android.content.Context +import android.net.Uri +import androidx.compose.runtime.Immutable +import androidx.navigation.NavArgumentBuilder import androidx.navigation.NavType +import com.joeloewi.croissant.R import com.joeloewi.croissant.domain.common.LoggableWorker +@Immutable sealed class AttendancesDestination { - abstract val arguments: List>> + abstract val arguments: List Unit>> protected abstract val plainRoute: String - val route: String + open val route: String get() = "${plainRoute}${ arguments.map { it.first }.joinToString( separator = "/", @@ -18,44 +24,70 @@ sealed class AttendancesDestination { ) { "{$it}" } }" - object AttendancesScreen : AttendancesDestination() { - override val arguments: List>> = listOf() + data object AttendancesScreen : AttendancesDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute = "attendancesScreen" } - object CreateAttendanceScreen : AttendancesDestination() { - override val arguments: List>> = listOf() + data object CreateAttendanceScreen : AttendancesDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute = "createAttendanceScreen" } - object LoginHoYoLabScreen : AttendancesDestination() { - override val arguments: List>> = listOf() + data object LoginHoYoLabScreen : AttendancesDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute = "loginHoYoLabScreen" } - class AttendanceDetailScreen : AttendancesDestination() { - companion object { - const val ATTENDANCE_ID = "attendanceId" - } + data object AttendanceDetailScreen : AttendancesDestination() { + const val ATTENDANCE_ID = "attendanceId" + const val FROM_DEEPLINK = "fromDeeplink" - override val arguments: List>> = listOf( - ATTENDANCE_ID to NavType.LongType + override val arguments: List Unit)>> = listOf( + ATTENDANCE_ID to { + type = NavType.LongType + + }, + FROM_DEEPLINK to { + type = NavType.BoolType + defaultValue = false + } ) override val plainRoute: String = "attendanceDetailScreen" - fun generateRoute(attendanceId: Long) = "${plainRoute}/${attendanceId}" - } + override val route: String + get() = "${plainRoute}/{${ATTENDANCE_ID}}?${FROM_DEEPLINK}={${FROM_DEEPLINK}}" - class AttendanceLogsCalendarScreen : AttendancesDestination() { - companion object { - const val ATTENDANCE_ID = "attendanceId" - const val LOGGABLE_WORKER = "loggableWorker" - } + fun generateRoute( + attendanceId: Long, + fromDeeplink: Boolean = false + ) = "${plainRoute}/$attendanceId?${FROM_DEEPLINK}={$fromDeeplink}" + + fun generateDeepLinkUri( + context: Context, + attendanceId: Long, + fromDeeplink: Boolean = true + ): Uri = Uri.Builder() + .scheme(context.getString(R.string.deep_link_scheme)) + .authority(context.packageName) + .appendEncodedPath(plainRoute) + .appendPath("$attendanceId") + .appendQueryParameter(FROM_DEEPLINK, "$fromDeeplink") + .build() + } - override val arguments: List>> = listOf( - ATTENDANCE_ID to NavType.LongType, - LOGGABLE_WORKER to NavType.EnumType(LoggableWorker::class.java) + data object AttendanceLogsCalendarScreen : AttendancesDestination() { + const val ATTENDANCE_ID = "attendanceId" + const val LOGGABLE_WORKER = "loggableWorker" + + override val arguments: List Unit>> = listOf( + ATTENDANCE_ID to { + type = NavType.LongType + }, + LOGGABLE_WORKER to { + type = NavType.EnumType(LoggableWorker::class.java) + } ) override val plainRoute: String = "attendanceLogsCalendarScreen" @@ -64,17 +96,21 @@ sealed class AttendancesDestination { "${plainRoute}/${attendanceId}/${loggableWorker}" } - class AttendanceLogsDayScreen : AttendancesDestination() { - companion object { - const val ATTENDANCE_ID = "attendanceId" - const val LOGGABLE_WORKER = "loggableWorker" - const val LOCAL_DATE = "localDate" - } - - override val arguments: List>> = listOf( - ATTENDANCE_ID to NavType.LongType, - LOGGABLE_WORKER to NavType.EnumType(LoggableWorker::class.java), - LOCAL_DATE to NavType.StringType + data object AttendanceLogsDayScreen : AttendancesDestination() { + const val ATTENDANCE_ID = "attendanceId" + const val LOGGABLE_WORKER = "loggableWorker" + const val LOCAL_DATE = "localDate" + + override val arguments: List Unit>> = listOf( + ATTENDANCE_ID to { + type = NavType.LongType + }, + LOGGABLE_WORKER to { + type = NavType.EnumType(LoggableWorker::class.java) + }, + LOCAL_DATE to { + type = NavType.StringType + } ) override val plainRoute: String = "attendanceLogsDayScreen" diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceDetailScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceDetailScreen.kt index 81d97a9e..ff417eb0 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceDetailScreen.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceDetailScreen.kt @@ -1,5 +1,6 @@ package com.joeloewi.croissant.ui.navigation.main.attendances.screen +import androidx.activity.compose.BackHandler import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -170,6 +171,10 @@ private fun AttendanceDetailContent( ) var showConfirmDeleteDialog by remember { mutableStateOf(false) } + BackHandler { + onNavigateUp() + } + LaunchedEffect(snackbarHostState) { withContext(Dispatchers.IO) { snapshotFlow(newCookie).catch { }.collect { diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/GlobalDestination.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/GlobalDestination.kt index 3bdab030..25d1658e 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/GlobalDestination.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/global/GlobalDestination.kt @@ -1,9 +1,11 @@ package com.joeloewi.croissant.ui.navigation.main.global -import androidx.navigation.NavType +import androidx.compose.runtime.Immutable +import androidx.navigation.NavArgumentBuilder +@Immutable sealed class GlobalDestination { - abstract val arguments: List>> + abstract val arguments: List Unit>> abstract val plainRoute: String open val route: String get() = "${plainRoute}${ @@ -18,12 +20,12 @@ sealed class GlobalDestination { }" data object EmptyScreen : GlobalDestination() { - override val arguments: List>> = emptyList() + override val arguments: List Unit>> = listOf() override val plainRoute: String = "EmptyScreen" } data object FirstLaunchScreen : GlobalDestination() { - override val arguments: List>> = listOf() + override val arguments: List Unit>> = listOf() override val plainRoute = "firstLaunchScreen" } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/redemptioncodes/RedemptionCodesDestination.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/redemptioncodes/RedemptionCodesDestination.kt index 771689fc..5432f8b1 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/redemptioncodes/RedemptionCodesDestination.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/redemptioncodes/RedemptionCodesDestination.kt @@ -1,9 +1,11 @@ package com.joeloewi.croissant.ui.navigation.main.redemptioncodes -import androidx.navigation.NavType +import androidx.compose.runtime.Immutable +import androidx.navigation.NavArgumentBuilder +@Immutable sealed class RedemptionCodesDestination { - abstract val arguments: List>> + abstract val arguments: List Unit>> protected abstract val plainRoute: String val route: String by lazy { "${plainRoute}${ @@ -18,8 +20,8 @@ sealed class RedemptionCodesDestination { }" } - object RedemptionCodesScreen : RedemptionCodesDestination() { - override val arguments: List>> = listOf() + data object RedemptionCodesScreen : RedemptionCodesDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute: String = "redemptionCodesScreen" } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/SettingsDestination.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/SettingsDestination.kt index 8d105aab..fc8b2228 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/SettingsDestination.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/SettingsDestination.kt @@ -1,9 +1,11 @@ package com.joeloewi.croissant.ui.navigation.main.settings -import androidx.navigation.NavType +import androidx.compose.runtime.Immutable +import androidx.navigation.NavArgumentBuilder +@Immutable sealed class SettingsDestination { - abstract val arguments: List>> + abstract val arguments: List Unit>> protected abstract val plainRoute: String val route: String by lazy { "${plainRoute}${ @@ -18,13 +20,13 @@ sealed class SettingsDestination { }" } - object SettingsScreen : SettingsDestination() { - override val arguments: List>> = listOf() + data object SettingsScreen : SettingsDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute: String = "settingsScreen" } - object DeveloperInfoScreen : SettingsDestination() { - override val arguments: List>> = listOf() + data object DeveloperInfoScreen : SettingsDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute: String = "developerInfoScreen" } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/ResinStatusWidgetConfigurationNavigation.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/ResinStatusWidgetConfigurationNavigation.kt index f0258c98..32b91d78 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/ResinStatusWidgetConfigurationNavigation.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/ResinStatusWidgetConfigurationNavigation.kt @@ -3,7 +3,7 @@ package com.joeloewi.croissant.ui.navigation.widgetconfiguration.resinstatus sealed class ResinStatusWidgetConfigurationNavigation( val route: String = "" ) { - object Configuration : ResinStatusWidgetConfigurationNavigation( + data object Configuration : ResinStatusWidgetConfigurationNavigation( route = "configuration" ) } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/resinstatuswidgetconfiguration/ResinStatusWidgetConfigurationDestination.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/resinstatuswidgetconfiguration/ResinStatusWidgetConfigurationDestination.kt index 2f2d2e7a..663aaae0 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/resinstatuswidgetconfiguration/ResinStatusWidgetConfigurationDestination.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/widgetconfiguration/resinstatus/resinstatuswidgetconfiguration/ResinStatusWidgetConfigurationDestination.kt @@ -1,9 +1,13 @@ package com.joeloewi.croissant.ui.navigation.widgetconfiguration.resinstatus.resinstatuswidgetconfiguration +import android.appwidget.AppWidgetManager +import androidx.compose.runtime.Immutable +import androidx.navigation.NavArgumentBuilder import androidx.navigation.NavType +@Immutable sealed class ResinStatusWidgetConfigurationDestination { - abstract val arguments: List>> + abstract val arguments: List Unit>> protected abstract val plainRoute: String val route: String by lazy { "${plainRoute}${ @@ -18,14 +22,17 @@ sealed class ResinStatusWidgetConfigurationDestination { }" } - object EmptyScreen : ResinStatusWidgetConfigurationDestination() { - override val arguments: List>> = listOf() + data object EmptyScreen : ResinStatusWidgetConfigurationDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute: String = "emptyScreen" } class LoadingScreen( - override val arguments: List>> = listOf( - APP_WIDGET_ID to NavType.IntType + override val arguments: List Unit>> = listOf( + APP_WIDGET_ID to { + type = NavType.IntType + defaultValue = AppWidgetManager.INVALID_APPWIDGET_ID + } ), override val plainRoute: String = "loadingScreen" ) : ResinStatusWidgetConfigurationDestination() { @@ -37,8 +44,8 @@ sealed class ResinStatusWidgetConfigurationDestination { } class CreateResinStatusWidgetScreen( - override val arguments: List>> = listOf( - APP_WIDGET_ID to NavType.IntType + override val arguments: List Unit>> = listOf( + APP_WIDGET_ID to { type = NavType.IntType } ), override val plainRoute: String = "createResinStatusWidgetScreen" ) : ResinStatusWidgetConfigurationDestination() { @@ -50,8 +57,8 @@ sealed class ResinStatusWidgetConfigurationDestination { } class ResinStatusWidgetDetailScreen( - override val arguments: List>> = listOf( - APP_WIDGET_ID to NavType.IntType + override val arguments: List Unit>> = listOf( + APP_WIDGET_ID to { type = NavType.IntType } ), override val plainRoute: String = "resinStatusWidgetDetailScreen" ) : ResinStatusWidgetConfigurationDestination() { @@ -62,8 +69,8 @@ sealed class ResinStatusWidgetConfigurationDestination { fun generateRoute(appWidgetId: Int) = "${plainRoute}/${appWidgetId}" } - object LoginHoYoLABScreen : ResinStatusWidgetConfigurationDestination() { - override val arguments: List>> = listOf() + data object LoginHoYoLABScreen : ResinStatusWidgetConfigurationDestination() { + override val arguments: List Unit>> = listOf() override val plainRoute: String = "loginHoYoLABScreen" } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt index 60b54cc3..6ed573c7 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt @@ -179,14 +179,9 @@ class NotificationGenerator( addNextIntentWithParentStack( Intent( Intent.ACTION_VIEW, - Uri.Builder() - .scheme(context.getString(R.string.deep_link_scheme)) - .authority(context.packageName) - .appendEncodedPath( - AttendancesDestination.AttendanceDetailScreen() - .generateRoute(attendanceId) - ) - .build() + AttendancesDestination.AttendanceDetailScreen.generateDeepLinkUri( + context, attendanceId + ) ) ) getPendingIntent(0, pendingIntentFlagUpdateCurrent) @@ -238,14 +233,9 @@ class NotificationGenerator( addNextIntentWithParentStack( Intent( Intent.ACTION_VIEW, - Uri.Builder() - .scheme(context.getString(R.string.deep_link_scheme)) - .authority(context.packageName) - .appendEncodedPath( - AttendancesDestination.AttendanceDetailScreen() - .generateRoute(attendanceId) - ) - .build() + AttendancesDestination.AttendanceDetailScreen.generateDeepLinkUri( + context, attendanceId + ) ) ) getPendingIntent(0, pendingIntentFlagUpdateCurrent)