diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a134a827..17537b68 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,8 +31,11 @@ jobs: distribution: 'zulu' # See 'Supported distributions' for available options java-version: '17' - - name: Setup Android SDK - uses: android-actions/setup-android@v3 + - name: Install GMD image for baseline profile generation + run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;aosp_atd;x86_64" + + - name: Accept Android licenses + run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -47,16 +50,13 @@ jobs: fileDir: './' encodedString: ${{ secrets.SIGNING_KEY }} - - name: Build release variant including baseline profile generation - run: ./gradlew :app:assembleRelease - -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - -Pandroid.experimental.androidTest.numManagedDeviceShards=1 - -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 - - - name: Build Release AAB - run: ./gradlew bundleRelease + - name: Generate baseline profile + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + target: aosp_atd + arch: x86_64 + script: ./gradlew :app:bundleRelease - name: Upload Android Release to Play Store uses: r0adkll/upload-google-play@v1 diff --git a/.github/workflows/on_pull_request.yml b/.github/workflows/on_pull_request.yml index 7d682dc9..0a4a9d0e 100644 --- a/.github/workflows/on_pull_request.yml +++ b/.github/workflows/on_pull_request.yml @@ -31,8 +31,11 @@ jobs: distribution: 'zulu' # See 'Supported distributions' for available options java-version: '17' - - name: Setup Android SDK - uses: android-actions/setup-android@v3 + - name: Install GMD image for baseline profile generation + run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager "system-images;android-34;aosp_atd;x86_64" + + - name: Accept Android licenses + run: yes | "$ANDROID_HOME"/cmdline-tools/latest/bin/sdkmanager --licenses || true - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -47,10 +50,10 @@ jobs: fileDir: './' encodedString: ${{ secrets.SIGNING_KEY }} - - name: Build release variant including baseline profile generation - run: ./gradlew :app:assembleRelease - -Pandroid.testInstrumentationRunnerArguments.androidx.benchmark.enabledRules=BaselineProfile - -Pandroid.testoptions.manageddevices.emulator.gpu="swiftshader_indirect" - -Pandroid.experimental.testOptions.managedDevices.emulator.showKernelLogging=true - -Pandroid.experimental.androidTest.numManagedDeviceShards=1 - -Pandroid.experimental.testOptions.managedDevices.maxConcurrentDevices=1 \ No newline at end of file + - name: Generate baseline profile + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: 34 + target: aosp_atd + arch: x86_64 + script: ./gradlew :app:bundleRelease \ No newline at end of file diff --git a/.gitignore b/.gitignore index 99917ad8..56581edf 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,7 @@ obj/ .idea/navEditor.xml .idea/inspectionProfiles .idea/deploymentTargetDropDown.xml +.idea/appInsightsSettings.xml # Legacy Eclipse project files .classpath diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 813db3aa..dd892c4c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,7 +20,7 @@ android { defaultConfig { applicationId = "com.joeloewi.croissant" - versionCode = 43 + versionCode = 44 versionName = "1.2.1" targetSdk = 34 @@ -63,9 +63,7 @@ android { } baselineProfile { - // Don't build on every iteration of a full assemble. - // Instead enable generation directly for the release build variant. - automaticGenerationDuringBuild = false + automaticGenerationDuringBuild = true } dependencies { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 66724a24..e9523993 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,7 +10,6 @@ - diff --git a/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt b/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt index 5a063791..5e6229b3 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/MainActivity.kt @@ -143,7 +143,7 @@ class MainActivity : AppCompatActivity() { LocalHourFormat provides hourFormat ) { RequireAppUpdate( - appUpdateResultState = appUpdateResultState, + appUpdateResultState = { appUpdateResultState }, ) { CroissantApp( isDeviceRooted = isDeviceRooted @@ -388,6 +388,15 @@ fun CroissantNavHost( onLoginHoYoLAB = { navController.value.navigate(AttendancesDestination.LoginHoYoLabScreen.route) }, + onNavigateToAttendanceDetailScreen = { + navController.value.navigate( + AttendancesDestination.AttendanceDetailScreen().generateRoute(it) + ) { + popUpTo(AttendancesDestination.CreateAttendanceScreen.route) { + inclusive = true + } + } + }, onNavigateUp = { navController.value.navigateUp() } 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 04226e41..b4e81d92 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/AlarmReceiver.kt @@ -26,6 +26,7 @@ import com.joeloewi.croissant.worker.AttendCheckInEventWorker import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch @@ -55,12 +56,15 @@ class AlarmReceiver : BroadcastReceiver() { @Inject lateinit var alarmManager: AlarmManager + @Inject + lateinit var workManager: WorkManager + 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) { getAllOneShotAttendanceUseCase().map { attendance -> - async(Dispatchers.IO) { + async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { attendance.runCatching { val alarmIntent = Intent(application, AlarmReceiver::class.java).apply { @@ -130,7 +134,7 @@ class AlarmReceiver : BroadcastReceiver() { ) .build() - WorkManager.getInstance(application).beginUniqueWork( + workManager.beginUniqueWork( attendance.oneTimeAttendCheckInEventWorkerName.toString(), ExistingWorkPolicy.APPEND_OR_REPLACE, oneTimeWork 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 4313eab2..a1377c32 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/MigrationHelper.kt @@ -14,6 +14,7 @@ import com.joeloewi.croissant.domain.usecase.ResinStatusWidgetUseCase import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch @@ -41,6 +42,9 @@ class MigrationHelper : BroadcastReceiver() { @Inject lateinit var getAllOneShotAttendanceUseCase: AttendanceUseCase.GetAllOneShot + @Inject + lateinit var workManager: WorkManager + override fun onReceive(p0: Context, p1: Intent) { when (p1.action) { Intent.ACTION_MY_PACKAGE_REPLACED -> { @@ -48,9 +52,8 @@ class MigrationHelper : BroadcastReceiver() { //because work manager's job can be deferred, cancel check in event worker //instead of work manager, use alarm manager getAllOneShotAttendanceUseCase().map { attendance -> - async(Dispatchers.IO) { - WorkManager.getInstance(application) - .cancelUniqueWork(attendance.attendCheckInEventWorkerName.toString()) + async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { + workManager.cancelUniqueWork(attendance.attendCheckInEventWorkerName.toString()) } }.awaitAll() } 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 7200b7c0..825fe9eb 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/ResinStatusWidgetProvider.kt @@ -1,15 +1,9 @@ package com.joeloewi.croissant.receiver -import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetProvider import android.content.Context -import android.content.Intent -import android.os.Build import android.os.PowerManager -import android.provider.Settings -import android.view.View -import android.widget.RemoteViews import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.work.Constraints @@ -20,15 +14,15 @@ import androidx.work.WorkManager import androidx.work.workDataOf import com.google.firebase.Firebase import com.google.firebase.crashlytics.crashlytics -import com.joeloewi.croissant.R import com.joeloewi.croissant.domain.usecase.ResinStatusWidgetUseCase +import com.joeloewi.croissant.util.createErrorDueToPowerSaveModeRemoteViews import com.joeloewi.croissant.util.isIgnoringBatteryOptimizationsCompat -import com.joeloewi.croissant.util.pendingIntentFlagUpdateCurrent import com.joeloewi.croissant.worker.RefreshResinStatusWorker import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.launch @@ -53,6 +47,9 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { @Inject lateinit var deleteByAppWidgetIdResinStatusWidgetUseCase: ResinStatusWidgetUseCase.DeleteByAppWidgetId + @Inject + lateinit var workManager: WorkManager + override fun onUpdate( context: Context, appWidgetManager: AppWidgetManager, @@ -63,55 +60,15 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { _processLifecycleOwner.lifecycleScope.launch(_coroutineContext) { appWidgetIds.map { appWidgetId -> - async(Dispatchers.IO) { + async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { if (powerManager.isPowerSaveMode && !powerManager.isIgnoringBatteryOptimizationsCompat( context ) ) { - RemoteViews( - context.packageName, - R.layout.widget_resin_status_battery_optimization_enabled - ).apply { - setOnClickPendingIntent( - R.id.button_retry, - PendingIntent.getBroadcast( - context, - appWidgetId, - Intent( - context, - ResinStatusWidgetProvider::class.java - ).apply { - action = AppWidgetManager.ACTION_APPWIDGET_UPDATE - - putExtra( - AppWidgetManager.EXTRA_APPWIDGET_IDS, - intArrayOf(appWidgetId) - ) - }, - pendingIntentFlagUpdateCurrent - ) - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setOnClickPendingIntent( - R.id.button_change_setting, - PendingIntent.getActivity( - context, - appWidgetId, - Intent( - Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS - ), - pendingIntentFlagUpdateCurrent - ) - ) - } else { - setViewVisibility(R.id.button_change_setting, View.INVISIBLE) - } - }.also { remoteViews -> - appWidgetManager.updateAppWidget( - appWidgetId, - remoteViews - ) - } + appWidgetManager.updateAppWidget( + appWidgetId, + createErrorDueToPowerSaveModeRemoteViews(context, appWidgetId) + ) } else { getOneByAppWidgetIdResinStatusWidgetUseCase.runCatching { invoke(appWidgetId) @@ -128,7 +85,7 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { ) .build() - WorkManager.getInstance(context).enqueueUniqueWork( + workManager.enqueueUniqueWork( it.resinStatusWidget.id.toString(), ExistingWorkPolicy.APPEND_OR_REPLACE, oneTimeWorkRequest @@ -147,16 +104,14 @@ class ResinStatusWidgetProvider : AppWidgetProvider() { override fun onDeleted(context: Context, appWidgetIds: IntArray) { super.onDeleted(context, appWidgetIds) - _processLifecycleOwner.lifecycleScope.launch(Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { + _processLifecycleOwner.lifecycleScope.launch(_coroutineContext) { appWidgetIds.run { map { appWidgetId -> - async(Dispatchers.IO) { + async(SupervisorJob() + Dispatchers.IO + CoroutineExceptionHandler { _, _ -> }) { getOneByAppWidgetIdResinStatusWidgetUseCase.runCatching { invoke(appWidgetId) }.onSuccess { - WorkManager.getInstance(context) - .cancelUniqueWork(it.resinStatusWidget.refreshGenshinResinStatusWorkerName.toString()) - + workManager.cancelUniqueWork(it.resinStatusWidget.refreshGenshinResinStatusWorkerName.toString()) } } }.awaitAll() 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 54f7ecbc..0775a157 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/receiver/TimeZoneChangedReceiver.kt @@ -3,14 +3,28 @@ package com.joeloewi.croissant.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope +import com.google.firebase.Firebase +import com.google.firebase.crashlytics.crashlytics import com.joeloewi.croissant.util.NotificationGenerator import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.util.UUID import javax.inject.Inject @AndroidEntryPoint class TimeZoneChangedReceiver @Inject constructor( ) : BroadcastReceiver() { + 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 @@ -18,12 +32,14 @@ class TimeZoneChangedReceiver @Inject constructor( override fun onReceive(context: Context, intent: Intent) { when (intent.action) { Intent.ACTION_TIMEZONE_CHANGED -> { - with(notificationGenerator) { - safeNotify( - UUID.randomUUID().toString(), - 0, - createTimezoneChangedNotification() - ) + _processLifecycleScope.launch(_coroutineContext) { + with(notificationGenerator) { + safeNotify( + UUID.randomUUID().toString(), + 0, + createTimezoneChangedNotification() + ) + } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/state/CroissantAppState.kt b/app/src/main/kotlin/com/joeloewi/croissant/state/CroissantAppState.kt deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/main/kotlin/com/joeloewi/croissant/state/LoginHoYoLABState.kt b/app/src/main/kotlin/com/joeloewi/croissant/state/LoginHoYoLABState.kt deleted file mode 100644 index e69de29b..00000000 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 7c922b3e..81d97a9e 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 @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.systemBars -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -52,20 +51,17 @@ import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.DefaultAlpha import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastForEach import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import coil.compose.AsyncImage import coil.request.ImageRequest -import com.google.accompanist.placeholder.PlaceholderHighlight -import com.google.accompanist.placeholder.fade -import com.google.accompanist.placeholder.placeholder import com.joeloewi.croissant.R import com.joeloewi.croissant.domain.common.HoYoLABGame import com.joeloewi.croissant.domain.common.LoggableWorker @@ -82,6 +78,7 @@ import com.joeloewi.croissant.util.gameNameStringResId import com.joeloewi.croissant.util.navigationIconButton import com.joeloewi.croissant.util.requestReview import com.joeloewi.croissant.viewmodel.AttendanceDetailViewModel +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine @@ -166,7 +163,7 @@ private fun AttendanceDetailContent( val snackbarHostState = remember { SnackbarHostState() } val pressSaveButton = stringResource(id = R.string.press_save_button_to_commit) val list by rememberUpdatedState( - newValue = listOf( + newValue = persistentListOf( stringResource(id = R.string.uid) to uid().toString(), stringResource(id = R.string.nickname) to nickname, ) @@ -206,6 +203,7 @@ private fun AttendanceDetailContent( snapshotFlow(deleteAttendanceState).catch { }.collect { when (it) { is ILCE.Content -> { + showConfirmDeleteDialog = false onNavigateUp() } @@ -299,7 +297,7 @@ private fun AttendanceDetailContent( } item("sessionInfos") { - list.forEach { + list.fastForEach { SessionInfoRow( key = it.first, value = it.second @@ -457,7 +455,6 @@ fun ConnectedGameListItem( ) Card( - enabled = game.type != HoYoLABGame.GenshinImpact, onClick = { val checked = checkedGames().contains(game) @@ -487,7 +484,6 @@ fun ConnectedGameListItem( ) Checkbox( - enabled = game.type != HoYoLABGame.GenshinImpact, modifier = Modifier.weight(1f), checked = checkedGames().contains(game), onCheckedChange = null @@ -501,81 +497,6 @@ fun ConnectedGameListItem( model = ImageRequest.Builder(LocalContext.current) .data(hoYoLABGame.gameIconUrl) .build(), - alpha = if (game.type != HoYoLABGame.GenshinImpact) { - DefaultAlpha - } else { - 0.38f - }, - contentDescription = null - ) - } - } -} - -@Composable -fun ConnectedGameListItemPlaceHolder( - modifier: Modifier, -) { - Card( - modifier = modifier.size(120.dp) - ) { - Column( - modifier = Modifier - .padding(DefaultDp) - .fillMaxSize(), - verticalArrangement = Arrangement.SpaceBetween - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Text( - modifier = Modifier - .width(64.dp) - .placeholder( - visible = true, - shape = MaterialTheme.shapes.extraSmall, - color = MaterialTheme.colorScheme.outline, - highlight = PlaceholderHighlight.fade( - highlightColor = MaterialTheme.colorScheme.surfaceVariant, - ) - ), - text = "" - ) - - AsyncImage( - modifier = Modifier - .size(IconDp) - .placeholder( - visible = true, - shape = MaterialTheme.shapes.extraSmall, - color = MaterialTheme.colorScheme.outline, - highlight = PlaceholderHighlight.fade( - highlightColor = MaterialTheme.colorScheme.surfaceVariant, - ) - ), - model = ImageRequest.Builder( - LocalContext.current - ).build(), - contentDescription = null - ) - } - - AsyncImage( - modifier = Modifier - .size(IconDp) - .placeholder( - visible = true, - shape = MaterialTheme.shapes.extraSmall, - color = MaterialTheme.colorScheme.outline, - highlight = PlaceholderHighlight.fade( - highlightColor = MaterialTheme.colorScheme.surfaceVariant, - ) - ), - model = ImageRequest.Builder( - LocalContext.current - ).build(), contentDescription = null ) } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceLogsCalendarScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceLogsCalendarScreen.kt index 5dca4263..773d620a 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceLogsCalendarScreen.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/AttendanceLogsCalendarScreen.kt @@ -189,14 +189,15 @@ private fun AttendanceLogsCalendarContent( modifier = Modifier.fillMaxSize(), state = pagerState, key = { - startToEnd().first.plusMonths(it.toLong()) + startToEnd().second.minusMonths(it.toLong()) .format(DateTimeFormatter.ofPattern("yyyy-MM")) - } + }, + reverseLayout = true ) { page -> MonthPage( yearMonth = { - with(startToEnd().first.plusMonths(page.toLong())) { + with(startToEnd().second.minusMonths(page.toLong())) { Year.of(year).atMonth(month) } }, diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/CreateAttendanceScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/CreateAttendanceScreen.kt index f1534863..027ed671 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/CreateAttendanceScreen.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/CreateAttendanceScreen.kt @@ -56,6 +56,7 @@ fun CreateAttendanceScreen( createAttendanceViewModel: CreateAttendanceViewModel = hiltViewModel(), newCookie: () -> String, onLoginHoYoLAB: () -> Unit, + onNavigateToAttendanceDetailScreen: (Long) -> Unit, onNavigateUp: () -> Unit ) { val insertAttendanceState by createAttendanceViewModel.insertAttendanceState.collectAsStateWithLifecycle() @@ -78,6 +79,7 @@ fun CreateAttendanceScreen( onHourOfDayChange = createAttendanceViewModel::setHourOfDay, onMinuteChange = createAttendanceViewModel::setMinute, onCreateAttendance = createAttendanceViewModel::createAttendance, + onNavigateToAttendanceDetailScreen = onNavigateToAttendanceDetailScreen, onNavigateUp = onNavigateUp ) } @@ -97,6 +99,7 @@ fun CreateAttendanceContent( onHourOfDayChange: (Int) -> Unit, onMinuteChange: (Int) -> Unit, onCreateAttendance: () -> Unit, + onNavigateToAttendanceDetailScreen: (Long) -> Unit, onNavigateUp: () -> Unit ) { val pagerState = rememberPagerState { 3 } @@ -185,12 +188,8 @@ fun CreateAttendanceContent( pagerState.scrollToPage(page + 1) } }, - onNavigateToAttendanceDetailScreen = { - - }, - onCancelCreateAttendance = { - - } + onNavigateToAttendanceDetailScreen = onNavigateToAttendanceDetailScreen, + onCancelCreateAttendance = onNavigateUp ) } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SelectGames.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SelectGames.kt index dec55e53..744cd149 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SelectGames.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SelectGames.kt @@ -40,8 +40,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment @@ -97,6 +99,9 @@ fun SelectGames( val containsNotSupportedGame = stringResource(id = R.string.contains_not_supported_game) val chooseAtLeastOneGame = stringResource(id = R.string.choose_at_least_one_game) val lazyListState = rememberLazyListState() + var showDuplicateAlertDialog by remember(duplicatedAttendance()) { + mutableStateOf(duplicatedAttendance() != null) + } LaunchedEffect(Unit) { withContext(Dispatchers.IO) { @@ -151,6 +156,7 @@ fun SelectGames( snackbarHostState.currentSnackbarData?.dismiss() } } + else -> { } @@ -276,13 +282,16 @@ fun SelectGames( } } - if (duplicatedAttendance() != null) { + if (showDuplicateAlertDialog) { AlertDialog( - onDismissRequest = { }, + onDismissRequest = { + showDuplicateAlertDialog = false + }, confirmButton = { TextButton( onClick = { duplicatedAttendance()?.id?.let { + showDuplicateAlertDialog = false onNavigateToAttendanceDetailScreen(it) } } @@ -293,6 +302,7 @@ fun SelectGames( dismissButton = { TextButton( onClick = { + showDuplicateAlertDialog = false onCancelCreateAttendance() } ) { @@ -414,8 +424,7 @@ fun ConnectedGamesContentListItem( val enabled by remember(hoYoLABGame, gameRecord) { derivedStateOf { - hoYoLABGame == HoYoLABGame.TearsOfThemis || hoYoLABGame == HoYoLABGame.HonkaiStarRail || - (hoYoLABGame != HoYoLABGame.GenshinImpact && currentGameRecord.value.gameId != GameRecord.INVALID_GAME_ID) + hoYoLABGame == HoYoLABGame.TearsOfThemis || hoYoLABGame == HoYoLABGame.HonkaiStarRail || currentGameRecord.value.gameId != GameRecord.INVALID_GAME_ID } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SetTime.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SetTime.kt index dc2b3113..e44f702a 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SetTime.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/attendances/screen/createattendance/composable/SetTime.kt @@ -15,8 +15,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Done -import androidx.compose.material.icons.filled.Star -import androidx.compose.material3.Card import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -33,10 +31,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.withStyle import androidx.lifecycle.flowWithLifecycle import com.joeloewi.croissant.R import com.joeloewi.croissant.ui.theme.DefaultDp @@ -137,31 +131,6 @@ fun SetTime( minute = minute ) } - - Card( - modifier = Modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier.padding(DefaultDp), - ) { - Icon( - modifier = Modifier.padding(DefaultDp), - imageVector = Icons.Default.Star, - contentDescription = Icons.Default.Star.name - ) - Text( - modifier = Modifier.padding(DefaultDp), - text = buildAnnotatedString { - withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) { - append(stringResource(id = R.string.note)) - append(": ") - } - append(stringResource(id = R.string.first_execution_is_for_reference)) - }, - style = MaterialTheme.typography.bodyMedium - ) - } - } } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/SettingsScreen.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/SettingsScreen.kt index 1112b839..7ae0d5b2 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/SettingsScreen.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/navigation/main/settings/screen/SettingsScreen.kt @@ -1,8 +1,6 @@ package com.joeloewi.croissant.ui.navigation.main.settings.screen import android.content.Intent -import android.net.Uri -import android.provider.Settings import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.clickable @@ -164,13 +162,6 @@ fun SettingsContent( value = ignoreBatteryOptimizations.status.isGranted, role = Role.Switch, onValueChange = { - if (ignoreBatteryOptimizations.status.isGranted) { - Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { - data = Uri.fromParts("package", activity.packageName, null) - }.let { - activity.startActivity(it) - } - } ignoreBatteryOptimizations.launchPermissionRequest() } ), diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Color.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Color.kt index 217375a0..42ef3d7a 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Color.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Color.kt @@ -2,65 +2,218 @@ package com.joeloewi.croissant.ui.theme import androidx.compose.ui.graphics.Color -val md_theme_light_primary = Color(0xFF3852CB) -val md_theme_light_onPrimary = Color(0xFFFFFFFF) -val md_theme_light_primaryContainer = Color(0xFFDEE1FF) -val md_theme_light_onPrimaryContainer = Color(0xFF001258) -val md_theme_light_secondary = Color(0xFF5A5D72) -val md_theme_light_onSecondary = Color(0xFFFFFFFF) -val md_theme_light_secondaryContainer = Color(0xFFDFE1F9) -val md_theme_light_onSecondaryContainer = Color(0xFF171A2C) -val md_theme_light_tertiary = Color(0xFF76546E) -val md_theme_light_onTertiary = Color(0xFFFFFFFF) -val md_theme_light_tertiaryContainer = Color(0xFFFFD7F2) -val md_theme_light_onTertiaryContainer = Color(0xFF2D1228) -val md_theme_light_error = Color(0xFFBA1A1A) -val md_theme_light_errorContainer = Color(0xFFFFDAD6) -val md_theme_light_onError = Color(0xFFFFFFFF) -val md_theme_light_onErrorContainer = Color(0xFF410002) -val md_theme_light_background = Color(0xFFFEFBFF) -val md_theme_light_onBackground = Color(0xFF1B1B1F) -val md_theme_light_outline = Color(0xFF767680) -val md_theme_light_inverseOnSurface = Color(0xFFF3F0F4) -val md_theme_light_inverseSurface = Color(0xFF303034) -val md_theme_light_inversePrimary = Color(0xFFB9C3FF) -val md_theme_light_surfaceTint = Color(0xFF3852CB) -val md_theme_light_outlineVariant = Color(0xFFC6C5D0) -val md_theme_light_scrim = Color(0xFF000000) -val md_theme_light_surface = Color(0xFFFBF8FD) -val md_theme_light_onSurface = Color(0xFF1B1B1F) -val md_theme_light_surfaceVariant = Color(0xFFE3E1EC) -val md_theme_light_onSurfaceVariant = Color(0xFF46464F) +val primaryLight = Color(0xFF505B92) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFFDEE1FF) +val onPrimaryContainerLight = Color(0xFF09164B) +val secondaryLight = Color(0xFF5A5D72) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFFDFE1F9) +val onSecondaryContainerLight = Color(0xFF171A2C) +val tertiaryLight = Color(0xFF76546E) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFFFD7F2) +val onTertiaryContainerLight = Color(0xFF2D1228) +val errorLight = Color(0xFFBA1A1A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFFDAD6) +val onErrorContainerLight = Color(0xFF410002) +val backgroundLight = Color(0xFFFBF8FF) +val onBackgroundLight = Color(0xFF1B1B21) +val surfaceLight = Color(0xFFFBF8FF) +val onSurfaceLight = Color(0xFF1B1B21) +val surfaceVariantLight = Color(0xFFE3E1EC) +val onSurfaceVariantLight = Color(0xFF46464F) +val outlineLight = Color(0xFF767680) +val outlineVariantLight = Color(0xFFC6C5D0) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF303036) +val inverseOnSurfaceLight = Color(0xFFF2F0F7) +val inversePrimaryLight = Color(0xFFB9C3FF) +val surfaceDimLight = Color(0xFFDBD9E0) +val surfaceBrightLight = Color(0xFFFBF8FF) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF5F2FA) +val surfaceContainerLight = Color(0xFFEFEDF4) +val surfaceContainerHighLight = Color(0xFFE9E7EF) +val surfaceContainerHighestLight = Color(0xFFE3E1E9) -val md_theme_dark_primary = Color(0xFFB9C3FF) -val md_theme_dark_onPrimary = Color(0xFF00218C) -val md_theme_dark_primaryContainer = Color(0xFF1838B3) -val md_theme_dark_onPrimaryContainer = Color(0xFFDEE1FF) -val md_theme_dark_secondary = Color(0xFFC3C5DD) -val md_theme_dark_onSecondary = Color(0xFF2C2F42) -val md_theme_dark_secondaryContainer = Color(0xFF434659) -val md_theme_dark_onSecondaryContainer = Color(0xFFDFE1F9) -val md_theme_dark_tertiary = Color(0xFFE5BAD8) -val md_theme_dark_onTertiary = Color(0xFF44263E) -val md_theme_dark_tertiaryContainer = Color(0xFF5D3C55) -val md_theme_dark_onTertiaryContainer = Color(0xFFFFD7F2) -val md_theme_dark_error = Color(0xFFFFB4AB) -val md_theme_dark_errorContainer = Color(0xFF93000A) -val md_theme_dark_onError = Color(0xFF690005) -val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6) -val md_theme_dark_background = Color(0xFF1B1B1F) -val md_theme_dark_onBackground = Color(0xFFE4E1E6) -val md_theme_dark_outline = Color(0xFF90909A) -val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F) -val md_theme_dark_inverseSurface = Color(0xFFE4E1E6) -val md_theme_dark_inversePrimary = Color(0xFF3852CB) -val md_theme_dark_surfaceTint = Color(0xFFB9C3FF) -val md_theme_dark_outlineVariant = Color(0xFF46464F) -val md_theme_dark_scrim = Color(0xFF000000) -val md_theme_dark_surface = Color(0xFF131316) -val md_theme_dark_onSurface = Color(0xFFC8C6CA) -val md_theme_dark_surfaceVariant = Color(0xFF46464F) -val md_theme_dark_onSurfaceVariant = Color(0xFFC6C5D0) +val primaryLightMediumContrast = Color(0xFF343F74) +val onPrimaryLightMediumContrast = Color(0xFFFFFFFF) +val primaryContainerLightMediumContrast = Color(0xFF6771AA) +val onPrimaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val secondaryLightMediumContrast = Color(0xFF3F4255) +val onSecondaryLightMediumContrast = Color(0xFFFFFFFF) +val secondaryContainerLightMediumContrast = Color(0xFF717389) +val onSecondaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryLightMediumContrast = Color(0xFF583951) +val onTertiaryLightMediumContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightMediumContrast = Color(0xFF8E6984) +val onTertiaryContainerLightMediumContrast = Color(0xFFFFFFFF) +val errorLightMediumContrast = Color(0xFF8C0009) +val onErrorLightMediumContrast = Color(0xFFFFFFFF) +val errorContainerLightMediumContrast = Color(0xFFDA342E) +val onErrorContainerLightMediumContrast = Color(0xFFFFFFFF) +val backgroundLightMediumContrast = Color(0xFFFBF8FF) +val onBackgroundLightMediumContrast = Color(0xFF1B1B21) +val surfaceLightMediumContrast = Color(0xFFFBF8FF) +val onSurfaceLightMediumContrast = Color(0xFF1B1B21) +val surfaceVariantLightMediumContrast = Color(0xFFE3E1EC) +val onSurfaceVariantLightMediumContrast = Color(0xFF42424B) +val outlineLightMediumContrast = Color(0xFF5E5E67) +val outlineVariantLightMediumContrast = Color(0xFF7A7A83) +val scrimLightMediumContrast = Color(0xFF000000) +val inverseSurfaceLightMediumContrast = Color(0xFF303036) +val inverseOnSurfaceLightMediumContrast = Color(0xFFF2F0F7) +val inversePrimaryLightMediumContrast = Color(0xFFB9C3FF) +val surfaceDimLightMediumContrast = Color(0xFFDBD9E0) +val surfaceBrightLightMediumContrast = Color(0xFFFBF8FF) +val surfaceContainerLowestLightMediumContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightMediumContrast = Color(0xFFF5F2FA) +val surfaceContainerLightMediumContrast = Color(0xFFEFEDF4) +val surfaceContainerHighLightMediumContrast = Color(0xFFE9E7EF) +val surfaceContainerHighestLightMediumContrast = Color(0xFFE3E1E9) +val primaryLightHighContrast = Color(0xFF111D52) +val onPrimaryLightHighContrast = Color(0xFFFFFFFF) +val primaryContainerLightHighContrast = Color(0xFF343F74) +val onPrimaryContainerLightHighContrast = Color(0xFFFFFFFF) +val secondaryLightHighContrast = Color(0xFF1E2133) +val onSecondaryLightHighContrast = Color(0xFFFFFFFF) +val secondaryContainerLightHighContrast = Color(0xFF3F4255) +val onSecondaryContainerLightHighContrast = Color(0xFFFFFFFF) +val tertiaryLightHighContrast = Color(0xFF34182F) +val onTertiaryLightHighContrast = Color(0xFFFFFFFF) +val tertiaryContainerLightHighContrast = Color(0xFF583951) +val onTertiaryContainerLightHighContrast = Color(0xFFFFFFFF) +val errorLightHighContrast = Color(0xFF4E0002) +val onErrorLightHighContrast = Color(0xFFFFFFFF) +val errorContainerLightHighContrast = Color(0xFF8C0009) +val onErrorContainerLightHighContrast = Color(0xFFFFFFFF) +val backgroundLightHighContrast = Color(0xFFFBF8FF) +val onBackgroundLightHighContrast = Color(0xFF1B1B21) +val surfaceLightHighContrast = Color(0xFFFBF8FF) +val onSurfaceLightHighContrast = Color(0xFF000000) +val surfaceVariantLightHighContrast = Color(0xFFE3E1EC) +val onSurfaceVariantLightHighContrast = Color(0xFF22232B) +val outlineLightHighContrast = Color(0xFF42424B) +val outlineVariantLightHighContrast = Color(0xFF42424B) +val scrimLightHighContrast = Color(0xFF000000) +val inverseSurfaceLightHighContrast = Color(0xFF303036) +val inverseOnSurfaceLightHighContrast = Color(0xFFFFFFFF) +val inversePrimaryLightHighContrast = Color(0xFFEAEAFF) +val surfaceDimLightHighContrast = Color(0xFFDBD9E0) +val surfaceBrightLightHighContrast = Color(0xFFFBF8FF) +val surfaceContainerLowestLightHighContrast = Color(0xFFFFFFFF) +val surfaceContainerLowLightHighContrast = Color(0xFFF5F2FA) +val surfaceContainerLightHighContrast = Color(0xFFEFEDF4) +val surfaceContainerHighLightHighContrast = Color(0xFFE9E7EF) +val surfaceContainerHighestLightHighContrast = Color(0xFFE3E1E9) -val seed = Color(0xFF657EF8) +val primaryDark = Color(0xFFB9C3FF) +val onPrimaryDark = Color(0xFF212C61) +val primaryContainerDark = Color(0xFF384379) +val onPrimaryContainerDark = Color(0xFFDEE1FF) +val secondaryDark = Color(0xFFC3C5DD) +val onSecondaryDark = Color(0xFF2C2F42) +val secondaryContainerDark = Color(0xFF434659) +val onSecondaryContainerDark = Color(0xFFDFE1F9) +val tertiaryDark = Color(0xFFE5BAD8) +val onTertiaryDark = Color(0xFF44263E) +val tertiaryContainerDark = Color(0xFF5D3C55) +val onTertiaryContainerDark = Color(0xFFFFD7F2) +val errorDark = Color(0xFFFFB4AB) +val onErrorDark = Color(0xFF690005) +val errorContainerDark = Color(0xFF93000A) +val onErrorContainerDark = Color(0xFFFFDAD6) +val backgroundDark = Color(0xFF121318) +val onBackgroundDark = Color(0xFFE3E1E9) +val surfaceDark = Color(0xFF121318) +val onSurfaceDark = Color(0xFFE3E1E9) +val surfaceVariantDark = Color(0xFF46464F) +val onSurfaceVariantDark = Color(0xFFC6C5D0) +val outlineDark = Color(0xFF90909A) +val outlineVariantDark = Color(0xFF46464F) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE3E1E9) +val inverseOnSurfaceDark = Color(0xFF303036) +val inversePrimaryDark = Color(0xFF505B92) +val surfaceDimDark = Color(0xFF121318) +val surfaceBrightDark = Color(0xFF38393F) +val surfaceContainerLowestDark = Color(0xFF0D0E13) +val surfaceContainerLowDark = Color(0xFF1B1B21) +val surfaceContainerDark = Color(0xFF1F1F25) +val surfaceContainerHighDark = Color(0xFF292A2F) +val surfaceContainerHighestDark = Color(0xFF34343A) + +val primaryDarkMediumContrast = Color(0xFFBFC8FF) +val onPrimaryDarkMediumContrast = Color(0xFF030F46) +val primaryContainerDarkMediumContrast = Color(0xFF838DC8) +val onPrimaryContainerDarkMediumContrast = Color(0xFF000000) +val secondaryDarkMediumContrast = Color(0xFFC7C9E1) +val onSecondaryDarkMediumContrast = Color(0xFF121526) +val secondaryContainerDarkMediumContrast = Color(0xFF8D8FA6) +val onSecondaryContainerDarkMediumContrast = Color(0xFF000000) +val tertiaryDarkMediumContrast = Color(0xFFE9BEDC) +val onTertiaryDarkMediumContrast = Color(0xFF270C23) +val tertiaryContainerDarkMediumContrast = Color(0xFFAC85A1) +val onTertiaryContainerDarkMediumContrast = Color(0xFF000000) +val errorDarkMediumContrast = Color(0xFFFFBAB1) +val onErrorDarkMediumContrast = Color(0xFF370001) +val errorContainerDarkMediumContrast = Color(0xFFFF5449) +val onErrorContainerDarkMediumContrast = Color(0xFF000000) +val backgroundDarkMediumContrast = Color(0xFF121318) +val onBackgroundDarkMediumContrast = Color(0xFFE3E1E9) +val surfaceDarkMediumContrast = Color(0xFF121318) +val onSurfaceDarkMediumContrast = Color(0xFFFCFAFF) +val surfaceVariantDarkMediumContrast = Color(0xFF46464F) +val onSurfaceVariantDarkMediumContrast = Color(0xFFCBCAD4) +val outlineDarkMediumContrast = Color(0xFFA2A2AC) +val outlineVariantDarkMediumContrast = Color(0xFF82828C) +val scrimDarkMediumContrast = Color(0xFF000000) +val inverseSurfaceDarkMediumContrast = Color(0xFFE3E1E9) +val inverseOnSurfaceDarkMediumContrast = Color(0xFF292A2F) +val inversePrimaryDarkMediumContrast = Color(0xFF3A447A) +val surfaceDimDarkMediumContrast = Color(0xFF121318) +val surfaceBrightDarkMediumContrast = Color(0xFF38393F) +val surfaceContainerLowestDarkMediumContrast = Color(0xFF0D0E13) +val surfaceContainerLowDarkMediumContrast = Color(0xFF1B1B21) +val surfaceContainerDarkMediumContrast = Color(0xFF1F1F25) +val surfaceContainerHighDarkMediumContrast = Color(0xFF292A2F) +val surfaceContainerHighestDarkMediumContrast = Color(0xFF34343A) + +val primaryDarkHighContrast = Color(0xFFFCFAFF) +val onPrimaryDarkHighContrast = Color(0xFF000000) +val primaryContainerDarkHighContrast = Color(0xFFBFC8FF) +val onPrimaryContainerDarkHighContrast = Color(0xFF000000) +val secondaryDarkHighContrast = Color(0xFFFCFAFF) +val onSecondaryDarkHighContrast = Color(0xFF000000) +val secondaryContainerDarkHighContrast = Color(0xFFC7C9E1) +val onSecondaryContainerDarkHighContrast = Color(0xFF000000) +val tertiaryDarkHighContrast = Color(0xFFFFF9FA) +val onTertiaryDarkHighContrast = Color(0xFF000000) +val tertiaryContainerDarkHighContrast = Color(0xFFE9BEDC) +val onTertiaryContainerDarkHighContrast = Color(0xFF000000) +val errorDarkHighContrast = Color(0xFFFFF9F9) +val onErrorDarkHighContrast = Color(0xFF000000) +val errorContainerDarkHighContrast = Color(0xFFFFBAB1) +val onErrorContainerDarkHighContrast = Color(0xFF000000) +val backgroundDarkHighContrast = Color(0xFF121318) +val onBackgroundDarkHighContrast = Color(0xFFE3E1E9) +val surfaceDarkHighContrast = Color(0xFF121318) +val onSurfaceDarkHighContrast = Color(0xFFFFFFFF) +val surfaceVariantDarkHighContrast = Color(0xFF46464F) +val onSurfaceVariantDarkHighContrast = Color(0xFFFCFAFF) +val outlineDarkHighContrast = Color(0xFFCBCAD4) +val outlineVariantDarkHighContrast = Color(0xFFCBCAD4) +val scrimDarkHighContrast = Color(0xFF000000) +val inverseSurfaceDarkHighContrast = Color(0xFFE3E1E9) +val inverseOnSurfaceDarkHighContrast = Color(0xFF000000) +val inversePrimaryDarkHighContrast = Color(0xFF1A255A) +val surfaceDimDarkHighContrast = Color(0xFF121318) +val surfaceBrightDarkHighContrast = Color(0xFF38393F) +val surfaceContainerLowestDarkHighContrast = Color(0xFF0D0E13) +val surfaceContainerLowDarkHighContrast = Color(0xFF1B1B21) +val surfaceContainerDarkHighContrast = Color(0xFF1F1F25) +val surfaceContainerHighDarkHighContrast = Color(0xFF292A2F) +val surfaceContainerHighestDarkHighContrast = Color(0xFF34343A) \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Theme.kt b/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Theme.kt index df794ecb..e02ae122 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Theme.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/ui/theme/Theme.kt @@ -16,6 +16,7 @@ package com.joeloewi.croissant.ui.theme +import android.app.Activity import android.os.Build import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.MaterialTheme @@ -24,72 +25,196 @@ import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat -private val LightColorScheme = lightColorScheme( - primary = md_theme_light_primary, - onPrimary = md_theme_light_onPrimary, - primaryContainer = md_theme_light_primaryContainer, - onPrimaryContainer = md_theme_light_onPrimaryContainer, - secondary = md_theme_light_secondary, - onSecondary = md_theme_light_onSecondary, - secondaryContainer = md_theme_light_secondaryContainer, - onSecondaryContainer = md_theme_light_onSecondaryContainer, - tertiary = md_theme_light_tertiary, - onTertiary = md_theme_light_onTertiary, - tertiaryContainer = md_theme_light_tertiaryContainer, - onTertiaryContainer = md_theme_light_onTertiaryContainer, - error = md_theme_light_error, - errorContainer = md_theme_light_errorContainer, - onError = md_theme_light_onError, - onErrorContainer = md_theme_light_onErrorContainer, - background = md_theme_light_background, - onBackground = md_theme_light_onBackground, - outline = md_theme_light_outline, - inverseOnSurface = md_theme_light_inverseOnSurface, - inverseSurface = md_theme_light_inverseSurface, - inversePrimary = md_theme_light_inversePrimary, - surfaceTint = md_theme_light_surfaceTint, - outlineVariant = md_theme_light_outlineVariant, - scrim = md_theme_light_scrim, - surface = md_theme_light_surface, - onSurface = md_theme_light_onSurface, - surfaceVariant = md_theme_light_surfaceVariant, - onSurfaceVariant = md_theme_light_onSurfaceVariant, +private val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, ) +private val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, +) + +private val mediumContrastLightColorScheme = lightColorScheme( + primary = primaryLightMediumContrast, + onPrimary = onPrimaryLightMediumContrast, + primaryContainer = primaryContainerLightMediumContrast, + onPrimaryContainer = onPrimaryContainerLightMediumContrast, + secondary = secondaryLightMediumContrast, + onSecondary = onSecondaryLightMediumContrast, + secondaryContainer = secondaryContainerLightMediumContrast, + onSecondaryContainer = onSecondaryContainerLightMediumContrast, + tertiary = tertiaryLightMediumContrast, + onTertiary = onTertiaryLightMediumContrast, + tertiaryContainer = tertiaryContainerLightMediumContrast, + onTertiaryContainer = onTertiaryContainerLightMediumContrast, + error = errorLightMediumContrast, + onError = onErrorLightMediumContrast, + errorContainer = errorContainerLightMediumContrast, + onErrorContainer = onErrorContainerLightMediumContrast, + background = backgroundLightMediumContrast, + onBackground = onBackgroundLightMediumContrast, + surface = surfaceLightMediumContrast, + onSurface = onSurfaceLightMediumContrast, + surfaceVariant = surfaceVariantLightMediumContrast, + onSurfaceVariant = onSurfaceVariantLightMediumContrast, + outline = outlineLightMediumContrast, + outlineVariant = outlineVariantLightMediumContrast, + scrim = scrimLightMediumContrast, + inverseSurface = inverseSurfaceLightMediumContrast, + inverseOnSurface = inverseOnSurfaceLightMediumContrast, + inversePrimary = inversePrimaryLightMediumContrast, +) + +private val highContrastLightColorScheme = lightColorScheme( + primary = primaryLightHighContrast, + onPrimary = onPrimaryLightHighContrast, + primaryContainer = primaryContainerLightHighContrast, + onPrimaryContainer = onPrimaryContainerLightHighContrast, + secondary = secondaryLightHighContrast, + onSecondary = onSecondaryLightHighContrast, + secondaryContainer = secondaryContainerLightHighContrast, + onSecondaryContainer = onSecondaryContainerLightHighContrast, + tertiary = tertiaryLightHighContrast, + onTertiary = onTertiaryLightHighContrast, + tertiaryContainer = tertiaryContainerLightHighContrast, + onTertiaryContainer = onTertiaryContainerLightHighContrast, + error = errorLightHighContrast, + onError = onErrorLightHighContrast, + errorContainer = errorContainerLightHighContrast, + onErrorContainer = onErrorContainerLightHighContrast, + background = backgroundLightHighContrast, + onBackground = onBackgroundLightHighContrast, + surface = surfaceLightHighContrast, + onSurface = onSurfaceLightHighContrast, + surfaceVariant = surfaceVariantLightHighContrast, + onSurfaceVariant = onSurfaceVariantLightHighContrast, + outline = outlineLightHighContrast, + outlineVariant = outlineVariantLightHighContrast, + scrim = scrimLightHighContrast, + inverseSurface = inverseSurfaceLightHighContrast, + inverseOnSurface = inverseOnSurfaceLightHighContrast, + inversePrimary = inversePrimaryLightHighContrast, +) -private val DarkColorScheme = darkColorScheme( - primary = md_theme_dark_primary, - onPrimary = md_theme_dark_onPrimary, - primaryContainer = md_theme_dark_primaryContainer, - onPrimaryContainer = md_theme_dark_onPrimaryContainer, - secondary = md_theme_dark_secondary, - onSecondary = md_theme_dark_onSecondary, - secondaryContainer = md_theme_dark_secondaryContainer, - onSecondaryContainer = md_theme_dark_onSecondaryContainer, - tertiary = md_theme_dark_tertiary, - onTertiary = md_theme_dark_onTertiary, - tertiaryContainer = md_theme_dark_tertiaryContainer, - onTertiaryContainer = md_theme_dark_onTertiaryContainer, - error = md_theme_dark_error, - errorContainer = md_theme_dark_errorContainer, - onError = md_theme_dark_onError, - onErrorContainer = md_theme_dark_onErrorContainer, - background = md_theme_dark_background, - onBackground = md_theme_dark_onBackground, - outline = md_theme_dark_outline, - inverseOnSurface = md_theme_dark_inverseOnSurface, - inverseSurface = md_theme_dark_inverseSurface, - inversePrimary = md_theme_dark_inversePrimary, - surfaceTint = md_theme_dark_surfaceTint, - outlineVariant = md_theme_dark_outlineVariant, - scrim = md_theme_dark_scrim, - surface = md_theme_dark_surface, - onSurface = md_theme_dark_onSurface, - surfaceVariant = md_theme_dark_surfaceVariant, - onSurfaceVariant = md_theme_dark_onSurfaceVariant, +private val mediumContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkMediumContrast, + onPrimary = onPrimaryDarkMediumContrast, + primaryContainer = primaryContainerDarkMediumContrast, + onPrimaryContainer = onPrimaryContainerDarkMediumContrast, + secondary = secondaryDarkMediumContrast, + onSecondary = onSecondaryDarkMediumContrast, + secondaryContainer = secondaryContainerDarkMediumContrast, + onSecondaryContainer = onSecondaryContainerDarkMediumContrast, + tertiary = tertiaryDarkMediumContrast, + onTertiary = onTertiaryDarkMediumContrast, + tertiaryContainer = tertiaryContainerDarkMediumContrast, + onTertiaryContainer = onTertiaryContainerDarkMediumContrast, + error = errorDarkMediumContrast, + onError = onErrorDarkMediumContrast, + errorContainer = errorContainerDarkMediumContrast, + onErrorContainer = onErrorContainerDarkMediumContrast, + background = backgroundDarkMediumContrast, + onBackground = onBackgroundDarkMediumContrast, + surface = surfaceDarkMediumContrast, + onSurface = onSurfaceDarkMediumContrast, + surfaceVariant = surfaceVariantDarkMediumContrast, + onSurfaceVariant = onSurfaceVariantDarkMediumContrast, + outline = outlineDarkMediumContrast, + outlineVariant = outlineVariantDarkMediumContrast, + scrim = scrimDarkMediumContrast, + inverseSurface = inverseSurfaceDarkMediumContrast, + inverseOnSurface = inverseOnSurfaceDarkMediumContrast, + inversePrimary = inversePrimaryDarkMediumContrast, +) + +private val highContrastDarkColorScheme = darkColorScheme( + primary = primaryDarkHighContrast, + onPrimary = onPrimaryDarkHighContrast, + primaryContainer = primaryContainerDarkHighContrast, + onPrimaryContainer = onPrimaryContainerDarkHighContrast, + secondary = secondaryDarkHighContrast, + onSecondary = onSecondaryDarkHighContrast, + secondaryContainer = secondaryContainerDarkHighContrast, + onSecondaryContainer = onSecondaryContainerDarkHighContrast, + tertiary = tertiaryDarkHighContrast, + onTertiary = onTertiaryDarkHighContrast, + tertiaryContainer = tertiaryContainerDarkHighContrast, + onTertiaryContainer = onTertiaryContainerDarkHighContrast, + error = errorDarkHighContrast, + onError = onErrorDarkHighContrast, + errorContainer = errorContainerDarkHighContrast, + onErrorContainer = onErrorContainerDarkHighContrast, + background = backgroundDarkHighContrast, + onBackground = onBackgroundDarkHighContrast, + surface = surfaceDarkHighContrast, + onSurface = onSurfaceDarkHighContrast, + surfaceVariant = surfaceVariantDarkHighContrast, + onSurfaceVariant = onSurfaceVariantDarkHighContrast, + outline = outlineDarkHighContrast, + outlineVariant = outlineVariantDarkHighContrast, + scrim = scrimDarkHighContrast, + inverseSurface = inverseSurfaceDarkHighContrast, + inverseOnSurface = inverseOnSurfaceDarkHighContrast, + inversePrimary = inversePrimaryDarkHighContrast, ) @Composable @@ -97,7 +222,7 @@ fun CroissantTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, - content: @Composable() () -> Unit + content: @Composable () -> Unit ) { val colorScheme = when { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { @@ -105,8 +230,15 @@ fun CroissantTheme( if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) } - darkTheme -> DarkColorScheme - else -> LightColorScheme + darkTheme -> darkScheme + else -> lightScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme + } } MaterialTheme( diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/LifecycleExtensions.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/LifecycleExtensions.kt deleted file mode 100644 index 88da2122..00000000 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/LifecycleExtensions.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.joeloewi.croissant.util - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver - -@Composable -fun Lifecycle.observeAsState(): State { - val state = remember { mutableStateOf(Lifecycle.Event.ON_ANY) } - DisposableEffect(this) { - val observer = LifecycleEventObserver { _, event -> - state.value = event - } - this@observeAsState.addObserver(observer) - onDispose { - this@observeAsState.removeObserver(observer) - } - } - return state -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/NavControllerUtils.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/NavControllerUtils.kt index f8f78b61..2b8eea06 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/NavControllerUtils.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/NavControllerUtils.kt @@ -11,7 +11,6 @@ import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.graphics.Color import androidx.lifecycle.ViewModelStoreOwner import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavHostController @Composable fun ViewModelStoreOwner?.navigationIconButton( @@ -35,14 +34,4 @@ fun ViewModelStoreOwner?.navigationIconButton( } } -} - - -fun getResultFromPreviousComposable( - navController: NavHostController, - key: String -): T? = navController.currentBackStackEntry?.savedStateHandle?.run { - get(key).apply { - this@run.remove(key) - } } \ No newline at end of file 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 a82390bb..a7f6af8b 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/NotificationGenerator.kt @@ -20,6 +20,8 @@ import com.joeloewi.croissant.R import com.joeloewi.croissant.data.common.generateGameIntent import com.joeloewi.croissant.domain.common.HoYoLABGame import com.joeloewi.croissant.ui.navigation.main.attendances.AttendancesDestination +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext class NotificationGenerator( private val context: Context, @@ -64,6 +66,24 @@ class NotificationGenerator( .build() } + private suspend fun NotificationCompat.Builder.setLargeIconViaCoil( + resource: Any, + context: Context + ): NotificationCompat.Builder { + val gameIcon = context.imageLoader.runCatching { + execute( + ImageRequest.Builder(context = context) + .data(resource) + .build() + ).drawable + }.getOrNull() + + if (gameIcon != null) { + return setLargeIcon(gameIcon.toBitmap()) + } + return this + } + fun createNotificationChannels() = _notificationManagerCompat.createNotificationChannelsCompat( listOf( _attendanceNotificationChannel, @@ -115,17 +135,7 @@ class NotificationGenerator( .setContentText("$message (${retCode})") .setAutoCancel(true) .setSmallIcon(R.drawable.ic_baseline_bakery_dining_24) - .apply { - context.imageLoader.runCatching { - execute( - ImageRequest.Builder(context = context) - .data(hoYoLABGame.gameIconUrl) - .build() - ).drawable - }.getOrNull()?.run { - setLargeIcon(toBitmap()) - } - } + .setLargeIconViaCoil(hoYoLABGame.gameIconUrl, context) .apply { val pendingIntentFlag = pendingIntentFlagUpdateCurrent @@ -162,17 +172,7 @@ class NotificationGenerator( .setContentText(context.getString(R.string.attendance_failed)) .setAutoCancel(true) .setSmallIcon(R.drawable.ic_baseline_bakery_dining_24) - .apply { - context.imageLoader.runCatching { - execute( - ImageRequest.Builder(context = context) - .data(hoYoLABGame.gameIconUrl) - .build() - ).drawable - }.getOrNull()?.run { - setLargeIcon(toBitmap()) - } - } + .setLargeIconViaCoil(hoYoLABGame.gameIconUrl, context) .apply { val pendingIntent = TaskStackBuilder.create(context).run { addNextIntentWithParentStack( @@ -211,17 +211,16 @@ class NotificationGenerator( .build() .run { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ForegroundInfo( + return@run ForegroundInfo( notificationId, this, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) - } else { - ForegroundInfo( - notificationId, - this - ) } + return@run ForegroundInfo( + notificationId, + this + ) } fun createCheckSessionNotification( @@ -254,11 +253,11 @@ class NotificationGenerator( } .build() - fun safeNotify( + suspend fun safeNotify( tag: String, notificationId: Int, notification: Notification - ) { + ) = withContext(Dispatchers.IO) { if (context.packageManager.checkPermission( CroissantPermission.PostNotifications.permission, context.packageName diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/RequireAppUpdate.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/RequireAppUpdate.kt index 7a31a9f1..689b8ef5 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/RequireAppUpdate.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/RequireAppUpdate.kt @@ -3,49 +3,53 @@ package com.joeloewi.croissant.util import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.snapshotFlow import com.google.android.play.core.ktx.AppUpdateResult import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.catch //under LocalActivity @Composable fun RequireAppUpdate( - appUpdateResultState: AppUpdateResult, + appUpdateResultState: () -> AppUpdateResult, content: @Composable () -> Unit ) { - val requestCode = remember { 22050 } val activity = LocalActivity.current val updatedContent by rememberUpdatedState(newValue = content) - LaunchedEffect(appUpdateResultState) { - when (appUpdateResultState) { - is AppUpdateResult.Available -> { - appUpdateResultState.runCatching { - startImmediateUpdate(activity = activity, requestCode = requestCode) - }.onSuccess { + LaunchedEffect(Unit) { + val requestCode = 22050 - }.onFailure { cause -> - if (cause is CancellationException) { - throw cause + snapshotFlow(appUpdateResultState).catch { }.collect { + when (it) { + is AppUpdateResult.Available -> { + appUpdateResultState.runCatching { + it.startImmediateUpdate(activity = activity, requestCode = requestCode) + }.onSuccess { + + }.onFailure { cause -> + if (cause is CancellationException) { + throw cause + } } } - } - is AppUpdateResult.Downloaded -> { - appUpdateResultState.runCatching { - completeUpdate() - }.onSuccess { + is AppUpdateResult.Downloaded -> { + appUpdateResultState.runCatching { + it.completeUpdate() + }.onSuccess { - }.onFailure { cause -> - if (cause is CancellationException) { - throw cause + }.onFailure { cause -> + if (cause is CancellationException) { + throw cause + } } } - } - else -> { + else -> { + } } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/ResinStatusWidgetRemoteViews.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/ResinStatusWidgetRemoteViews.kt new file mode 100644 index 00000000..01ffe791 --- /dev/null +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/ResinStatusWidgetRemoteViews.kt @@ -0,0 +1,189 @@ +package com.joeloewi.croissant.util + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.Settings +import android.view.View +import android.widget.RemoteViews +import androidx.core.os.bundleOf +import com.joeloewi.croissant.R +import com.joeloewi.croissant.receiver.ResinStatusWidgetProvider +import com.joeloewi.croissant.service.RemoteViewsFactoryService +import java.time.Instant +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle + +fun createErrorDueToPowerSaveModeRemoteViews( + context: Context, + appWidgetId: Int +) = RemoteViews( + context.packageName, + R.layout.widget_resin_status_battery_optimization_enabled +).apply { + setOnClickPendingIntent( + R.id.button_retry, + PendingIntent.getBroadcast( + context, + appWidgetId, + Intent( + context, + ResinStatusWidgetProvider::class.java + ).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + + putExtra( + AppWidgetManager.EXTRA_APPWIDGET_IDS, + intArrayOf(appWidgetId) + ) + }, + pendingIntentFlagUpdateCurrent + ) + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setOnClickPendingIntent( + R.id.button_change_setting, + PendingIntent.getActivity( + context, + appWidgetId, + Intent( + Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS + ), + pendingIntentFlagUpdateCurrent + ) + ) + } else { + setViewVisibility(R.id.button_change_setting, View.INVISIBLE) + } +} + +fun createUnknownErrorRemoteViews( + context: Context, + appWidgetId: Int +) = RemoteViews( + context.packageName, + R.layout.widget_resin_status_error +).apply { + setOnClickPendingIntent( + R.id.button_retry, + PendingIntent.getBroadcast( + context, + appWidgetId, + Intent( + context, + ResinStatusWidgetProvider::class.java + ).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + + putExtra( + AppWidgetManager.EXTRA_APPWIDGET_IDS, + intArrayOf(appWidgetId) + ) + }, + pendingIntentFlagUpdateCurrent + ) + ) +} + +fun createLoadingRemoteViews( + context: Context, +) = RemoteViews( + context.packageName, + R.layout.widget_resin_status_loading +) + +fun createContentRemoteViews( + context: Context, + appWidgetId: Int, + resinStatuses: List +) = RemoteViews( + context.packageName, + R.layout.widget_resin_status +).apply { + //set timestamp + val dateTimeFormatter = + DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) + + val localDateTime = + Instant.now() + .atZone(ZoneId.systemDefault()) + .toLocalDateTime() + val readableTimestamp = dateTimeFormatter.format(localDateTime) + + setTextViewText(R.id.widget_timestamp, readableTimestamp) + + //set click listener + setOnClickPendingIntent( + R.id.widget_refresh, + PendingIntent.getBroadcast( + context, + appWidgetId, + Intent( + context, + ResinStatusWidgetProvider::class.java + ).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + + putExtra( + AppWidgetManager.EXTRA_APPWIDGET_IDS, + intArrayOf(appWidgetId) + ) + }, + pendingIntentFlagUpdateCurrent + ) + ) + + //set resin status + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val items = RemoteViews.RemoteCollectionItems.Builder() + .apply { + resinStatuses.forEach { + addItem( + it.id, + RemoteViews( + context.packageName, + android.R.layout.two_line_list_item + ).apply { + setTextViewText( + android.R.id.text1, + it.nickname + ) + setTextViewText( + android.R.id.text2, + "${it.currentResin} / ${it.maxResin}" + ) + } + ) + } + } + .setViewTypeCount(1) + .setHasStableIds(false) + .build() + + setRemoteAdapter( + R.id.resin_statuses, + items + ) + } else { + val serviceIntent = Intent( + context, + RemoteViewsFactoryService::class.java + ) + + val extrasBundle = bundleOf().apply { + putParcelableArrayList( + ListRemoteViewsFactory.RESIN_STATUSES, + ArrayList(resinStatuses) + ) + } + + serviceIntent.putExtra(ListRemoteViewsFactory.BUNDLE, extrasBundle) + + setRemoteAdapter( + R.id.resin_statuses, serviceIntent + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/SpannedExtensions.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/SpannedExtensions.kt index 3e2f986e..652aefb9 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/SpannedExtensions.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/SpannedExtensions.kt @@ -31,12 +31,13 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp +import androidx.compose.ui.util.fastForEach @OptIn(ExperimentalTextApi::class) fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString { val spanned = this@toAnnotatedString append(spanned.toString()) - getSpans(0, spanned.length, Any::class.java).forEachIndexed { _, span -> + getSpans(0, spanned.length, Any::class.java).toList().fastForEach { span -> val start = getSpanStart(span) val end = getSpanEnd(span) @@ -51,7 +52,7 @@ fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString { ), start, end ) - is AlignmentSpan -> addStyle( + /*is AlignmentSpan -> addStyle( ParagraphStyle( textAlign = when (span.alignment) { Layout.Alignment.ALIGN_CENTER -> { @@ -69,7 +70,7 @@ fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString { else -> null } ), start, end - ) + )*/ is BackgroundColorSpan -> addStyle( SpanStyle(background = Color(span.backgroundColor)), diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/SpecialPermissionState.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/SpecialPermissionState.kt index e6aeecc6..eac36a2c 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/SpecialPermissionState.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/SpecialPermissionState.kt @@ -4,7 +4,6 @@ import android.annotation.SuppressLint import android.app.AlarmManager import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build import android.os.PowerManager import android.provider.Settings @@ -45,17 +44,7 @@ enum class SpecialPermission { IgnoreBatteryOptimization { override fun getIntentForRequest(context: Context): Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val withPackage = - Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { - data = Uri.parse("package:${context.packageName}") - } - val withoutPackage = - Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) - - listOf( - withPackage, - withoutPackage - ).find { it.resolveActivity(context.packageManager) != null }!! + Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS) } else { //this intent won't be launched Intent() diff --git a/app/src/main/kotlin/com/joeloewi/croissant/util/TimeAndTimePicker.kt b/app/src/main/kotlin/com/joeloewi/croissant/util/TimeAndTimePicker.kt index 0f46f05a..43b526d2 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/util/TimeAndTimePicker.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/util/TimeAndTimePicker.kt @@ -31,9 +31,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties +import com.joeloewi.croissant.R import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.format.FormatStyle @@ -47,29 +49,30 @@ fun TimeAndTimePicker( onHourOfDayChange: (Int) -> Unit, onMinuteChange: (Int) -> Unit ) { - var showTimePicker by remember { mutableStateOf(false) } - val state = rememberTimePickerState( - initialHour = hourOfDay(), - initialMinute = minute() - ) val configuration = LocalConfiguration.current - var showingPicker by remember { mutableStateOf(true) } - val localTime by remember { - derivedStateOf { - ZonedDateTime.now().withHour(hourOfDay()).withMinute(minute()) - .toLocalTime() - .format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)) - } - } + var showTimePicker by remember { mutableStateOf(false) } TextButton( modifier = modifier, onClick = { showTimePicker = true } ) { + val localTime by remember { + derivedStateOf { + ZonedDateTime.now().withHour(hourOfDay()).withMinute(minute()) + .toLocalTime() + .format(DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)) + } + } + Text(text = localTime) } if (showTimePicker) { + var showingPicker by remember { mutableStateOf(true) } + val state = rememberTimePickerState( + initialHour = hourOfDay(), + initialMinute = minute() + ) TimePickerDialog( onCancel = { showTimePicker = false }, onConfirm = { @@ -109,7 +112,7 @@ fun TimeAndTimePicker( @Composable fun TimePickerDialog( - title: String = "Select Time", + title: String = stringResource(id = R.string.select_time), onCancel: () -> Unit, onConfirm: () -> Unit, toggle: @Composable () -> Unit = {}, @@ -154,12 +157,12 @@ fun TimePickerDialog( TextButton( onClick = onCancel ) { - Text("Cancel") + Text(stringResource(id = R.string.dismiss)) } TextButton( onClick = onConfirm ) { - Text("OK") + Text(stringResource(id = R.string.confirm)) } } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/CreateResinStatusWidgetViewModel.kt b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/CreateResinStatusWidgetViewModel.kt index 616b2458..23e0faf1 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/CreateResinStatusWidgetViewModel.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/CreateResinStatusWidgetViewModel.kt @@ -26,7 +26,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.concurrent.TimeUnit @@ -70,7 +69,7 @@ class CreateResinStatusWidgetViewModel @Inject constructor( } fun setInterval(interval: Long) { - _interval.update { interval } + _interval.value = interval } fun configureAppWidget() { diff --git a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/RedemptionCodesViewModel.kt b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/RedemptionCodesViewModel.kt index 0ae595c4..ed570df7 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/RedemptionCodesViewModel.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/viewmodel/RedemptionCodesViewModel.kt @@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope import com.joeloewi.croissant.domain.common.HoYoLABGame import com.joeloewi.croissant.domain.usecase.ArcaLiveAppUseCase import com.joeloewi.croissant.state.LCE +import com.joeloewi.croissant.state.foldAsLce import com.joeloewi.croissant.util.toAnnotatedString import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList @@ -18,7 +19,6 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jsoup.Jsoup @@ -39,31 +39,22 @@ class RedemptionCodesViewModel @Inject constructor( } fun getRedemptionCodes() { - _hoYoLABGameRedemptionCodesState.update { LCE.Loading } + _hoYoLABGameRedemptionCodesState.value = LCE.Loading viewModelScope.launch(Dispatchers.IO) { - _hoYoLABGameRedemptionCodesState.update { - HoYoLABGame.entries.filter { - !listOf(HoYoLABGame.Unknown, HoYoLABGame.TearsOfThemis).contains(it) - }.runCatching { - map { - async(SupervisorJob() + Dispatchers.IO) { - it to HtmlCompat.fromHtml( - getRedemptionCodesFromHtml(it).getOrThrow(), - HtmlCompat.FROM_HTML_MODE_COMPACT - ).toAnnotatedString() - } + _hoYoLABGameRedemptionCodesState.value = HoYoLABGame.entries.filter { + !listOf(HoYoLABGame.Unknown, HoYoLABGame.TearsOfThemis).contains(it) + }.runCatching { + map { + async(SupervisorJob() + Dispatchers.IO) { + it to HtmlCompat.fromHtml( + getRedemptionCodesFromHtml(it).getOrThrow(), + HtmlCompat.FROM_HTML_MODE_COMPACT + ).toAnnotatedString() } - }.mapCatching { - it.awaitAll().toImmutableList() - }.fold( - onSuccess = { - LCE.Content(it) - }, - onFailure = { - LCE.Error(it) - } - ) - } + } + }.mapCatching { + it.awaitAll().toImmutableList() + }.foldAsLce() } } @@ -107,7 +98,7 @@ class RedemptionCodesViewModel @Inject constructor( repeat(9) { select("p:last-child").remove() } - }.select("p:nth-child(n+49)").html().replace("https://oo.pe/", "") + }.select("p:nth-child(n+46)").html().replace("https://oo.pe/", "") } } diff --git a/app/src/main/kotlin/com/joeloewi/croissant/worker/RefreshResinStatusWorker.kt b/app/src/main/kotlin/com/joeloewi/croissant/worker/RefreshResinStatusWorker.kt index c68055d3..3937050e 100644 --- a/app/src/main/kotlin/com/joeloewi/croissant/worker/RefreshResinStatusWorker.kt +++ b/app/src/main/kotlin/com/joeloewi/croissant/worker/RefreshResinStatusWorker.kt @@ -1,41 +1,27 @@ package com.joeloewi.croissant.worker -import android.app.PendingIntent import android.appwidget.AppWidgetManager import android.content.Context -import android.content.Intent -import android.os.Build import android.os.PowerManager -import android.provider.Settings -import android.view.View -import android.widget.RemoteViews -import androidx.core.os.bundleOf import androidx.hilt.work.HiltWorker import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.google.firebase.Firebase import com.google.firebase.crashlytics.crashlytics -import com.joeloewi.croissant.R import com.joeloewi.croissant.domain.common.HoYoLABGame import com.joeloewi.croissant.domain.entity.DataSwitch import com.joeloewi.croissant.domain.usecase.HoYoLABUseCase import com.joeloewi.croissant.domain.usecase.ResinStatusWidgetUseCase -import com.joeloewi.croissant.receiver.ResinStatusWidgetProvider -import com.joeloewi.croissant.service.RemoteViewsFactoryService -import com.joeloewi.croissant.util.ListRemoteViewsFactory import com.joeloewi.croissant.util.ResinStatus -import com.joeloewi.croissant.util.pendingIntentFlagUpdateCurrent +import com.joeloewi.croissant.util.createContentRemoteViews +import com.joeloewi.croissant.util.createErrorDueToPowerSaveModeRemoteViews +import com.joeloewi.croissant.util.createLoadingRemoteViews +import com.joeloewi.croissant.util.createUnknownErrorRemoteViews import dagger.assisted.Assisted import dagger.assisted.AssistedInject import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.coroutines.withContext -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle @HiltWorker class RefreshResinStatusWorker @AssistedInject constructor( @@ -58,165 +44,68 @@ class RefreshResinStatusWorker @AssistedInject constructor( runCatching { if (powerManager.isInteractive) { //loading view - RemoteViews( - context.packageName, - R.layout.widget_resin_status_loading - ).also { remoteViews -> - _appWidgetManager.updateAppWidget( - _appWidgetId, - remoteViews - ) - } + _appWidgetManager.updateAppWidget( + _appWidgetId, + createLoadingRemoteViews(context) + ) val resinStatusWidgetWithAccounts = getOneByAppWidgetIdResinStatusWidgetUseCase(_appWidgetId) - val resinStatuses = - resinStatusWidgetWithAccounts.accounts.map { account -> - async(Dispatchers.IO) { - getGameRecordCardHoYoLABUseCase.runCatching { - invoke( - cookie = account.cookie, - uid = account.uid - ).getOrThrow()?.list - }.mapCatching { gameRecords -> - gameRecords?.find { gameRecord -> - HoYoLABGame.findByGameId(gameRecord.gameId) == HoYoLABGame.GenshinImpact - }!! - }.mapCatching { gameRecord -> - val isDailyNoteEnabled = - gameRecord.dataSwitches.find { it.switchId == DataSwitch.GENSHIN_IMPACT_DAILY_NOTE_SWITCH_ID }?.isPublic - - if (isDailyNoteEnabled == false) { - changeDataSwitchHoYoLABUseCase( - cookie = account.cookie, - switchId = DataSwitch.GENSHIN_IMPACT_DAILY_NOTE_SWITCH_ID, - isPublic = true, - gameId = gameRecord.gameId, - ).getOrThrow() - } - - val genshinDailyNote = getGenshinDailyNoteHoYoLABUseCase( - cookie = account.cookie, - server = gameRecord.region, - roleId = gameRecord.gameRoleId - ).getOrThrow() - - ResinStatus( - id = account.id, - nickname = gameRecord.nickname, - currentResin = genshinDailyNote?.currentResin - ?: 0, - maxResin = genshinDailyNote?.maxResin - ?: 0 - ) - }.fold( - onSuccess = { - it - }, - onFailure = { cause -> - if (cause is CancellationException) { - throw cause - } - ResinStatus() - } - ) + val resinStatuses = resinStatusWidgetWithAccounts.accounts.map { account -> + getGameRecordCardHoYoLABUseCase.runCatching { + invoke( + cookie = account.cookie, + uid = account.uid + ).getOrThrow()?.list + }.mapCatching { gameRecords -> + gameRecords?.find { gameRecord -> + HoYoLABGame.findByGameId(gameRecord.gameId) == HoYoLABGame.GenshinImpact + }!! + }.mapCatching { gameRecord -> + val isDailyNoteEnabled = + gameRecord.dataSwitches.find { it.switchId == DataSwitch.GENSHIN_IMPACT_DAILY_NOTE_SWITCH_ID }?.isPublic + + if (isDailyNoteEnabled == false) { + changeDataSwitchHoYoLABUseCase( + cookie = account.cookie, + switchId = DataSwitch.GENSHIN_IMPACT_DAILY_NOTE_SWITCH_ID, + isPublic = true, + gameId = gameRecord.gameId, + ).getOrThrow() } - }.awaitAll() - - RemoteViews( - context.packageName, - R.layout.widget_resin_status - ).apply { - //set timestamp - val dateTimeFormatter = - DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT) - - val localDateTime = - Instant.now() - .atZone(ZoneId.systemDefault()) - .toLocalDateTime() - val readableTimestamp = dateTimeFormatter.format(localDateTime) - setTextViewText(R.id.widget_timestamp, readableTimestamp) - - //set click listener - setOnClickPendingIntent( - R.id.widget_refresh, - PendingIntent.getBroadcast( - context, - _appWidgetId, - Intent( - context, - ResinStatusWidgetProvider::class.java - ).apply { - action = AppWidgetManager.ACTION_APPWIDGET_UPDATE - - putExtra( - AppWidgetManager.EXTRA_APPWIDGET_IDS, - intArrayOf(_appWidgetId) - ) - }, - pendingIntentFlagUpdateCurrent + val genshinDailyNote = getGenshinDailyNoteHoYoLABUseCase( + cookie = account.cookie, + server = gameRecord.region, + roleId = gameRecord.gameRoleId + ).getOrThrow() + + ResinStatus( + id = account.id, + nickname = gameRecord.nickname, + currentResin = genshinDailyNote?.currentResin + ?: 0, + maxResin = genshinDailyNote?.maxResin + ?: 0 ) - ) - - //set resin status - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val items = RemoteViews.RemoteCollectionItems.Builder() - .apply { - resinStatuses.forEach { - addItem( - it.id, - RemoteViews( - context.packageName, - android.R.layout.two_line_list_item - ).apply { - setTextViewText( - android.R.id.text1, - it.nickname - ) - setTextViewText( - android.R.id.text2, - "${it.currentResin} / ${it.maxResin}" - ) - } - ) - } + }.fold( + onSuccess = { + it + }, + onFailure = { cause -> + if (cause is CancellationException) { + throw cause } - .setViewTypeCount(1) - .setHasStableIds(false) - .build() - - setRemoteAdapter( - R.id.resin_statuses, - items - ) - } else { - val serviceIntent = Intent( - context, - RemoteViewsFactoryService::class.java - ) - - val extrasBundle = bundleOf().apply { - putParcelableArrayList( - ListRemoteViewsFactory.RESIN_STATUSES, - ArrayList(resinStatuses) - ) + ResinStatus() } - - serviceIntent.putExtra(ListRemoteViewsFactory.BUNDLE, extrasBundle) - - setRemoteAdapter( - R.id.resin_statuses, serviceIntent - ) - } - }.let { remoteViews -> - _appWidgetManager.updateAppWidget( - _appWidgetId, - remoteViews ) } + + _appWidgetManager.updateAppWidget( + _appWidgetId, + createContentRemoteViews(context, _appWidgetId, resinStatuses) + ) } }.fold( onSuccess = { @@ -234,84 +123,18 @@ class RefreshResinStatusWorker @AssistedInject constructor( recordException(cause) } - if (powerManager.isPowerSaveMode) { - RemoteViews( - context.packageName, - R.layout.widget_resin_status_battery_optimization_enabled - ).apply { - setOnClickPendingIntent( - R.id.button_retry, - PendingIntent.getBroadcast( - context, - _appWidgetId, - Intent( - context, - ResinStatusWidgetProvider::class.java - ).apply { - action = AppWidgetManager.ACTION_APPWIDGET_UPDATE - - putExtra( - AppWidgetManager.EXTRA_APPWIDGET_IDS, - intArrayOf(_appWidgetId) - ) - }, - pendingIntentFlagUpdateCurrent - ) - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setOnClickPendingIntent( - R.id.button_change_setting, - PendingIntent.getActivity( - context, - _appWidgetId, - Intent( - Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS - ), - pendingIntentFlagUpdateCurrent - ) - ) - } else { - setViewVisibility(R.id.button_change_setting, View.INVISIBLE) - } - }.also { remoteViews -> - _appWidgetManager?.updateAppWidget( - _appWidgetId, - remoteViews - ) - } + val remoteViews = if (powerManager.isPowerSaveMode) { + createErrorDueToPowerSaveModeRemoteViews(context, _appWidgetId) } else { //error view - RemoteViews( - context.packageName, - R.layout.widget_resin_status_error - ).apply { - setOnClickPendingIntent( - R.id.button_retry, - PendingIntent.getBroadcast( - context, - _appWidgetId, - Intent( - context, - ResinStatusWidgetProvider::class.java - ).apply { - action = AppWidgetManager.ACTION_APPWIDGET_UPDATE - - putExtra( - AppWidgetManager.EXTRA_APPWIDGET_IDS, - intArrayOf(_appWidgetId) - ) - }, - pendingIntentFlagUpdateCurrent - ) - ) - }.also { remoteViews -> - _appWidgetManager.updateAppWidget( - _appWidgetId, - remoteViews - ) - } + createUnknownErrorRemoteViews(context, _appWidgetId) } + _appWidgetManager?.updateAppWidget( + _appWidgetId, + remoteViews + ) + Result.failure() } ) diff --git a/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml b/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml deleted file mode 100644 index ce6a6b7c..00000000 --- a/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/widget_resin_status_battery_optimization_enabled.xml b/app/src/main/res/layout/widget_resin_status_battery_optimization_enabled.xml index 0833d665..0f2ade24 100644 --- a/app/src/main/res/layout/widget_resin_status_battery_optimization_enabled.xml +++ b/app/src/main/res/layout/widget_resin_status_battery_optimization_enabled.xml @@ -10,14 +10,12 @@ android:theme="@style/Theme.Croissant.AppWidgetContainer"> In this step, HoYoLAB screen will be closed automatically after cookie is identified and will move to next step. if there is no reaction after login, please press check button which is located in upper right. Caution - Login via SNS account is not supported. Not supported game detected. Please wait for next update. Choose at least one game Next step @@ -36,7 +35,6 @@ Theme Dark theme Refresh interval - %1$d selected Widget configuration Always use dark theme Failure @@ -61,28 +59,22 @@ Type time Choose time your attendance be executed everyday. Note - First execution is for reference Create attendance Unsaved contents will be disappeared. Continue? Game to attend Schedule time setting Execution log summary Execution log - Execution log is empty Session validation All execution log will be deleted. Contuinue? - No attendance Attendance is empty You can attend event by creating attendance job - Everyday %1$s : %2$s Cookie is not identified. Please login again. Certification error The Website you try to access has error in certification. Continue? Account to check resin Please click right bottom button and login HoYoLAB. - Latest version of app is downloading - Installing will be executed automatically after download. %1$s \'s attendance job The account you\'ve login is already exists in attendance job. Do you want to see details of your attedance job? Session validation failed @@ -93,34 +85,21 @@ You can attend HoYoLAB Check in event automatically or check genshin impact resin by simple settings. Before start using this app, following permissions should be granted. Grant permissions and start - Receive notification has been granted %1$s Retry Widget will not be updated while battery optimization is enabled for croissant Device rooting dectected. App wiil be closed. - Battery optimization for this app is enabled. In this mode, almost features are not working properly. Please disable battery optimization. Confirm button will locate to system settings. Change setting Login via SNS account may occurs some errors. Facebook login is not supported. - At version 1.0.9, problem which not executed at user\'s desire time was modified. If you have made attendance job previous version, please delete and create new job (recommended) or modify in detail screen. - Do not show again - Location - Seoul, South Korea - Careers - Now - Currently looking for a job - Jongdal Lab Websites Contacts Email Others Developer information Android app developer - 1 year, 9 months attendance execution notification Attendance job is running - App can\'t schedule exact alarm, which is essential for scheduling attendance job. Please allow setting alarms and reminders. Confirm button will locate to system settings. New cookie was found. Press save button which is in the upper-right to commit changes. Attendance failed due to unknown error. Please try manually. - Please select at least one game. Contents may not be shown due to web site\'s policy even after retrying %1$d item(s) deleted This screen can be shown again to request new permissions @@ -136,4 +115,5 @@ Prevent problems that can caused by power saver mode Disable app hibernation Prevent problems that can happen when system treat this app as unused app + Select time \ No newline at end of file diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index 57e5679b..54407a6d 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -24,7 +24,6 @@ 로그인 중 접속정보가 확인되면 웹 화면이 자동으로 닫히고 다음 단계로 진행됩니다. 로그인 완료 후에도 진행되지 않는다면 웹 화면의 우측 상단 체크 버튼을 눌러주세요. 경고 - SNS 계정을 통한 로그인은 지원되지 않으니 HoYoLAB 계정으로 로그인하시기 바랍니다. 지원되지 않는 게임이 있습니다. 추후 업데이트를 기다려주세요. 한 개 이상의 게임을 선택해주세요. 다음 단계로 @@ -44,7 +43,6 @@ 오늘 내일 참고 - 위 최초 실행 시점은 출석 작업 작성완료 시점으로 참고용입니다. 출석 작업 만들기 이전 화면으로 돌아가게 되면 저장완료되지 않은 내용은 사라집니다. 계속하시겠습니까? %1$s의 출석 작업 @@ -58,13 +56,11 @@ 출석 작업 접속 정보 유효성 검사 실행 기록 - 실행 기록이 없습니다. 현재 화면의 실행기록들이 모두 삭제됩니다. 계속하시겠습니까? 성공 실패 저장된 출석 작업이 없습니다. 출석 작업을 만들어 HoYoLAB 출석 이벤트에 참여할 수 있습니다. - 매일 %1$s : %2$s 접속정보가 정확하지 않습니다. 다시 로그인해주세요. 인증서 오류 방문하려는 웹사이트 @@ -73,17 +69,13 @@ 어두운 테마 시스템 설정과 상관없이 항상 어두운 테마를 사용합니다. 위젯 설정 - %1$d개 선택됨 새로고침 간격 %1$d 분 레진을 확인할 계정 - 저장된 출석 작업이 없습니다. 우측 하단의 버튼을 눌러 HoYoLAB에 로그인해주세요. 수정 완료 저장 중 잠시만 기다려주세요. - 새로운 버전의 앱 다운로드 중 - 다운로드 완료 후 자동으로 설치를 진행합니다. 접속 정보 유효성 검사 실패 상세 화면에서 접속 정보를 갱신해주세요. 시간대 변경 감지 @@ -92,34 +84,21 @@ 크루아상 시작하기 간편한 설정을 통해 HoYoLAB 자동 출석체크 및 원신 레진 알림 위젯 기능을 사용할 수 있습니다. 시작하기 전에 다음 권한에 대한 허가가 필요합니다. - 알림 수신에 동의하였습니다. %1$s 다시시도 앱이 배터리 최적화 대상으로 설정되어있어 위젯을 업데이트 할 수 없습니다. 루팅이 감지되었습니다. 앱을 종료합니다. - 현재 시스템 설정에서는 백그라운드 작업이 제한되어 기능이 원활히 동작하지 않습니다. 확인을 눌러 표시되는 화면의 모든 앱 중 크루아상을 찾아 최적화를 해제하여 주세요. 설정 바꾸기 SNS 로그인은 오류가 발생할 수 있으며 페이스북 로그인은 지원되지 않습니다. - 1.0.9버전에서 자동 출석체크가 설정된 시간에 동작되지 않는 문제를 수정하였습니다. 이전 버전에서 출석 작업을 만드셨다면 삭제 후 다시 만들어주시거나(권장) 또는 상세화면에서 수정하시면 새로운 방식으로 사용하실 수 있습니다. - 다시 보지 않기 - 위치 - 대한민국 서울 - 경력 - 현재 - 구직 중 - 종달랩 웹사이트 연락처 이메일 기타 개발자 정보 안드로이드 앱 개발자 - 1년 9개월 출석 실행 알림 출석 작업 실행 중 - 출석 작업을 예약할 때 필수적인 기능이 허용되어 있지 않습니다. 확인을 눌러 표시되는 화면의 모든 앱 중 크루아상을 찾아 최적화를 해제하여 주세요. 새로운 접속 정보가 확인되었습니다. 우측 상단의 저장버튼을 눌러 적용해주세요. 알 수 없는 문제로 인해 출석하지 못했습니다. 수동으로 수행해주세요. - 최소 한 개 이상의 게임을 선택해주세요. 원본 웹사이트의 정책으로 인해 크롤링이 차단되어 다시시도 이후에도 내용이 표시되지 않을 수 있습니다. %1$d 개 삭제됨 이 화면은 새로운 권한 요청 등의 이유로 다시 나타날 수 있습니다. @@ -135,4 +114,5 @@ 절전모드 등으로 인해 출석이 안 될 수도 있는 문제를 방지합니다. 앱 최대 절전모드 비활성화 앱을 오랫동안 실행하지 않아 출석이 안 될 수도 있는 문제를 방지합니다. + 시간 설정 \ No newline at end of file diff --git a/app/src/main/res/values-night/theme_overlays.xml b/app/src/main/res/values-night/theme_overlays.xml new file mode 100644 index 00000000..d85ea0f3 --- /dev/null +++ b/app/src/main/res/values-night/theme_overlays.xml @@ -0,0 +1,130 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 00595691..a1096990 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,69 +1,101 @@ - + \ No newline at end of file diff --git a/app/src/main/res/values-v31/styles.xml b/app/src/main/res/values-v31/styles.xml deleted file mode 100644 index 018686d1..00000000 --- a/app/src/main/res/values-v31/styles.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml deleted file mode 100644 index 3d37fe8f..00000000 --- a/app/src/main/res/values-v31/themes.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 12b492e1..35f126d4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,62 +1,145 @@ #657EF8 - #3852CB - #FFFFFF - #DEE1FF - #001258 - #5A5D72 - #FFFFFF - #DFE1F9 - #171A2C - #76546E - #FFFFFF - #FFD7F2 - #2D1228 - #BA1A1A - #FFDAD6 - #FFFFFF - #410002 - #FEFBFF - #1B1B1F - #FEFBFF - #1B1B1F - #E3E1EC - #46464F - #767680 - #F3F0F4 - #303034 - #B9C3FF - #000000 - #3852CB - #3852CB - #B9C3FF - #00218C - #1838B3 - #DEE1FF - #C3C5DD - #2C2F42 - #434659 - #DFE1F9 - #E5BAD8 - #44263E - #5D3C55 - #FFD7F2 - #FFB4AB - #93000A - #690005 - #FFDAD6 - #1B1B1F - #E4E1E6 - #1B1B1F - #E4E1E6 - #46464F - #C6C5D0 - #90909A - #1B1B1F - #E4E1E6 - #3852CB - #000000 - #B9C3FF - #B9C3FF + #505B92 + #FFFFFF + #DEE1FF + #09164B + #5A5D72 + #FFFFFF + #DFE1F9 + #171A2C + #76546E + #FFFFFF + #FFD7F2 + #2D1228 + #BA1A1A + #FFFFFF + #FFDAD6 + #410002 + #FBF8FF + #1B1B21 + #FBF8FF + #1B1B21 + #E3E1EC + #46464F + #767680 + #C6C5D0 + #000000 + #303036 + #F2F0F7 + #B9C3FF + #DEE1FF + #09164B + #B9C3FF + #384379 + #DFE1F9 + #171A2C + #C3C5DD + #434659 + #FFD7F2 + #2D1228 + #E5BAD8 + #5D3C55 + #DBD9E0 + #FBF8FF + #FFFFFF + #F5F2FA + #EFEDF4 + #E9E7EF + #E3E1E9 + #343F74 + #FFFFFF + #6771AA + #FFFFFF + #3F4255 + #FFFFFF + #717389 + #FFFFFF + #583951 + #FFFFFF + #8E6984 + #FFFFFF + #8C0009 + #FFFFFF + #DA342E + #FFFFFF + #FBF8FF + #1B1B21 + #FBF8FF + #1B1B21 + #E3E1EC + #42424B + #5E5E67 + #7A7A83 + #000000 + #303036 + #F2F0F7 + #B9C3FF + #6771AA + #FFFFFF + #4E5890 + #FFFFFF + #717389 + #FFFFFF + #585B6F + #FFFFFF + #8E6984 + #FFFFFF + #74516B + #FFFFFF + #DBD9E0 + #FBF8FF + #FFFFFF + #F5F2FA + #EFEDF4 + #E9E7EF + #E3E1E9 + #111D52 + #FFFFFF + #343F74 + #FFFFFF + #1E2133 + #FFFFFF + #3F4255 + #FFFFFF + #34182F + #FFFFFF + #583951 + #FFFFFF + #4E0002 + #FFFFFF + #8C0009 + #FFFFFF + #FBF8FF + #1B1B21 + #FBF8FF + #000000 + #E3E1EC + #22232B + #42424B + #42424B + #000000 + #303036 + #FFFFFF + #EAEAFF + #343F74 + #FFFFFF + #1D285D + #FFFFFF + #3F4255 + #FFFFFF + #282C3E + #FFFFFF + #583951 + #FFFFFF + #40233A + #FFFFFF + #DBD9E0 + #FBF8FF + #FFFFFF + #F5F2FA + #EFEDF4 + #E9E7EF + #E3E1E9 \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml deleted file mode 100644 index 471b6462..00000000 --- a/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - 0dp - 48dp - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f25686b..6281b8d7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,7 +29,6 @@ In this step, HoYoLAB screen will be closed automatically after cookie is identified and will move to next step. if there is no reaction after login, please press check button which is located in upper right. Caution - Login via SNS account is not supported. Not supported game detected. Please wait for next update. Choose at least one game Next step @@ -41,7 +40,6 @@ Theme Dark theme Refresh interval - %1$d selected Widget configuration Always use dark theme Failure @@ -66,28 +64,22 @@ Type time Choose time your attendance be executed everyday. Note - First execution is for reference Create attendance Unsaved contents will be disappeared. Continue? Game to attend Schedule time setting Execution log summary Execution log - Execution log is empty Session validation All execution log will be deleted. Contuinue? - No attendance Attendance is empty You can attend event by creating attendance job - Everyday %1$s : %2$s Cookie is not identified. Please login again. Certification error The Website you try to access has error in certification. Continue? Account to check resin Please click right bottom button and login HoYoLAB. - Latest version of app is downloading - Installing will be executed automatically after download. %1$s \'s attendance job The account you\'ve login is already exists in attendance job. Do you want to see details of your attedance job? Session validation failed @@ -98,33 +90,20 @@ You can attend HoYoLAB Check in event automatically or check genshin impact resin by simple settings. Before start using this app, following permissions should be granted. Grant permissions and start - Receive notification has been granted %1$s retry Widget will not be updated while battery optimization is enabled for croissant Device rooting dectected. App wiil be closed. - Battery optimization for this app is enabled. In this mode, almost features are not working properly. Please disable battery optimization. Confirm button will locate to system settings. Change setting Login via SNS account may occurs some errors. Facebook login is not supported. - At version 1.0.9, problem which not executed at user\'s desire time was modified. If you have made attendance job previous version, please delete and create new job (recommended) or modify in detail screen. - Do not show again - Location - Seoul, South Korea - Careers - Now - Currently looking for a job - Jongdal Lab Websites Contacts Email Others Developer information Android app developer - 1 year, 9 months Attendance job is running - App can\'t schedule exact alarm, which is essential for scheduling attendance job. Please allow setting alarms and reminders. Confirm button will locate to system settings. New cookie was found. Press save button which is in the upper-right to commit changes. Attendance failed due to unknown error. Please try manually. - Please select at least one game. Contents may not be shown due to web site\'s policy even after retrying %1$d item(s) deleted This screen can be shown again to request new permissions @@ -140,4 +119,5 @@ Prevent problems that can caused by power saver mode Disable app hibernation Prevent problems that can happen when system treat this app as unused app + Select time \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index dd5985f9..4193953f 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -8,8 +8,4 @@ @drawable/app_widget_background - \ No newline at end of file diff --git a/app/src/main/res/values/theme_overlays.xml b/app/src/main/res/values/theme_overlays.xml new file mode 100644 index 00000000..e03bf484 --- /dev/null +++ b/app/src/main/res/values/theme_overlays.xml @@ -0,0 +1,130 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index c5b3526d..f1be421f 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,70 +1,102 @@ - + - - \ No newline at end of file diff --git a/baselineprofile/build.gradle.kts b/baselineprofile/build.gradle.kts index 9598d129..00db1a7f 100644 --- a/baselineprofile/build.gradle.kts +++ b/baselineprofile/build.gradle.kts @@ -41,8 +41,7 @@ android { // This is the configuration block for the Baseline Profile plugin. // You can specify to run the generators on a managed devices or connected devices. baselineProfile { - managedDevices += "pixel2Api34" - useConnectedDevices = false + useConnectedDevices = true } dependencies { diff --git a/baselineprofile/src/main/kotlin/com/joeloewi/croissant/baselineprofile/BaselineProfileGenerator.kt b/baselineprofile/src/main/kotlin/com/joeloewi/croissant/baselineprofile/BaselineProfileGenerator.kt index ad3f740a..b117313c 100644 --- a/baselineprofile/src/main/kotlin/com/joeloewi/croissant/baselineprofile/BaselineProfileGenerator.kt +++ b/baselineprofile/src/main/kotlin/com/joeloewi/croissant/baselineprofile/BaselineProfileGenerator.kt @@ -38,14 +38,14 @@ class BaselineProfileGenerator { @Test fun generate() { rule.collect( - packageName = "com.zerodesktop.appdetox.qualitytime", + packageName = "com.joeloewi.croissant", includeInStartupProfile = true ) { pressHome() startActivityAndWait() } - rule.collect("com.zerodesktop.appdetox.qualitytime") { + rule.collect("com.joeloewi.croissant") { // This block defines the app's critical user journey. Here we are interested in // optimizing for app startup. But you can also navigate and scroll // through your most important UI. diff --git a/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/AndroidCompose.kt b/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/AndroidCompose.kt index 7662e69b..9db147a7 100644 --- a/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/AndroidCompose.kt +++ b/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/AndroidCompose.kt @@ -2,9 +2,7 @@ package com.joeloewi.croissant import com.android.build.api.dsl.CommonExtension import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import java.io.File /** @@ -13,8 +11,6 @@ import java.io.File internal fun Project.configureAndroidCompose( commonExtension: CommonExtension<*, *, *, *, *>, ) { - val libs = extensions.getByType().named("libs") - commonExtension.apply { buildFeatures { compose = true diff --git a/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/KotlinAndroid.kt index 14413a7e..37628321 100644 --- a/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/com/joeloewi/croissant/KotlinAndroid.kt @@ -3,10 +3,8 @@ package com.joeloewi.croissant import com.android.build.api.dsl.CommonExtension import org.gradle.api.JavaVersion import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension import org.gradle.api.plugins.ExtensionAware import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions /** @@ -43,8 +41,6 @@ internal fun Project.configureKotlinAndroid( } } - val libs = extensions.getByType().named("libs") - dependencies { "coreLibraryDesugaring"(libs.findLibrary("android.desugarJdkLibs").get()) diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/CheckInService.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/CheckInService.kt index 13106310..47121328 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/CheckInService.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/CheckInService.kt @@ -33,13 +33,6 @@ interface CheckInService { @Header("Cookie") cookie: String ): Call> - @POST("event/sol/sign") - fun attendCheckInGenshinImpact( - @Query("act_id") actId: String = "e202102251931481", - @Query("lang") language: String = Locale.getDefault().toLanguageTag().lowercase(), - @Header("Cookie") cookie: String - ): Call> - @POST("event/mani/sign") fun attendCheckInHonkaiImpact3rd( @Query("act_id") actId: String = "e202110291205111", diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/GenshinImpactCheckInService.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/GenshinImpactCheckInService.kt new file mode 100644 index 00000000..80a7a620 --- /dev/null +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/GenshinImpactCheckInService.kt @@ -0,0 +1,20 @@ +package com.joeloewi.croissant.data.api.dao + +import com.joeloewi.croissant.data.api.model.response.AttendanceResponse +import com.skydoves.sandwich.ApiResponse +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.POST +import retrofit2.http.Query +import java.util.Locale + +interface GenshinImpactCheckInService { + + @POST("event/sol/sign") + fun attend( + @Query("lang") language: String = Locale.getDefault().toLanguageTag().lowercase(), + @Header("Cookie") cookie: String, + @Body params: Map = mapOf("act_id" to "e202102251931481") + ): Call> +} \ No newline at end of file diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/HoYoLABService.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/HoYoLABService.kt index f95b5899..a02ebb6c 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/HoYoLABService.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/api/dao/HoYoLABService.kt @@ -26,11 +26,15 @@ import com.joeloewi.croissant.data.common.generateDS import com.skydoves.sandwich.ApiResponse import retrofit2.Call import retrofit2.http.* +import java.time.Instant interface HoYoLABService { - @GET("community/user/wapi/getUserFullInfo") - fun getUserFullInfo(@Header("Cookie") cookie: String): Call> + @GET("community/painter/wapi/user/full") + fun getUserFullInfo( + @Header("Cookie") cookie: String, + @Query("t") currentMillis: Long = Instant.now().toEpochMilli() + ): Call> @GET("game_record/card/wapi/getGameRecordCard") fun getGameRecordCard( diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/common/GameIntentGenerator.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/common/GameIntentGenerator.kt index 05e09360..0ed4ba8c 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/common/GameIntentGenerator.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/common/GameIntentGenerator.kt @@ -42,7 +42,7 @@ fun generateGameIntent( context.packageManager.getLaunchIntentForPackage(it.first) ?: if (it.second.authority?.contains("taptap.io") == true) { //for chinese server - Intent(Intent.ACTION_VIEW, it.second) + return@let Intent(Intent.ACTION_VIEW, it.second) } else { Intent(Intent.ACTION_VIEW).apply { addCategory(Intent.CATEGORY_DEFAULT) diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/di/ApiModule.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/di/ApiModule.kt index 2b2ce5fe..98af7d51 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/di/ApiModule.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/di/ApiModule.kt @@ -63,10 +63,9 @@ object ApiModule { }) .run { if (BuildConfig.DEBUG) { - addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) - } else { - this + return@run addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) } + return@run this } .build() @@ -117,6 +116,14 @@ object ApiModule { .build() .create() + @Singleton + @Provides + fun provideGenshinImpactCheckInService(retrofitBuilder: Retrofit.Builder): GenshinImpactCheckInService = + retrofitBuilder + .baseUrl("https://sg-hk4e-api.hoyolab.com/") + .build() + .create() + @Singleton @Provides fun provideCommonCheckInService(retrofitBuilder: Retrofit.Builder): CheckInService = diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/repository/remote/impl/CheckInDataSourceImpl.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/repository/remote/impl/CheckInDataSourceImpl.kt index e3e3a1bd..b9dfce81 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/repository/remote/impl/CheckInDataSourceImpl.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/repository/remote/impl/CheckInDataSourceImpl.kt @@ -17,6 +17,7 @@ package com.joeloewi.croissant.data.repository.remote.impl import com.joeloewi.croissant.data.api.dao.CheckInService +import com.joeloewi.croissant.data.api.dao.GenshinImpactCheckInService import com.joeloewi.croissant.data.api.model.response.AttendanceResponse import com.joeloewi.croissant.data.repository.remote.CheckInDataSource import com.joeloewi.croissant.data.util.executeAndAwait @@ -24,7 +25,8 @@ import com.skydoves.sandwich.ApiResponse import javax.inject.Inject class CheckInDataSourceImpl @Inject constructor( - private val checkInService: CheckInService + private val checkInService: CheckInService, + private val genshinImpactCheckInService: GenshinImpactCheckInService ) : CheckInDataSource { override suspend fun attend(actId: String, cookie: String): ApiResponse = @@ -33,7 +35,7 @@ class CheckInDataSourceImpl @Inject constructor( override suspend fun attendCheckInGenshinImpact( cookie: String ): ApiResponse = - checkInService.attendCheckInGenshinImpact(cookie = cookie).executeAndAwait() + genshinImpactCheckInService.attend(cookie = cookie).executeAndAwait() override suspend fun attendCheckInHonkaiImpact3rd( cookie: String diff --git a/data/src/main/kotlin/com/joeloewi/croissant/data/system/RootChecker.kt b/data/src/main/kotlin/com/joeloewi/croissant/data/system/RootChecker.kt index 508d2f35..98583c05 100644 --- a/data/src/main/kotlin/com/joeloewi/croissant/data/system/RootChecker.kt +++ b/data/src/main/kotlin/com/joeloewi/croissant/data/system/RootChecker.kt @@ -60,21 +60,21 @@ class RootChecker( suspend fun isDeviceRooted(): Boolean = withContext(Dispatchers.IO) { awaitAll( - async(SupervisorJob()) { + async(SupervisorJob() + Dispatchers.IO) { try { isRootFilesExists() } catch (_: Throwable) { false } }, - async(SupervisorJob()) { + async(SupervisorJob() + Dispatchers.IO) { try { isSUExists() } catch (_: Throwable) { false } }, - async(SupervisorJob()) { + async(SupervisorJob() + Dispatchers.IO) { try { hasRootPackages() } catch (_: Throwable) { @@ -106,14 +106,7 @@ class RootChecker( process = it it.inputStream.bufferedReader(Charset.forName("UTF-8")) .use { reader -> reader.readLine() } != null - }.fold( - onSuccess = { - it - }, - onFailure = { - false - } - ).also { + }.getOrDefault(false).also { process?.destroy() } } @@ -129,13 +122,6 @@ class RootChecker( it.getPackageInfo(pkg, 0) } } - }.fold( - onSuccess = { - true - }, - onFailure = { - false - } - ) + }.getOrNull() != null } } \ No newline at end of file