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 a6bacde4..30a019c5 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 new file mode 100644 index 00000000..86add60f --- /dev/null +++ b/app/src/main/kotlin/org/stypox/dicio/ui/about/AboutScreen.kt @@ -0,0 +1,187 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + +package org.stypox.dicio.ui.about + +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.ContentCopy +import androidx.compose.material.icons.filled.Group +import androidx.compose.material.icons.filled.Policy +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +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.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 org.stypox.dicio.BuildConfig +import org.stypox.dicio.R +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 + +@Composable +fun AboutScreen( + navigationIcon: @Composable () -> Unit, +) { + val context = LocalContext.current + + Scaffold( + topBar = { + TopAppBar( + title = { Text(stringResource(R.string.about)) }, + navigationIcon = navigationIcon, + ) + }, + ) { paddingValues -> + LazyColumn( + contentPadding = PaddingValues(bottom = 4.dp), + modifier = Modifier.padding(paddingValues) + ) { + item { + 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 { + AboutItem( + title = R.string.about_repository_title, + icon = Icons.Default.Code, + description = R.string.about_repository_description, + link = R.string.about_repository_link + ) + } + + item { + AboutItem( + title = R.string.about_issues_title, + icon = Icons.Default.BugReport, + description = R.string.about_issues_description, + link = R.string.about_issues_link + ) + } + + item { + AboutItem( + title = R.string.about_contributing_title, + icon = Icons.Default.Group, + description = R.string.about_contributing_description, + link = R.string.about_contributing_link + ) + } + + item { + AboutItem( + title = R.string.about_privacy_title, + icon = Icons.Default.Policy, + description = R.string.about_privacy_description, + link = R.string.about_privacy_link + ) + } + } + } +} + +@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 +@Composable +private fun AboutScreenPreview() { + AppTheme { + AboutScreen( + navigationIcon = {}, + ) + } +} 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 00000000..f9b4f82e --- /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/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt b/app/src/main/kotlin/org/stypox/dicio/ui/nav/Drawer.kt index edf5c164..2c912c12 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 92997944..3a6f4f30 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,7 @@ 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.home.HomeScreen @Composable @@ -41,6 +42,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 +62,17 @@ fun Navigation() { composable { SkillSettingsScreen(navigationIcon = backIcon) } + + composable { + AboutScreen(navigationIcon = backIcon) + } } } @Composable fun ScreenWithDrawer( onSettingsClick: () -> Unit, + onAboutClick: () -> Unit, onSpeechToTextPopupClick: () -> Unit, screen: @Composable (navigationIcon: @Composable () -> Unit) -> Unit ) { @@ -77,6 +84,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 a99de1cc..24af61df 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,6 @@ object MainSettings @Serializable object SkillSettings + +@Serializable +object About diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 58ec7126..ea8db666 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -262,4 +262,20 @@ Flashlight turned on Flashlight turned off Unable to control the flashlight + About + %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 diff --git a/meta/README.md b/meta/README.md index 4d1c75a6..e2fabbb6 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 00000000..0322c59b --- /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 00000000..da56f603 --- /dev/null +++ b/meta/logo_with_background.svg @@ -0,0 +1 @@ + \ No newline at end of file