From 9b4ea75b0f42724575910aa0e8735a2da44c229c Mon Sep 17 00:00:00 2001 From: Josiah Campbell <9521010+jocmp@users.noreply.github.com> Date: Mon, 1 Jul 2024 20:37:39 -0500 Subject: [PATCH] Add UI for OPML imports/exports --- .../ui/accounts/AccountNavigation.kt | 2 +- .../capyreader/ui/articles/AddFeedView.kt | 2 +- .../ui/components/CrashReportingCheckbox.kt | 11 +- .../ui/settings/AccountSettingsStrings.kt | 32 ++++ .../ui/settings/OPMLExportButton.kt | 26 ++++ .../ui/settings/OPMLImportButton.kt | 23 +++ .../capyreader/ui/settings/SettingsScreen.kt | 9 +- .../capyreader/ui/settings/SettingsView.kt | 142 +++++++++++++----- .../ui/settings/SettingsViewModel.kt | 8 +- app/src/main/res/values/strings.xml | 15 +- .../main/java/com/jocmp/capy/OPMLImporter.kt | 1 + 11 files changed, 220 insertions(+), 51 deletions(-) create mode 100644 app/src/main/java/com/jocmp/capyreader/ui/settings/AccountSettingsStrings.kt create mode 100644 app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLExportButton.kt create mode 100644 app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLImportButton.kt create mode 100644 capy/src/main/java/com/jocmp/capy/OPMLImporter.kt diff --git a/app/src/main/java/com/jocmp/capyreader/ui/accounts/AccountNavigation.kt b/app/src/main/java/com/jocmp/capyreader/ui/accounts/AccountNavigation.kt index a5aae0f5..70ddc74c 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/accounts/AccountNavigation.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/accounts/AccountNavigation.kt @@ -30,7 +30,7 @@ fun NavGraphBuilder.accountsGraph( } dynamicLayout(isCompactWidth) { SettingsScreen( - onLogout = onLogout, + onRemoveAccount = onLogout, onNavigateBack = onNavigateBackFromSettings ) } diff --git a/app/src/main/java/com/jocmp/capyreader/ui/articles/AddFeedView.kt b/app/src/main/java/com/jocmp/capyreader/ui/articles/AddFeedView.kt index 708bb14f..e0ff7c0b 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/articles/AddFeedView.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/articles/AddFeedView.kt @@ -84,7 +84,7 @@ fun AddFeedView( Text(stringResource(resource)) } }, - maxLines = 1, + singleLine = true, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), diff --git a/app/src/main/java/com/jocmp/capyreader/ui/components/CrashReportingCheckbox.kt b/app/src/main/java/com/jocmp/capyreader/ui/components/CrashReportingCheckbox.kt index 4da52639..561a185e 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/components/CrashReportingCheckbox.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/components/CrashReportingCheckbox.kt @@ -1,12 +1,17 @@ package com.jocmp.capyreader.ui.components +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.Checkbox +import androidx.compose.material3.RadioButton +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -35,10 +40,12 @@ fun CrashReportingCheckbox( } Row( - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() ) { - Checkbox(checked = enableCrashReporting, onCheckedChange = updateCrashReporting) Text(text = stringResource(R.string.crash_reporting_checkbox_title)) + Switch(checked = enableCrashReporting, onCheckedChange = updateCrashReporting) } } diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/AccountSettingsStrings.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/AccountSettingsStrings.kt new file mode 100644 index 00000000..9f0f195f --- /dev/null +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/AccountSettingsStrings.kt @@ -0,0 +1,32 @@ +package com.jocmp.capyreader.ui.settings + +import androidx.annotation.StringRes +import com.jocmp.capy.accounts.Source +import com.jocmp.capyreader.R + +data class AccountSettingsStrings( + @StringRes val dialogTitle: Int, + @StringRes val dialogMessage: Int, + @StringRes val dialogConfirmText: Int, + @StringRes val requestRemoveText: Int +) { + companion object { + fun find(source: Source): AccountSettingsStrings { + return when (source) { + Source.LOCAL -> AccountSettingsStrings( + dialogTitle = R.string.settings_remove_account_title_local, + dialogMessage = R.string.settings_remove_account_message_local, + dialogConfirmText = R.string.settings_remove_account_confirm_local, + requestRemoveText = R.string.settings_remove_account_button_local, + ) + + Source.FEEDBIN -> AccountSettingsStrings( + dialogTitle = R.string.settings_remove_account_title_feedbin, + dialogMessage = R.string.settings_remove_account_message_feedbin, + dialogConfirmText = R.string.settings_remove_account_confirm_feedbin, + requestRemoveText = R.string.settings_remove_account_button_feedbin, + ) + } + } + } +} diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLExportButton.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLExportButton.kt new file mode 100644 index 00000000..c19a8e87 --- /dev/null +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLExportButton.kt @@ -0,0 +1,26 @@ +package com.jocmp.capyreader.ui.settings + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun OPMLExportButton() { + Column { + Button( + onClick = {}, + colors = ButtonDefaults.buttonColors( + containerColor = colorScheme.secondary, + contentColor = colorScheme.onSecondary + ), + modifier = Modifier.fillMaxWidth() + ) { + Text("Export to OPML") + } + } +} diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLImportButton.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLImportButton.kt new file mode 100644 index 00000000..6e2849b5 --- /dev/null +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/OPMLImportButton.kt @@ -0,0 +1,23 @@ +package com.jocmp.capyreader.ui.settings + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun OPMLImportButton() { + Button( + onClick = {}, + colors = ButtonDefaults.buttonColors( + containerColor = colorScheme.secondary, + contentColor = colorScheme.onSecondary + ), + modifier = Modifier.fillMaxWidth() + ) { + Text("Import from File") + } +} diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsScreen.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsScreen.kt index 628536a6..80067485 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsScreen.kt @@ -6,7 +6,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun SettingsScreen( viewModel: SettingsViewModel = koinViewModel(), - onLogout: () -> Unit, + onRemoveAccount: () -> Unit, onNavigateBack: () -> Unit, ) { SettingsView( @@ -14,9 +14,10 @@ fun SettingsScreen( updateRefreshInterval = viewModel::updateRefreshInterval, accountName = viewModel.accountName, onNavigateBack = { onNavigateBack() }, - onRequestLogout = { - viewModel.logOut() - onLogout() + accountSource = viewModel.accountSource, + onRequestRemoveAccount = { + viewModel.removeAccount() + onRemoveAccount() }, ) } diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsView.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsView.kt index 62f9ed4f..09329e99 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsView.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsView.kt @@ -2,7 +2,6 @@ package com.jocmp.capyreader.ui.settings import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -13,11 +12,13 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Close import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -25,7 +26,6 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext @@ -33,6 +33,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.jocmp.capy.accounts.Source import com.jocmp.capyreader.R import com.jocmp.capyreader.refresher.RefreshInterval import com.jocmp.capyreader.setupCommonModules @@ -47,9 +48,11 @@ fun SettingsView( refreshInterval: RefreshInterval, updateRefreshInterval: (interval: RefreshInterval) -> Unit, onNavigateBack: () -> Unit, - onRequestLogout: () -> Unit, + onRequestRemoveAccount: () -> Unit, + accountSource: Source, accountName: String ) { + val strings = AccountSettingsStrings.find(accountSource) val (isRemoveDialogOpen, setRemoveDialogOpen) = remember { mutableStateOf(false) } val onRemoveCancel = { @@ -58,7 +61,7 @@ fun SettingsView( val onRemove = { setRemoveDialogOpen(false) - onRequestLogout() + onRequestRemoveAccount() } Scaffold( @@ -90,48 +93,45 @@ fun SettingsView( Modifier .verticalScroll(rememberScrollState()) ) { - Column( - Modifier - .padding(bottom = 8.dp) - .padding(horizontal = 16.dp) - ) { - Text( - text = "Account", - fontSize = 12.sp, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - Text(text = accountName) + if (showAccountName(accountSource)) { + Section( + title = stringResource(R.string.settings_section_account), + ) { + Text(text = accountName) + } } - Column( - Modifier - .padding(bottom = 8.dp) - .padding(horizontal = 16.dp) - ) { + Section(title = "Refresh") { RefreshIntervalMenu( refreshInterval = refreshInterval, updateRefreshInterval = updateRefreshInterval, ) } - Row(Modifier.padding(start = 8.dp)) { + if (showImportButton(accountSource)) { + Section(title = stringResource(R.string.settings_section_import)) { + OPMLImportButton() + } + } + + Section(title = stringResource(R.string.settings_section_export)) { + OPMLExportButton() + } + + Section(title = "Privacy") { CrashReportingCheckbox() } - } + Section { + HorizontalDivider(modifier = Modifier.padding(bottom = 8.dp)) - Column( - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(16.dp), - modifier = Modifier - .padding(horizontal = 16.dp) - ) { - HorizontalDivider() - Button( - onClick = { setRemoveDialogOpen(true) }, - Modifier.fillMaxWidth() - ) { - Text(stringResource(R.string.settings_log_out_button)) + Button( + onClick = { setRemoveDialogOpen(true) }, + colors = removeAccountButtonColors(accountSource), + modifier = Modifier.fillMaxWidth() + ) { + Text(stringResource(strings.requestRemoveText)) + } } } } @@ -140,8 +140,8 @@ fun SettingsView( if (isRemoveDialogOpen) { AlertDialog( onDismissRequest = onRemoveCancel, - title = { Text(stringResource(R.string.settings_logout_dialog_title)) }, - text = { Text(stringResource(R.string.settings_logout_dialog_message)) }, + title = { Text(stringResource(strings.dialogTitle)) }, + text = { Text(stringResource(strings.dialogMessage)) }, dismissButton = { TextButton(onClick = onRemoveCancel) { Text(stringResource(R.string.dialog_cancel)) @@ -149,13 +149,36 @@ fun SettingsView( }, confirmButton = { TextButton(onClick = onRemove) { - Text(stringResource(R.string.settings_logout_submit)) + Text(text = stringResource(strings.dialogConfirmText)) } } ) } } +@Composable +fun Section( + title: String? = null, + content: @Composable () -> Unit, +) { + Column( + Modifier + .padding(bottom = 16.dp) + .padding(horizontal = 16.dp) + ) { + if (title != null) { + Text( + text = title, + fontSize = 12.sp, + color = colorScheme.surfaceTint, + modifier = Modifier.padding(bottom = 8.dp) + ) + } + + content() + } +} + @Composable private fun backButton(): ImageVector { val showBackArrow = isCompact() @@ -167,6 +190,26 @@ private fun backButton(): ImageVector { } } +@Composable +fun removeAccountButtonColors(source: Source) = when(source) { + Source.LOCAL -> ButtonDefaults.buttonColors( + containerColor = colorScheme.error, + contentColor = colorScheme.onError + ) + Source.FEEDBIN -> ButtonDefaults.buttonColors( + containerColor = colorScheme.secondary, + contentColor = colorScheme.onSecondary + ) +} + +fun showImportButton(source: Source): Boolean { + return source == Source.LOCAL +} + +fun showAccountName(source: Source): Boolean { + return source == Source.FEEDBIN +} + @Preview @Composable fun AccountSettingsViewPreview() { @@ -181,9 +224,32 @@ fun AccountSettingsViewPreview() { SettingsView( refreshInterval = RefreshInterval.EVERY_HOUR, updateRefreshInterval = {}, - onRequestLogout = {}, + onRequestRemoveAccount = {}, onNavigateBack = {}, + accountSource = Source.FEEDBIN, accountName = "hello@example.com" ) } } + +@Preview +@Composable +fun AccountSettingsView_LocalPreview() { + val context = LocalContext.current + + KoinApplication( + application = { + androidContext(context) + setupCommonModules() + } + ) { + SettingsView( + refreshInterval = RefreshInterval.EVERY_HOUR, + updateRefreshInterval = {}, + onRequestRemoveAccount = {}, + onNavigateBack = {}, + accountSource = Source.LOCAL, + accountName = "" + ) + } +} diff --git a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsViewModel.kt index 6b90d502..56ba7746 100644 --- a/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/jocmp/capyreader/ui/settings/SettingsViewModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.jocmp.capy.Account import com.jocmp.capy.AccountManager +import com.jocmp.capy.accounts.Source import com.jocmp.capyreader.common.AppPreferences import com.jocmp.capyreader.refresher.RefreshInterval import com.jocmp.capyreader.refresher.RefreshScheduler @@ -19,6 +20,8 @@ class SettingsViewModel( val refreshInterval: RefreshInterval get() = _refreshInterval.value + val accountSource: Source = account.source + val accountName: String get() = account.preferences.username.get() @@ -28,8 +31,11 @@ class SettingsViewModel( _refreshInterval.value = interval } - fun logOut() { + fun removeAccount() { appPreferences.clearAll() accountManager.removeAccount(accountID = account.id) } + + fun importOPML() { + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb329be1..e73b315b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -22,10 +22,14 @@ Feed updated Unsubscribed Accounts - Log Out - Log Out - Are you sure you want to log out? - Log Out + Log Out + Log Out + Are you sure you want to log out? + Log Out + Delete Account + Delete Account + Are you sure you want to remove your account? + Confirm Name saved Mark as read Star @@ -77,4 +81,7 @@ Local Add Account Export Subscriptions + Account + Import + Export diff --git a/capy/src/main/java/com/jocmp/capy/OPMLImporter.kt b/capy/src/main/java/com/jocmp/capy/OPMLImporter.kt new file mode 100644 index 00000000..aece2fc9 --- /dev/null +++ b/capy/src/main/java/com/jocmp/capy/OPMLImporter.kt @@ -0,0 +1 @@ +package com.jocmp.capy