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