From fe745c88bebf8a5a99f17b8d30768b446e7e95d4 Mon Sep 17 00:00:00 2001 From: tylxr59 <102394635+tylxr59@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:56:06 -0500 Subject: [PATCH 1/5] Add basic About page for app --- .../org/stypox/dicio/ui/about/AboutScreen.kt | 224 ++++++++++++++++++ .../stypox/dicio/ui/about/PrivacyScreen.kt | 103 ++++++++ .../kotlin/org/stypox/dicio/ui/nav/Drawer.kt | 14 +- .../org/stypox/dicio/ui/nav/Navigation.kt | 16 ++ .../kotlin/org/stypox/dicio/ui/nav/Routes.kt | 6 + app/src/main/res/values/strings.xml | 26 ++ 6 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt create mode 100644 app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt new file mode 100644 index 000000000..976b8a434 --- /dev/null +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt @@ -0,0 +1,224 @@ +package org.stypox.dicio.ui.about + +import android.os.Build +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.BugReport +import androidx.compose.material.icons.filled.Code +import androidx.compose.material.icons.filled.Description +import androidx.compose.material.icons.filled.Gavel +import androidx.compose.material.icons.filled.Group +import androidx.compose.material.icons.filled.Info +import androidx.compose.material.icons.filled.LibraryBooks +import androidx.compose.material.icons.filled.Policy +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import org.stypox.dicio.BuildConfig +import org.stypox.dicio.R +import org.stypox.dicio.settings.ui.SettingsCategoryTitle +import org.stypox.dicio.settings.ui.SettingsItem +import org.stypox.dicio.ui.theme.AppTheme +import org.stypox.dicio.util.ShareUtils + +@Composable +fun AboutScreen( + navigationIcon: @Composable () -> Unit, + navigateToPrivacy: () -> Unit, +) { + val context = LocalContext.current + val snackbarHostState = remember { SnackbarHostState() } + val scope = rememberCoroutineScope() + + Scaffold( + topBar = { + @OptIn(ExperimentalMaterial3Api::class) + TopAppBar( + title = { Text(stringResource(R.string.about)) }, + navigationIcon = navigationIcon, + ) + }, + snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // App Information Section + item { + SettingsCategoryTitle(title = stringResource(R.string.about_app_info)) + } + + item { + SettingsItem( + title = stringResource(R.string.app_name), + description = stringResource(R.string.about_description), + ) + } + + item { + SettingsItem( + title = stringResource(R.string.about_version), + description = BuildConfig.VERSION_NAME, + ) + } + + item { + SettingsItem( + title = stringResource(R.string.about_application_id), + description = BuildConfig.APPLICATION_ID, + ) + } + + // Build Information Section + item { + SettingsCategoryTitle(title = stringResource(R.string.about_build_info)) + } + + item { + SettingsItem( + title = stringResource(R.string.about_version_code), + description = BuildConfig.VERSION_CODE.toString(), + ) + } + + item { + SettingsItem( + title = stringResource(R.string.about_copy_version_info), + icon = Icons.Default.Description, + modifier = Modifier.clickable { + val versionInfo = buildVersionInfo() + ShareUtils.copyToClipboard(context, versionInfo) + scope.launch { + snackbarHostState.showSnackbar( + context.getString(R.string.copied_to_clipboard) + ) + } + }, + ) + } + + // Links Section + item { + SettingsCategoryTitle(title = stringResource(R.string.about_links)) + } + + item { + SettingsItem( + title = stringResource(R.string.about_source_code), + icon = Icons.Default.Code, + description = "github.com/Stypox/dicio-android", + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser( + context, + "https://github.com/Stypox/dicio-android" + ) + }, + ) + } + + item { + SettingsItem( + title = stringResource(R.string.about_report_issue), + icon = Icons.Default.BugReport, + description = "Report bugs and request features", + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser( + context, + "https://github.com/Stypox/dicio-android/issues" + ) + }, + ) + } + + // Legal Section + item { + SettingsCategoryTitle(title = stringResource(R.string.about_legal)) + } + + item { + SettingsItem( + title = stringResource(R.string.about_license), + icon = Icons.Default.Gavel, + description = stringResource(R.string.about_license_gpl), + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser( + context, + "https://www.gnu.org/licenses/gpl-3.0.html" + ) + }, + ) + } + + item { + SettingsItem( + title = stringResource(R.string.about_privacy_policy), + icon = Icons.Default.Policy, + description = stringResource(R.string.privacy_intro), + modifier = Modifier.clickable(onClick = navigateToPrivacy), + ) + } + + // Credits Section + item { + SettingsCategoryTitle(title = stringResource(R.string.about_credits)) + } + + item { + SettingsItem( + title = stringResource(R.string.about_contributors), + icon = Icons.Default.Group, + description = stringResource(R.string.about_contributors_description), + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser( + context, + "https://github.com/Stypox/dicio-android/graphs/contributors" + ) + }, + ) + } + + item { + HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) + } + } + } +} + +private fun buildVersionInfo(): String { + return """ + ${BuildConfig.APPLICATION_ID} + Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) + + Device: ${Build.MANUFACTURER} ${Build.MODEL} + Android: ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT}) + """.trimIndent() +} + +@Preview +@Composable +private fun AboutScreenPreview() { + AppTheme { + AboutScreen( + navigationIcon = {}, + navigateToPrivacy = {}, + ) + } +} diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt new file mode 100644 index 000000000..fff31e53d --- /dev/null +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt @@ -0,0 +1,103 @@ +package org.stypox.dicio.ui.about + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.stypox.dicio.R +import org.stypox.dicio.ui.theme.AppTheme + +@Composable +fun PrivacyScreen( + navigationIcon: @Composable () -> Unit, +) { + Scaffold( + topBar = { + @OptIn(ExperimentalMaterial3Api::class) + TopAppBar( + title = { Text(stringResource(R.string.privacy_policy)) }, + navigationIcon = navigationIcon, + ) + }, + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .padding(horizontal = 16.dp, vertical = 12.dp) + ) { + item { + Text( + text = stringResource(R.string.privacy_intro), + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + + item { + PrivacySection( + title = "Data Storage", + content = """The data that Dicio itself stores about the user are preferences, history and logs. They are kept in local and private databases which are only used by Dicio. Therefore, no such personal data is ever shared with any online service, except for specific preferences as explained shortly. Preferences are used to allow the user to customize their experience. Preferences strictly related to a specific online website might be sent to them for the purpose of enforcing such preference (for example, the language preference is used to download the correct Speech-To-Text model). History is used to make relevant suggestions and better understand user input. Logs are used only for debugging and troubleshooting purposes, and do not contain any specific user information.""" + ) + } + + item { + PrivacySection( + title = "Skills and Internet Connectivity", + content = """Dicio contains multiple skills, that is separate components that provide services, so that users can interact with those using the assistant interface (for example weather, lyrics, ...). Some skills work without ever connecting to the internet, while others may connect to various online websites as a requirement for providing their service (for example, the weather skill needs to access the internet to obtain weather data). Skills will not share any personal information with online services, unless it is strictly necessary for them to function (for example, when the weather skill makes a web request, the city is necessarily included). Users may enable or disable each skill individually, so they have full control over which online websites may be contacted.""" + ) + } + + item { + PrivacySection( + title = "Voice Recognition", + content = """The voice recognition in Dicio works offline, without ever sending audio to any external service. Once the Vosk AI Speech-To-Text model has been downloaded, Dicio can use it without needing to connect to the internet ever again. Therefore, once the STT model is ready, Dicio can be used without an internet connection, except for skills that need to find information on the internet (for example, the timer will work, while the weather will not). If the user wishes to, he can disable Dicio's access to the internet from Android settings, and the app will continue working normally. The user may even disable voice recognition completely from application settings.""" + ) + } + + item { + PrivacySection( + title = "Data Deletion", + content = """The user can easily delete all of his personal data by just clearing the app's storage from Android settings, or by uninstalling the app completely.""" + ) + } + } + } +} + +@Composable +private fun PrivacySection( + title: String, + content: String, +) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) + ) + Text( + text = content, + style = MaterialTheme.typography.bodyMedium, + lineHeight = MaterialTheme.typography.bodyMedium.lineHeight.times(1.5f), + ) +} + +@Preview +@Composable +private fun PrivacyScreenPreview() { + AppTheme { + PrivacyScreen(navigationIcon = {}) + } +} diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt index edf5c1643..2c912c12c 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.widthIn import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.RecordVoiceOver import androidx.compose.material.icons.filled.Settings import androidx.compose.material3.Icon @@ -34,6 +35,7 @@ import org.stypox.dicio.R @Composable fun DrawerContent( onSettingsClick: () -> Unit, + onAboutClick: () -> Unit, onSpeechToTextPopupClick: () -> Unit, closeDrawer: () -> Unit, ) { @@ -67,13 +69,23 @@ fun DrawerContent( }, modifier = Modifier.padding(horizontal = 12.dp), ) + + DrawerItem( + icon = Icons.Default.Info, + label = R.string.about, + onClick = { + onAboutClick() + closeDrawer() + }, + modifier = Modifier.padding(horizontal = 12.dp), + ) } } @Preview @Composable private fun DrawerContentPreview() { - DrawerContent(onSettingsClick = {}, onSpeechToTextPopupClick = {}, closeDrawer = {}) + DrawerContent(onSettingsClick = {}, onAboutClick = {}, onSpeechToTextPopupClick = {}, closeDrawer = {}) } @Preview diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt index 929979444..80c535afd 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt @@ -20,6 +20,8 @@ import org.stypox.dicio.R import org.stypox.dicio.io.input.stt_popup.SttPopupActivity import org.stypox.dicio.settings.MainSettingsScreen import org.stypox.dicio.settings.SkillSettingsScreen +import org.stypox.dicio.ui.about.AboutScreen +import org.stypox.dicio.ui.about.PrivacyScreen import org.stypox.dicio.ui.home.HomeScreen @Composable @@ -41,6 +43,7 @@ fun Navigation() { val context = LocalContext.current ScreenWithDrawer( onSettingsClick = { navController.navigate(MainSettings) }, + onAboutClick = { navController.navigate(About) }, onSpeechToTextPopupClick = { val intent = Intent(context, SttPopupActivity::class.java) context.startActivity(intent) @@ -60,12 +63,24 @@ fun Navigation() { composable { SkillSettingsScreen(navigationIcon = backIcon) } + + composable { + AboutScreen( + navigationIcon = backIcon, + navigateToPrivacy = { navController.navigate(Privacy) }, + ) + } + + composable { + PrivacyScreen(navigationIcon = backIcon) + } } } @Composable fun ScreenWithDrawer( onSettingsClick: () -> Unit, + onAboutClick: () -> Unit, onSpeechToTextPopupClick: () -> Unit, screen: @Composable (navigationIcon: @Composable () -> Unit) -> Unit ) { @@ -77,6 +92,7 @@ fun ScreenWithDrawer( drawerContent = { DrawerContent( onSettingsClick = onSettingsClick, + onAboutClick = onAboutClick, onSpeechToTextPopupClick = onSpeechToTextPopupClick, closeDrawer = { scope.launch { diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt index a99de1cc8..eba5d01bc 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt @@ -10,3 +10,9 @@ object MainSettings @Serializable object SkillSettings + +@Serializable +object About + +@Serializable +object Privacy diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58ec71269..901ce319a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -262,4 +262,30 @@ Flashlight turned on Flashlight turned off Unable to control the flashlight + + + About + About Dicio + Free and open source voice assistant + Version + App information + Application ID + Build information + Version code + Links + Source code + Report an issue + GitHub + Legal + License + GNU General Public License v3.0 + Privacy policy + Credits + Contributors + Dicio is made possible by contributors around the world + Copy version info + + + Privacy policy + Dicio respects your privacy and processes all data locally on your device. From 015b2790e724168413c3f01d3e78230f7728471c Mon Sep 17 00:00:00 2001 From: tylxr59 <102394635+tylxr59@users.noreply.github.com> Date: Sun, 14 Dec 2025 21:20:23 -0500 Subject: [PATCH 2/5] Privacy policy now links to PRIVACY.md --- .../org/stypox/dicio/ui/about/AboutScreen.kt | 9 +- .../stypox/dicio/ui/about/PrivacyScreen.kt | 103 ------------------ .../org/stypox/dicio/ui/nav/Navigation.kt | 10 +- .../kotlin/org/stypox/dicio/ui/nav/Routes.kt | 3 - 4 files changed, 7 insertions(+), 118 deletions(-) delete mode 100644 app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt index 976b8a434..37cb7eeeb 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt @@ -40,7 +40,6 @@ import org.stypox.dicio.util.ShareUtils @Composable fun AboutScreen( navigationIcon: @Composable () -> Unit, - navigateToPrivacy: () -> Unit, ) { val context = LocalContext.current val snackbarHostState = remember { SnackbarHostState() } @@ -172,7 +171,12 @@ fun AboutScreen( title = stringResource(R.string.about_privacy_policy), icon = Icons.Default.Policy, description = stringResource(R.string.privacy_intro), - modifier = Modifier.clickable(onClick = navigateToPrivacy), + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser( + context, + "https://github.com/Stypox/dicio-android/blob/master/PRIVACY.md" + ) + }, ) } @@ -218,7 +222,6 @@ private fun AboutScreenPreview() { AppTheme { AboutScreen( navigationIcon = {}, - navigateToPrivacy = {}, ) } } diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt deleted file mode 100644 index fff31e53d..000000000 --- a/app/src/main/kotlin/org/stypox/dicio/ui/about/PrivacyScreen.kt +++ /dev/null @@ -1,103 +0,0 @@ -package org.stypox.dicio.ui.about - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import org.stypox.dicio.R -import org.stypox.dicio.ui.theme.AppTheme - -@Composable -fun PrivacyScreen( - navigationIcon: @Composable () -> Unit, -) { - Scaffold( - topBar = { - @OptIn(ExperimentalMaterial3Api::class) - TopAppBar( - title = { Text(stringResource(R.string.privacy_policy)) }, - navigationIcon = navigationIcon, - ) - }, - ) { paddingValues -> - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - .padding(horizontal = 16.dp, vertical = 12.dp) - ) { - item { - Text( - text = stringResource(R.string.privacy_intro), - style = MaterialTheme.typography.bodyLarge, - fontWeight = FontWeight.Medium, - modifier = Modifier.padding(bottom = 16.dp) - ) - } - - item { - PrivacySection( - title = "Data Storage", - content = """The data that Dicio itself stores about the user are preferences, history and logs. They are kept in local and private databases which are only used by Dicio. Therefore, no such personal data is ever shared with any online service, except for specific preferences as explained shortly. Preferences are used to allow the user to customize their experience. Preferences strictly related to a specific online website might be sent to them for the purpose of enforcing such preference (for example, the language preference is used to download the correct Speech-To-Text model). History is used to make relevant suggestions and better understand user input. Logs are used only for debugging and troubleshooting purposes, and do not contain any specific user information.""" - ) - } - - item { - PrivacySection( - title = "Skills and Internet Connectivity", - content = """Dicio contains multiple skills, that is separate components that provide services, so that users can interact with those using the assistant interface (for example weather, lyrics, ...). Some skills work without ever connecting to the internet, while others may connect to various online websites as a requirement for providing their service (for example, the weather skill needs to access the internet to obtain weather data). Skills will not share any personal information with online services, unless it is strictly necessary for them to function (for example, when the weather skill makes a web request, the city is necessarily included). Users may enable or disable each skill individually, so they have full control over which online websites may be contacted.""" - ) - } - - item { - PrivacySection( - title = "Voice Recognition", - content = """The voice recognition in Dicio works offline, without ever sending audio to any external service. Once the Vosk AI Speech-To-Text model has been downloaded, Dicio can use it without needing to connect to the internet ever again. Therefore, once the STT model is ready, Dicio can be used without an internet connection, except for skills that need to find information on the internet (for example, the timer will work, while the weather will not). If the user wishes to, he can disable Dicio's access to the internet from Android settings, and the app will continue working normally. The user may even disable voice recognition completely from application settings.""" - ) - } - - item { - PrivacySection( - title = "Data Deletion", - content = """The user can easily delete all of his personal data by just clearing the app's storage from Android settings, or by uninstalling the app completely.""" - ) - } - } - } -} - -@Composable -private fun PrivacySection( - title: String, - content: String, -) { - Text( - text = title, - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(top = 16.dp, bottom = 8.dp) - ) - Text( - text = content, - style = MaterialTheme.typography.bodyMedium, - lineHeight = MaterialTheme.typography.bodyMedium.lineHeight.times(1.5f), - ) -} - -@Preview -@Composable -private fun PrivacyScreenPreview() { - AppTheme { - PrivacyScreen(navigationIcon = {}) - } -} diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt index 80c535afd..3a6f4f30b 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Navigation.kt @@ -21,7 +21,6 @@ import org.stypox.dicio.io.input.stt_popup.SttPopupActivity import org.stypox.dicio.settings.MainSettingsScreen import org.stypox.dicio.settings.SkillSettingsScreen import org.stypox.dicio.ui.about.AboutScreen -import org.stypox.dicio.ui.about.PrivacyScreen import org.stypox.dicio.ui.home.HomeScreen @Composable @@ -65,14 +64,7 @@ fun Navigation() { } composable { - AboutScreen( - navigationIcon = backIcon, - navigateToPrivacy = { navController.navigate(Privacy) }, - ) - } - - composable { - PrivacyScreen(navigationIcon = backIcon) + AboutScreen(navigationIcon = backIcon) } } } diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt index eba5d01bc..24af61df8 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Routes.kt @@ -13,6 +13,3 @@ object SkillSettings @Serializable object About - -@Serializable -object Privacy From ad21e2ab2125111066e363759fd2c4a2da3a44e3 Mon Sep 17 00:00:00 2001 From: tylxr <102394635+tylxr59@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:13:05 -0500 Subject: [PATCH 3/5] Strings cleanup --- app/src/main/res/values/strings.xml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 901ce319a..1adddd266 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -265,7 +265,6 @@ About - About Dicio Free and open source voice assistant Version App information @@ -275,17 +274,13 @@ Links Source code Report an issue - GitHub + GitHub Legal License - GNU General Public License v3.0 + GNU General Public License v3.0 Privacy policy Credits Contributors Dicio is made possible by contributors around the world Copy version info - - - Privacy policy - Dicio respects your privacy and processes all data locally on your device. From 332c0ed66935eb8977cdc11137aee06f5dc8f904 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 23 Feb 2026 16:18:14 +0100 Subject: [PATCH 4/5] Add more logo formats (including ImageVector) --- .../dicio/ui/about/DicioSquircleIcon.kt | 212 ++++++++++++++++++ meta/README.md | 2 +- meta/logo.svg | 42 ++++ meta/logo_with_background.svg | 1 + 4 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 app/src/main/kotlin/org/stypox/dicio/ui/about/DicioSquircleIcon.kt create mode 100644 meta/logo.svg create mode 100644 meta/logo_with_background.svg diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/DicioSquircleIcon.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/DicioSquircleIcon.kt new file mode 100644 index 000000000..f9b4f82ec --- /dev/null +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/DicioSquircleIcon.kt @@ -0,0 +1,212 @@ +package org.stypox.dicio.ui.about + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +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.graphics.SolidColor +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.path +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import org.stypox.dicio.ui.theme.AppTheme + +/** + * Generated with https://github.com/rafaeltonholo/svg-to-compose/ + * based on meta/logo_with_background.svg. + */ +val DicioSquircleIcon: ImageVector + get() { + val current = _logo + if (current != null) return current + + return ImageVector.Builder( + name = "DicioSquircleIcon", + defaultWidth = 100.0.dp, + defaultHeight = 100.0.dp, + viewportWidth = 100.0f, + viewportHeight = 100.0f, + ).apply { + + // M0 50 C0 15 15 0 50 0 s50 15 50 50 -15 50 -50 50 S0 85 0 50 + path( + fill = SolidColor(Color(0xFFDAEC21)), + ) { + // M 0 50 + moveTo(x = 0.0f, y = 50.0f) + // C 0 15 15 0 50 0 + curveTo( + x1 = 0.0f, + y1 = 15.0f, + x2 = 15.0f, + y2 = 0.0f, + x3 = 50.0f, + y3 = 0.0f, + ) + // s 50 15 50 50 + reflectiveCurveToRelative( + dx1 = 50.0f, + dy1 = 15.0f, + dx2 = 50.0f, + dy2 = 50.0f, + ) + // s -15 50 -50 50 + reflectiveCurveToRelative( + dx1 = -15.0f, + dy1 = 50.0f, + dx2 = -50.0f, + dy2 = 50.0f, + ) + // S 0 85 0 50 + reflectiveCurveTo( + x1 = 0.0f, + y1 = 85.0f, + x2 = 0.0f, + y2 = 50.0f, + ) + } + // M32.73 17.4 c-5.9 42.09 -1.83 50.85 2.9 53.77 -14.64 -1.4 -11.6 10.07 -2.3 11.13 55.78 6.34 63.52 -66.13 -.6 -64.9 m20.63 13.82 c3.47 0 6.27 2.8 6.27 6.26 L59.6 50.01 a6.26 6.26 0 1 1 -12.5 0 V37.48 c-.01 -3.46 2.79 -6.26 6.25 -6.26 m-14.62 18.8 h3.55 c0 6.26 5.3 10.64 11.07 10.64 5.76 0 11.07 -4.38 11.07 -10.65 h3.55 c0 7.15 -5.68 13.03 -12.53 14.04 v6.85 h-4.18 v-6.85 C44.42 63.02 38.74 57.13 38.74 50 + path( + fill = SolidColor(Color(0xFF006800)), + ) { + // M 32.73 17.4 + moveTo(x = 32.73f, y = 17.4f) + // c -5.9 42.09 -1.83 50.85 2.9 53.77 + curveToRelative( + dx1 = -5.9f, + dy1 = 42.09f, + dx2 = -1.83f, + dy2 = 50.85f, + dx3 = 2.9f, + dy3 = 53.77f, + ) + // c -14.64 -1.4 -11.6 10.07 -2.3 11.13 + curveToRelative( + dx1 = -14.64f, + dy1 = -1.4f, + dx2 = -11.6f, + dy2 = 10.07f, + dx3 = -2.3f, + dy3 = 11.13f, + ) + // c 55.78 6.34 63.52 -66.13 -0.6 -64.9 + curveToRelative( + dx1 = 55.78f, + dy1 = 6.34f, + dx2 = 63.52f, + dy2 = -66.13f, + dx3 = -0.6f, + dy3 = -64.9f, + ) + // m 20.63 13.82 + moveToRelative(dx = 20.63f, dy = 13.82f) + // c 3.47 0 6.27 2.8 6.27 6.26 + curveToRelative( + dx1 = 3.47f, + dy1 = 0.0f, + dx2 = 6.27f, + dy2 = 2.8f, + dx3 = 6.27f, + dy3 = 6.26f, + ) + // L 59.6 50.01 + lineTo(x = 59.6f, y = 50.01f) + // a 6.26 6.26 0 1 1 -12.5 0 + arcToRelative( + a = 6.26f, + b = 6.26f, + theta = 0.0f, + isMoreThanHalf = true, + isPositiveArc = true, + dx1 = -12.5f, + dy1 = 0.0f, + ) + // V 37.48 + verticalLineTo(y = 37.48f) + // c -0.01 -3.46 2.79 -6.26 6.25 -6.26 + curveToRelative( + dx1 = -0.01f, + dy1 = -3.46f, + dx2 = 2.79f, + dy2 = -6.26f, + dx3 = 6.25f, + dy3 = -6.26f, + ) + // m -14.62 18.8 + moveToRelative(dx = -14.62f, dy = 18.8f) + // h 3.55 + horizontalLineToRelative(dx = 3.55f) + // c 0 6.26 5.3 10.64 11.07 10.64 + curveToRelative( + dx1 = 0.0f, + dy1 = 6.26f, + dx2 = 5.3f, + dy2 = 10.64f, + dx3 = 11.07f, + dy3 = 10.64f, + ) + // c 5.76 0 11.07 -4.38 11.07 -10.65 + curveToRelative( + dx1 = 5.76f, + dy1 = 0.0f, + dx2 = 11.07f, + dy2 = -4.38f, + dx3 = 11.07f, + dy3 = -10.65f, + ) + // h 3.55 + horizontalLineToRelative(dx = 3.55f) + // c 0 7.15 -5.68 13.03 -12.53 14.04 + curveToRelative( + dx1 = 0.0f, + dy1 = 7.15f, + dx2 = -5.68f, + dy2 = 13.03f, + dx3 = -12.53f, + dy3 = 14.04f, + ) + // v 6.85 + verticalLineToRelative(dy = 6.85f) + // h -4.18 + horizontalLineToRelative(dx = -4.18f) + // v -6.85 + verticalLineToRelative(dy = -6.85f) + // C 44.42 63.02 38.74 57.13 38.74 50 + curveTo( + x1 = 44.42f, + y1 = 63.02f, + x2 = 38.74f, + y2 = 57.13f, + x3 = 38.74f, + y3 = 50.0f, + ) + } + }.build().also { _logo = it } + } + +@Preview +@Composable +private fun IconPreview() { + AppTheme { + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Image( + imageVector = DicioSquircleIcon, + contentDescription = null, + modifier = Modifier + .width((100.0).dp) + .height((100.0).dp), + ) + } + } +} + +@Suppress("ObjectPropertyName") +private var _logo: ImageVector? = null diff --git a/meta/README.md b/meta/README.md index 4d1c75a64..e2fabbb63 100644 --- a/meta/README.md +++ b/meta/README.md @@ -16,7 +16,7 @@ The launcher icon can be generated using [IconKitchen](https://icon.kitchen). Th - **Effect: Drop shadow** - **Padding: 17%** - Background type: Color -- **Bakcground color: `#daec21`** from above +- **Background color: `#daec21`** from above - Texture: None - Badge: none - Filename: none (defaults to `ic_launcher`) diff --git a/meta/logo.svg b/meta/logo.svg new file mode 100644 index 000000000..0322c59be --- /dev/null +++ b/meta/logo.svg @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/meta/logo_with_background.svg b/meta/logo_with_background.svg new file mode 100644 index 000000000..da56f6033 --- /dev/null +++ b/meta/logo_with_background.svg @@ -0,0 +1 @@ + \ No newline at end of file From 81fef7eff5a8d38c78b6bdabd9919362fb959f83 Mon Sep 17 00:00:00 2001 From: Stypox Date: Mon, 23 Feb 2026 16:18:36 +0100 Subject: [PATCH 5/5] Another version of the about screen --- .../org/stypox/dicio/error/ErrorActivity.kt | 2 +- .../org/stypox/dicio/ui/about/AboutScreen.kt | 264 ++++++++---------- app/src/main/res/values/strings.xml | 35 +-- 3 files changed, 128 insertions(+), 173 deletions(-) diff --git a/app/src/main/kotlin/org/stypox/dicio/error/ErrorActivity.kt b/app/src/main/kotlin/org/stypox/dicio/error/ErrorActivity.kt index a6bacde40..30a019c5e 100644 --- a/app/src/main/kotlin/org/stypox/dicio/error/ErrorActivity.kt +++ b/app/src/main/kotlin/org/stypox/dicio/error/ErrorActivity.kt @@ -99,7 +99,7 @@ class ErrorActivity : BaseActivity() { val CURRENT_TIMESTAMP_FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") - private fun getOsInfo(): String { + fun getOsInfo(): String { val osBase = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) Build.VERSION.BASE_OS else diff --git a/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt index 37cb7eeeb..86add60f3 100644 --- a/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt @@ -1,38 +1,47 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package org.stypox.dicio.ui.about -import android.os.Build +import androidx.annotation.StringRes +import androidx.compose.foundation.Image import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BugReport import androidx.compose.material.icons.filled.Code -import androidx.compose.material.icons.filled.Description -import androidx.compose.material.icons.filled.Gavel +import androidx.compose.material.icons.filled.ContentCopy import androidx.compose.material.icons.filled.Group -import androidx.compose.material.icons.filled.Info -import androidx.compose.material.icons.filled.LibraryBooks import androidx.compose.material.icons.filled.Policy import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch import org.stypox.dicio.BuildConfig import org.stypox.dicio.R -import org.stypox.dicio.settings.ui.SettingsCategoryTitle +import org.stypox.dicio.error.ErrorActivity import org.stypox.dicio.settings.ui.SettingsItem import org.stypox.dicio.ui.theme.AppTheme import org.stypox.dicio.util.ShareUtils @@ -42,178 +51,129 @@ fun AboutScreen( navigationIcon: @Composable () -> Unit, ) { val context = LocalContext.current - val snackbarHostState = remember { SnackbarHostState() } - val scope = rememberCoroutineScope() Scaffold( topBar = { - @OptIn(ExperimentalMaterial3Api::class) TopAppBar( title = { Text(stringResource(R.string.about)) }, navigationIcon = navigationIcon, ) }, - snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) + contentPadding = PaddingValues(bottom = 4.dp), + modifier = Modifier.padding(paddingValues) ) { - // App Information Section - item { - SettingsCategoryTitle(title = stringResource(R.string.about_app_info)) - } - - item { - SettingsItem( - title = stringResource(R.string.app_name), - description = stringResource(R.string.about_description), - ) - } - - item { - SettingsItem( - title = stringResource(R.string.about_version), - description = BuildConfig.VERSION_NAME, - ) - } - - item { - SettingsItem( - title = stringResource(R.string.about_application_id), - description = BuildConfig.APPLICATION_ID, - ) - } - - // Build Information Section item { - SettingsCategoryTitle(title = stringResource(R.string.about_build_info)) - } - - item { - SettingsItem( - title = stringResource(R.string.about_version_code), - description = BuildConfig.VERSION_CODE.toString(), - ) - } - - item { - SettingsItem( - title = stringResource(R.string.about_copy_version_info), - icon = Icons.Default.Description, - modifier = Modifier.clickable { - val versionInfo = buildVersionInfo() - ShareUtils.copyToClipboard(context, versionInfo) - scope.launch { - snackbarHostState.showSnackbar( - context.getString(R.string.copied_to_clipboard) - ) - } - }, - ) - } - - // Links Section - item { - SettingsCategoryTitle(title = stringResource(R.string.about_links)) + Column( + modifier = Modifier + .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) + .fillMaxWidth() + .wrapContentSize(Alignment.Center), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + imageVector = DicioSquircleIcon, + contentDescription = stringResource(R.string.app_name), + modifier = Modifier.size(64.dp) + ) + Spacer(Modifier.height(4.dp)) + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.titleLarge, + textAlign = TextAlign.Center + ) + val versionTextSizeUnit = MaterialTheme.typography.titleMedium.fontSize.value.dp + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(versionTextSizeUnit * 0.25f), + modifier = Modifier.clickable( + onClickLabel = stringResource(R.string.about_version_copy) + ) { ShareUtils.copyToClipboard(context, getVersionInfoString()) } + ) { + Text( + text = stringResource( + R.string.about_version, + BuildConfig.VERSION_NAME, + BuildConfig.VERSION_CODE + ), + style = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Light), + textAlign = TextAlign.Center + ) + Icon( + imageVector = Icons.Default.ContentCopy, + contentDescription = null, + modifier = Modifier.size(versionTextSizeUnit * 0.8f) + ) + } + Text( + modifier = Modifier.fillMaxWidth(), + text = stringResource(R.string.about_description), + textAlign = TextAlign.Center + ) + } } - + item { - SettingsItem( - title = stringResource(R.string.about_source_code), + AboutItem( + title = R.string.about_repository_title, icon = Icons.Default.Code, - description = "github.com/Stypox/dicio-android", - modifier = Modifier.clickable { - ShareUtils.openUrlInBrowser( - context, - "https://github.com/Stypox/dicio-android" - ) - }, + description = R.string.about_repository_description, + link = R.string.about_repository_link ) } - + item { - SettingsItem( - title = stringResource(R.string.about_report_issue), + AboutItem( + title = R.string.about_issues_title, icon = Icons.Default.BugReport, - description = "Report bugs and request features", - modifier = Modifier.clickable { - ShareUtils.openUrlInBrowser( - context, - "https://github.com/Stypox/dicio-android/issues" - ) - }, + description = R.string.about_issues_description, + link = R.string.about_issues_link ) } - - // Legal Section - item { - SettingsCategoryTitle(title = stringResource(R.string.about_legal)) - } - + item { - SettingsItem( - title = stringResource(R.string.about_license), - icon = Icons.Default.Gavel, - description = stringResource(R.string.about_license_gpl), - modifier = Modifier.clickable { - ShareUtils.openUrlInBrowser( - context, - "https://www.gnu.org/licenses/gpl-3.0.html" - ) - }, + AboutItem( + title = R.string.about_contributing_title, + icon = Icons.Default.Group, + description = R.string.about_contributing_description, + link = R.string.about_contributing_link ) } - + item { - SettingsItem( - title = stringResource(R.string.about_privacy_policy), + AboutItem( + title = R.string.about_privacy_title, icon = Icons.Default.Policy, - description = stringResource(R.string.privacy_intro), - modifier = Modifier.clickable { - ShareUtils.openUrlInBrowser( - context, - "https://github.com/Stypox/dicio-android/blob/master/PRIVACY.md" - ) - }, - ) - } - - // Credits Section - item { - SettingsCategoryTitle(title = stringResource(R.string.about_credits)) - } - - item { - SettingsItem( - title = stringResource(R.string.about_contributors), - icon = Icons.Default.Group, - description = stringResource(R.string.about_contributors_description), - modifier = Modifier.clickable { - ShareUtils.openUrlInBrowser( - context, - "https://github.com/Stypox/dicio-android/graphs/contributors" - ) - }, + description = R.string.about_privacy_description, + link = R.string.about_privacy_link ) } - - item { - HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp)) - } } } } -private fun buildVersionInfo(): String { - return """ - ${BuildConfig.APPLICATION_ID} - Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) - - Device: ${Build.MANUFACTURER} ${Build.MODEL} - Android: ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT}) - """.trimIndent() +@Composable +fun AboutItem( + @StringRes title: Int, + icon: ImageVector, + @StringRes description: Int, + @StringRes link: Int +) { + val context = LocalContext.current + SettingsItem( + title = stringResource(title), + icon = icon, + description = stringResource(description), + modifier = Modifier.clickable { + ShareUtils.openUrlInBrowser(context, context.getString(link)) + }, + ) +} + +private fun getVersionInfoString(): String { + return "${BuildConfig.APPLICATION_ID} ${BuildConfig.VERSION_NAME} (" + + "${BuildConfig.VERSION_CODE}) running on ${ErrorActivity.getOsInfo()}" } @Preview diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1adddd266..ea8db6663 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -262,25 +262,20 @@ Flashlight turned on Flashlight turned off Unable to control the flashlight - - About - Free and open source voice assistant - Version - App information - Application ID - Build information - Version code - Links - Source code - Report an issue - GitHub - Legal - License - GNU General Public License v3.0 - Privacy policy - Credits - Contributors - Dicio is made possible by contributors around the world - Copy version info + %1$s (%2$d) + Copy version info + Multilanguage, local and libre voice assistant + Source code repository + Dicio is free and open source, licensed under GPLv3 + https://github.com/Stypox/dicio-android + Found a bug? + Feel free to request new features or report broken things (remember to attach logs) + https://github.com/Stypox/dicio-android/issues + Contributing + Dicio is developed by volunteers around the world. You can help out too by contributing code improvements, translations and even new skills! + https://github.com/Stypox/dicio-android#contributing + Privacy policy + Dicio connects to external services only when a skill is expected to do so, or to download machine learning models during setup. All speech processing is performed locally on-device, and most skills can be used offline. + https://stypox.org/dicio-privacy-policy.html