diff --git a/README.md b/README.md index 4a3ad7e..97dad46 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,6 @@ Cross platform updates are supported. However, GitHub Releases will be the faste ## License See [LICENSE](./LICENSE) for details. + +[F-Droid Icon License](https://gitlab.com/fdroid/artwork/-/blob/master/fdroid-logo-2015/README.md) + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d01d387..1a994b1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.android.hilt) alias(libs.plugins.kotlin.ksp) alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.auto.license) kotlin(libs.plugins.kotlin.serialization.get().pluginId).version(libs.versions.kotlin) } @@ -15,8 +16,8 @@ android { applicationId = "dev.chungjungsoo.gptmobile" minSdk = 28 targetSdk = 34 - versionCode = 5 - versionName = "0.3.2" + versionCode = 6 + versionName = "0.4.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { @@ -93,6 +94,10 @@ dependencies { implementation(libs.ktor.logging) implementation(libs.ktor.serialization) + // License page UI + implementation(libs.auto.license.core) + implementation(libs.auto.license.ui) + // Markdown implementation(libs.compose.markdown) implementation(libs.richtext) diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/data/repository/SettingRepositoryImpl.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/data/repository/SettingRepositoryImpl.kt index 293819e..cefa3bd 100644 --- a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/data/repository/SettingRepositoryImpl.kt +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/data/repository/SettingRepositoryImpl.kt @@ -62,7 +62,7 @@ class SettingRepositoryImpl @Inject constructor( } if (platform.systemPrompt != null) { - settingDataSource.updateSystemPrompt(platform.name, platform.systemPrompt) + settingDataSource.updateSystemPrompt(platform.name, platform.systemPrompt.trim()) } } } diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/NavigationGraph.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/NavigationGraph.kt index f4a8518..d2b3ee9 100644 --- a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/NavigationGraph.kt +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/NavigationGraph.kt @@ -17,6 +17,8 @@ import androidx.navigation.navigation import dev.chungjungsoo.gptmobile.data.model.ApiType import dev.chungjungsoo.gptmobile.presentation.ui.chat.ChatScreen import dev.chungjungsoo.gptmobile.presentation.ui.home.HomeScreen +import dev.chungjungsoo.gptmobile.presentation.ui.setting.AboutScreen +import dev.chungjungsoo.gptmobile.presentation.ui.setting.LicenseScreen import dev.chungjungsoo.gptmobile.presentation.ui.setting.PlatformSettingScreen import dev.chungjungsoo.gptmobile.presentation.ui.setting.SettingScreen import dev.chungjungsoo.gptmobile.presentation.ui.setting.SettingViewModel @@ -181,13 +183,14 @@ fun NavGraphBuilder.settingNavigation(navController: NavHostController) { SettingScreen( settingViewModel = settingViewModel, onNavigationClick = { navController.navigateUp() }, - onNavigate = { apiType -> + onNavigateToPlatformSetting = { apiType -> when (apiType) { ApiType.OPENAI -> navController.navigate(Route.OPENAI_SETTINGS) ApiType.ANTHROPIC -> navController.navigate(Route.ANTHROPIC_SETTINGS) ApiType.GOOGLE -> navController.navigate(Route.GOOGLE_SETTINGS) } - } + }, + onNavigateToAboutPage = { navController.navigate(Route.ABOUT_PAGE) } ) } composable(Route.OPENAI_SETTINGS) { @@ -220,5 +223,14 @@ fun NavGraphBuilder.settingNavigation(navController: NavHostController) { apiType = ApiType.GOOGLE ) { navController.navigateUp() } } + composable(Route.ABOUT_PAGE) { + AboutScreen( + onNavigationClick = { navController.navigateUp() }, + onNavigationToLicense = { navController.navigate(Route.LICENSE) } + ) + } + composable(Route.LICENSE) { + LicenseScreen(onNavigationClick = { navController.navigateUp() }) + } } } diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/Route.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/Route.kt index 6437ed6..228e304 100644 --- a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/Route.kt +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/common/Route.kt @@ -21,4 +21,5 @@ object Route { const val ANTHROPIC_SETTINGS = "anthropic_settings" const val GOOGLE_SETTINGS = "google_settings" const val ABOUT_PAGE = "about" + const val LICENSE = "license" } diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/AboutScreen.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/AboutScreen.kt new file mode 100644 index 0000000..49d00b8 --- /dev/null +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/AboutScreen.kt @@ -0,0 +1,180 @@ +package dev.chungjungsoo.gptmobile.presentation.ui.setting + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import dev.chungjungsoo.gptmobile.R +import dev.chungjungsoo.gptmobile.presentation.common.SettingItem + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AboutScreen( + onNavigationClick: () -> Unit, + onNavigationToLicense: () -> Unit +) { + val scrollState = rememberScrollState() + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val context = LocalContext.current + val version = context.packageManager.getPackageInfo(context.packageName, 0).versionName + val clipboardManager = LocalClipboardManager.current + val uriHandler = LocalUriHandler.current + val githubLink = stringResource(R.string.github_link) + val fdroidLink = stringResource(R.string.f_droid_link) + val googlePlayLink = stringResource(R.string.play_store_link) + val feedbackLink = stringResource(R.string.feedback_link) + + Scaffold( + modifier = Modifier + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + AboutTopAppBar( + scrollBehavior = scrollBehavior, + navigationOnClick = onNavigationClick + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .padding(innerPadding) + .verticalScroll(scrollState) + ) { + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.version), + description = "v$version", + onItemClick = { clipboardManager.setText(AnnotatedString("v$version")) }, + showTrailingIcon = false, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_info), + contentDescription = stringResource(R.string.version_icon) + ) + } + ) + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.license), + description = stringResource(R.string.license_description), + onItemClick = onNavigationToLicense, + showTrailingIcon = true, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_license), + contentDescription = stringResource(R.string.license_icon) + ) + } + ) + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.github), + onItemClick = { uriHandler.openUri(githubLink) }, + showTrailingIcon = false, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_github), + contentDescription = stringResource(R.string.github_icon) + ) + } + ) + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.f_droid), + onItemClick = { uriHandler.openUri(fdroidLink) }, + showTrailingIcon = false, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_f_droid), + contentDescription = stringResource(R.string.f_droid_icon) + ) + } + ) + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.play_store), + onItemClick = { uriHandler.openUri(googlePlayLink) }, + showTrailingIcon = false, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_play_store), + contentDescription = stringResource(R.string.play_store_icon) + ) + } + ) + SettingItem( + modifier = Modifier.height(64.dp), + title = stringResource(R.string.feedback), + description = stringResource(R.string.feedback_description), + onItemClick = { uriHandler.openUri(feedbackLink) }, + showTrailingIcon = false, + showLeadingIcon = true, + leadingIcon = { + Icon( + ImageVector.vectorResource(id = R.drawable.ic_feedback), + contentDescription = stringResource(R.string.feedback_icon) + ) + } + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AboutTopAppBar( + scrollBehavior: TopAppBarScrollBehavior, + navigationOnClick: () -> Unit +) { + LargeTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background, + titleContentColor = MaterialTheme.colorScheme.onBackground + ), + title = { + Text( + modifier = Modifier.padding(4.dp), + text = stringResource(R.string.about), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + modifier = Modifier.padding(4.dp), + onClick = navigationOnClick + ) { + Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) + } + }, + scrollBehavior = scrollBehavior + ) +} diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/LicenseScreen.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/LicenseScreen.kt new file mode 100644 index 0000000..cc240fc --- /dev/null +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/LicenseScreen.kt @@ -0,0 +1,77 @@ +package dev.chungjungsoo.gptmobile.presentation.ui.setting + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer +import dev.chungjungsoo.gptmobile.R + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LicenseScreen( + onNavigationClick: () -> Unit +) { + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + modifier = Modifier + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + LicenseTopAppBar(onNavigationClick, scrollBehavior) + } + ) { innerPadding -> + Column( + modifier = Modifier.padding(innerPadding) + ) { + LibrariesContainer(modifier = Modifier.fillMaxSize()) + } + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun LicenseTopAppBar( + onNavigationClick: () -> Unit, + scrollBehavior: TopAppBarScrollBehavior +) { + LargeTopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background, + titleContentColor = MaterialTheme.colorScheme.onBackground + ), + title = { + Text( + modifier = Modifier.padding(4.dp), + text = stringResource(R.string.license), + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + navigationIcon = { + IconButton( + modifier = Modifier.padding(4.dp), + onClick = onNavigationClick + ) { + Icon(imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.go_back)) + } + }, + scrollBehavior = scrollBehavior + ) +} diff --git a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/SettingScreen.kt b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/SettingScreen.kt index 5192d14..089d941 100644 --- a/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/SettingScreen.kt +++ b/app/src/main/kotlin/dev/chungjungsoo/gptmobile/presentation/ui/setting/SettingScreen.kt @@ -50,7 +50,8 @@ fun SettingScreen( modifier: Modifier = Modifier, settingViewModel: SettingViewModel = hiltViewModel(), onNavigationClick: () -> Unit, - onNavigate: (ApiType) -> Unit + onNavigateToPlatformSetting: (ApiType) -> Unit, + onNavigateToAboutPage: () -> Unit ) { val scrollState = rememberScrollState() val scrollBehavior = pinnedExitUntilCollapsedScrollBehavior( @@ -78,11 +79,12 @@ fun SettingScreen( SettingItem( title = getPlatformSettingTitle(apiType), description = getPlatformSettingDescription(apiType), - onItemClick = { onNavigate(apiType) }, + onItemClick = { onNavigateToPlatformSetting(apiType) }, showTrailingIcon = true, showLeadingIcon = false ) } + AboutPageItem(onItemClick = onNavigateToAboutPage) if (dialogState.isThemeDialogOpen) { ThemeSettingDialog(settingViewModel) @@ -135,6 +137,19 @@ fun ThemeSetting( ) } +@Composable +fun AboutPageItem( + onItemClick: () -> Unit +) { + SettingItem( + title = stringResource(R.string.about), + description = stringResource(R.string.about_description), + onItemClick = onItemClick, + showTrailingIcon = true, + showLeadingIcon = false + ) +} + @Composable fun ThemeSettingDialog( settingViewModel: SettingViewModel = hiltViewModel() diff --git a/app/src/main/res/drawable/ic_f_droid.xml b/app/src/main/res/drawable/ic_f_droid.xml new file mode 100644 index 0000000..dc1e5a9 --- /dev/null +++ b/app/src/main/res/drawable/ic_f_droid.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_feedback.xml b/app/src/main/res/drawable/ic_feedback.xml new file mode 100644 index 0000000..afeb454 --- /dev/null +++ b/app/src/main/res/drawable/ic_feedback.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_github.xml b/app/src/main/res/drawable/ic_github.xml new file mode 100644 index 0000000..c12bd88 --- /dev/null +++ b/app/src/main/res/drawable/ic_github.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..273542c --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml new file mode 100644 index 0000000..92133a7 --- /dev/null +++ b/app/src/main/res/drawable/ic_license.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_play_store.xml b/app/src/main/res/drawable/ic_play_store.xml new file mode 100644 index 0000000..efa986d --- /dev/null +++ b/app/src/main/res/drawable/ic_play_store.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko-rKR/strings.xml index cf09e90..4ad5475 100644 --- a/app/src/main/res/values-ko-rKR/strings.xml +++ b/app/src/main/res/values-ko-rKR/strings.xml @@ -60,7 +60,7 @@ OpenAI 설정 Anthropic 설정 Google 설정 - API 키, 모델 + API 키, 모델, 시스템 프롬프트 API 활성화 API 키 API 모델 @@ -94,4 +94,17 @@ Temperature는 답변에 주입되는 무작위성의 정도입니다. 일반적으로 기본값은 1.0입니다.\n보다 집중적이고 결정적인 답변을 원하면 Temperature를 낮게 설정하고, 무작위 답변을 원하면 Temperature를 높게 설정합니다. Nucleus sampling 설정 Nucleus sampling이란 모델이 특정 확률 값(p)에 따라 단어를 선택하는 설정입니다.\n대부분의 모델은 일반적으로 이 값 또는 Temperature 중 하나를 변경할 것을 권장하지만 둘 다 변경하는건 권장하지 않습니다. + 앱 정보 + 버전, 라이선스, 피드백 + 버전 + 라이선스 + 피드백 + 오픈 소스 라이선스 + 다양한 피드백에 열려 있습니다! + 버전 아이콘 + 라이선스 아이콘 + 피드백 아이콘 + GitHub 아이콘 + F-Droid 아이콘 + Play Store 아이콘 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f90d143..4542fc2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,7 +77,7 @@ OpenAI Settings Anthropic Settings Google Settings - API Key, Model + API Key, Model, System Prompt Enable API API Key API Model @@ -113,4 +113,24 @@ Temperature is an amount of randomness injected into the response. Usually defaults to 1.0.\nSet low temperature for more focused and deterministic answers, and high for random answers. Nucleus Sampling Setting In nucleus sampling, we compute the cumulative distribution over all the options for each subsequent token in decreasing probability order and cut it off once it reaches a particular probability specified by this value.\nMost models usually recommend altering this or temperature but not both. + About + Version, License, Feedback + Version + License + GitHub + F-Droid + Play Store + Feedback + Open Source Licenses + We are open to all kinds of feedback! + Version Icon + License Icon + Feedback Icon + GitHub Icon + F-Droid Icon + Play Store Icon + https://github.com/Taewan-P/gpt_mobile + https://f-droid.org/packages/dev.chungjungsoo.gptmobile/ + https://play.google.com/store/apps/details?id=dev.chungjungsoo.gptmobile + https://github.com/Taewan-P/gpt_mobile/issues/new \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index cce32f7..55c1844 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,5 +6,6 @@ plugins { alias(libs.plugins.kotlin.kapt) apply false alias(libs.plugins.kotlin.ksp) apply false alias(libs.plugins.kotlin.parcelize) apply false + alias(libs.plugins.auto.license).version(libs.versions.autoLicense) apply false kotlin(libs.plugins.kotlin.serialization.get().pluginId).version(libs.versions.kotlin).apply(false) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bca682f..7f35e29 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,16 @@ [versions] agp = "8.5.0" +autoLicense = "11.2.2" kotlin = "1.9.23" coreKtx = "1.13.1" junit = "4.13.2" -junitVersion = "1.1.5" -espressoCore = "3.5.1" -lifecycleRuntime = "2.8.2" +junitVersion = "1.2.1" +espressoCore = "3.6.1" +lifecycleRuntime = "2.8.3" activityCompose = "1.9.0" composeBom = "2024.06.00" datastore = "1.1.1" -gemini = "0.8.0" +gemini = "0.9.0" hilt = "2.51.1" ksp = "1.9.23-1.0.20" ktor = "2.3.11" @@ -39,6 +40,8 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +auto-license-core = { group = "com.mikepenz", name = "aboutlibraries-core", version.ref = "autoLicense" } +auto-license-ui = { group = "com.mikepenz", name = "aboutlibraries-compose-m3", version.ref = "autoLicense" } compose-markdown = { group = "com.halilibo.compose-richtext", name = "richtext-commonmark", version.ref = "markdown" } gemini = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "gemini"} hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } @@ -61,6 +64,7 @@ androidx-lifecycle-runtime-compose-android = { group = "androidx.lifecycle", nam [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +auto-license = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "autoLicense" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }