diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml index 3cea932cb..4ebf91f5d 100644 --- a/android/Omnivore/app/src/main/AndroidManifest.xml +++ b/android/Omnivore/app/src/main/AndroidManifest.xml @@ -22,6 +22,7 @@ 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 408d285a3..8996416ad 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 @@ -4,12 +4,11 @@ import android.os.Bundle import android.view.View import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat @@ -24,61 +23,65 @@ import app.omnivore.omnivore.ui.settings.SettingsViewModel import app.omnivore.omnivore.ui.theme.OmnivoreTheme import com.pspdfkit.PSPDFKit import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch +@OptIn(DelicateCoroutinesApi::class) @AndroidEntryPoint class MainActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) - val loginViewModel: LoginViewModel by viewModels() - val libraryViewModel: LibraryViewModel by viewModels() - val settingsViewModel: SettingsViewModel by viewModels() - val searchViewModel: SearchViewModel by viewModels() - val labelsViewModel: LabelsViewModel by viewModels() - val saveViewModel: SaveViewModel by viewModels() - val editInfoViewModel: EditInfoViewModel by viewModels() + val loginViewModel: LoginViewModel by viewModels() + val libraryViewModel: LibraryViewModel by viewModels() + val settingsViewModel: SettingsViewModel by viewModels() + val searchViewModel: SearchViewModel by viewModels() + val labelsViewModel: LabelsViewModel by viewModels() + val saveViewModel: SaveViewModel by viewModels() + val editInfoViewModel: EditInfoViewModel by viewModels() - val context = this + val context = this - GlobalScope.launch(Dispatchers.IO) { - val licenseKey = getString(R.string.pspdfkit_license_key) + GlobalScope.launch(Dispatchers.IO) { + val licenseKey = getString(R.string.pspdfkit_license_key) - if (licenseKey.length > 30) { - PSPDFKit.initialize(context, licenseKey) - } else { - PSPDFKit.initialize(context, null) - } - } - - setContent { - OmnivoreTheme { - Box( - modifier = Modifier - .fillMaxSize() - .background(color = Color.Black) - ) { - RootView( - loginViewModel, - searchViewModel, - libraryViewModel, - settingsViewModel, - labelsViewModel, - saveViewModel, - editInfoViewModel) + if (licenseKey.length > 30) { + PSPDFKit.initialize(context, licenseKey) + } else { + PSPDFKit.initialize(context, null) + } } - } - } - // animate the view up when keyboard appears - WindowCompat.setDecorFitsSystemWindows(window, false) - val rootView = findViewById(android.R.id.content).rootView - ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> - val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom - rootView.setPadding(0, 0, 0, imeHeight) - insets + enableEdgeToEdge() + + setContent { + OmnivoreTheme { + Box( + modifier = Modifier + .fillMaxSize() + ) { + RootView( + loginViewModel, + searchViewModel, + libraryViewModel, + settingsViewModel, + labelsViewModel, + saveViewModel, + editInfoViewModel + ) + } + } + } + + // animate the view up when keyboard appears + WindowCompat.setDecorFitsSystemWindows(window, false) + val rootView = findViewById(android.R.id.content).rootView + ViewCompat.setOnApplyWindowInsetsListener(rootView) { _, insets -> + val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom + rootView.setPadding(0, 0, 0, imeHeight) + insets + } } - } } diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt index 6d1e2e138..45dfe5095 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/auth/WelcomeScreen.kt @@ -4,7 +4,7 @@ import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.* @@ -14,7 +14,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -23,154 +22,173 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import app.omnivore.omnivore.R import app.omnivore.omnivore.ui.theme.OmnivoreTheme +import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.android.gms.common.GoogleApiAvailability import kotlinx.coroutines.launch @Composable fun WelcomeScreen(viewModel: LoginViewModel) { - OmnivoreTheme(darkTheme = false) { - Surface(modifier = Modifier.fillMaxSize(), color = Color(0xFFFCEBA8)) { - WelcomeScreenContent(viewModel = viewModel) + + val systemUiController = rememberSystemUiController() + val useDarkIcons = !isSystemInDarkTheme() + + DisposableEffect(systemUiController, useDarkIcons) { + systemUiController.setSystemBarsColor( + color = Color.Black, + darkIcons = true + ) + onDispose { + systemUiController.setSystemBarsColor( + color = Color.Black, + darkIcons = useDarkIcons + ) + } + } + + OmnivoreTheme(darkTheme = false) { + Surface(modifier = Modifier.fillMaxSize(), color = Color(0xFFFCEBA8)) { + WelcomeScreenContent(viewModel = viewModel) + } } - } } @SuppressLint("CoroutineCreationDuringComposition") @Composable fun WelcomeScreenContent(viewModel: LoginViewModel) { - val registrationState: RegistrationState by viewModel - .registrationStateLiveData - .observeAsState(RegistrationState.SocialLogin) + val registrationState: RegistrationState by viewModel + .registrationStateLiveData + .observeAsState(RegistrationState.SocialLogin) - val snackBarHostState = remember { SnackbarHostState() } - val coroutineScope = rememberCoroutineScope() - val focusManager = LocalFocusManager.current + val snackBarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() - Column( - verticalArrangement = Arrangement.SpaceAround, - horizontalAlignment = Alignment.Start, - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp) - .clickable { focusManager.clearFocus() } - ) { - Spacer(modifier = Modifier.height(50.dp)) - Image( - painter = painterResource(id = R.drawable.ic_omnivore_name_logo), - contentDescription = "Omnivore Icon with Name" - ) - Spacer(modifier = Modifier.height(50.dp)) - - when(registrationState) { - RegistrationState.EmailSignIn -> { - EmailLoginView(viewModel = viewModel) - } - RegistrationState.EmailSignUp -> { - EmailSignUpView(viewModel = viewModel) - } - RegistrationState.SelfHosted -> { - SelfHostedView(viewModel = viewModel) - } - RegistrationState.SocialLogin -> { - Text( - text = stringResource(id = R.string.welcome_title), - style = MaterialTheme.typography.headlineLarge + Column( + verticalArrangement = Arrangement.SpaceAround, + horizontalAlignment = Alignment.Start, + modifier = Modifier + .fillMaxSize() + .padding(16.dp) + ) { + Spacer(modifier = Modifier.height(50.dp)) + Image( + painter = painterResource(id = R.drawable.ic_omnivore_name_logo), + contentDescription = "Omnivore Icon with Name" ) - - Text( - text = stringResource(id = R.string.welcome_subtitle), - style = MaterialTheme.typography.titleSmall - ) - - MoreInfoButton() - Spacer(modifier = Modifier.height(50.dp)) - AuthProviderView(viewModel = viewModel) - } - RegistrationState.PendingUser -> { - CreateUserProfileView(viewModel = viewModel) - } + when (registrationState) { + RegistrationState.EmailSignIn -> { + EmailLoginView(viewModel = viewModel) + } + + RegistrationState.EmailSignUp -> { + EmailSignUpView(viewModel = viewModel) + } + + RegistrationState.SelfHosted -> { + SelfHostedView(viewModel = viewModel) + } + + RegistrationState.SocialLogin -> { + Text( + text = stringResource(id = R.string.welcome_title), + style = MaterialTheme.typography.headlineLarge + ) + + Text( + text = stringResource(id = R.string.welcome_subtitle), + style = MaterialTheme.typography.titleSmall + ) + + MoreInfoButton() + + Spacer(modifier = Modifier.height(50.dp)) + + AuthProviderView(viewModel = viewModel) + } + + RegistrationState.PendingUser -> { + CreateUserProfileView(viewModel = viewModel) + } + } + + Spacer(modifier = Modifier.weight(1.0F)) } - Spacer(modifier = Modifier.weight(1.0F)) - } + if (viewModel.errorMessage != null) { + coroutineScope.launch { + val result = snackBarHostState + .showSnackbar( + viewModel.errorMessage!!, + actionLabel = "Dismiss", + duration = SnackbarDuration.Indefinite + ) + when (result) { + SnackbarResult.ActionPerformed -> viewModel.resetErrorMessage() + else -> {} + } + } - if (viewModel.errorMessage != null) { - coroutineScope.launch { - val result = snackBarHostState - .showSnackbar( - viewModel.errorMessage!!, - actionLabel = "Dismiss", - duration = SnackbarDuration.Indefinite - ) - when (result) { - SnackbarResult.ActionPerformed -> viewModel.resetErrorMessage() - else -> {} - } + SnackbarHost(hostState = snackBarHostState) } - - SnackbarHost(hostState = snackBarHostState) - } } @Composable fun AuthProviderView(viewModel: LoginViewModel) { - val isGoogleAuthAvailable: Boolean = GoogleApiAvailability - .getInstance() - .isGooglePlayServicesAvailable(LocalContext.current) == 0 + val isGoogleAuthAvailable: Boolean = GoogleApiAvailability + .getInstance() + .isGooglePlayServicesAvailable(LocalContext.current) == 0 - Row( - horizontalArrangement = Arrangement.Center - ) { - Spacer(modifier = Modifier.weight(1.0F)) - Column( - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalAlignment = Alignment.CenterHorizontally + Row( + horizontalArrangement = Arrangement.Center ) { - if (isGoogleAuthAvailable) { - GoogleAuthButton(viewModel) - } + Spacer(modifier = Modifier.weight(1.0F)) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + if (isGoogleAuthAvailable) { + GoogleAuthButton(viewModel) + } - AppleAuthButton(viewModel) + AppleAuthButton(viewModel) - ClickableText( - text = AnnotatedString(stringResource(R.string.welcome_screen_action_continue_with_email)), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showEmailSignIn() } - ) + ClickableText( + text = AnnotatedString(stringResource(R.string.welcome_screen_action_continue_with_email)), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showEmailSignIn() } + ) - Spacer(modifier = Modifier.weight(1.0F)) + Spacer(modifier = Modifier.weight(1.0F)) - ClickableText( - text = AnnotatedString(stringResource(R.string.welcome_screen_action_self_hosting_options)), - style = MaterialTheme.typography.titleMedium - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { viewModel.showSelfHostedSettings() }, - modifier = Modifier - .padding(vertical = 10.dp) - ) + ClickableText( + text = AnnotatedString(stringResource(R.string.welcome_screen_action_self_hosting_options)), + style = MaterialTheme.typography.titleMedium + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { viewModel.showSelfHostedSettings() }, + modifier = Modifier + .padding(vertical = 10.dp) + ) + } + Spacer(modifier = Modifier.weight(1.0F)) } - Spacer(modifier = Modifier.weight(1.0F)) - } } @Composable fun MoreInfoButton() { - val context = LocalContext.current - val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse("https://omnivore.app/about")) } + val context = LocalContext.current + val intent = remember { Intent(Intent.ACTION_VIEW, Uri.parse("https://omnivore.app/about")) } - ClickableText( - text = AnnotatedString( - stringResource(id = R.string.learn_more), - ), - style = MaterialTheme.typography.titleSmall - .plus(TextStyle(textDecoration = TextDecoration.Underline)), - onClick = { - context.startActivity(intent) - }, - modifier = Modifier.padding(vertical = 6.dp) - ) + ClickableText( + text = AnnotatedString( + stringResource(id = R.string.learn_more), + ), + style = MaterialTheme.typography.titleSmall + .plus(TextStyle(textDecoration = TextDecoration.Underline)), + onClick = { + context.startActivity(intent) + }, + modifier = Modifier.padding(vertical = 6.dp) + ) } - diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt index caf8816b2..32e0d66a4 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/library/LibraryView.kt @@ -20,7 +20,6 @@ import androidx.compose.material.DismissValue import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.FractionalThreshold import androidx.compose.material.Icon -import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.Scaffold import androidx.compose.material.ScaffoldState import androidx.compose.material.SwipeToDismiss @@ -32,11 +31,11 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.rememberDismissState -import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material.rememberScaffoldState import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -70,7 +69,6 @@ import app.omnivore.omnivore.ui.savedItemViews.SavedItemCard import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterialApi::class) @Composable fun LibraryView( libraryViewModel: LibraryViewModel, @@ -146,7 +144,7 @@ fun showAddLinkBottomSheet(libraryViewModel: LibraryViewModel) { libraryViewModel.bottomSheetState.value = LibraryBottomSheetState.ADD_LINK } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun LabelBottomSheet( libraryViewModel: LibraryViewModel, diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt index e411f9d12..cdb12f527 100644 --- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt +++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt @@ -1,14 +1,10 @@ package app.omnivore.omnivore.ui.root -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController @@ -18,109 +14,98 @@ import app.omnivore.omnivore.ui.auth.WelcomeScreen import app.omnivore.omnivore.ui.components.LabelsViewModel import app.omnivore.omnivore.ui.editinfo.EditInfoViewModel import app.omnivore.omnivore.ui.library.LibraryView -import app.omnivore.omnivore.ui.library.SearchView import app.omnivore.omnivore.ui.library.LibraryViewModel +import app.omnivore.omnivore.ui.library.SearchView import app.omnivore.omnivore.ui.library.SearchViewModel import app.omnivore.omnivore.ui.save.SaveViewModel import app.omnivore.omnivore.ui.settings.PolicyWebView import app.omnivore.omnivore.ui.settings.SettingsView import app.omnivore.omnivore.ui.settings.SettingsViewModel -import com.google.accompanist.systemuicontroller.rememberSystemUiController @Composable fun RootView( - loginViewModel: LoginViewModel, - searchViewModel: SearchViewModel, - libraryViewModel: LibraryViewModel, - settingsViewModel: SettingsViewModel, - labelsViewModel: LabelsViewModel, - saveViewModel: SaveViewModel, - editInfoViewModel: EditInfoViewModel, + loginViewModel: LoginViewModel, + searchViewModel: SearchViewModel, + libraryViewModel: LibraryViewModel, + settingsViewModel: SettingsViewModel, + labelsViewModel: LabelsViewModel, + saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, ) { - val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false) - val systemUiController = rememberSystemUiController() - val useDarkIcons = !isSystemInDarkTheme() + val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false) - DisposableEffect(systemUiController, useDarkIcons) { - systemUiController.setSystemBarsColor( - color = Color.Black, - darkIcons = false - ) + Box { + if (hasAuthToken) { + PrimaryNavigator( + loginViewModel = loginViewModel, + searchViewModel = searchViewModel, + libraryViewModel = libraryViewModel, + settingsViewModel = settingsViewModel, + labelsViewModel = labelsViewModel, + saveViewModel = saveViewModel, + editInfoViewModel = editInfoViewModel, + ) + } else { + WelcomeScreen(viewModel = loginViewModel) + } - onDispose {} - } - - Box( - modifier = Modifier - .systemBarsPadding() - ) { - if (hasAuthToken) { - PrimaryNavigator( - loginViewModel = loginViewModel, - searchViewModel = searchViewModel, - libraryViewModel = libraryViewModel, - settingsViewModel = settingsViewModel, - labelsViewModel = labelsViewModel, - saveViewModel = saveViewModel, - editInfoViewModel = editInfoViewModel, - ) - } else { - WelcomeScreen(viewModel = loginViewModel) + DisposableEffect(hasAuthToken) { + if (hasAuthToken) { + loginViewModel.registerUser() + } + onDispose {} + } } - - DisposableEffect(hasAuthToken) { - if (hasAuthToken) { - loginViewModel.registerUser() - } - onDispose {} - } - } } @Composable fun PrimaryNavigator( - loginViewModel: LoginViewModel, - libraryViewModel: LibraryViewModel, - searchViewModel: SearchViewModel, - settingsViewModel: SettingsViewModel, - labelsViewModel: LabelsViewModel, - saveViewModel: SaveViewModel, - editInfoViewModel: EditInfoViewModel, + loginViewModel: LoginViewModel, + libraryViewModel: LibraryViewModel, + searchViewModel: SearchViewModel, + settingsViewModel: SettingsViewModel, + labelsViewModel: LabelsViewModel, + saveViewModel: SaveViewModel, + editInfoViewModel: EditInfoViewModel, ) { - val navController = rememberNavController() + val navController = rememberNavController() - NavHost(navController = navController, startDestination = Routes.Library.route) { - composable(Routes.Library.route) { - LibraryView( - libraryViewModel = libraryViewModel, - navController = navController, - labelsViewModel = labelsViewModel, - saveViewModel = saveViewModel, - editInfoViewModel = editInfoViewModel, - ) - } + NavHost(navController = navController, startDestination = Routes.Library.route) { + composable(Routes.Library.route) { + LibraryView( + libraryViewModel = libraryViewModel, + navController = navController, + labelsViewModel = labelsViewModel, + saveViewModel = saveViewModel, + editInfoViewModel = editInfoViewModel, + ) + } - composable(Routes.Search.route) { - SearchView( - viewModel = searchViewModel, - navController = navController - ) - } + composable(Routes.Search.route) { + SearchView( + viewModel = searchViewModel, + navController = navController + ) + } - composable(Routes.Settings.route) { - SettingsView(loginViewModel = loginViewModel, settingsViewModel = settingsViewModel, navController = navController) - } + composable(Routes.Settings.route) { + SettingsView( + loginViewModel = loginViewModel, + settingsViewModel = settingsViewModel, + navController = navController + ) + } - composable(Routes.Documentation.route) { - PolicyWebView(navController = navController, url = "https://docs.omnivore.app") - } + composable(Routes.Documentation.route) { + PolicyWebView(navController = navController, url = "https://docs.omnivore.app") + } - composable(Routes.PrivacyPolicy.route) { - PolicyWebView(navController = navController, url = "https://omnivore.app/privacy") - } + composable(Routes.PrivacyPolicy.route) { + PolicyWebView(navController = navController, url = "https://omnivore.app/privacy") + } - composable(Routes.TermsAndConditions.route) { - PolicyWebView(navController = navController, url = "https://omnivore.app/app/terms") + composable(Routes.TermsAndConditions.route) { + PolicyWebView(navController = navController, url = "https://omnivore.app/app/terms") + } } - } } diff --git a/android/Omnivore/app/src/main/res/values/themes.xml b/android/Omnivore/app/src/main/res/values/themes.xml index fa82596de..f08194bdc 100644 --- a/android/Omnivore/app/src/main/res/values/themes.xml +++ b/android/Omnivore/app/src/main/res/values/themes.xml @@ -18,7 +18,5 @@ true false true - - @android:color/transparent