Skip to content

Commit

Permalink
Minor UI improvements, update strings
Browse files Browse the repository at this point in the history
- Add dialog message for backup/restore
- Add subtext for Rate & Review and Translations
- Change backup and restore buttons from Text to TextButton
- Update privacy policy and subtext for chat and GitHub
  • Loading branch information
pilot51 committed Jan 1, 2025
1 parent 427bcf8 commit 23c31dc
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 43 deletions.
1 change: 1 addition & 0 deletions app/src/main/java/com/pilot51/voicenotify/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ package com.pilot51.voicenotify

object Constants {
const val REGEX_PREFIX = "\$regex:"
const val DEV_EMAIL = "[email protected]"
}
6 changes: 3 additions & 3 deletions app/src/main/java/com/pilot51/voicenotify/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ fun MainScreen(
var statusSummary by remember { mutableStateOf("") }
val statusIntent = remember { Common.notificationListenerSettingsIntent }
if (isPreview) {
statusTitle = context.getString(R.string.service_running)
statusSummary = context.getString(R.string.status_summary_notification_access_enabled)
statusTitle = stringResource(R.string.service_running)
statusSummary = stringResource(R.string.status_summary_notification_access_enabled)
}
val isRunning by (if (isPreview) MutableStateFlow(false) else Service.isRunning).collectAsState()
val isSuspended by (if (isPreview) MutableStateFlow(false) else Service.isSuspended).collectAsState()
Expand Down Expand Up @@ -328,7 +328,7 @@ fun MainScreen(
) { showQuietTimeEnd = false }
}
if (showTestNotification) {
TestNotificationDialog(context) { showTestNotification = false }
TestNotificationDialog { showTestNotification = false }
}
if (showLog) {
NotificationLogDialog { showLog = false }
Expand Down
90 changes: 64 additions & 26 deletions app/src/main/java/com/pilot51/voicenotify/PreferenceDialogs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.app.NotificationCompat
import androidx.core.text.isDigitsOnly
import com.pilot51.voicenotify.Constants.DEV_EMAIL
import com.pilot51.voicenotify.PreferenceHelper.DEFAULT_SHAKE_THRESHOLD
import com.pilot51.voicenotify.PreferencesViewModel.Companion.readDebugLog
import com.pilot51.voicenotify.PreferencesViewModel.Companion.sendEmail
Expand All @@ -69,6 +70,12 @@ import com.pilot51.voicenotify.db.Settings.Companion.DEFAULT_TTS_STRING
import kotlinx.coroutines.*
import kotlin.time.Duration.Companion.seconds

private val debugLogPath @Composable get() = if (isPreview) {
"Android/data/${LocalContext.current.packageName}/files/debug.log"
} else {
logFile?.relativeTo(Environment.getExternalStorageDirectory()).toString()
}

private fun openBrowser(context: Context, url: String) {
try {
context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
Expand Down Expand Up @@ -538,16 +545,15 @@ private const val NOTIFICATION_CHANNEL_ID = "test"

@Composable
fun TestNotificationDialog(
context: Context,
onDismiss: () -> Unit
) {
val context = LocalContext.current
TextEditDialog(
titleRes = R.string.test,
messageRes = R.string.test_summary,
initialText = context.getString(R.string.test_content_text),
onDismiss = onDismiss
) {
val content = it
) { content ->
CoroutineScope(Dispatchers.IO).launch {
val vnApp = Common.findOrAddApp(context.packageName)!!
if (!vnApp.isEnabled) {
Expand Down Expand Up @@ -618,14 +624,19 @@ fun BackupDialog(onDismiss: () -> Unit) {
},
title = { Text(stringResource(R.string.backup_restore)) },
text = {
LazyColumn {
supportItem(title = R.string.backup_settings) {
Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
Text(stringResource(R.string.backup_restore_message))
TextButton(onClick = {
val version = BuildConfig.VERSION_NAME
.replace(" ", "-").replace(Regex("[\\[\\]]"), "")
exportBackupLauncher.launch("voice_notify_${version}_backup.zip")
}) {
Text(stringResource(R.string.backup_settings))
}
supportItem(title = R.string.restore_settings) {
TextButton(onClick = {
importBackupLauncher.launch(arrayOf("application/zip"))
}) {
Text(stringResource(R.string.restore_settings))
}
}
}
Expand All @@ -649,7 +660,10 @@ fun SupportDialog(onDismiss: () -> Unit) {
text = {
Column {
LazyColumn {
supportItem(title = R.string.support_rate) {
supportItem(
title = R.string.support_rate,
subtext = R.string.support_rate_subtext
) {
val iMarket = Intent(
Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.pilot51.voicenotify")
Expand Down Expand Up @@ -681,7 +695,10 @@ fun SupportDialog(onDismiss: () -> Unit) {
) {
openBrowser(context, "https://matrix.to/#/#voicenotify:p51.me")
}
supportItem(title = R.string.support_translations) {
supportItem(
title = R.string.support_translations,
subtext = R.string.support_translations_subtext
) {
openBrowser(context, "https://hosted.weblate.org/projects/voice-notify")
}
supportItem(
Expand Down Expand Up @@ -709,22 +726,7 @@ fun SupportDialog(onDismiss: () -> Unit) {
EmailDialog { showEmailDialog = false }
}
if (showPrivacyDialog) {
AlertDialog(
onDismissRequest = { showPrivacyDialog = false },
confirmButton = { },
dismissButton = {
TextButton(onClick = { showPrivacyDialog = false }) {
Text(stringResource(android.R.string.ok))
}
},
title = { Text(stringResource(R.string.support_privacy)) },
text = {
Text(
text = stringResource(R.string.support_privacy_message),
modifier = Modifier.verticalScroll(rememberScrollState())
)
}
)
PrivacyDialog { showPrivacyDialog = false }
}
}

Expand Down Expand Up @@ -778,8 +780,7 @@ fun EmailDialog(onDismiss: () -> Unit) {
}
}
Text(
text = stringResource(R.string.support_email_dialog_message_log,
logFile?.relativeTo(Environment.getExternalStorageDirectory()).toString()),
text = stringResource(R.string.support_email_dialog_message_log, debugLogPath),
modifier = Modifier.fillMaxWidth(),
fontSize = 20.sp
)
Expand Down Expand Up @@ -843,6 +844,27 @@ fun EmailDialog(onDismiss: () -> Unit) {
}
}

@Composable
private fun PrivacyDialog(onDismiss: () -> Unit) {
AlertDialog(
onDismissRequest = onDismiss,
confirmButton = { },
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(android.R.string.ok))
}
},
title = { Text(stringResource(R.string.support_privacy)) },
text = {
Text(
text = stringResource(R.string.support_privacy_message,
NotifyList.HISTORY_LIMIT, debugLogPath, DEV_EMAIL),
modifier = Modifier.verticalScroll(rememberScrollState())
)
}
)
}

@Composable
private fun TextCheckbox(
@StringRes textRes: Int,
Expand Down Expand Up @@ -971,6 +993,14 @@ private fun QuietTimeDialogPreview() {
}
}

@VNPreview
@Composable
private fun BackupDialogPreview() {
AppTheme {
BackupDialog {}
}
}

@VNPreview
@Composable
private fun SupportDialogPreview() {
Expand All @@ -986,3 +1016,11 @@ private fun EmailDialogPreview() {
EmailDialog {}
}
}

@VNPreview
@Composable
private fun PrivacyDialogPreview() {
AppTheme {
PrivacyDialog {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import androidx.compose.runtime.*
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pilot51.voicenotify.Constants.DEV_EMAIL
import com.pilot51.voicenotify.PreferenceHelper.DEFAULT_SHAKE_THRESHOLD
import com.pilot51.voicenotify.PreferenceHelper.KEY_SHAKE_THRESHOLD
import com.pilot51.voicenotify.PreferenceHelper.dataFiles
Expand Down Expand Up @@ -149,8 +150,6 @@ class PreferencesViewModel : ViewModel(), IPreferencesViewModel {
}

companion object {
private const val DEV_EMAIL = "[email protected]"

fun updateListItem(
position: Int,
textFrom: String,
Expand Down
31 changes: 19 additions & 12 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,13 @@
<string name="yes">Yes</string>
<string name="backup_restore">Backup / Restore</string>
<string name="backup_restore_summary">Backup or restore all Voice Notify settings</string>
<string name="backup_restore_message">Backup or restore a zip file containing all settings.\n\nBackups are only guaranteed to be compatible with the same version or later of Voice Notify.\nThere is no guarantee for backups from a beta or non-release build.</string>
<string name="backup_settings">Backup Settings</string>
<string name="restore_settings">Restore Settings</string>
<string name="support">Help &amp; Support</string>
<string name="support_summary">Rate &amp; review, email the developer, community chat, translations, source code, issue tracker, privacy policy</string>
<string name="support_rate">Rate &amp; Review</string>
<string name="support_rate_subtext">View the app on Play Store</string>
<string name="support_email">Email Developer</string>
<string name="support_email_subtext">General feedback</string>
<string name="support_email_dialog_title">Select attachments</string>
Expand All @@ -134,26 +136,31 @@
<string name="settings">Settings</string>
<string name="support_discord">Discord</string>
<string name="support_matrix">Matrix</string>
<string name="support_chat_subtext">User chat, fastest developer response</string>
<string name="support_chat_subtext">Community chat, fastest developer response</string>
<string name="support_translations">Translations</string>
<string name="support_translations_subtext">Crowd-sourced translations at Weblate.org</string>
<string name="support_github">GitHub</string>
<string name="support_github_subtext">Bug reports, feature requests, source code</string>
<string name="support_github_subtext">Bug reports, feature requests, release notes, beta releases, source code</string>
<string name="support_privacy">Privacy Policy</string>
<string name="support_privacy_message">Voice Notify does not collect or transmit data off of the device. However, it does use third party apps or services that may transmit data, as outlined below under \"Third party apps and services\".
\n\nIn the unlikely event that sensitive or personal information is received by the developer, it will not be sold, shared, copied, or used without the express consent of who the information belongs to. In the absence of consent, the information will be deleted if possible.
\n\nThe purpose of Voice Notify is to speak notifications, and as such it is likely that spoken notifications may be heard by people or microphones in the vicinity. It is recommended to configure Voice Notify and the device to prevent undesired spoken notifications. Use at your own risk.
\n\nThe notifications received by Voice Notify are only held in memory to be displayed in the Notification Log (up to the most recent 20) and are not written to storage. This prevents other apps from accessing the data, especially if the device is rooted. As a result, if the Voice Notify process is terminated, the notification log is cleared.
\n\nInstalled apps are read and listed to allow users to choose which apps have their notifications spoken. The app titles and package names are stored in a database in Voice Notify\'s internal private data storage on the device to save selections and act as a cache to improve performance of loading the app list.
\n\nThe purpose of Voice Notify is to speak notifications, and as such it is likely that spoken notifications may be heard by nearby people or microphones. It is recommended to configure Voice Notify and the device to prevent undesired spoken notifications. Use at your own risk.
\n\nIn the unlikely event that sensitive information is received by the developer, it will not be sold, shared, or used without the express consent of the rightful owner of the information. Any sensitive information that is knowingly received will be deleted if it is unrelated to a reported issue or once the issue is resolved.
\n\nThe notifications received by Voice Notify are only held in memory to be displayed in the Notification Log (up to the most recent %1$d) and are not written to storage. This prevents other apps from accessing the data even if the device is rooted. As a result, if the Voice Notify process is terminated, the notification log is cleared.
\n\nInstalled apps are read and listed to allow users to choose which apps have their notifications spoken and configure per-app settings. The app titles and package names are stored in a database in Voice Notify\'s internal private data storage on the device to save selections and act as a cache to improve performance of loading the app list.
\n\nA debug log is saved to external storage at %2$s which is accessible from a computer via USB and optionally sent when using the Email Developer feature.\nIt could potentially contain sensitive information from a notification if there was an error. The Email Developer dialog provides a basic log viewer.
\n\nThe backup feature saves a zip file containing all settings including installed apps to a location of the user\'s choice.
\n\nThird party apps and services:
\n• Text-To-Speech engine - Voice Notify passes notification text to be spoken to the default Text-To-Speech engine, which is outside the direct control of Voice Notify. Please read the privacy policy of the TTS engine for how it uses data.
\n• Google Play Services may send anonymized crash and basic analytical data back to the developer to assist in improving the app.
\n• Messaging app - The \"Contact Developer\" option opens the user\'s chosen or default messaging app, such as an email app, and pre-populates the message with the Voice Notify version, Android version, Android build number, and device name. For an email app, the \"To\" field is set to the developer\'s address ([email protected]). By sending the message, the user understands and consents to their name and contact information being shared with the recipient(s), as determined by the messaging app.
\n• Other options under \"Help &amp; Support\" open the relevant external apps or websites (Play Store, Weblate.org, GitHub.com), only sending a hard coded URL to the Voice Notify page.
\n• Google Play Services may send anonymized data for analytics, crashes, and freezes back to the developer to assist in improving the app. This is the same for all apps distributed through the Play Store.
\n• Messaging app - The \"Contact Developer\" option opens the user\'s chosen or default messaging app, such as an email app, and pre-populates the message with the Voice Notify version, Android version, Android build number, and device name. The user may choose to attach the debug log and settings files. When sending to an email app, the \"To\" field is set to the developer\'s address (%3$s). By sending the message, the user understands and consents to their name and contact information being shared with the recipient(s) as determined by the messaging app.
\n• Other options under \"Help &amp; Support\" open the relevant external apps or websites (Play Store, Discord, Matrix, Weblate.org, GitHub.com) to the Voice Notify page or chat room.
\n\nBelow are the permissions used by Voice Notify and why they\'re needed:
\n• Bluetooth - Required to detect whether Bluetooth headset is connected.
\n• Vibrate - Required for Test feature while phone is in vibrate mode.
\n• Post Notifications - Required to post the test notification. This is typically the only permission that Android shows to the user.
\n• Query All Packages - Required to fetch a list of all installed apps for the App List and allow for per-app settings.
\n• Bluetooth - Required to detect whether a Bluetooth headset is connected.
\n• Modify Audio Settings - Required for improved wired headset detection.
\n• Read Phone State - Required to interrupt TTS if a phone call becomes active.</string>
\n• Vibrate - Required for Test feature while device is in vibrate mode.
\n• Read Phone State - Required in Android 11 and below to interrupt TTS if a phone call becomes active.</string>
<string name="error_market">Error: Unable to find Google Play Store installed.</string>
<string name="error_email">Error: Unable to find an installed email app.</string>
<string name="error_browser">Error: Unable to find an installed browser app.</string>
Expand Down

0 comments on commit 23c31dc

Please sign in to comment.