Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 8 additions & 12 deletions app/src/main/java/de/psdev/devdrawer/DevDrawerApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ fun DevDrawerApp(
}

val snackbarHostState = remember { SnackbarHostState() }
val optInThanksMessage = stringResource(id = R.string.analytics_opt_in_thanks)
val okLabel = stringResource(id = R.string.ok)
val scope = rememberCoroutineScope()

DevDrawerTheme(
Expand Down Expand Up @@ -114,8 +116,8 @@ fun DevDrawerApp(
trackingService.optIn()
scope.launch {
snackbarHostState.showSnackbar(
message = "Thank you! You can change your decision anytime on the settings tab.",
actionLabel = "OK",
message = optInThanksMessage,
actionLabel = okLabel,
duration = SnackbarDuration.Long
)
}
Expand Down Expand Up @@ -195,31 +197,25 @@ fun AnalyticsOptInDialog(
AlertDialog(
onDismissRequest = { /* Not cancelable */ },
title = {
Text(text = "Usage analytics", fontWeight = FontWeight.Bold)
Text(text = stringResource(id = R.string.analytics_opt_in_title), fontWeight = FontWeight.Bold)
},
text = {
Text(
text = "In order for us to be able to better understand your use of the app we would like to analyse your usage.\n\n" +
"We use Firebase Analytics to track opened screens and certain interactions.\n\n" +
"We don't store personally identifiable data.\n\n" +
"Additionally we use Firebase Crashlytics for app crashes.\n\n" +
"Thank you for considering!"
)
Text(text = stringResource(id = R.string.analytics_opt_in_message))
},
confirmButton = {
TextButton(
enabled = buttonsEnabled,
onClick = onOptIn
) {
Text("Opt-in")
Text(stringResource(id = R.string.analytics_opt_in))
}
},
dismissButton = {
TextButton(
enabled = buttonsEnabled,
onClick = onOptOut
) {
Text("Opt-out")
Text(stringResource(id = R.string.analytics_opt_out))
}
}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.Toast
import de.psdev.devdrawer.R
import de.psdev.devdrawer.adapters.ActivityListAdapter
import de.psdev.devdrawer.databinding.ActivityChoiceBinding
import mu.KLogging
Expand Down Expand Up @@ -46,7 +47,7 @@ class ChooseActivityDialog: Activity(), AdapterView.OnItemClickListener {
finish()
}
if (activitiesList.isEmpty()) {
Toast.makeText(this, "This app has no activities", Toast.LENGTH_LONG).show()
Toast.makeText(this, R.string.no_activities, Toast.LENGTH_LONG).show()
finish()
}
binding.listView.adapter = ActivityListAdapter(this, activitiesList)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ fun WidgetInUseErrorAlertDialog(
AlertDialog(
onDismissRequest = { },
title = {
Text(text = "Error")
Text(text = stringResource(id = R.string.error_profile_in_use))
},
text = {
Text(text = "The profile ${state.widgetProfile.name} is used by: \n" + state.widgets.joinToString("\n") { it.name })
Text(
text = stringResource(
id = R.string.profile_used_by_widgets,
state.widgetProfile.name,
state.widgets.joinToString("\n") { it.name }
)
)
},
confirmButton = {
TextButton(onClick = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ internal fun AddAppSignaturePackageFilterDialog(
}
}

is ViewState.Error -> Text(text = "Error: ${viewState.message}")
is ViewState.Error -> Text(text = stringResource(id = R.string.error_message, viewState.message))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal fun AddPackageNamePackageFilterDialog(
}
}
}
is Error -> Text(text = "Error: ${viewState.message}")
is Error -> Text(text = stringResource(id = R.string.error_message, viewState.message))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ internal fun PackageFilterPreviewDialog(
}
}

is Error -> Text(text = "Error: ${viewState.message}")
is Error -> Text(text = stringResource(id = R.string.error_message, viewState.message))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,13 @@ fun WidgetProfilesScreen(
)
when (val state = deleteDialogShown) {
DeleteDialogState.Hidden -> Unit
is DeleteDialogState.Showing -> AlertDialog(
onDismissRequest = { deleteDialogShown = DeleteDialogState.Hidden },
title = {
Text(text = "Confirm")
},
text = {
Text(text = "Do you really want to delete the profile '${state.widgetProfile.name}'?")
},
confirmButton = {
TextButton(onClick = {
viewModel.deleteProfile(state.widgetProfile)
deleteDialogShown = DeleteDialogState.Hidden
}) {
Text("Delete")
}
is DeleteDialogState.Showing -> DeleteProfileDialog(
widgetProfile = state.widgetProfile,
onConfirm = {
viewModel.deleteProfile(state.widgetProfile)
deleteDialogShown = DeleteDialogState.Hidden
},
dismissButton = {
TextButton(onClick = {
deleteDialogShown = DeleteDialogState.Hidden
}) {
Text("Cancel")
}
}
onDismiss = { deleteDialogShown = DeleteDialogState.Hidden }
)

is DeleteDialogState.InUseError -> {
Expand All @@ -101,6 +85,33 @@ fun WidgetProfilesScreen(
}
}

@Composable
internal fun DeleteProfileDialog(
widgetProfile: WidgetProfile,
onConfirm: () -> Unit,
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(text = stringResource(id = R.string.delete_profile))
},
text = {
Text(text = stringResource(id = R.string.delete_profile_confirmation, widgetProfile.name))
},
confirmButton = {
TextButton(onClick = onConfirm) {
Text(stringResource(id = R.string.delete))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(id = R.string.cancel))
}
}
)
}

@Composable
fun WidgetProfileListScreen(
viewState: UiState<List<WidgetProfile>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ fun <T> ListPreference(
label: String,
values: Map<T, String>,
currentValue: T,
dialogTitle: String = "Select option",
dialogTitle: String = stringResource(id = R.string.select_option),
onClick: (T) -> Unit = {}
) {
var selectionDialog by remember {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ fun ColorSelectionDialog(
AlertDialog(
onDismissRequest = { },
title = {
Text(text = "Select color")
Text(text = stringResource(id = R.string.pick_widget_color))
},
text = {
ColorGrid(
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,15 @@
<item>Hell</item>
<item>Dunkel</item>
</array>

<string name="select_option">Option auswählen</string>
<string name="analytics_opt_in_title">Nutzungsanalyse</string>
<string name="analytics_opt_in_message">Damit wir besser verstehen, wie du die App verwendest, würden wir deine Nutzung gerne analysieren.\n\nWir verwenden Firebase Analytics, um geöffnete Bildschirme und bestimmte Interaktionen zu erfassen.\n\nWir speichern keine personenbezogenen Daten.\n\nZusätzlich verwenden wir Firebase Crashlytics für App-Abstürze.\n\nDanke, dass du darüber nachdenkst!</string>
<string name="analytics_opt_in">Zustimmen</string>
<string name="analytics_opt_out">Ablehnen</string>
<string name="analytics_opt_in_thanks">Danke! Du kannst deine Entscheidung jederzeit im Einstellungen-Tab ändern.</string>
<string name="delete_profile_confirmation">Möchtest du das Profil \'%1$s\' wirklich löschen?</string>
<string name="error_message">Fehler: %1$s</string>
<string name="profile_used_by_widgets">Das Profil %1$s wird verwendet von: \n%2$s</string>
<string name="no_activities">Diese App hat keine Activities</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,16 @@
<string name="discard_changes">Discard changes?</string>
<string name="discard_changes_confirmation">You have unsaved changes. Do you want to discard them?</string>
<string name="discard">Discard</string>

<!-- Dialogs -->
<string name="select_option">Select option</string>
<string name="analytics_opt_in_title">Usage analytics</string>
<string name="analytics_opt_in_message">In order for us to be able to better understand your use of the app we would like to analyse your usage.\n\nWe use Firebase Analytics to track opened screens and certain interactions.\n\nWe don\'t store personally identifiable data.\n\nAdditionally we use Firebase Crashlytics for app crashes.\n\nThank you for considering!</string>
<string name="analytics_opt_in">Opt-in</string>
<string name="analytics_opt_out">Opt-out</string>
<string name="analytics_opt_in_thanks">Thank you! You can change your decision anytime on the settings tab.</string>
<string name="delete_profile_confirmation">Do you really want to delete the profile \'%1$s\'?</string>
<string name="error_message">Error: %1$s</string>
<string name="profile_used_by_widgets">The profile %1$s is used by: \n%2$s</string>
<string name="no_activities">This app has no activities</string>
</resources>
137 changes: 137 additions & 0 deletions app/src/test/java/de/psdev/devdrawer/DialogStringResourcesTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package de.psdev.devdrawer

import android.graphics.Color
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import de.psdev.devdrawer.appwidget.SortOrder
import de.psdev.devdrawer.database.Widget
import de.psdev.devdrawer.database.WidgetProfile
import de.psdev.devdrawer.profiles.DeleteDialogState
import de.psdev.devdrawer.profiles.WidgetInUseErrorAlertDialog
import de.psdev.devdrawer.profiles.ui.editor.AddAppSignaturePackageFilterDialog
import de.psdev.devdrawer.profiles.ui.editor.AddAppSignaturePackageFilterDialogViewModel
import de.psdev.devdrawer.profiles.ui.editor.AddPackageNamePackageFilterDialog
import de.psdev.devdrawer.profiles.ui.editor.AddPackageNamePackageFilterDialogViewModel
import de.psdev.devdrawer.profiles.ui.editor.PackageFilterPreviewDialog
import de.psdev.devdrawer.profiles.ui.editor.PackageFilterPreviewDialogViewModel
import de.psdev.devdrawer.profiles.ui.list.DeleteProfileDialog
import de.psdev.devdrawer.settings.ListPreference
import de.psdev.devdrawer.ui.theme.DevDrawerTheme
import de.psdev.devdrawer.widgets.ui.editor.ColorSelectionDialog
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

/**
* Dialog text comes from string resources, so it is translated: under a German locale every dialog reads German.
*/
@RunWith(RobolectricTestRunner::class)
// SDK 36 requires Java 21; use SDK 33 which is compatible with the project's Java 17 toolchain.
@Config(sdk = [33], qualifiers = "de")
class DialogStringResourcesTest {

@get:Rule
val composeTestRule = createComposeRule()

private val widgetProfile = WidgetProfile(name = "Arbeit")

@Test
fun `given a German locale, when showing the analytics opt-in dialog, then it reads German`() {
// Given / When
composeTestRule.setContent {
DevDrawerTheme { AnalyticsOptInDialog(onOptIn = {}, onOptOut = {}) }
}

// Then
assertTexts("Nutzungsanalyse", "Zustimmen", "Ablehnen")
composeTestRule.onNodeWithText("Wir verwenden Firebase Analytics", substring = true).assertExists()
}

@Test
fun `given a German locale, when showing the delete profile dialog, then it reads German`() {
// Given / When
composeTestRule.setContent {
DevDrawerTheme { DeleteProfileDialog(widgetProfile = widgetProfile, onConfirm = {}, onDismiss = {}) }
}

// Then
assertTexts("Profil löschen?", "Möchtest du das Profil 'Arbeit' wirklich löschen?", "Löschen", "Abbrechen")
}

@Test
fun `given a German locale, when showing the profile in use error, then it reads German`() {
// Given
val state = DeleteDialogState.InUseError(
widgetProfile = widgetProfile,
widgets = listOf(Widget(1, "Widget 1", Color.BLACK, widgetProfile.id))
)

// When
composeTestRule.setContent {
DevDrawerTheme { WidgetInUseErrorAlertDialog(state = state) }
}

// Then
assertTexts("Profil kann nicht gelöscht werden, da es noch von Widgets verwendet wird", "Schließen")
composeTestRule.onNodeWithText("Das Profil Arbeit wird verwendet von:", substring = true).assertExists()
}

@Test
fun `given a German locale, when showing the color selection dialog, then it reads German`() {
// Given / When
composeTestRule.setContent {
DevDrawerTheme { ColorSelectionDialog(initialColor = Color.BLACK) }
}

// Then
assertTexts("Widget-Farbe wählen")
}

@Test
fun `given a German locale, when opening a list preference, then the default dialog title reads German`() {
// Given
composeTestRule.setContent {
DevDrawerTheme {
ListPreference(
label = "Sortierung",
values = mapOf(SortOrder.LAST_UPDATED to "Zuletzt aktualisiert"),
currentValue = SortOrder.LAST_UPDATED
)
}
}

// When
composeTestRule.onNodeWithText("Sortierung").performClick()

// Then
assertTexts("Option auswählen")
}

@Test
fun `given a German locale, when a filter dialog fails to load, then the error reads German`() {
// Given / When
composeTestRule.setContent {
DevDrawerTheme {
AddAppSignaturePackageFilterDialog(
viewState = AddAppSignaturePackageFilterDialogViewModel.ViewState.Error("kaputt")
)
AddPackageNamePackageFilterDialog(
viewState = AddPackageNamePackageFilterDialogViewModel.ViewState.Error("defekt")
)
PackageFilterPreviewDialog(
viewState = PackageFilterPreviewDialogViewModel.ViewState.Error("weg")
)
}
}

// Then
assertTexts("Fehler: kaputt", "Fehler: defekt", "Fehler: weg")
}

private fun assertTexts(vararg texts: String) {
texts.forEach { composeTestRule.onNodeWithText(it).assertExists() }
}
}
Loading