diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
index 688db9601..233a285d4 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
@@ -35,7 +35,6 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
val loginViewModel: LoginViewModel by viewModels()
- val settingsViewModel: SettingsViewModel by viewModels()
val searchViewModel: SearchViewModel by viewModels()
val labelsViewModel: LabelsViewModel by viewModels()
val saveViewModel: SaveViewModel by viewModels()
@@ -64,7 +63,6 @@ class MainActivity : ComponentActivity() {
RootView(
loginViewModel,
searchViewModel,
- settingsViewModel,
labelsViewModel,
saveViewModel,
editInfoViewModel
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/ui/LinkIcon.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/ui/LinkIcon.kt
new file mode 100644
index 000000000..79e6f83fa
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/core/ui/LinkIcon.kt
@@ -0,0 +1,33 @@
+package app.omnivore.omnivore.core.ui
+
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun LinkIcon(
+ label: String,
+ icon: ImageVector,
+ url: String,
+ modifier: Modifier = Modifier,
+) {
+ val uriHandler = LocalUriHandler.current
+ IconButton(
+ modifier = modifier.padding(4.dp),
+ onClick = { uriHandler.openUri(url) },
+ ) {
+ Icon(
+ imageVector = icon,
+ contentDescription = label,
+ modifier = Modifier.size(24.dp),
+ tint = MaterialTheme.colorScheme.onSurface
+ )
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt
index d749a709d..fea94ab3d 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/library/LibraryView.kt
@@ -411,7 +411,7 @@ fun LibraryViewContent(
dismissContent = {
val selected =
currentItem.savedItemId == selectedItem?.savedItem?.savedItemId
- val test = SavedItemWithLabelsAndHighlights(
+ val savedItem = SavedItemWithLabelsAndHighlights(
savedItem = cardDataWithLabels.savedItem,
labels = cardDataWithLabels.labels,
highlights = cardDataWithLabels.highlights
@@ -419,7 +419,7 @@ fun LibraryViewContent(
SavedItemCard(
selected = selected,
savedItemViewModel = libraryViewModel,
- savedItem = test,
+ savedItem = savedItem,
onClickHandler = {
libraryViewModel.actionsMenuItemLiveData.postValue(null)
val activityClass =
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt
index f68b250b8..5e48ad2ff 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/root/RootView.kt
@@ -1,10 +1,23 @@
package app.omnivore.omnivore.feature.root
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.WindowInsetsSides
+import androidx.compose.foundation.layout.consumeWindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.only
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.safeDrawing
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@@ -16,41 +29,57 @@ import app.omnivore.omnivore.feature.library.LibraryView
import app.omnivore.omnivore.feature.library.SearchView
import app.omnivore.omnivore.feature.library.SearchViewModel
import app.omnivore.omnivore.feature.save.SaveViewModel
-import app.omnivore.omnivore.feature.settings.PolicyWebView
-import app.omnivore.omnivore.feature.settings.SettingsView
-import app.omnivore.omnivore.feature.settings.SettingsViewModel
+import app.omnivore.omnivore.feature.settings.SettingsScreen
+import app.omnivore.omnivore.feature.settings.about.AboutScreen
+import app.omnivore.omnivore.feature.settings.account.AccountScreen
+import app.omnivore.omnivore.feature.web.WebViewScreen
import app.omnivore.omnivore.navigation.Routes
@Composable
fun RootView(
loginViewModel: LoginViewModel,
searchViewModel: SearchViewModel,
- settingsViewModel: SettingsViewModel,
labelsViewModel: LabelsViewModel,
saveViewModel: SaveViewModel,
editInfoViewModel: EditInfoViewModel,
) {
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
+ val snackbarHostState = remember { SnackbarHostState() }
- Box {
- if (hasAuthToken) {
- PrimaryNavigator(
- loginViewModel = loginViewModel,
- searchViewModel = searchViewModel,
- settingsViewModel = settingsViewModel,
- labelsViewModel = labelsViewModel,
- saveViewModel = saveViewModel,
- editInfoViewModel = editInfoViewModel,
- )
- } else {
- WelcomeScreen(viewModel = loginViewModel)
- }
-
- DisposableEffect(hasAuthToken) {
+ Scaffold(
+ snackbarHost = { SnackbarHost(snackbarHostState) },
+ ) { padding ->
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(padding)
+ .consumeWindowInsets(padding)
+ .windowInsetsPadding(
+ WindowInsets.safeDrawing.only(
+ WindowInsetsSides.Horizontal,
+ ),
+ )
+ ){
if (hasAuthToken) {
- loginViewModel.registerUser()
+ PrimaryNavigator(
+ loginViewModel = loginViewModel,
+ searchViewModel = searchViewModel,
+ labelsViewModel = labelsViewModel,
+ saveViewModel = saveViewModel,
+ editInfoViewModel = editInfoViewModel,
+ snackbarHostState = snackbarHostState
+
+ )
+ } else {
+ WelcomeScreen(viewModel = loginViewModel)
+ }
+
+ DisposableEffect(hasAuthToken) {
+ if (hasAuthToken) {
+ loginViewModel.registerUser()
+ }
+ onDispose {}
}
- onDispose {}
}
}
}
@@ -59,14 +88,17 @@ fun RootView(
fun PrimaryNavigator(
loginViewModel: LoginViewModel,
searchViewModel: SearchViewModel,
- settingsViewModel: SettingsViewModel,
labelsViewModel: LabelsViewModel,
saveViewModel: SaveViewModel,
editInfoViewModel: EditInfoViewModel,
+ snackbarHostState: SnackbarHostState
) {
val navController = rememberNavController()
- NavHost(navController = navController, startDestination = Routes.Library.route) {
+ NavHost(
+ navController = navController,
+ startDestination = Routes.Library.route
+ ) {
composable(Routes.Library.route) {
LibraryView(
navController = navController,
@@ -78,29 +110,40 @@ fun PrimaryNavigator(
composable(Routes.Search.route) {
SearchView(
- viewModel = searchViewModel,
- navController = navController
+ viewModel = searchViewModel, navController = navController
)
}
composable(Routes.Settings.route) {
- SettingsView(
+ SettingsScreen(
loginViewModel = loginViewModel,
- settingsViewModel = settingsViewModel,
navController = navController
)
}
+ composable(Routes.About.route) {
+ AboutScreen(
+ navController = navController
+ )
+ }
+
+ composable(Routes.Account.route) {
+ AccountScreen(
+ navController = navController,
+ snackbarHostState = snackbarHostState,
+ )
+ }
+
composable(Routes.Documentation.route) {
- PolicyWebView(navController = navController, url = "https://docs.omnivore.app")
+ WebViewScreen(navController = navController, url = "https://docs.omnivore.app")
}
composable(Routes.PrivacyPolicy.route) {
- PolicyWebView(navController = navController, url = "https://omnivore.app/privacy")
+ WebViewScreen(navController = navController, url = "https://omnivore.app/privacy")
}
composable(Routes.TermsAndConditions.route) {
- PolicyWebView(navController = navController, url = "https://omnivore.app/app/terms")
+ WebViewScreen(navController = navController, url = "https://omnivore.app/app/terms")
}
}
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/LogoutDialog.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/LogoutDialog.kt
index 993944f8f..02ca000c8 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/LogoutDialog.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/LogoutDialog.kt
@@ -1,8 +1,10 @@
package app.omnivore.omnivore.feature.settings
import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
@@ -12,31 +14,37 @@ import com.google.android.gms.auth.api.signin.GoogleSignInOptions
@Composable
fun LogoutDialog(onClose: (Boolean) -> Unit) {
- val context = LocalContext.current
+ val context = LocalContext.current
- AlertDialog(
- onDismissRequest = { onClose(false) },
- title = { Text(text = stringResource(R.string.logout_dialog_title)) },
- text = {
- Text(stringResource(R.string.logout_dialog_confirm_msg))
- },
- confirmButton = {
- Button(onClick = {
- // Sign out google users
- val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
- .build()
+ AlertDialog(onDismissRequest = { onClose(false) },
+ title = { Text(text = stringResource(R.string.logout_dialog_title)) },
+ text = {
+ Text(stringResource(R.string.logout_dialog_confirm_msg))
+ },
+ containerColor = MaterialTheme.colorScheme.background,
+ confirmButton = {
+ TextButton(
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.onSurface
+ ),
+ onClick = {
+ val signInOptions =
+ GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).build()
- val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
- googleSignIn.signOut()
- onClose(true)
- }) {
- Text(stringResource(R.string.logout_dialog_action_confirm))
- }
- },
- dismissButton = {
- Button(onClick = { onClose(false) }) {
- Text(stringResource(R.string.logout_dialog_action_cancel))
- }
- }
- )
+ val googleSignIn = GoogleSignIn.getClient(context, signInOptions)
+ googleSignIn.signOut()
+ onClose(true)
+ }) {
+ Text(stringResource(R.string.logout_dialog_action_confirm))
+ }
+ },
+ dismissButton = {
+ TextButton(
+ colors = ButtonDefaults.textButtonColors(
+ contentColor = MaterialTheme.colorScheme.onSurface
+ ),
+ onClick = { onClose(false) }) {
+ Text(stringResource(R.string.logout_dialog_action_cancel))
+ }
+ })
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/ManageAccount.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/ManageAccount.kt
deleted file mode 100644
index daeec8318..000000000
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/ManageAccount.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-package app.omnivore.omnivore.feature.settings
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Text
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Refresh
-import androidx.compose.material3.Icon
-import androidx.compose.material3.Surface
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.window.Dialog
-import app.omnivore.omnivore.R
-
-@Composable
-fun ManageAccountDialog(onDismiss: () -> Unit, settingsViewModel: SettingsViewModel) {
- Dialog(onDismissRequest = { onDismiss() }) {
- Surface(
- shape = RoundedCornerShape(16.dp),
- color = Color.White,
- modifier = Modifier
- .height(300.dp)
- ) {
- ManageAccountView(settingsViewModel = settingsViewModel)
- }
- }
-}
-
-@Composable
-fun ManageAccountView(settingsViewModel: SettingsViewModel) {
- Column(
- modifier = Modifier
- .padding(top = 6.dp, start = 6.dp, end = 6.dp, bottom = 6.dp)
- ) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 12.dp, bottom = 12.dp),
- horizontalArrangement = Arrangement.Center
- ) {
- Text(stringResource(R.string.manage_account_title))
- }
-
- Column(
- modifier = Modifier
- .verticalScroll(rememberScrollState())
- ) {
- Row(
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .clickable(onClick = { settingsViewModel.resetDataCache() })
- ) {
- Text(stringResource(R.string.manage_account_action_reset_data_cache))
- Spacer(modifier = Modifier.weight(1.0F))
- Icon(imageVector = Icons.Filled.Refresh, contentDescription = null)
- }
- }
- }
-}
-
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/PolicyWebView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/PolicyWebView.kt
deleted file mode 100644
index b7f7d6d8f..000000000
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/PolicyWebView.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package app.omnivore.omnivore.feature.settings
-
-import android.annotation.SuppressLint
-import android.view.ViewGroup
-import android.webkit.WebView
-import android.webkit.WebViewClient
-import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.foundation.layout.Box
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Settings
-import androidx.compose.material3.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.navigation.NavHostController
-import app.omnivore.omnivore.navigation.Routes
-import app.omnivore.omnivore.R
-
-@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SetJavaScriptEnabled")
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun PolicyWebView(navController: NavHostController, url: String) {
- Scaffold(
- topBar = {
- TopAppBar(
- title = { Text(stringResource(R.string.policy_webview_title)) },
- actions = {
- IconButton(onClick = { navController.navigate(Routes.Settings.route) }) {
- Icon(
- imageVector = Icons.Default.Settings,
- contentDescription = null
- )
- }
- }, colors = TopAppBarDefaults.topAppBarColors(
- containerColor = MaterialTheme.colorScheme.surfaceVariant
- )
- )
- }
- ) {
- val isDarkMode = isSystemInDarkTheme()
-
- Box {
- AndroidView(factory = {
- WebView(it).apply {
- if (isDarkMode) {
- setBackgroundColor(Color.Transparent.hashCode())
- }
-
- layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
-
- settings.javaScriptEnabled = true
- settings.allowContentAccess = true
- settings.allowFileAccess = true
- settings.domStorageEnabled = true
-
- alpha = 0.0f
-
- webViewClient = object : WebViewClient() {
- override fun onPageFinished(view: WebView?, url: String?) {
- super.onPageFinished(view, url)
- view?.animate()?.alpha(1.0f)?.duration = 200
- }
- }
-
-// val themeID = if (isDarkMode) "Gray" else "LightGray"
-// loadUrl(url, mutableMapOf("Set-Cookie" to "theme=$themeID; Max-Age=31536000;"))
- loadUrl(url, mutableMapOf())
- }
- }, update = {}
- )
- }
- }
-}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsContent.kt
deleted file mode 100644
index a2e34294d..000000000
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsContent.kt
+++ /dev/null
@@ -1,175 +0,0 @@
-package app.omnivore.omnivore.feature.settings
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Home
-import androidx.compose.material3.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import androidx.navigation.NavHostController
-import app.omnivore.omnivore.BuildConfig
-import app.omnivore.omnivore.R
-import app.omnivore.omnivore.navigation.Routes
-import app.omnivore.omnivore.feature.auth.LoginViewModel
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun SettingsView(
- loginViewModel: LoginViewModel,
- settingsViewModel: SettingsViewModel,
- navController: NavHostController,
-) {
- Scaffold(
- topBar = {
- TopAppBar(
- title = { Text(stringResource(R.string.settings_view_title)) },
- actions = {
- IconButton(onClick = { navController.navigate(Routes.Library.route) }) {
- Icon(
- imageVector = Icons.Default.Home,
- contentDescription = null
- )
- }
- }, colors = TopAppBarDefaults.topAppBarColors(
- containerColor = MaterialTheme.colorScheme.surfaceVariant
- )
- )
- }
- ) { paddingValues ->
- SettingsViewContent(
- loginViewModel = loginViewModel,
- settingsViewModel = settingsViewModel,
- navController = navController,
- modifier = Modifier
- .padding(
- top = paddingValues.calculateTopPadding(),
- bottom = paddingValues.calculateBottomPadding()
- )
- )
- }
-}
-
-@Composable
-fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: SettingsViewModel, navController: NavHostController, modifier: Modifier) {
- val showLogoutDialog = remember { mutableStateOf(false) }
- val showManageAccountDialog = remember { mutableStateOf(false ) }
-
- Box(
- modifier = modifier.fillMaxSize()
- ) {
-
- val version = "Omnivore Version: " + BuildConfig.VERSION_NAME
-
- Column(
- verticalArrangement = Arrangement.Top,
- horizontalAlignment = Alignment.Start,
- modifier = Modifier
- .background(MaterialTheme.colorScheme.background)
- .fillMaxSize()
- .padding(horizontal = 6.dp)
- .verticalScroll(rememberScrollState())
- ) {
-
-
- SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) {
- navController.navigate(Routes.Documentation.route)
- }
- RowDivider()
- SettingRow(text = stringResource(R.string.settings_view_setting_row_feedback)) {
- settingsViewModel.presentIntercom()
- }
- RowDivider()
- SettingRow(text = stringResource(R.string.settings_view_setting_row_privacy_policy)) {
- navController.navigate(Routes.PrivacyPolicy.route)
- }
- RowDivider()
- SettingRow(text = stringResource(R.string.settings_view_setting_row_terms_and_conditions)) {
- navController.navigate(Routes.TermsAndConditions.route)
- }
-
- SectionSpacer()
-
- SettingRow(text = stringResource(R.string.settings_view_setting_row_manage_account)) {
- showManageAccountDialog.value = true
- }
- RowDivider()
- SettingRow(text = stringResource(R.string.settings_view_setting_row_logout), includeIcon = false) {
- showLogoutDialog.value = true
- }
- RowDivider()
-
- Text(
- text = version,
- fontSize = 12.sp,
- modifier = Modifier
- .padding(15.dp)
- )
- }
-
- if (showLogoutDialog.value) {
- LogoutDialog { performLogout ->
- if (performLogout) {
- loginViewModel.logout()
- }
- showLogoutDialog.value = false
- }
- }
-
- if (showManageAccountDialog.value) {
- ManageAccountDialog(
- onDismiss = { showManageAccountDialog.value = false },
- settingsViewModel = settingsViewModel
- )
- }
- }
-}
-
-@Composable
-private fun RowDivider() {
- Divider(
- modifier = Modifier.padding(horizontal = 12.dp),
- color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.1f)
- )
-}
-
-@Composable
-private fun SectionSpacer() {
- RowDivider()
- Spacer(Modifier.height(60.dp))
- RowDivider()
-}
-
-@Composable
-private fun SettingRow(text: String, includeIcon: Boolean = true, tapAction: () -> Unit) {
- Row(
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically,
- modifier = Modifier
- .fillMaxWidth()
- .clickable { tapAction() }
- ) {
- Text(
- text = text,
- modifier = Modifier
- .align(Alignment.CenterVertically)
- .padding(16.dp),
- style = MaterialTheme.typography.titleMedium
- )
-
- if (includeIcon) {
- Icon(
- painter = painterResource(id = R.drawable.chevron_right),
- contentDescription = null
- )
- }
- }
-}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsScreen.kt
new file mode 100644
index 000000000..4d99f2bdb
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsScreen.kt
@@ -0,0 +1,155 @@
+package app.omnivore.omnivore.feature.settings
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+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.draw.alpha
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavHostController
+import app.omnivore.omnivore.R
+import app.omnivore.omnivore.feature.auth.LoginViewModel
+import app.omnivore.omnivore.navigation.Routes
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun SettingsScreen(
+ loginViewModel: LoginViewModel,
+ navController: NavHostController
+) {
+ Scaffold(topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.settings_view_title)) },
+ navigationIcon = {
+ IconButton(onClick = { navController.navigateUp() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
+ contentDescription = null
+ )
+ }
+
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background
+ ),
+ )
+ }) { paddingValues ->
+ SettingsViewContent(
+ loginViewModel = loginViewModel,
+ navController = navController,
+ paddingValues = paddingValues
+ )
+ }
+}
+
+@Composable
+fun SettingsViewContent(
+ loginViewModel: LoginViewModel,
+ navController: NavHostController,
+ paddingValues: PaddingValues
+) {
+ val showLogoutDialog = remember { mutableStateOf(false) }
+
+ val state = rememberLazyListState()
+
+ LazyColumn(
+ state = state,
+ contentPadding = paddingValues,
+ ) {
+
+ item {
+ SettingRow(title = stringResource(R.string.settings_view_setting_row_manage_account)) {
+ navController.navigate(Routes.Account.route)
+ }
+ }
+
+ item {
+ SettingRow(
+ title = stringResource(R.string.settings_view_setting_row_logout)
+ ) {
+ showLogoutDialog.value = true
+ }
+ }
+
+ item {
+ SettingRow(title = stringResource(R.string.about_view_title)) {
+ navController.navigate(Routes.About.route)
+ }
+ }
+ }
+
+ if (showLogoutDialog.value) {
+ LogoutDialog { performLogout ->
+ if (performLogout) {
+ loginViewModel.logout()
+ }
+ showLogoutDialog.value = false
+ }
+ }
+}
+
+
+@Composable
+internal fun SettingRow(
+ title: String, subtitle: String? = null, onClick: (() -> Unit)?
+) {
+ Row(
+ modifier = Modifier
+ .clickable(enabled = onClick != null, onClick = { onClick?.invoke() })
+ .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = SettingsVerticalPadding)
+ ) {
+ Text(
+ modifier = Modifier.padding(horizontal = SettingsHorizontalPadding),
+ text = title,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 2,
+ style = MaterialTheme.typography.titleLarge,
+ fontSize = SettingsTitleFontSize,
+ )
+ if (!subtitle.isNullOrBlank()) {
+ Text(
+ text = subtitle,
+ modifier = Modifier
+ .padding(horizontal = SettingsHorizontalPadding)
+ .alpha(SettingsSecondaryItemAlpha),
+ style = MaterialTheme.typography.bodySmall,
+ maxLines = 10,
+ )
+ }
+ }
+ }
+}
+
+internal val SettingsHorizontalPadding = 16.dp
+internal val SettingsVerticalPadding = 16.dp
+internal const val SettingsSecondaryItemAlpha = .78f
+internal val SettingsTitleFontSize = 16.sp
+
+internal const val RELEASE_URL = "https://github.com/omnivore-app/omnivore/releases"
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsViewModel.kt
index 6dde3d23f..0058ba321 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsViewModel.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/SettingsViewModel.kt
@@ -1,40 +1,58 @@
package app.omnivore.omnivore.feature.settings
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import app.omnivore.omnivore.utils.DatastoreKeys
-import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.data.DataService
+import app.omnivore.omnivore.core.datastore.DatastoreRepository
import app.omnivore.omnivore.core.network.Networker
import app.omnivore.omnivore.core.network.viewer
+import app.omnivore.omnivore.utils.DatastoreKeys
import dagger.hilt.android.lifecycle.HiltViewModel
import io.intercom.android.sdk.Intercom
import io.intercom.android.sdk.IntercomSpace
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(
- private val networker: Networker,
- private val dataService: DataService,
- private val datastoreRepo: DatastoreRepository
-): ViewModel() {
- fun resetDataCache() {
- viewModelScope.launch {
- datastoreRepo.clearValue(DatastoreKeys.libraryLastSyncTimestamp)
- dataService.clearDatabase()
- }
- }
+ private val networker: Networker,
+ private val dataService: DataService,
+ private val datastoreRepo: DatastoreRepository
+) : ViewModel() {
- fun presentIntercom() {
- viewModelScope.launch {
- val viewer = networker.viewer()
- viewer?.let { v ->
- v.intercomHash?.let { intercomHash ->
- Intercom.client().setUserHash(intercomHash)
+ var snackbarMessage by mutableStateOf("Resetting data...")
+
+ var isResettingData by mutableStateOf(false)
+ var isDataResetCompleted by mutableStateOf(false)
+
+ fun resetDataCache() {
+ isResettingData = true
+
+ viewModelScope.launch {
+ datastoreRepo.clearValue(DatastoreKeys.libraryLastSyncTimestamp)
+ dataService.clearDatabase()
+ delay(1000)
+ isResettingData = false
+
+ if (!isDataResetCompleted) {
+ isDataResetCompleted = true
+ }
+ }
+ }
+
+ fun presentIntercom() {
+ viewModelScope.launch {
+ val viewer = networker.viewer()
+ viewer?.let { v ->
+ v.intercomHash?.let { intercomHash ->
+ Intercom.client().setUserHash(intercomHash)
+ }
+ Intercom.client().present(space = IntercomSpace.Messages)
+ }
}
- Intercom.client().present(space = IntercomSpace.Messages)
- }
}
- }
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/AboutScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/AboutScreen.kt
new file mode 100644
index 000000000..13ee241d3
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/AboutScreen.kt
@@ -0,0 +1,152 @@
+package app.omnivore.omnivore.feature.settings.about
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
+import androidx.compose.material.icons.outlined.Public
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import app.omnivore.omnivore.BuildConfig
+import app.omnivore.omnivore.R
+import app.omnivore.omnivore.core.ui.LinkIcon
+import app.omnivore.omnivore.feature.settings.RELEASE_URL
+import app.omnivore.omnivore.feature.settings.SettingRow
+import app.omnivore.omnivore.feature.settings.SettingsViewModel
+import app.omnivore.omnivore.navigation.Routes
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun AboutScreen(
+ navController: NavHostController,
+ settingsViewModel: SettingsViewModel = hiltViewModel()
+) {
+
+ val uriHandler = LocalUriHandler.current
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.about_view_title)) },
+ navigationIcon = {
+ IconButton(onClick = { navController.navigateUp() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null
+ )
+ }
+
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background
+ ),
+ )
+ },
+ ) { contentPadding ->
+ LazyColumn(
+ contentPadding = contentPadding,
+ ) {
+ item {
+ LogoHeader()
+ }
+
+ item {
+ SettingRow(
+ title = stringResource(R.string.about_view_row_whats_new),
+ onClick = { uriHandler.openUri(RELEASE_URL) },
+ )
+ }
+
+ item {
+ SettingRow(title = stringResource(R.string.settings_view_setting_row_documentation)) {
+ navController.navigate(Routes.Documentation.route)
+ }
+ }
+
+ item {
+ SettingRow(title = stringResource(R.string.settings_view_setting_row_feedback)) {
+ settingsViewModel.presentIntercom()
+ }
+ }
+
+ item {
+ SettingRow(title = stringResource(R.string.settings_view_setting_row_privacy_policy)) {
+ navController.navigate(Routes.PrivacyPolicy.route)
+ }
+ }
+
+ item {
+ SettingRow(title = stringResource(R.string.settings_view_setting_row_terms_and_conditions)) {
+ navController.navigate(Routes.TermsAndConditions.route)
+ }
+ }
+
+ item {
+ SettingRow(
+ title = stringResource(R.string.about_view_row_version),
+ subtitle = getVersionName(),
+ onClick = { },
+ )
+ }
+
+ item {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(vertical = 8.dp),
+ horizontalArrangement = Arrangement.Center,
+ ) {
+ LinkIcon(
+ label = stringResource(R.string.website),
+ icon = Icons.Outlined.Public,
+ url = "https://omnivore.app",
+ )
+ LinkIcon(
+ label = "Discord",
+ icon = ImageVector.vectorResource(R.drawable.ic_discord),
+ url = "https://discord.gg/h2z5rppzz9",
+ )
+ LinkIcon(
+ label = "X",
+ icon = ImageVector.vectorResource(R.drawable.ic_x),
+ url = "https://x.com/omnivoreapp",
+ )
+
+ LinkIcon(
+ label = "GitHub",
+ icon = ImageVector.vectorResource(R.drawable.ic_github),
+ url = "https://github.com/omnivore-app",
+ )
+ }
+ }
+ }
+ }
+}
+
+fun getVersionName(): String {
+ return when {
+ BuildConfig.DEBUG -> {
+ "Debug ${BuildConfig.VERSION_NAME}"
+ }
+ else -> {
+ "Stable ${BuildConfig.VERSION_NAME}"
+ }
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/LogoHeader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/LogoHeader.kt
new file mode 100644
index 000000000..9f2a77933
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/about/LogoHeader.kt
@@ -0,0 +1,34 @@
+package app.omnivore.omnivore.feature.settings.about
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.HorizontalDivider
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import app.omnivore.omnivore.R
+
+@Composable
+fun LogoHeader() {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Icon(
+ painter = painterResource(R.drawable.ic_omnivore),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurface,
+ modifier = Modifier
+ .padding(vertical = 32.dp)
+ .size(128.dp),
+ )
+
+ HorizontalDivider()
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/account/AccountScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/account/AccountScreen.kt
new file mode 100644
index 000000000..bb1988e84
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/settings/account/AccountScreen.kt
@@ -0,0 +1,84 @@
+package app.omnivore.omnivore.feature.settings.account
+
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.outlined.ArrowBack
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SnackbarDuration
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.res.stringResource
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import app.omnivore.omnivore.R
+import app.omnivore.omnivore.feature.settings.SettingRow
+import app.omnivore.omnivore.feature.settings.SettingsViewModel
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun AccountScreen(
+ navController: NavHostController,
+ snackbarHostState: SnackbarHostState,
+ settingsViewModel: SettingsViewModel = hiltViewModel()
+) {
+ LaunchedEffect(settingsViewModel.isResettingData) {
+ if (settingsViewModel.isResettingData) {
+ val result = snackbarHostState.showSnackbar(
+ message = settingsViewModel.snackbarMessage,
+ duration = SnackbarDuration.Indefinite,
+ )
+ when (result) {
+ SnackbarResult.Dismissed -> {
+ settingsViewModel.isResettingData = false
+ }
+ SnackbarResult.ActionPerformed -> { }
+ }
+ } else if (settingsViewModel.isDataResetCompleted) {
+ snackbarHostState.showSnackbar(
+ message = "Data reset completed.",
+ duration = SnackbarDuration.Short
+ )
+ }
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ title = { Text(stringResource(R.string.manage_account_title)) },
+ navigationIcon = {
+ IconButton(onClick = { navController.navigateUp() }) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Outlined.ArrowBack, contentDescription = null
+ )
+ }
+
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.background
+ ),
+ )
+ },
+ ) { contentPadding ->
+ LazyColumn(
+ contentPadding = contentPadding,
+ ) {
+ item {
+ SettingRow(
+ title = stringResource(R.string.manage_account_action_reset_data_cache),
+ onClick = {
+ settingsViewModel.resetDataCache()
+ },
+ )
+ }
+ }
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/web/WebViewScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/web/WebViewScreen.kt
new file mode 100644
index 000000000..d0dfc0f9c
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/feature/web/WebViewScreen.kt
@@ -0,0 +1,74 @@
+package app.omnivore.omnivore.feature.web
+
+import android.annotation.SuppressLint
+import android.view.ViewGroup
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.navigation.NavHostController
+import app.omnivore.omnivore.R
+import app.omnivore.omnivore.navigation.Routes
+
+@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SetJavaScriptEnabled")
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun WebViewScreen(navController: NavHostController, url: String) {
+ Scaffold(topBar = {
+ TopAppBar(title = { Text(stringResource(R.string.policy_webview_title)) }, actions = {
+ IconButton(onClick = { navController.navigate(Routes.Settings.route) }) {
+ Icon(
+ imageVector = Icons.Default.Settings, contentDescription = null
+ )
+ }
+ }, colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant
+ )
+ )
+ }) {
+ val isDarkMode = isSystemInDarkTheme()
+
+ Box {
+ AndroidView(factory = {
+ WebView(it).apply {
+ if (isDarkMode) {
+ setBackgroundColor(Color.Transparent.hashCode())
+ }
+
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+
+ settings.javaScriptEnabled = true
+ settings.allowContentAccess = true
+ settings.allowFileAccess = true
+ settings.domStorageEnabled = true
+
+ alpha = 0.0f
+
+ webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+ view?.animate()?.alpha(1.0f)?.duration = 200
+ }
+ }
+ loadUrl(url, mutableMapOf())
+ }
+ }, update = {})
+ }
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/navigation/Routes.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/navigation/Routes.kt
index 62f1a780b..53a39b62d 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/navigation/Routes.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/navigation/Routes.kt
@@ -3,6 +3,8 @@ package app.omnivore.omnivore.navigation
sealed class Routes(val route: String) {
object Library : Routes("Library")
object Settings : Routes("Settings")
+ object About : Routes("About")
+ object Account : Routes("Account")
object Search : Routes("Search")
object Documentation : Routes("Documentation")
object PrivacyPolicy : Routes("PrivacyPolicy")
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_discord.xml b/android/Omnivore/app/src/main/res/drawable/ic_discord.xml
new file mode 100644
index 000000000..bdec4191f
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_discord.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_github.xml b/android/Omnivore/app/src/main/res/drawable/ic_github.xml
new file mode 100644
index 000000000..34241eaa3
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_github.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_omnivore.xml b/android/Omnivore/app/src/main/res/drawable/ic_omnivore.xml
new file mode 100644
index 000000000..ecaad383b
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_omnivore.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/android/Omnivore/app/src/main/res/drawable/ic_x.xml b/android/Omnivore/app/src/main/res/drawable/ic_x.xml
new file mode 100644
index 000000000..9177522c5
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/drawable/ic_x.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/Omnivore/app/src/main/res/values/strings.xml b/android/Omnivore/app/src/main/res/values/strings.xml
index d1cce247c..fba0f250b 100644
--- a/android/Omnivore/app/src/main/res/values/strings.xml
+++ b/android/Omnivore/app/src/main/res/values/strings.xml
@@ -212,6 +212,12 @@
Manage Account
Logout
+
+ About
+ Version
+ What\'s new
+ Website
+
Inbox
Non-Feed Items