feat(settings): add troubleshooting page and app preferences import/export tool (#672)
This commit is contained in:
parent
d749107bea
commit
826819a10b
@ -94,12 +94,5 @@ class AccountService @Inject constructor(
|
||||
rssService.get().cancelSync()
|
||||
context.dataStore.put(DataStoreKeys.CurrentAccountId, account.id!!)
|
||||
context.dataStore.put(DataStoreKeys.CurrentAccountType, account.type.id)
|
||||
|
||||
// Restart
|
||||
// context.packageManager.getLaunchIntentForPackage(context.packageName)?.let {
|
||||
// it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
// context.startActivity(it)
|
||||
// android.os.Process.killProcess(android.os.Process.myPid())
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ fun CrashReportPage(
|
||||
val startIndex = msg.indexOf(hyperLinkText)
|
||||
val endIndex = startIndex + hyperLinkText.length
|
||||
addUrlAnnotation(
|
||||
UrlAnnotation("https://github.com/Ashinch/ReadYou/issues/new?assignees=&labels=bug&projects=&template=bug_report.md&title="),
|
||||
UrlAnnotation(stringResource(R.string.issue_tracer_url)),
|
||||
start = startIndex,
|
||||
end = endIndex
|
||||
)
|
||||
@ -226,4 +226,4 @@ fun CrashReportPage(
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,3 +183,9 @@ fun Context.getCustomTabsPackages(): List<String> {
|
||||
return@mapNotNull null
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun Context.getPreferencesFile(): File =
|
||||
File(filesDir.absolutePath + File.separator +
|
||||
"datastore" + File.separator +
|
||||
"settings.preferences_pb"
|
||||
)
|
||||
|
@ -27,9 +27,9 @@ val Context.skipVersionNumber: String
|
||||
val Context.isFirstLaunch: Boolean
|
||||
get() = this.dataStore.get(DataStoreKeys.IsFirstLaunch) ?: true
|
||||
val Context.currentAccountId: Int
|
||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountId)!!
|
||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountId) ?: 1
|
||||
val Context.currentAccountType: Int
|
||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountType)!!
|
||||
get() = this.dataStore.get(DataStoreKeys.CurrentAccountType) ?: 1
|
||||
|
||||
val Context.initialPage: Int
|
||||
get() = this.dataStore.get(DataStoreKeys.InitialPage) ?: 0
|
||||
|
@ -35,4 +35,8 @@ fun File.mkDir() {
|
||||
val newF = File("${dirArray[0]}$pathTemp")
|
||||
if (!newF.exists()) newF.mkdir()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ByteArray.isProbableProtobuf(): Boolean =
|
||||
if (size < 2) false
|
||||
else get(0) == 0x0a.toByte() && get(1) == 0x16.toByte()
|
||||
|
@ -8,7 +8,7 @@ import java.security.MessageDigest
|
||||
object MimeType {
|
||||
|
||||
const val ANY = "*/*"
|
||||
const val FONT = "font/ttf" // Not supported yet
|
||||
const val FONT = "font/ttf"
|
||||
const val OPML = "text/x-opml" // Not supported yet
|
||||
const val JSON = "application/json"
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import me.ash.reader.ui.page.settings.interaction.InteractionPage
|
||||
import me.ash.reader.ui.page.settings.languages.LanguagesPage
|
||||
import me.ash.reader.ui.page.settings.tips.LicenseListPage
|
||||
import me.ash.reader.ui.page.settings.tips.TipsAndSupportPage
|
||||
import me.ash.reader.ui.page.settings.troubleshooting.TroubleshootingPage
|
||||
import me.ash.reader.ui.page.startup.StartupPage
|
||||
import me.ash.reader.ui.theme.AppTheme
|
||||
|
||||
@ -237,6 +238,11 @@ fun HomeEntry(
|
||||
LanguagesPage(navController = navController)
|
||||
}
|
||||
|
||||
// Troubleshooting
|
||||
forwardAndBackwardComposable(route = RouteName.TROUBLESHOOTING) {
|
||||
TroubleshootingPage(navController = navController)
|
||||
}
|
||||
|
||||
// Tips & Support
|
||||
forwardAndBackwardComposable(route = RouteName.TIPS_AND_SUPPORT) {
|
||||
TipsAndSupportPage(navController)
|
||||
|
@ -36,6 +36,9 @@ object RouteName {
|
||||
// Languages
|
||||
const val LANGUAGES = "languages"
|
||||
|
||||
// Troubleshooting
|
||||
const val TROUBLESHOOTING = "troubleshooting"
|
||||
|
||||
// Tips & Support
|
||||
const val TIPS_AND_SUPPORT = "tips_and_support"
|
||||
const val LICENSE_LIST = "license_list"
|
||||
|
@ -35,8 +35,10 @@ import me.ash.reader.ui.component.RenameDialog
|
||||
import me.ash.reader.ui.component.base.ClipboardTextField
|
||||
import me.ash.reader.ui.component.base.RYDialog
|
||||
import me.ash.reader.ui.component.base.TextFieldDialog
|
||||
import me.ash.reader.ui.ext.MimeType
|
||||
import me.ash.reader.ui.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.roundClick
|
||||
import me.ash.reader.ui.ext.showToast
|
||||
import me.ash.reader.ui.page.home.feeds.FeedOptionView
|
||||
|
||||
@OptIn(
|
||||
@ -51,12 +53,12 @@ fun SubscribeDialog(
|
||||
val focusManager = LocalFocusManager.current
|
||||
val subscribeUiState = subscribeViewModel.subscribeUiState.collectAsStateValue()
|
||||
val groupsState = subscribeUiState.groups.collectAsState(initial = emptyList())
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {
|
||||
it?.let { uri ->
|
||||
context.contentResolver.openInputStream(uri)?.let { inputStream ->
|
||||
subscribeViewModel.importFromInputStream(inputStream)
|
||||
}
|
||||
}
|
||||
} ?: context.showToast("Cannot open Input Stream with content resolver")
|
||||
} ?: context.showToast("Cannot get activity result with launcher")
|
||||
}
|
||||
|
||||
LaunchedEffect(subscribeUiState.visible) {
|
||||
@ -180,7 +182,7 @@ fun SubscribeDialog(
|
||||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
launcher.launch("*/*")
|
||||
launcher.launch(arrayOf(MimeType.ANY))
|
||||
subscribeViewModel.hideDrawer()
|
||||
}
|
||||
) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.ash.reader.ui.page.home.feeds.subscribe
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.rometools.rome.feed.synd.SyndFeed
|
||||
@ -59,12 +58,8 @@ class SubscribeViewModel @Inject constructor(
|
||||
|
||||
fun importFromInputStream(inputStream: InputStream) {
|
||||
applicationScope.launch {
|
||||
try {
|
||||
opmlService.saveToDatabase(inputStream)
|
||||
rssService.get().doSync()
|
||||
} catch (e: Exception) {
|
||||
Log.e("FeedsViewModel", "importFromInputStream: ", e)
|
||||
}
|
||||
opmlService.saveToDatabase(inputStream)
|
||||
rssService.get().doSync()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,21 @@
|
||||
package me.ash.reader.ui.page.settings
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.AccountCircle
|
||||
import androidx.compose.material.icons.outlined.BugReport
|
||||
import androidx.compose.material.icons.outlined.Language
|
||||
import androidx.compose.material.icons.outlined.Lightbulb
|
||||
import androidx.compose.material.icons.outlined.Palette
|
||||
import androidx.compose.material.icons.outlined.TipsAndUpdates
|
||||
import androidx.compose.material.icons.outlined.TouchApp
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -17,7 +28,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import me.ash.reader.R
|
||||
@ -134,6 +144,17 @@ fun SettingsPage(
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
SelectableSettingGroupItem(
|
||||
title = stringResource(R.string.troubleshooting),
|
||||
desc = stringResource(R.string.troubleshooting_desc),
|
||||
icon = Icons.Outlined.BugReport,
|
||||
) {
|
||||
navController.navigate(RouteName.TROUBLESHOOTING) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
SelectableSettingGroupItem(
|
||||
title = stringResource(R.string.tips_and_support),
|
||||
|
@ -19,9 +19,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.DeleteSweep
|
||||
import androidx.compose.material.icons.outlined.PersonOff
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@ -61,6 +61,7 @@ import me.ash.reader.ui.component.base.Subtitle
|
||||
import me.ash.reader.ui.component.base.TextFieldDialog
|
||||
import me.ash.reader.ui.component.base.Tips
|
||||
import me.ash.reader.ui.ext.DateFormat
|
||||
import me.ash.reader.ui.ext.MimeType
|
||||
import me.ash.reader.ui.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.getCurrentVersion
|
||||
import me.ash.reader.ui.ext.showToast
|
||||
@ -103,14 +104,14 @@ fun AccountDetailsPage(
|
||||
}
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("*/*")
|
||||
ActivityResultContracts.CreateDocument(MimeType.ANY)
|
||||
) { result ->
|
||||
viewModel.exportAsOPML(selectedAccount!!.id!!) { string ->
|
||||
result?.let { uri ->
|
||||
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
outputStream.write(string.toByteArray())
|
||||
}
|
||||
}
|
||||
} ?: context.showToast("Cannot open Input Stream with content resolver")
|
||||
} ?: context.showToast("Cannot get activity result with launcher")
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,7 +484,7 @@ fun AccountDetailsPage(
|
||||
TextButton(
|
||||
onClick = {
|
||||
exportOPMLModeDialogVisible = false
|
||||
launcherOPMLFile(context, launcher)
|
||||
subscriptionOPMLFileLauncher(context, launcher)
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.export))
|
||||
@ -502,11 +503,11 @@ fun AccountDetailsPage(
|
||||
)
|
||||
}
|
||||
|
||||
private fun launcherOPMLFile(
|
||||
private fun subscriptionOPMLFileLauncher(
|
||||
context: Context,
|
||||
launcher: ManagedActivityResultLauncher<String, Uri?>,
|
||||
) {
|
||||
launcher.launch("Read-You-" +
|
||||
"${context.getCurrentVersion()}-export-" +
|
||||
"${context.getCurrentVersion()}-subscription-" +
|
||||
"${Date().toString(DateFormat.YYYY_MM_DD_DASH_HH_MM_SS_DASH)}.opml")
|
||||
}
|
||||
|
@ -0,0 +1,188 @@
|
||||
package me.ash.reader.ui.page.settings.troubleshooting
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.windowInsetsBottomHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.Info
|
||||
import androidx.compose.material.icons.outlined.ReportGmailerrorred
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import me.ash.reader.R
|
||||
import me.ash.reader.infrastructure.preference.OpenLinkPreference
|
||||
import me.ash.reader.ui.component.base.Banner
|
||||
import me.ash.reader.ui.component.base.DisplayText
|
||||
import me.ash.reader.ui.component.base.FeedbackIconButton
|
||||
import me.ash.reader.ui.component.base.RYDialog
|
||||
import me.ash.reader.ui.component.base.RYScaffold
|
||||
import me.ash.reader.ui.component.base.Subtitle
|
||||
import me.ash.reader.ui.ext.DateFormat
|
||||
import me.ash.reader.ui.ext.MimeType
|
||||
import me.ash.reader.ui.ext.collectAsStateValue
|
||||
import me.ash.reader.ui.ext.getCurrentVersion
|
||||
import me.ash.reader.ui.ext.openURL
|
||||
import me.ash.reader.ui.ext.showToast
|
||||
import me.ash.reader.ui.ext.toString
|
||||
import me.ash.reader.ui.page.settings.SettingItem
|
||||
import me.ash.reader.ui.theme.palette.onLight
|
||||
import java.util.Date
|
||||
|
||||
@Composable
|
||||
fun TroubleshootingPage(
|
||||
navController: NavHostController,
|
||||
viewModel: TroubleshootingViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val uiState = viewModel.troubleshootingUiState.collectAsStateValue()
|
||||
var byteArray by remember { mutableStateOf(ByteArray(0)) }
|
||||
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.CreateDocument(MimeType.ANY)
|
||||
) { result ->
|
||||
viewModel.exportPreferencesAsJSON(context) { byteArray ->
|
||||
result?.let { uri ->
|
||||
context.contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
outputStream.write(byteArray)
|
||||
} ?: context.showToast("Cannot open Input Stream with content resolver")
|
||||
} ?: context.showToast("Cannot get activity result with launcher")
|
||||
}
|
||||
}
|
||||
|
||||
val importLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) {
|
||||
it?.let { uri ->
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
byteArray = inputStream.readBytes()
|
||||
viewModel.tryImport(context, byteArray)
|
||||
} ?: context.showToast("Cannot open Input Stream with content resolver")
|
||||
} ?: context.showToast("Cannot get activity result with launcher")
|
||||
}
|
||||
|
||||
RYScaffold(
|
||||
containerColor = MaterialTheme.colorScheme.surface onLight MaterialTheme.colorScheme.inverseOnSurface,
|
||||
navigationIcon = {
|
||||
FeedbackIconButton(
|
||||
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
) {
|
||||
navController.popBackStack()
|
||||
}
|
||||
},
|
||||
content = {
|
||||
LazyColumn {
|
||||
item {
|
||||
DisplayText(text = stringResource(R.string.troubleshooting), desc = "")
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Banner(
|
||||
title = stringResource(R.string.bug_report),
|
||||
icon = Icons.Outlined.Info,
|
||||
action = {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.KeyboardArrowRight,
|
||||
contentDescription = stringResource(R.string.go_to),
|
||||
)
|
||||
},
|
||||
) {
|
||||
context.openURL(
|
||||
context.getString(R.string.issue_tracer_url),
|
||||
OpenLinkPreference.AutoPreferCustomTabs
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
item {
|
||||
Subtitle(
|
||||
modifier = Modifier.padding(horizontal = 24.dp),
|
||||
text = stringResource(R.string.app_preferences),
|
||||
)
|
||||
SettingItem(
|
||||
title = stringResource(R.string.import_from_protobuf_file),
|
||||
onClick = {
|
||||
importLauncher.launch(arrayOf(MimeType.ANY))
|
||||
},
|
||||
) {}
|
||||
SettingItem(
|
||||
title = stringResource(R.string.export_as_protobuf_file),
|
||||
onClick = {
|
||||
preferenceFileLauncher(context, exportLauncher)
|
||||
},
|
||||
) {}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Spacer(modifier = Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
RYDialog(
|
||||
visible = uiState.warningDialogVisible,
|
||||
onDismissRequest = { viewModel.hideWarningDialog() },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.ReportGmailerrorred,
|
||||
contentDescription = stringResource(R.string.import_from_protobuf_file),
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(text = stringResource(R.string.import_from_protobuf_file))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.invalid_protobuf_file_warning))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.hideWarningDialog()
|
||||
viewModel.importPreferencesFromJSON(context, byteArray)
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { viewModel.hideWarningDialog() }) {
|
||||
Text(text = stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun preferenceFileLauncher(
|
||||
context: Context,
|
||||
launcher: ManagedActivityResultLauncher<String, Uri?>,
|
||||
) {
|
||||
launcher.launch("Read-You-" +
|
||||
"${context.getCurrentVersion()}-settings-" +
|
||||
"${Date().toString(DateFormat.YYYY_MM_DD_DASH_HH_MM_SS_DASH)}.preferences_pb")
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
package me.ash.reader.ui.page.settings.troubleshooting
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import me.ash.reader.domain.service.AccountService
|
||||
import me.ash.reader.domain.service.OpmlService
|
||||
import me.ash.reader.domain.service.RssService
|
||||
import me.ash.reader.infrastructure.di.ApplicationScope
|
||||
import me.ash.reader.infrastructure.di.DefaultDispatcher
|
||||
import me.ash.reader.infrastructure.di.IODispatcher
|
||||
import me.ash.reader.infrastructure.di.MainDispatcher
|
||||
import me.ash.reader.ui.ext.getPreferencesFile
|
||||
import me.ash.reader.ui.ext.isProbableProtobuf
|
||||
import me.ash.reader.ui.ext.restart
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class TroubleshootingViewModel @Inject constructor(
|
||||
private val accountService: AccountService,
|
||||
private val rssService: RssService,
|
||||
private val opmlService: OpmlService,
|
||||
@IODispatcher
|
||||
private val ioDispatcher: CoroutineDispatcher,
|
||||
@DefaultDispatcher
|
||||
private val defaultDispatcher: CoroutineDispatcher,
|
||||
@MainDispatcher
|
||||
private val mainDispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope
|
||||
private val applicationScope: CoroutineScope,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _troubleshootingUiState = MutableStateFlow(TroubleshootingUiState())
|
||||
val troubleshootingUiState: StateFlow<TroubleshootingUiState> =
|
||||
_troubleshootingUiState.asStateFlow()
|
||||
|
||||
fun showWarningDialog() {
|
||||
_troubleshootingUiState.update { it.copy(warningDialogVisible = true) }
|
||||
}
|
||||
|
||||
fun hideWarningDialog() {
|
||||
_troubleshootingUiState.update { it.copy(warningDialogVisible = false) }
|
||||
}
|
||||
|
||||
fun tryImport(context: Context, byteArray: ByteArray) {
|
||||
if (!byteArray.isProbableProtobuf()) {
|
||||
showWarningDialog()
|
||||
} else {
|
||||
importPreferencesFromJSON(context, byteArray)
|
||||
}
|
||||
}
|
||||
|
||||
fun importPreferencesFromJSON(context: Context, byteArray: ByteArray) {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val file = context.getPreferencesFile()
|
||||
if (file.exists()) file.delete()
|
||||
if (file.createNewFile()) file.writeBytes(byteArray)
|
||||
context.restart()
|
||||
}
|
||||
}
|
||||
|
||||
fun exportPreferencesAsJSON(context: Context, callback: (ByteArray) -> Unit = {}) {
|
||||
viewModelScope.launch(ioDispatcher) {
|
||||
val file = context.getPreferencesFile()
|
||||
callback(if (file.exists()) file.readBytes() else byteArrayOf())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class TroubleshootingUiState(
|
||||
val isLoading: Boolean = false,
|
||||
val warningDialogVisible: Boolean = false,
|
||||
)
|
@ -410,7 +410,7 @@
|
||||
<string name="grey_out_articles">Grey out articles</string>
|
||||
<string name="all_read">All read</string>
|
||||
<string name="read_excluding_starred">Read, excluding starred</string>
|
||||
<string name="external_links">External links</string>
|
||||
<string name="external_links">External Links</string>
|
||||
<string name="unexpected_error_title">Oops! Something went wrong…</string>
|
||||
<string name="copy_error_report">Copy error report</string>
|
||||
<string name="submit_bug_report">submit a bug report on GitHub</string>
|
||||
@ -432,4 +432,12 @@
|
||||
<string name="shared_content">Shared content</string>
|
||||
<string name="only_link">Only link</string>
|
||||
<string name="title_and_link">Title and link</string>
|
||||
<string name="troubleshooting">Troubleshooting</string>
|
||||
<string name="troubleshooting_desc">Bug report, debug tools</string>
|
||||
<string name="bug_report">Bug report</string>
|
||||
<string name="issue_tracer_url" translatable="false">https://github.com/Ashinch/ReadYou/issues</string>
|
||||
<string name="app_preferences">App preferences</string>
|
||||
<string name="import_from_protobuf_file">Import from protobuf file</string>
|
||||
<string name="export_as_protobuf_file">Export as protobuf file</string>
|
||||
<string name="invalid_protobuf_file_warning">This file may not be a valid protobuf file. Importing it could potentially corrupt the app and result in the loss of current preferences. Are you sure you want to proceed?</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user