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