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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
id("dagger.hilt.android.plugin")
alias(libs.plugins.compose.compiler)
alias(libs.plugins.dropshots)
id("com.google.android.gms.oss-licenses-plugin")
}

android {
Expand Down Expand Up @@ -49,7 +50,10 @@ android {
create("foss") { dimension = "store" }
}
kotlin { jvmToolchain(21) }
buildFeatures { compose = true }
buildFeatures {
compose = true
buildConfig = true
}
configurations.all {
resolutionStrategy.force("org.jetbrains.kotlin:kotlin-metadata-jvm:2.3.0")
exclude(group = "com.google.guava", module = "listenablefuture")
Expand All @@ -76,6 +80,7 @@ dependencies {
implementation(project(":database"))
implementation(project(":network"))
implementation(libs.androidx.core.splashscreen)
implementation(libs.play.services.oss.licenses)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.com.google.android.material)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Translate
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
Expand All @@ -28,6 +29,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.anysoftkeyboard.janus.app.ui.AboutScreen
import com.anysoftkeyboard.janus.app.ui.BookmarksScreen
import com.anysoftkeyboard.janus.app.ui.HistoryScreen
import com.anysoftkeyboard.janus.app.ui.TranslateScreen
Expand Down Expand Up @@ -91,6 +93,7 @@ fun JanusApp(initialText: String? = null) {
composable(TabScreen.Translate.route) { TranslateScreen(hiltViewModel(), initialText) }
composable(TabScreen.History.route) { HistoryScreen(hiltViewModel()) }
composable(TabScreen.Bookmarks.route) { BookmarksScreen(hiltViewModel()) }
composable(TabScreen.About.route) { AboutScreen() }
}
}
}
Expand All @@ -103,6 +106,7 @@ enum class TabScreen(
Translate("translate", R.string.tab_translate, Icons.Default.Translate),
History("history", R.string.tab_history, Icons.Default.History),
Bookmarks("bookmarks", R.string.tab_bookmarks, Icons.Default.Favorite),
About("about", R.string.tab_about, Icons.Default.Info),
}

@Preview(showBackground = true)
Expand Down
168 changes: 168 additions & 0 deletions app/src/main/java/com/anysoftkeyboard/janus/app/ui/AboutScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.anysoftkeyboard.janus.app.ui

import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.anysoftkeyboard.janus.app.BuildConfig
import com.anysoftkeyboard.janus.app.R
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity

/**
* About screen displaying application information, explanation of the unique translation mechanism
* (The Colophon), links to GitHub repository and issue tracker, and the open-source licenses
* attribution page.
*/
@Composable
fun AboutScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
val scrollState = rememberScrollState()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Observation] The AboutScreen uses a nested Scaffold. Ensure that it doesn't cause conflicting inset handling with the Scaffold in JanusApp. It appears safe here as the inner Scaffold has no bars.

Scaffold(modifier = modifier) { paddingValues ->
Column(
modifier =
Modifier.fillMaxSize()
.verticalScroll(scrollState)
.padding(paddingValues)
.padding(horizontal = 24.dp, vertical = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
// 1. Header
Icon(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Maintenance] The Icon component uses a tint on a mipmap resource (ic_launcher_foreground). This matches the monochromatic design goal but is fragile for bitmaps. Consider using a dedicated vector or setting tint to Color.Unspecified.

painter = painterResource(R.mipmap.ic_launcher_foreground),
contentDescription = null,
modifier = Modifier.size(96.dp),
tint = MaterialTheme.colorScheme.primary,
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(R.string.about_title),
style = MaterialTheme.typography.headlineMedium,
color = MaterialTheme.colorScheme.onSurface,
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.about_version, BuildConfig.VERSION_NAME),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.secondary,
)
Spacer(modifier = Modifier.height(28.dp))

// 2. Abstract
Text(
text = stringResource(R.string.about_abstract),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(28.dp))

// 3. Methodology
Text(
text = stringResource(R.string.about_methodology_title),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.align(Alignment.Start),
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.about_methodology_text),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.align(Alignment.Start),
)
Spacer(modifier = Modifier.height(28.dp))

// 4. References
ReferenceLinkRow(
label = stringResource(R.string.about_link_source_code),
url = "https://github.com/AnySoftKeyboard/janus",
context = context,
)
Spacer(modifier = Modifier.height(4.dp))
ReferenceLinkRow(
label = stringResource(R.string.about_link_issue_tracker),
url = "https://github.com/AnySoftKeyboard/janus/issues",
context = context,
)
Spacer(modifier = Modifier.height(28.dp))

