mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Show a screen while the app migrates after an update
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package eu.kanade.presentation.more
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import tachiyomi.i18n.MR
|
||||
import tachiyomi.presentation.core.i18n.stringResource
|
||||
|
||||
/**
|
||||
* Shown instead of the app while migrations from the previous version run, so a slow first launch
|
||||
* after an update doesn't look like the app hanging.
|
||||
*/
|
||||
@Composable
|
||||
fun AppMigratingScreen() {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.safeDrawingPadding()
|
||||
.padding(horizontal = 32.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterVertically),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
Text(
|
||||
text = stringResource(MR.strings.app_migrating_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(MR.strings.app_migrating_summary),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,7 @@ import eu.kanade.presentation.components.AppStateBanners
|
||||
import eu.kanade.presentation.components.DownloadedOnlyBannerBackgroundColor
|
||||
import eu.kanade.presentation.components.IncognitoModeBannerBackgroundColor
|
||||
import eu.kanade.presentation.components.IndexingBannerBackgroundColor
|
||||
import eu.kanade.presentation.more.AppMigratingScreen
|
||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionStoresScreen
|
||||
import eu.kanade.presentation.more.settings.screen.data.RestoreBackupScreen
|
||||
import eu.kanade.presentation.util.AssistContentScreen
|
||||
@@ -95,6 +96,7 @@ import eu.kanade.tachiyomi.util.system.isNavigationBarNeedsScrim
|
||||
import eu.kanade.tachiyomi.util.system.updaterEnabled
|
||||
import eu.kanade.tachiyomi.util.view.setComposeContent
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.drop
|
||||
@@ -107,6 +109,7 @@ import mihon.app.di.AppGraph
|
||||
import mihon.app.di.appGraph
|
||||
import mihon.core.metro.metroGraph
|
||||
import mihon.core.migration.Migrator
|
||||
import mihon.domain.database.repository.DatabaseRepository
|
||||
import mihon.feature.support.SupportUsScreen
|
||||
import mihon.icons.materialsymbols.MaterialSymbols
|
||||
import mihon.icons.materialsymbols.automirroredrounded.OpenInNew
|
||||
@@ -138,6 +141,10 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
@Inject private lateinit var chapterCache: ChapterCache
|
||||
|
||||
@Inject private lateinit var databaseRepository: DatabaseRepository
|
||||
|
||||
private val isAppMigrating = MutableStateFlow(Migrator.isRunning)
|
||||
|
||||
@Inject private lateinit var getIncognitoState: GetIncognitoState
|
||||
|
||||
@Inject private lateinit var extensionManager: ExtensionManager
|
||||
@@ -160,7 +167,10 @@ class MainActivity : BaseActivity() {
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Migrator.awaitAndRelease()
|
||||
lifecycleScope.launch {
|
||||
Migrator.awaitAndRelease()
|
||||
isAppMigrating.value = false
|
||||
}
|
||||
|
||||
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
|
||||
if (!isTaskRoot) {
|
||||
@@ -169,6 +179,15 @@ class MainActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
setComposeContent {
|
||||
val appMigrating by isAppMigrating.collectAsState()
|
||||
val databaseMigrating by databaseRepository.isMigrating.collectAsState()
|
||||
if (appMigrating || databaseMigrating) {
|
||||
AppMigratingScreen()
|
||||
// Release the splash screen so the reason for the wait shows
|
||||
LaunchedEffect(Unit) { ready = true }
|
||||
return@setComposeContent
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
var incognito by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -5,7 +5,6 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
object Migrator {
|
||||
|
||||
@@ -35,7 +34,10 @@ object Migrator {
|
||||
result = null
|
||||
}
|
||||
|
||||
fun awaitAndRelease(): Boolean = runBlocking {
|
||||
await().also { release() }
|
||||
val isRunning: Boolean
|
||||
get() = result?.isCompleted == false
|
||||
|
||||
suspend fun awaitAndRelease(): Boolean {
|
||||
return await().also { release() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package mihon.data.database
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import mihon.domain.database.repository.DatabaseRepository
|
||||
|
||||
@Inject
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DatabaseRepositoryImpl : DatabaseRepository {
|
||||
|
||||
override val isMigrating = MutableStateFlow(false)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import androidx.sqlite.SQLiteConnection
|
||||
import androidx.sqlite.SQLiteDriver
|
||||
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
|
||||
import androidx.sqlite.execSQL
|
||||
import app.cash.sqldelight.db.QueryResult
|
||||
import app.cash.sqldelight.db.SqlDriver
|
||||
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConcurrencyModel.MultipleReadersSingleWriter
|
||||
import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration
|
||||
@@ -19,6 +20,7 @@ import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import mihon.data.database.DatabaseRepositoryImpl
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@BindingContainer
|
||||
@@ -30,7 +32,7 @@ object DatabaseBindings {
|
||||
*/
|
||||
@Provides
|
||||
@SingleIn(AppScope::class)
|
||||
fun providesSqlDriver(context: Context): SqlDriver {
|
||||
fun providesSqlDriver(context: Context, databaseRepository: DatabaseRepositoryImpl): SqlDriver {
|
||||
val isLowRam = context.getSystemService<ActivityManager>()?.isLowRamDevice == true
|
||||
return AndroidxSqliteDriver(
|
||||
connectionFactory = object : AndroidxSqliteConnectionFactory {
|
||||
@@ -48,6 +50,14 @@ object DatabaseBindings {
|
||||
isForeignKeyConstraintsEnabled = true,
|
||||
concurrencyModel = MultipleReadersSingleWriter(isWal = true, walCount = if (isLowRam) 1 else 4),
|
||||
),
|
||||
// Runs before the driver compares the schema version, and onOpen once the schema is ready
|
||||
onConfigure = {
|
||||
val version = executePragmaQuery("user_version", { QueryResult.Value(it.apply { next() }.getLong(0)) })
|
||||
databaseRepository.isMigrating.value = version in 1..<Database.Schema.version
|
||||
},
|
||||
onOpen = {
|
||||
databaseRepository.isMigrating.value = false
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package mihon.domain.database.repository
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface DatabaseRepository {
|
||||
|
||||
/**
|
||||
* True while the database is being migrated from an older schema, which happens on its first use after an
|
||||
* app update.
|
||||
*/
|
||||
val isMigrating: StateFlow<Boolean>
|
||||
}
|
||||
@@ -188,6 +188,8 @@
|
||||
<string name="onboarding_storage_selection_required">A folder must be selected</string>
|
||||
<string name="onboarding_storage_help_info">Updating from an older version and not sure what to select? Refer to the storage guide for more information.</string>
|
||||
<string name="onboarding_storage_help_action">Storage guide</string>
|
||||
<string name="app_migrating_title">Finishing the update</string>
|
||||
<string name="app_migrating_summary">Getting things ready after the update. This can take a few minutes, so keep the app open.</string>
|
||||
<string name="onboarding_permission_install_apps">Install apps permission</string>
|
||||
<string name="onboarding_permission_install_apps_description">To install source extensions.</string>
|
||||
<string name="onboarding_permission_notifications">Notification permission</string>
|
||||
|
||||
Reference in New Issue
Block a user