Use XML resources for copy

This commit is contained in:
Remy Chantenay
2023-10-05 10:13:37 +02:00
parent 8d5e6455e3
commit d6b669fd01
26 changed files with 431 additions and 148 deletions

View File

@ -0,0 +1,16 @@
package app.omnivore.omnivore.ui
import android.content.Context
import androidx.annotation.StringRes
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ResourceProvider @Inject constructor(
@ApplicationContext private val context: Context
) {
fun getString(@StringRes stringResId: Int): String {
return context.getString(stringResId)
}
}

View File

@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Dialog
@ -28,8 +29,8 @@ fun AppleAuthButton(viewModel: LoginViewModel) {
val showDialog = remember { mutableStateOf(false) }
LoadingButtonWithIcon(
text = "Continue with Apple",
loadingText = "Signing in...",
text = stringResource(R.string.apple_auth_text),
loadingText = stringResource(R.string.apple_auth_loading),
isLoading = viewModel.isLoading,
icon = painterResource(id = R.drawable.ic_logo_apple),
modifier = Modifier.padding(vertical = 6.dp),

View File

@ -8,7 +8,6 @@ import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
@ -25,7 +24,6 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import org.intellij.lang.annotations.JdkConstants
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
@ -42,7 +40,7 @@ fun CreateUserProfileView(viewModel: LoginViewModel) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Create Your Profile",
text = stringResource(R.string.create_user_profile_title),
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier.padding(bottom = 8.dp)
)
@ -61,11 +59,11 @@ fun CreateUserProfileView(viewModel: LoginViewModel) {
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
Text(stringResource(R.string.create_user_profile_loading))
}
ClickableText(
text = AnnotatedString("Cancel Sign Up"),
text = AnnotatedString(stringResource(R.string.create_user_profile_action_cancel)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.cancelNewUserSignUp() }
@ -98,8 +96,8 @@ fun UserProfileFields(
) {
OutlinedTextField(
value = name,
placeholder = { Text(text = "Name") },
label = { Text(text = "Name") },
placeholder = { Text(stringResource(R.string.create_user_profile_field_placeholder_name)) },
label = { Text(stringResource(R.string.create_user_profile_field_label_name)) },
onValueChange = onNameChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
@ -111,8 +109,8 @@ fun UserProfileFields(
) {
OutlinedTextField(
value = username,
placeholder = { Text(text = "Username") },
label = { Text(text = "Username") },
placeholder = { Text(stringResource(R.string.create_user_profile_field_placeholder_username)) },
label = { Text(stringResource(R.string.create_user_profile_field_label_username)) },
onValueChange = onUsernameChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
@ -144,7 +142,7 @@ fun UserProfileFields(
} else {
Toast.makeText(
context,
"Please enter a valid name and username.",
context.getString(R.string.create_user_profile_error_msg),
Toast.LENGTH_SHORT
).show()
}
@ -154,7 +152,7 @@ fun UserProfileFields(
)
) {
Text(
text = "Submit",
text = stringResource(R.string.create_user_profile_action_submit),
modifier = Modifier.padding(horizontal = 100.dp)
)
}

View File

@ -1,10 +1,6 @@
package app.omnivore.omnivore.ui.auth
import android.annotation.SuppressLint
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText
@ -16,10 +12,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@ -27,8 +23,8 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
@ -55,28 +51,28 @@ fun EmailLoginView(viewModel: LoginViewModel) {
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
Text(stringResource(R.string.email_login_loading))
}
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
ClickableText(
text = AnnotatedString("Return to Social Login"),
text = AnnotatedString(stringResource(R.string.email_login_action_back)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showSocialLogin() }
)
ClickableText(
text = AnnotatedString("Don't have an account?"),
text = AnnotatedString(stringResource(R.string.email_login_action_no_account)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showEmailSignUp() }
)
ClickableText(
text = AnnotatedString("Forgot your password?"),
text = AnnotatedString(stringResource(R.string.email_login_action_forgot_password)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = {
@ -111,8 +107,8 @@ fun LoginFields(
) {
OutlinedTextField(
value = email,
placeholder = { Text(text = "user@email.com") },
label = { Text(text = "Email") },
placeholder = { Text(stringResource(R.string.email_login_field_placeholder_email)) },
label = { Text(stringResource(R.string.email_login_field_label_email)) },
onValueChange = onEmailChange,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
@ -123,8 +119,8 @@ fun LoginFields(
OutlinedTextField(
value = password,
placeholder = { Text(text = "Password") },
label = { Text(text = "Password") },
placeholder = { Text(stringResource(R.string.email_login_field_placeholder_password)) },
label = { Text(stringResource(R.string.email_login_field_label_password)) },
onValueChange = onPasswordChange,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(
@ -141,7 +137,7 @@ fun LoginFields(
} else {
Toast.makeText(
context,
"Please enter an email address and password.",
context.getString(R.string.email_login_error_msg),
Toast.LENGTH_SHORT
).show()
}
@ -151,7 +147,7 @@ fun LoginFields(
)
) {
Text(
text = "Login",
text = stringResource(R.string.email_login_action_login),
modifier = Modifier.padding(horizontal = 100.dp)
)
}

View File

@ -18,6 +18,7 @@ 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.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@ -25,6 +26,7 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
@Composable
fun EmailSignUpView(viewModel: LoginViewModel) {
@ -32,8 +34,6 @@ fun EmailSignUpView(viewModel: LoginViewModel) {
val email = viewModel.pendingEmailUserCreds?.email ?: ""
val password = viewModel.pendingEmailUserCreds?.password ?: ""
val verificationMessage = "We've sent a verification email to ${email}. Please verify your email and then tap the button below."
Row(
horizontalArrangement = Arrangement.Center
) {
@ -43,7 +43,7 @@ fun EmailSignUpView(viewModel: LoginViewModel) {
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = verificationMessage,
text = stringResource(R.string.email_signup_verification_message, email),
style = MaterialTheme.typography.titleMedium
)
@ -55,13 +55,13 @@ fun EmailSignUpView(viewModel: LoginViewModel) {
)
) {
Text(
text = "Check Status",
text = stringResource(R.string.email_signup_check_status),
modifier = Modifier.padding(horizontal = 100.dp)
)
}
ClickableText(
text = AnnotatedString("Use a different email?"),
text = AnnotatedString(stringResource(R.string.email_signup_action_use_different_email)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showEmailSignUp() }
@ -115,21 +115,21 @@ fun EmailSignUpForm(viewModel: LoginViewModel) {
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
Text(stringResource(R.string.email_signup_loading))
}
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
ClickableText(
text = AnnotatedString("Return to Social Login"),
text = AnnotatedString(stringResource(R.string.email_signup_action_back)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showSocialLogin() }
)
ClickableText(
text = AnnotatedString("Already have an account?"),
text = AnnotatedString(stringResource(R.string.email_signup_action_already_have_account)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showEmailSignIn() }
@ -167,8 +167,8 @@ fun EmailSignUpFields(
) {
OutlinedTextField(
value = email,
placeholder = { Text(text = "user@email.com") },
label = { Text(text = "Email") },
placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_email)) },
label = { Text(stringResource(R.string.email_signup_field_label_email)) },
onValueChange = onEmailChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
@ -176,8 +176,8 @@ fun EmailSignUpFields(
OutlinedTextField(
value = password,
placeholder = { Text(text = "Password") },
label = { Text(text = "Password") },
placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_password)) },
label = { Text(stringResource(R.string.email_signup_field_label_password)) },
onValueChange = onPasswordChange,
visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
@ -186,8 +186,8 @@ fun EmailSignUpFields(
OutlinedTextField(
value = name,
placeholder = { Text(text = "Name") },
label = { Text(text = "Name") },
placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_name)) },
label = { Text(stringResource(R.string.email_signup_field_label_name)) },
onValueChange = onNameChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
@ -199,8 +199,8 @@ fun EmailSignUpFields(
) {
OutlinedTextField(
value = username,
placeholder = { Text(text = "Username") },
label = { Text(text = "Username") },
placeholder = { Text(stringResource(R.string.email_signup_field_placeholder_username)) },
label = { Text(stringResource(R.string.email_signup_field_label_username)) },
onValueChange = onUsernameChange,
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
@ -231,7 +231,7 @@ fun EmailSignUpFields(
} else {
Toast.makeText(
context,
"Please complete all fields.",
context.getString(R.string.email_signup_error_msg),
Toast.LENGTH_SHORT
).show()
}
@ -241,7 +241,7 @@ fun EmailSignUpFields(
)
) {
Text(
text = "Sign Up",
text = stringResource(R.string.email_signup_action_sign_up),
modifier = Modifier.padding(horizontal = 100.dp)
)
}

View File

@ -7,6 +7,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.R
import com.google.android.gms.auth.api.signin.GoogleSignIn
@ -38,8 +39,8 @@ fun GoogleAuthButton(viewModel: LoginViewModel) {
}
LoadingButtonWithIcon(
text = "Continue with Google",
loadingText = "Signing in...",
text = stringResource(R.string.google_auth_text),
loadingText = stringResource(R.string.google_auth_loading),
isLoading = viewModel.isLoading,
icon = painterResource(id = R.drawable.ic_logo_google),
onClick = {

View File

@ -11,6 +11,7 @@ import app.omnivore.omnivore.dataService.DataService
import app.omnivore.omnivore.graphql.generated.ValidateUsernameQuery
import app.omnivore.omnivore.networking.Networker
import app.omnivore.omnivore.networking.viewer
import app.omnivore.omnivore.ui.ResourceProvider
import com.apollographql.apollo3.ApolloClient
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.common.api.ApiException
@ -22,6 +23,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import java.util.regex.Pattern
import javax.inject.Inject
enum class RegistrationState {
SocialLogin,
EmailSignIn,
@ -40,7 +42,8 @@ class LoginViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository,
private val eventTracker: EventTracker,
private val networker: Networker,
private val dataService: DataService
private val dataService: DataService,
private val resourceProvider: ResourceProvider
): ViewModel() {
private var validateUsernameJob: Job? = null
@ -76,7 +79,7 @@ class LoginViewModel @Inject constructor(
datastoreRepo.putString(DatastoreKeys.omnivoreSelfHostedWebServer, webServer)
Toast.makeText(
context,
"Self-hosting settings updated.",
context.getString(R.string.login_view_model_self_hosting_settings_updated),
Toast.LENGTH_SHORT
).show()
}
@ -88,7 +91,7 @@ class LoginViewModel @Inject constructor(
datastoreRepo.clearValue(DatastoreKeys.omnivoreSelfHostedWebServer)
Toast.makeText(
context,
"Self-hosting settings reset.",
context.getString(R.string.login_view_model_self_hosting_settings_reset),
Toast.LENGTH_SHORT
).show()
}
@ -156,7 +159,8 @@ class LoginViewModel @Inject constructor(
}
if (potentialUsername.length < 4 || potentialUsername.length > 15) {
usernameValidationErrorMessage = "Username must be between 4 and 15 characters long."
usernameValidationErrorMessage = resourceProvider.getString(
R.string.login_view_model_username_validation_length_error_msg)
hasValidUsername = false
return@launch
}
@ -166,7 +170,8 @@ class LoginViewModel @Inject constructor(
.matches()
if (!isValidPattern) {
usernameValidationErrorMessage = "Username can contain only letters and numbers"
usernameValidationErrorMessage = resourceProvider.getString(
R.string.login_view_model_username_validation_alphanumeric_error_msg)
hasValidUsername = false
return@launch
}
@ -185,11 +190,13 @@ class LoginViewModel @Inject constructor(
hasValidUsername = true
} else {
hasValidUsername = false
usernameValidationErrorMessage = "This username is not available."
usernameValidationErrorMessage = resourceProvider.getString(
R.string.login_view_model_username_not_available_error_msg)
}
} catch (e: java.lang.Exception) {
hasValidUsername = false
usernameValidationErrorMessage = "Sorry we're having trouble connecting to the server."
usernameValidationErrorMessage = resourceProvider.getString(
R.string.login_view_model_connection_error_msg)
}
}
}
@ -216,7 +223,8 @@ class LoginViewModel @Inject constructor(
if (result.body()?.authToken != null) {
datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!)
} else {
errorMessage = "Something went wrong. Please check your email/password and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_error_msg)
}
if (result.body()?.authCookieString != null) {
@ -251,7 +259,8 @@ class LoginViewModel @Inject constructor(
isLoading = false
if (result.errorBody() != null) {
errorMessage = "Something went wrong. Please check your entries and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_two_error_msg)
} else {
pendingEmailUserCreds = PendingEmailUserCreds(email, password)
}
@ -284,7 +293,8 @@ class LoginViewModel @Inject constructor(
if (result.body()?.authToken != null) {
datastoreRepo.putString(DatastoreKeys.omnivoreAuthToken, result.body()?.authToken!!)
} else {
errorMessage = "Something went wrong. Please check your email/password and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_error_msg)
}
if (result.body()?.authCookieString != null) {
@ -315,7 +325,7 @@ class LoginViewModel @Inject constructor(
}
fun showGoogleErrorMessage() {
errorMessage = "Failed to authenticate with Google."
errorMessage = resourceProvider.getString(R.string.login_view_model_google_auth_error_msg)
}
fun handleGoogleAuthTask(task: Task<GoogleSignInAccount>) {
@ -324,7 +334,8 @@ class LoginViewModel @Inject constructor(
// If token is missing then set the error message
if (googleIdToken == null) {
errorMessage = "No authentication token found."
errorMessage = resourceProvider.getString(
R.string.login_view_model_missing_auth_token_error_msg)
return
}
@ -361,10 +372,12 @@ class LoginViewModel @Inject constructor(
}
418 -> {
// Show pending email state
errorMessage = "Something went wrong. Please check your credentials and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_two_error_msg)
}
else -> {
errorMessage = "Something went wrong. Please check your credentials and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_two_error_msg)
}
}
}
@ -386,7 +399,8 @@ class LoginViewModel @Inject constructor(
)
registrationStateLiveData.value = RegistrationState.PendingUser
} else {
errorMessage = "Something went wrong. Please check your credentials and try again"
errorMessage = resourceProvider.getString(
R.string.login_view_model_something_went_wrong_two_error_msg)
}
}
}

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
@ -34,6 +35,7 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import app.omnivore.omnivore.BuildConfig
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.R
@SuppressLint("CoroutineCreationDuringComposition")
@Composable
@ -62,7 +64,7 @@ fun SelfHostedView(viewModel: LoginViewModel) {
// TODO: add a activity indicator (maybe after a delay?)
if (viewModel.isLoading) {
Text("Loading...")
Text(stringResource(R.string.self_hosted_view_loading))
}
Row(
@ -72,14 +74,14 @@ fun SelfHostedView(viewModel: LoginViewModel) {
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
ClickableText(
text = AnnotatedString("Reset"),
text = AnnotatedString(stringResource(R.string.self_hosted_view_action_reset)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.resetSelfHostingDetails(context) },
modifier = Modifier.align(Alignment.CenterHorizontally)
)
ClickableText(
text = AnnotatedString("Back"),
text = AnnotatedString(stringResource(R.string.self_hosted_view_action_back)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showSocialLogin() },
@ -91,7 +93,7 @@ fun SelfHostedView(viewModel: LoginViewModel) {
// "your private self-hosted instance.\n\n"
// )
ClickableText(
text = AnnotatedString("Learn more about self-hosting Omnivore"),
text = AnnotatedString(stringResource(R.string.self_hosted_view_action_learn_more)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = {
@ -130,7 +132,7 @@ fun SelfHostedFields(
OutlinedTextField(
value = apiServer,
placeholder = { Text(text = "https://api-prod.omnivore.app/") },
label = { Text(text = "API Server") },
label = { Text(stringResource(R.string.self_hosted_view_field_api_url_label)) },
onValueChange = onAPIServerChange,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
@ -142,7 +144,7 @@ fun SelfHostedFields(
OutlinedTextField(
value = webServer,
placeholder = { Text(text = "https://omnivore.app/") },
label = { Text(text = "Web Server") },
label = { Text(stringResource(R.string.self_hosted_view_field_web_url_label)) },
onValueChange = onWebServerChange,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
@ -158,7 +160,7 @@ fun SelfHostedFields(
} else {
Toast.makeText(
context,
"Please enter API Server and Web server addresses.",
context.getString(R.string.self_hosted_view_error_msg),
Toast.LENGTH_SHORT
).show()
}
@ -168,7 +170,7 @@ fun SelfHostedFields(
)
) {
Text(
text = "Save",
text = stringResource(R.string.self_hosted_view_action_save),
modifier = Modifier.padding(horizontal = 100.dp)
)
}

View File

@ -135,7 +135,7 @@ fun AuthProviderView(viewModel: LoginViewModel) {
AppleAuthButton(viewModel)
ClickableText(
text = AnnotatedString("Continue with Email"),
text = AnnotatedString(stringResource(R.string.welcome_screen_action_continue_with_email)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showEmailSignIn() }
@ -144,7 +144,7 @@ fun AuthProviderView(viewModel: LoginViewModel) {
Spacer(modifier = Modifier.weight(1.0F))
ClickableText(
text = AnnotatedString("Self-hosting options"),
text = AnnotatedString(stringResource(R.string.welcome_screen_action_self_hosting_options)),
style = MaterialTheme.typography.titleMedium
.plus(TextStyle(textDecoration = TextDecoration.Underline)),
onClick = { viewModel.showSelfHostedSettings() },

View File

@ -20,10 +20,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import app.omnivore.omnivore.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -59,13 +61,13 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit)
.fillMaxWidth()
) {
TextButton(onClick = onDismiss) {
Text(text = "Cancel")
Text(text = stringResource(R.string.label_creation_action_cancel))
}
Text("Create New Label", fontWeight = FontWeight.ExtraBold)
Text(stringResource(R.string.label_creation_title), fontWeight = FontWeight.ExtraBold)
TextButton(onClick = { onSave(labelName, selectedHex) }) {
Text(text = "Create")
Text(text = stringResource(R.string.label_creation_action_create))
}
}
@ -76,7 +78,7 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit)
.fillMaxWidth()
.padding(vertical = 10.dp)
) {
Text("Assign a name and color.")
Text(stringResource(R.string.label_creation_content))
}
Row(
@ -88,7 +90,7 @@ fun LabelCreationDialog(onDismiss: () -> Unit, onSave: (String, String) -> Unit)
) {
OutlinedTextField(
value = labelName,
placeholder = { Text(text = "Label Name") },
placeholder = { Text(stringResource(R.string.label_creation_label_placeholder)) },
onValueChange = { labelName = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })

View File

@ -36,6 +36,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.*
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
@ -44,6 +45,7 @@ import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.R
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.persistence.entities.SavedItemLabel
import app.omnivore.omnivore.ui.library.LibraryViewModel
@ -260,7 +262,9 @@ fun LabelsSelectionSheetContent(
it.name.toLowerCase(Locale.current) == text
}
val titleText = if (isLibraryMode) "Filter by Label" else "Set Labels"
val titleText = if (isLibraryMode)
stringResource(R.string.label_selection_sheet_title) else
stringResource(R.string.label_selection_sheet_title_alt)
Surface(
modifier = Modifier
@ -282,13 +286,15 @@ fun LabelsSelectionSheetContent(
.fillMaxWidth()
) {
TextButton(onClick = onCancel) {
Text(text = "Cancel")
Text(text = stringResource(R.string.label_selection_sheet_action_cancel))
}
Text(titleText, fontWeight = FontWeight.ExtraBold)
TextButton(onClick = { onSave(state.chips.map { it.label }) }) {
Text(text = if (isLibraryMode) "Search" else "Save")
Text(text = if (isLibraryMode)
stringResource(R.string.label_selection_sheet_action_search) else
stringResource(R.string.label_selection_sheet_action_save))
}
}
@ -341,7 +347,11 @@ fun LabelsSelectionSheetContent(
modifier = Modifier
.fillMaxWidth()
.clickable {
val label = findOrCreateLabel(labelsViewModel = labelsViewModel, labels = labels, name = filterTextValue)
val label = findOrCreateLabel(
labelsViewModel = labelsViewModel,
labels = labels,
name = filterTextValue
)
state.addChip(LabelChipView(label))
filterTextValue = TextFieldValue()
}
@ -354,7 +364,7 @@ fun LabelsSelectionSheetContent(
contentDescription = null,
modifier = Modifier.padding(end = 8.dp)
)
Text(text = "Create a new label named \"${filterTextValue.text}\"")
Text(text = stringResource(R.string.label_selection_sheet_text_create, filterTextValue.text))
}
}

View File

@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.toLowerCase
import androidx.compose.ui.unit.dp
@ -69,7 +70,7 @@ fun LibraryFilterBar(viewModel: LibraryViewModel) {
)
AssistChip(
onClick = { viewModel.showLabelsSelectionSheetLiveData.value = true },
label = { Text("Labels") },
label = { Text(stringResource(R.string.library_filter_bar_label_labels)) },
trailingIcon = {
Icon(
Icons.Default.ArrowDropDown,

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
@ -34,7 +35,9 @@ fun LibraryNavigationBar(
TopAppBar(
title = {
Text(if (actionsMenuItem == null) "Library" else "")
Text(if (actionsMenuItem == null)
stringResource(R.string.library_nav_bar_title) else
stringResource(R.string.library_nav_bar_title_alt))
},
modifier = Modifier.statusBarsPadding(),
colors = TopAppBarDefaults.topAppBarColors(
@ -218,7 +221,7 @@ fun SearchField(
value = searchText,
onValueChange = onSearchTextChanged,
placeholder = {
Text(text = "Search")
Text(text = stringResource(R.string.library_nav_bar_field_placeholder_search))
},
leadingIcon = {
IconButton(

View File

@ -19,6 +19,7 @@ import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
import app.omnivore.omnivore.models.ServerSyncStatus
import app.omnivore.omnivore.networking.*
import app.omnivore.omnivore.persistence.entities.*
import app.omnivore.omnivore.ui.ResourceProvider
import com.apollographql.apollo3.api.Optional
import com.apollographql.apollo3.api.Optional.Companion.presentIfNotNull
import dagger.hilt.android.lifecycle.HiltViewModel
@ -31,7 +32,8 @@ import javax.inject.Inject
class LibraryViewModel @Inject constructor(
private val networker: Networker,
private val dataService: DataService,
private val datastoreRepo: DatastoreRepository
private val datastoreRepo: DatastoreRepository,
private val resourceProvider: ResourceProvider
): ViewModel(), SavedItemViewModel {
private val contentRequestChannel = Channel<String>(capacity = Channel.UNLIMITED)
@ -348,9 +350,9 @@ class LibraryViewModel @Inject constructor(
dataService.db.savedItemAndSavedItemLabelCrossRefDao().insertAll(crossRefs)
if (!networkResult || labelCreationError) {
snackbarMessage = "Unable to set labels"
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_error)
} else {
snackbarMessage = "Labels updated"
snackbarMessage = resourceProvider.getString(R.string.library_view_model_snackbar_success)
}
CoroutineScope(Dispatchers.Main).launch {

View File

@ -43,6 +43,7 @@ import kotlinx.coroutines.launch
import app.omnivore.omnivore.persistence.entities.Highlight
import app.omnivore.omnivore.ui.theme.OmnivoreTheme
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.res.stringResource
fun notebookMD(notes: List<Highlight>, highlights: List<Highlight>): String {
@ -80,6 +81,9 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote:
val scrollState = rememberScrollState()
val coroutineScope = rememberCoroutineScope()
val snackBarHostState = remember { SnackbarHostState() }
val context = LocalContext.current
val snackBarHostStateMsg = context.getString(R.string.notebook_view_snackbar_msg)
val clipboard: ClipboardManager? =
LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
@ -89,7 +93,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote:
Scaffold(
topBar = {
TopAppBar(
title = { Text("Notebook") },
title = { Text(stringResource(R.string.notebook_view_title)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
@ -109,7 +113,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote:
onDismissRequest = { isMenuOpen = false }
) {
DropdownMenuItem(
text = { Text("Copy") },
text = { Text(stringResource(R.string.notebook_view_action_copy)) },
onClick = {
val clip = ClipData.newPlainText("notebook", notebookMD(notes, highlights))
clipboard?.let {
@ -117,7 +121,7 @@ fun NotebookView(savedItemId: String, viewModel: NotebookViewModel, onEditNote:
} ?: run {
coroutineScope.launch {
snackBarHostState
.showSnackbar("Notebook copied")
.showSnackbar(snackBarHostStateMsg)
}
}
isMenuOpen = false
@ -154,7 +158,7 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = { Text("Note") },
title = { Text(stringResource(R.string.edit_note_modal_title)) },
modifier = Modifier.statusBarsPadding(),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.background
@ -163,14 +167,14 @@ fun EditNoteModal(initialValue: String?, onDismiss: (save: Boolean, text: String
TextButton(onClick = {
onDismiss(false, initialValue)
}) {
Text(text = "Cancel")
Text(text = stringResource(R.string.edit_note_modal_action_cancel))
}
},
actions = {
TextButton(onClick = {
onDismiss(true, annotation.value)
}) {
Text(text = "Save")
Text(text = stringResource(R.string.edit_note_modal_action_save))
}
}
)
@ -205,7 +209,7 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl
.padding(start = 15.dp)
.padding(top = 20.dp, bottom = 50.dp)
) {
Text("Article Notes")
Text(stringResource(R.string.article_notes_title))
Divider(modifier = Modifier.padding(bottom= 15.dp))
notes.forEach { note ->
MarkdownText(
@ -231,7 +235,7 @@ fun ArticleNotes(viewModel: NotebookViewModel, item: SavedItemWithLabelsAndHighl
)
) {
Text(
text = "Add Notes...",
text = stringResource(R.string.article_notes_action_add_notes),
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 0.dp),
@ -250,6 +254,8 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
val coroutineScope = rememberCoroutineScope()
val snackBarHostState = remember { SnackbarHostState() }
val context = LocalContext.current
val snackBarHostStateMsg = context.getString(R.string.highlights_list_snackbar_msg)
val clipboard: ClipboardManager? =
LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
@ -258,7 +264,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
.padding(start = 15.dp)
.padding(top = 40.dp, bottom = 100.dp)
) {
Text("Highlights")
Text(stringResource(R.string.highlights_list_title))
Divider(modifier = Modifier.padding(bottom= 10.dp))
highlights.forEach { highlight ->
var isMenuOpen by remember { mutableStateOf(false) }
@ -282,7 +288,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
onDismissRequest = { isMenuOpen = false }
) {
DropdownMenuItem(
text = { Text("Copy") },
text = { Text(stringResource(R.string.highlights_list_action_copy)) },
onClick = {
val clip = ClipData.newPlainText("highlight", highlight.quote)
clipboard?.let {
@ -290,7 +296,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
} ?: run {
coroutineScope.launch {
snackBarHostState
.showSnackbar("Highlight copied")
.showSnackbar(snackBarHostStateMsg)
}
}
isMenuOpen = false
@ -354,7 +360,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
)
) {
Text(
text = "Add Note...",
text = stringResource(R.string.highlights_list_action_add_note),
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
modifier = Modifier
.padding(vertical = 2.dp, horizontal = 0.dp),
@ -365,7 +371,7 @@ fun HighlightsList(item: SavedItemWithLabelsAndHighlights, onEditNote: (note: Hi
}
if (highlights.isEmpty()) {
Text(
text = "You have not added any highlights to this page.",
text = stringResource(R.string.highlights_list_error_msg_no_highlights),
style = androidx.compose.material.MaterialTheme.typography.subtitle2,
modifier = Modifier.padding(vertical = 10.dp, horizontal = 10.dp)
)

View File

@ -20,6 +20,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@ -103,7 +104,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
}
}
Text("Font Size:", style = TextStyle(
Text(stringResource(R.string.reader_preferences_view_font_size), style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
color = Color(red = 137, green = 137, blue = 137)
@ -118,7 +119,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
valueRange = 10f..48f,
)
Text("Margin", style = TextStyle(
Text(stringResource(R.string.reader_preferences_view_margin), style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
color = Color(red = 137, green = 137, blue = 137)
@ -133,7 +134,7 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
valueRange = 60f..100f,
)
Text("Line Spacing", style = TextStyle(
Text(stringResource(R.string.reader_preferences_view_line_spacing), style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
color = Color(red = 137, green = 137, blue = 137)
@ -153,13 +154,13 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
modifier = Modifier
.padding(vertical = 4.dp)
) {
Text("Theme:", style = TextStyle(
Text(stringResource(R.string.reader_preferences_view_theme), style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
color = Color(red = 137, green = 137, blue = 137)
))
Spacer(modifier = Modifier.weight(1.0F))
Text("Auto", style = TextStyle(
Text(stringResource(R.string.reader_preferences_view_auto), style = TextStyle(
fontSize = 10.sp,
fontWeight = FontWeight.Normal,
color = Color(red = 137, green = 137, blue = 137)
@ -208,7 +209,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("High Contrast Text",
Text(
stringResource(R.string.reader_preferences_view_high_constrast_text),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
@ -225,7 +227,8 @@ fun ReaderPreferencesView(webReaderViewModel: WebReaderViewModel) {
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Justify Text",
Text(
stringResource(R.string.reader_preferences_view_justify_text),
style = TextStyle(
fontSize = 15.sp,
fontWeight = FontWeight.Normal,

View File

@ -44,6 +44,7 @@ import kotlinx.coroutines.launch
import kotlin.math.roundToInt
import androidx.compose.material3.Button
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.omnivore.omnivore.ui.components.LabelsViewModel
import app.omnivore.omnivore.ui.notebook.EditNoteModal
@ -80,7 +81,7 @@ class WebReaderLoadingContainerActivity: ComponentActivity() {
.background(color = Color.Black)
) {
if (viewModel.hasFetchError.value == true) {
Text("We were unable to fetch your content.")
Text(stringResource(R.string.web_reader_loading_container_error_msg))
} else {
WebReaderLoadingContainer(
requestID = requestID,
@ -200,13 +201,13 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
sheetContent = {
when (bottomSheetState) {
BottomSheetState.PREFERENCES -> {
BottomSheetUI("Reader Preferences") {
BottomSheetUI(stringResource(R.string.web_reader_loading_container_bottom_sheet_reader_preferences)) {
ReaderPreferencesView(webReaderViewModel)
}
}
BottomSheetState.NOTEBOOK -> {
webReaderParams?.let { params ->
BottomSheetUI(title = "Notebook") {
BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_notebook)) {
NotebookView(savedItemId = params.item.savedItemId, viewModel = notebookViewModel, onEditNote = {
notebookViewModel.highlightUnderEdit = it
webReaderViewModel.setBottomSheet(BottomSheetState.EDITNOTE)
@ -255,7 +256,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
)
}
BottomSheetState.LABELS -> {
BottomSheetUI(title = "Notebook") {
BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_notebook)) {
LabelsSelectionSheetContent(
labels = labels,
labelsViewModel = labelsViewModel,
@ -283,7 +284,7 @@ fun WebReaderLoadingContainer(slug: String? = null, requestID: String? = null,
}
}
BottomSheetState.LINK -> {
BottomSheetUI(title = "Open Link") {
BottomSheetUI(title = stringResource(R.string.web_reader_loading_container_bottom_sheet_open_link)) {
OpenLinkView(webReaderViewModel)
}
}
@ -460,25 +461,25 @@ fun OpenLinkView(webReaderViewModel: WebReaderViewModel) {
.padding(horizontal = 50.dp), verticalArrangement = Arrangement.spacedBy(20.dp)) {
Row {
Button(onClick = { webReaderViewModel.openCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Open in Browser")
Text(text = stringResource(R.string.open_link_view_action_open_in_browser))
}
}
Row() {
Button(onClick = { webReaderViewModel.saveCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Save to Omnivore")
Text(text = stringResource(R.string.open_link_view_action_save_to_omnivore))
}
}
Row() {
Button(onClick = {webReaderViewModel.copyCurrentLink(context) }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Copy Link")
Text(text = stringResource(R.string.open_link_view_action_copy_link))
}
}
Row {
Button(onClick = {webReaderViewModel.resetBottomSheet() }, modifier = Modifier.fillMaxWidth()) {
Text(text = "Cancel")
Text(text = stringResource(R.string.open_link_view_action_cancel))
}
}

View File

@ -12,6 +12,7 @@ import androidx.lifecycle.*
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.EventTracker
import app.omnivore.omnivore.R
import app.omnivore.omnivore.dataService.*
import app.omnivore.omnivore.graphql.generated.type.CreateLabelInput
import app.omnivore.omnivore.graphql.generated.type.SetLabelsInput
@ -139,7 +140,11 @@ class WebReaderViewModel @Inject constructor(
currentLink?.let {
viewModelScope.launch {
val success = networker.saveUrl(it)
Toast.makeText(context, if (success) "Link saved" else "Error saving link" , Toast.LENGTH_SHORT).show()
Toast.makeText(context,
if (success)
context.getString(R.string.web_reader_view_model_save_link_success) else
context.getString(R.string.web_reader_view_model_save_link_error),
Toast.LENGTH_SHORT).show()
}
}
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)
@ -153,7 +158,9 @@ class WebReaderViewModel @Inject constructor(
clipboard.setPrimaryClip(clip)
clipboard?.let {
clipboard?.setPrimaryClip(clip)
Toast.makeText(context, "Link Copied", Toast.LENGTH_SHORT).show()
Toast.makeText(context,
context.getString(R.string.web_reader_view_model_copy_link_success),
Toast.LENGTH_SHORT).show()
}
}
bottomSheetStateLiveData.postValue(BottomSheetState.NONE)

View File

@ -14,8 +14,10 @@ import androidx.compose.material.ButtonDefaults
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.omnivore.omnivore.MainActivity
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainerActivity
import kotlinx.coroutines.launch
@ -36,7 +38,7 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee
.fillMaxSize()
.padding(top = 48.dp, bottom = 32.dp)
) {
Text(text = viewModel.message ?: "Saving")
Text(text = viewModel.message ?: stringResource(R.string.save_content_msg))
Row {
if (enableReadNow) {
Button(
@ -55,7 +57,7 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee
backgroundColor = Color.White
)
) {
Text(text = "Read Now")
Text(text = stringResource(R.string.save_content_action_read_now))
}
Spacer(modifier = Modifier.width(8.dp))
@ -72,7 +74,9 @@ fun SaveContent(viewModel: SaveViewModel, modalBottomSheetState: ModalBottomShee
backgroundColor = Color(0xffffd234)
)
) {
Text(text = if (enableReadNow) "Read Later" else "Dismiss")
Text(text = if (enableReadNow)
stringResource(R.string.save_content_action_read_later) else
stringResource(R.string.save_content_action_dismiss))
}
}
}

View File

@ -12,8 +12,10 @@ import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.Constants
import app.omnivore.omnivore.DatastoreKeys
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.R
import app.omnivore.omnivore.graphql.generated.SaveUrlMutation
import app.omnivore.omnivore.graphql.generated.type.SaveUrlInput
import app.omnivore.omnivore.ui.ResourceProvider
import com.apollographql.apollo3.ApolloClient
import com.apollographql.apollo3.api.Optional
import dagger.hilt.android.lifecycle.HiltViewModel
@ -32,7 +34,8 @@ enum class SaveState {
@HiltViewModel
class SaveViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository
private val datastoreRepo: DatastoreRepository,
private val resourceProvider: ResourceProvider
) : ViewModel() {
val saveState = MutableLiveData(SaveState.NONE)
@ -62,13 +65,13 @@ class SaveViewModel @Inject constructor(
fun saveURL(url: String) {
viewModelScope.launch {
isLoading = true
message = "Saving to Omnivore..."
message = resourceProvider.getString(R.string.save_view_model_msg)
saveState.postValue(SaveState.SAVING)
val authToken = getAuthToken()
if (authToken == null) {
message = "You are not logged in. Please login before saving."
message = resourceProvider.getString(R.string.save_view_model_error_not_logged_in)
isLoading = false
return@launch
}
@ -103,15 +106,15 @@ class SaveViewModel @Inject constructor(
val success = (response.data?.saveUrl?.onSaveSuccess?.url != null)
message = if (success) {
"Page Saved"
resourceProvider.getString(R.string.save_view_model_page_saved_success)
} else {
"There was an error saving your page"
resourceProvider.getString(R.string.save_view_model_page_saved_error)
}
saveState.postValue(SaveState.SAVED)
Log.d(ContentValues.TAG, "Saved URL?: $success")
} catch (e: java.lang.Exception) {
message = "There was an error saving your page"
message = resourceProvider.getString(R.string.save_view_model_page_saved_error)
}
}
}

View File

@ -13,6 +13,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import app.omnivore.omnivore.R
import app.omnivore.omnivore.ui.library.SavedItemAction
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
@ -31,7 +32,7 @@ fun SavedItemContextMenu(
onDismissRequest = onDismiss
) {
DropdownMenuItem(
text = { Text("Edit Labels") },
text = { Text(stringResource(R.string.saved_item_context_menu_action_edit_labels)) },
onClick = {
actionHandler(SavedItemAction.EditLabels)
onDismiss()
@ -44,7 +45,9 @@ fun SavedItemContextMenu(
}
)
DropdownMenuItem(
text = { Text(if (isArchived) "Unarchive" else "Archive") },
text = { Text(if (isArchived)
stringResource(R.string.saved_item_context_menu_action_unarchive) else
stringResource(R.string.saved_item_context_menu_action_archive)) },
onClick = {
val action = if (isArchived) SavedItemAction.Unarchive else SavedItemAction.Archive
actionHandler(action)
@ -58,7 +61,7 @@ fun SavedItemContextMenu(
}
)
DropdownMenuItem(
text = { Text("Share Original") },
text = { Text(stringResource(R.string.saved_item_context_menu_action_share_original)) },
onClick = {
webReaderViewModel.showShareLinkSheet(context)
onDismiss()
@ -71,7 +74,7 @@ fun SavedItemContextMenu(
}
)
DropdownMenuItem(
text = { Text("Remove Item") },
text = { Text(stringResource(R.string.saved_item_context_menu_action_remove_item)) },
onClick = {
actionHandler(SavedItemAction.Delete)
onDismiss()

View File

@ -5,6 +5,8 @@ import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import app.omnivore.omnivore.R
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
@ -14,9 +16,9 @@ fun LogoutDialog(onClose: (Boolean) -> Unit) {
AlertDialog(
onDismissRequest = { onClose(false) },
title = { Text(text = "Logout") },
title = { Text(text = stringResource(R.string.logout_dialog_title)) },
text = {
Text("Are you sure you want to logout?")
Text(stringResource(R.string.logout_dialog_confirm_msg))
},
confirmButton = {
Button(onClick = {
@ -28,12 +30,12 @@ fun LogoutDialog(onClose: (Boolean) -> Unit) {
googleSignIn.signOut()
onClose(true)
}) {
Text("Confirm")
Text(stringResource(R.string.logout_dialog_action_confirm))
}
},
dismissButton = {
Button(onClick = { onClose(false) }) {
Text("Cancel")
Text(stringResource(R.string.logout_dialog_action_cancel))
}
}
)

View File

@ -14,8 +14,10 @@ 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) {
@ -43,7 +45,7 @@ fun ManageAccountView(settingsViewModel: SettingsViewModel) {
.padding(top = 12.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Center
) {
Text("Manage Account")
Text(stringResource(R.string.manage_account_title))
}
Column(
@ -55,7 +57,7 @@ fun ManageAccountView(settingsViewModel: SettingsViewModel) {
modifier = Modifier
.clickable(onClick = { settingsViewModel.resetDataCache() })
) {
Text("Reset Data Cache")
Text(stringResource(R.string.manage_account_action_reset_data_cache))
Spacer(modifier = Modifier.weight(1.0F))
Icon(imageVector = Icons.Filled.Refresh, contentDescription = null)
}

View File

@ -11,9 +11,11 @@ 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.Routes
import app.omnivore.omnivore.R
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter", "SetJavaScriptEnabled")
@OptIn(ExperimentalMaterial3Api::class)
@ -22,7 +24,7 @@ fun PolicyWebView(navController: NavHostController, url: String) {
Scaffold(
topBar = {
TopAppBar(
title = { androidx.compose.material3.Text("Settings") },
title = { androidx.compose.material3.Text(stringResource(R.string.policy_webview_title)) },
actions = {
IconButton(onClick = { navController.navigate(Routes.Settings.route) }) {
Icon(

View File

@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.R
@ -37,7 +38,7 @@ fun SettingsView(
Scaffold(
topBar = {
TopAppBar(
title = { Text("Settings") },
title = { Text(stringResource(R.string.settings_view_title)) },
actions = {
IconButton(onClick = { navController.navigate(Routes.Library.route) }) {
Icon(
@ -100,19 +101,31 @@ fun SettingsViewContent(loginViewModel: LoginViewModel, settingsViewModel: Setti
//
// SectionSpacer()
SettingRow(text = "Documentation") { navController.navigate(Routes.Documentation.route) }
SettingRow(text = stringResource(R.string.settings_view_setting_row_documentation)) {
navController.navigate(Routes.Documentation.route)
}
RowDivider()
SettingRow(text = "Feedback") { Intercom.client().present(space = IntercomSpace.Messages) }
SettingRow(text = stringResource(R.string.settings_view_setting_row_feedback)) {
Intercom.client().present(space = IntercomSpace.Messages)
}
RowDivider()
SettingRow(text = "Privacy Policy") { navController.navigate(Routes.PrivacyPolicy.route) }
SettingRow(text = stringResource(R.string.settings_view_setting_row_privacy_policy)) {
navController.navigate(Routes.PrivacyPolicy.route)
}
RowDivider()
SettingRow(text = "Terms and Conditions") { navController.navigate(Routes.TermsAndConditions.route) }
SettingRow(text = stringResource(R.string.settings_view_setting_row_terms_and_conditions)) {
navController.navigate(Routes.TermsAndConditions.route)
}
SectionSpacer()
SettingRow(text = "Manage Account") { showManageAccountDialog.value = true }
SettingRow(text = stringResource(R.string.settings_view_setting_row_manage_account)) {
showManageAccountDialog.value = true
}
RowDivider()
SettingRow(text = "Logout", includeIcon = false) { showLogoutDialog.value = true }
SettingRow(text = stringResource(R.string.settings_view_setting_row_logout), includeIcon = false) {
showLogoutDialog.value = true
}
RowDivider()
}

View File

@ -12,4 +12,195 @@
<string name="highlight_note">Note</string>
<string name="copyTextSelection">Copy</string>
<string name="pdf_highlight_menu_note">Note</string>
<!-- Apple Auth -->
<string name="apple_auth_text">Continue with Apple</string>
<string name="apple_auth_loading">Signing in...</string>
<!-- Create User Profile -->
<string name="create_user_profile_title">Create Your Profile</string>
<string name="create_user_profile_loading">Loading...</string>
<string name="create_user_profile_action_cancel">Cancel Sign Up</string>
<string name="create_user_profile_action_submit">Submit</string>
<string name="create_user_profile_field_placeholder_name">Name</string>
<string name="create_user_profile_field_label_name">Name</string>
<string name="create_user_profile_field_placeholder_username">Username</string>
<string name="create_user_profile_field_label_username">Username</string>
<string name="create_user_profile_error_msg">Please enter a valid name and username.</string>
<!-- Email Login -->
<string name="email_login_loading">Loading...</string>
<string name="email_login_action_back">Return to Social Login</string>
<string name="email_login_action_no_account">Don\'t have an account?</string>
<string name="email_login_action_forgot_password">Forgot your password?</string>
<string name="email_login_action_login">Login</string>
<string name="email_login_field_placeholder_email">user@email.com</string>
<string name="email_login_field_label_email">Email</string>
<string name="email_login_field_placeholder_password">Password</string>
<string name="email_login_field_label_password">Password</string>
<string name="email_login_error_msg">Please enter an email address and password.</string>
<!-- Email Sign Up -->
<string name="email_signup_verification_message">We\'ve sent a verification email to %1$s. Please verify your email and then tap the button below.</string>
<string name="email_signup_check_status">Check Status</string>
<string name="email_signup_action_use_different_email">Use a different email?</string>
<string name="email_signup_loading">Loading...</string>
<string name="email_signup_action_back">Return to Social Login</string>
<string name="email_signup_action_already_have_account">Already have an account?</string>
<string name="email_signup_action_sign_up">Sign Up</string>
<string name="email_signup_field_placeholder_email">user@email.com</string>
<string name="email_signup_field_label_email">Email</string>
<string name="email_signup_field_placeholder_password">Password</string>
<string name="email_signup_field_label_password">Password</string>
<string name="email_signup_field_placeholder_name">Name</string>
<string name="email_signup_field_label_name">Name</string>
<string name="email_signup_field_placeholder_username">Name</string>
<string name="email_signup_field_label_username">Name</string>
<string name="email_signup_error_msg">Please complete all fields.</string>
<!-- Google Auth -->
<string name="google_auth_text">Continue with Google</string>
<string name="google_auth_loading">Signing in...</string>
<!-- LoginViewModel -->
<string name="login_view_model_self_hosting_settings_updated">Self-hosting settings updated.</string>
<string name="login_view_model_self_hosting_settings_reset">Self-hosting settings reset.</string>
<string name="login_view_model_username_validation_length_error_msg">Username must be between 4 and 15 characters long.</string>
<string name="login_view_model_username_validation_alphanumeric_error_msg">Username can contain only letters and numbers.</string>
<string name="login_view_model_username_not_available_error_msg">This username is not available.</string>
<string name="login_view_model_connection_error_msg">Sorry we\'re having trouble connecting to the server.</string>
<string name="login_view_model_something_went_wrong_error_msg">Something went wrong. Please check your email/password and try again.</string>
<string name="login_view_model_something_went_wrong_two_error_msg">Something went wrong. Please check your credentials and try again.</string>
<string name="login_view_model_google_auth_error_msg">Failed to authenticate with Google.</string>
<string name="login_view_model_missing_auth_token_error_msg">No authentication token found.</string>
<!-- SelfHostedView -->
<string name="self_hosted_view_loading">Loading...</string>
<string name="self_hosted_view_action_reset">Reset</string>
<string name="self_hosted_view_action_back">Back</string>
<string name="self_hosted_view_action_save">Save</string>
<string name="self_hosted_view_action_learn_more">Learn more about self-hosting Omnivore</string>
<string name="self_hosted_view_field_api_url_label">API Server</string>
<string name="self_hosted_view_field_web_url_label">Web Server</string>
<string name="self_hosted_view_error_msg">Please enter API Server and Web server addresses.</string>
<!-- WelcomeScreen -->
<string name="welcome_screen_action_dismiss">Dismiss</string>
<string name="welcome_screen_action_continue_with_email">Continue with Email</string>
<string name="welcome_screen_action_self_hosting_options">Self-hosting options</string>
<!-- LabelCreationDialog -->
<string name="label_creation_title">Create New Label</string>
<string name="label_creation_content">Assign a name and color.</string>
<string name="label_creation_action_create">Create</string>
<string name="label_creation_action_cancel">Cancel</string>
<string name="label_creation_label_placeholder">Label Name</string>
<!-- LabelSelectionSheet -->
<string name="label_selection_sheet_title">Filter by Label</string>
<string name="label_selection_sheet_title_alt">Set Labels</string>
<string name="label_selection_sheet_action_cancel">Cancel</string>
<string name="label_selection_sheet_action_search">Search</string>
<string name="label_selection_sheet_action_save">Save</string>
<string name="label_selection_sheet_text_create">Create a new label named \"%1$s\"</string>
<!-- LibraryFilterBar -->
<string name="library_filter_bar_label_labels">Labels</string>
<!-- LibraryNavigationBar -->
<string name="library_nav_bar_title">Library</string>
<string name="library_nav_bar_title_alt"></string>
<string name="library_nav_bar_field_placeholder_search">Search</string>
<!-- LibraryViewModel -->
<string name="library_view_model_snackbar_success">Labels updated</string>
<string name="library_view_model_snackbar_error">Unable to set labels</string>
<!-- NotebookView -->
<string name="notebook_view_title">Notebook</string>
<string name="notebook_view_action_copy">Copy</string>
<string name="notebook_view_snackbar_msg">Notebook copied</string>
<!-- EditNoteModal -->
<string name="edit_note_modal_title">Note</string>
<string name="edit_note_modal_action_save">Save</string>
<string name="edit_note_modal_action_cancel">Cancel</string>
<!-- ArticleNotes -->
<string name="article_notes_title">Article Notes</string>
<string name="article_notes_action_add_notes">Add Notes...</string>
<!-- HighlightsList -->
<string name="highlights_list_title">Highlights</string>
<string name="highlights_list_action_copy">Copy</string>
<string name="highlights_list_snackbar_msg">Highlight copied</string>
<string name="highlights_list_action_add_note">Add Note...</string>
<string name="highlights_list_error_msg_no_highlights">You have not added any highlights to this page.</string>
<!-- ReaderPreferencesView -->
<string name="reader_preferences_view_font_size">Font Size:</string>
<string name="reader_preferences_view_margin">Margin</string>
<string name="reader_preferences_view_line_spacing">Line Spacing</string>
<string name="reader_preferences_view_theme">Theme:</string>
<string name="reader_preferences_view_auto">Auto</string>
<string name="reader_preferences_view_high_constrast_text">High Contrast Text</string>
<string name="reader_preferences_view_justify_text">Justify Text</string>
<!-- WebReaderLoadingContainer -->
<string name="web_reader_loading_container_error_msg">We were unable to fetch your content.</string>
<string name="web_reader_loading_container_bottom_sheet_reader_preferences">Reader Preferences</string>
<string name="web_reader_loading_container_bottom_sheet_notebook">Notebook</string>
<string name="web_reader_loading_container_bottom_sheet_open_link">Open Link</string>
<!-- OpenLinkView -->
<string name="open_link_view_action_open_in_browser">Open in Browser</string>
<string name="open_link_view_action_save_to_omnivore">Save to Omnivore</string>
<string name="open_link_view_action_copy_link">Copy Link</string>
<string name="open_link_view_action_cancel">Cancel</string>
<!-- WebReaderViewModel -->
<string name="web_reader_view_model_save_link_success">Link saved</string>
<string name="web_reader_view_model_save_link_error">Error saving link</string>
<string name="web_reader_view_model_copy_link_success">Link copied</string>
<!-- SaveContent -->
<string name="save_content_msg">Saving</string>
<string name="save_content_action_read_now">Read Now</string>
<string name="save_content_action_read_later">Read Later</string>
<string name="save_content_action_dismiss">Dismiss</string>
<!-- SaveViewModel -->
<string name="save_view_model_msg">Saving to Omnivore...</string>
<string name="save_view_model_error_not_logged_in">You are not logged in. Please login before saving.</string>
<string name="save_view_model_page_saved_success">Page Saved</string>
<string name="save_view_model_page_saved_error">There was an error saving your page</string>
<!-- SavedItemContextMenu -->
<string name="saved_item_context_menu_action_edit_labels">Edit Labels</string>
<string name="saved_item_context_menu_action_archive">Archive</string>
<string name="saved_item_context_menu_action_unarchive">Unarchive</string>
<string name="saved_item_context_menu_action_share_original">Share Original</string>
<string name="saved_item_context_menu_action_remove_item">Remove Item</string>
<!-- LogoutDialog -->
<string name="logout_dialog_title">Logout</string>
<string name="logout_dialog_confirm_msg">Are you sure you want to logout?</string>
<string name="logout_dialog_action_confirm">Confirm</string>
<string name="logout_dialog_action_cancel">Cancel</string>
<!-- ManageAccount -->
<string name="manage_account_title">Manage Account</string>
<string name="manage_account_action_reset_data_cache">Reset Data Cache</string>
<!-- PolicyWebView -->
<string name="policy_webview_title">Settings</string>
<!-- SettingsView -->
<string name="settings_view_title">Settings</string>
<string name="settings_view_setting_row_documentation">Documentation</string>
<string name="settings_view_setting_row_feedback">Feedback</string>
<string name="settings_view_setting_row_privacy_policy">Privacy Policy</string>
<string name="settings_view_setting_row_terms_and_conditions">Terms and Conditions</string>
<string name="settings_view_setting_row_manage_account">Manage Account</string>
<string name="settings_view_setting_row_logout">Logout</string>
</resources>