// 5. Attributions
OutlinedButton(
onClick = { context.startActivity(Intent(context, OssLicensesMenuActivity::class.java)) },
colors =
ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.secondary
),
border = BorderStroke(1.dp, MaterialTheme.colorScheme.secondary),
modifier = Modifier.fillMaxWidth(),
) {
Text(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Risk] The startActivity call for OssLicensesMenuActivity is not protected. Any failure to resolve the class or launch the intent will result in a crash. This should be wrapped in a try-catch block to handle ActivityNotFoundException.

text = stringResource(R.string.about_button_licenses),
style = MaterialTheme.typography.labelLarge,
)
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}

@Composable
private fun ReferenceLinkRow(
label: String,
url: String,
context: Context,
modifier: Modifier = Modifier,
) {
Row(
modifier =
modifier
.fillMaxWidth()
.clickable {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Risk] Launching an external browser using ACTION_VIEW can throw an ActivityNotFoundException on devices without a browser. This call must be protected to ensure system stability.

.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = label,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.weight(1f),
)
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
modifier = Modifier.size(20.dp),
)
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/values-ar/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">لغة غير معروفة</string>
<string name="error_detection_failed_message">تعذر اكتشاف لغة النص المُدخل.</string>

<!-- About screen -->
<string name="tab_about">حول</string>
<string name="about_title">Janus Glossa</string>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Observation] The version string in Arabic (إصدار %1$s) omits the 'v' prefix used in other locales. Consider if strict consistency across languages is required.

<string name="about_version">إصدار %1$s</string>
<string name="about_abstract">Janus Glossa هي أداة مفتوحة المصدر مصممة لترجمة المفاهيم داخل سياقها، باستخدام المعرفة الجماعية لـ ويكيبيديا.</string>
<string name="about_methodology_title">كيف يعمل</string>
<string name="about_methodology_text">على عكس القواميس القياسية، يقوم Janus Glossa بمحاذاة المفاهيم. فهو يبحث عن إدخال الموسوعة لمصطلحك، ثم يتبع الرابط بين اللغات إلى اللغة المستهدفة لتقديم ترجمة دقيقة ومناسبة للسياق.</string>
<string name="about_link_source_code">كود المصدر</string>
<string name="about_link_issue_tracker">متتبع المشكلات</string>
<string name="about_button_licenses">تراخيص البرمجيات مفتوحة المصدر</string>
<string name="content_description_about">حول Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">Sprache unbekannt</string>
<string name="error_detection_failed_message">Die Sprache des Eingabetextes konnte nicht erkannt werden.</string>

<!-- About screen -->
<string name="tab_about">Über</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa ist ein Open-Source-Tool zur Übersetzung von Konzepten in ihrem Kontext, das auf das gemeinschaftliche Wissen von Wikipedia zurückgreift.</string>
<string name="about_methodology_title">So funktioniert es</string>
<string name="about_methodology_text">Im Gegensatz zu herkömmlichen Wörterbüchern gleicht Janus Glossa Konzepte ab. Es findet den Enzyklopädieeintrag für Ihren Begriff und folgt dann dem interlingualen Link zur Zielsprache, um eine präzise, kontextsensitive Übersetzung zu liefern.</string>
<string name="about_link_source_code">Quellcode</string>
<string name="about_link_issue_tracker">Fehlerverfolgung</string>
<string name="about_button_licenses">Open-Source-Lizenzen</string>
<string name="content_description_about">Über Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">Idioma desconocido</string>
<string name="error_detection_failed_message">No se pudo detectar el idioma del texto de entrada.</string>

<!-- About screen -->
<string name="tab_about">Acerca de</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa es una herramienta de código abierto diseñada para traducir conceptos dentro de su contexto, utilizando el conocimiento colectivo de Wikipedia.</string>
<string name="about_methodology_title">Cómo funciona</string>
<string name="about_methodology_text">A diferencia de los diccionarios estándar, Janus Glossa alinea conceptos. Encuentra la entrada de la enciclopedia para su término y luego sigue el enlace interlingüístico al idioma de destino para proporcionar una traducción precisa y consciente del contexto.</string>
<string name="about_link_source_code">Código fuente</string>
<string name="about_link_issue_tracker">Seguimiento de problemas</string>
<string name="about_button_licenses">Licencias de código abierto</string>
<string name="content_description_about">Acerca de Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">Langue inconnue</string>
<string name="error_detection_failed_message">Impossible de détecter la langue du texte saisi.</string>

<!-- About screen -->
<string name="tab_about">À propos</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa est un outil open source conçu pour traduire des concepts dans leur contexte, en utilisant les connaissances collectives de Wikipédia.</string>
<string name="about_methodology_title">Comment ça marche</string>
<string name="about_methodology_text">Contrairement aux dictionnaires standard, Janus Glossa aligne les concepts. Il trouve l\'entrée d\'encyclopédie correspondant à votre terme, puis suit le lien interlangue vers la langue cible pour fournir une traduction précise et adaptée au contexte.</string>
<string name="about_link_source_code">Code source</string>
<string name="about_link_issue_tracker">Suivi des problèmes</string>
<string name="about_button_licenses">Licences open source</string>
<string name="content_description_about">À propos de Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values-he/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">שפה לא ידועה</string>
<string name="error_detection_failed_message">לא ניתן היה לזהות את השפה של הטקסט שהוזן.</string>

<!-- About screen -->
<string name="tab_about">אודות</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa הוא כלי בקוד פתוח שנועד לתרגם מושגים בתוך ההקשר שלהם, תוך שימוש בידע הקהילתי של ויקיפדיה.</string>
<string name="about_methodology_title">איך זה עובד</string>
<string name="about_methodology_text">בניגוד למילונים רגילים, Janus Glossa מתאים מושגים. הוא מוצא את הערך באנציקלופדיה עבור המונח שלך, ואז עוקב אחר הקישור הרב-לשוני לשפת היעד כדי לספק תרגום מדויק ותלוי הקשר.</string>
<string name="about_link_source_code">קוד מקור</string>
<string name="about_link_issue_tracker">מעקב תקלות</string>
<string name="about_button_licenses">רישיונות קוד פתוח</string>
<string name="content_description_about">אודות Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">Язык неизвестен</string>
<string name="error_detection_failed_message">Не удалось определить язык введенного текста.</string>

<!-- About screen -->
<string name="tab_about">О приложении</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa — это инструмент с открытым исходным кодом, разработанный для перевода понятий в их контексте с использованием коллективных знаний Википедии.</string>
<string name="about_methodology_title">Как это работает</string>
<string name="about_methodology_text">В отличие от обычных словарей, Janus Glossa сопоставляет понятия. Он находит словарную статью для вашего термина в энциклопедии, а затем переходит по межъязыковой ссылке на целевой язык, чтобы предоставить точный перевод с учетом контекста.</string>
<string name="about_link_source_code">Исходный код</string>
<string name="about_link_issue_tracker">Отслеживание ошибок</string>
<string name="about_button_licenses">Лицензии открытого ПО</string>
<string name="content_description_about">О приложении Janus Glossa</string>
</resources>
12 changes: 12 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,16 @@
<!-- Detection Failed -->
<string name="error_detection_failed_title">Language Unknown</string>
<string name="error_detection_failed_message">Could not detect the language of the input text.</string>

<!-- About screen -->
<string name="tab_about">About</string>
<string name="about_title">Janus Glossa</string>
<string name="about_version">v%1$s</string>
<string name="about_abstract">Janus Glossa is an open-source tool designed to translate concepts within their context, utilizing the crowd-sourced knowledge of Wikipedia.</string>
<string name="about_methodology_title">How it works</string>
<string name="about_methodology_text">Unlike standard dictionaries, Janus Glossa aligns concepts. It finds the encyclopedia entry for your term, then follows the inter-language link to the target language to provide a precise, context-aware translation.</string>
<string name="about_link_source_code">Source Code</string>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Observation] The string content_description_about is defined but remains unused. It should be applied to the app icon in the AboutScreen header or the tab icon to improve accessibility.

<string name="about_link_issue_tracker">Issue Tracker</string>
<string name="about_button_licenses">Open Source Licenses</string>
<string name="content_description_about">About Janus Glossa</string>
</resources>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.anysoftkeyboard.janus.app

import android.content.Intent
import androidx.compose.material.icons.filled.Info
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand All @@ -17,6 +18,7 @@ import dagger.hilt.android.testing.HiltTestApplication
import dagger.hilt.android.testing.UninstallModules
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
Expand Down Expand Up @@ -65,6 +67,14 @@ class MainActivityTest {
)
}

@Test
fun testTabScreenAbout_isCorrectlyConfigured() {
val aboutTab = TabScreen.About
assertEquals("about", aboutTab.route)
assertEquals(R.string.tab_about, aboutTab.titleRes)
assertEquals(androidx.compose.material.icons.Icons.Default.Info, aboutTab.icon)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Maintenance] The test uses a full package name inline: androidx.compose.material.icons.Icons.Default.Info. Per guidelines, use imports instead.

}

@Test
fun startMainActivity_withoutProcessText_doesNotSearch() = runTest {
val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java)
Expand Down
8 changes: 8 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies { classpath(libs.google.oss.licenses.classpath) }
}

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
Expand Down
Loading
Loading