Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
2edfcdb
feat: Add French caption check for 'bonjour'
google-labs-jules[bot] Nov 5, 2025
c37dead
feat: Add French caption check for 'bonjour' on next step
google-labs-jules[bot] Nov 5, 2025
a52e6c0
feat: Add French caption detection using ML Kit
google-labs-jules[bot] Nov 5, 2025
1dccca7
feat: Add French caption detection using ML Kit with backward compati…
google-labs-jules[bot] Nov 5, 2025
5a6c89d
feat: Add French caption detection with backward compatibility fix
google-labs-jules[bot] Nov 5, 2025
5b3fc1f
feat: Add French caption detection with backward compatibility fix
google-labs-jules[bot] Nov 5, 2025
c893e57
feat: Add French caption detection with compatibility fixes
google-labs-jules[bot] Nov 5, 2025
380b419
feat: Add French caption detection with compatibility fixes
google-labs-jules[bot] Nov 5, 2025
d223a46
feat: Add French caption detection with compatibility fixes
google-labs-jules[bot] Nov 5, 2025
a923344
Avoid bumping Android version requirement
nicolas-raoul Nov 5, 2025
a436e3c
feat: Add logging to French caption detection
google-labs-jules[bot] Nov 5, 2025
1ec789f
feat: Add French caption detection with build fixes
google-labs-jules[bot] Nov 5, 2025
2ac6c4b
feat: Add French caption detection with build and coroutine fixes
google-labs-jules[bot] Nov 5, 2025
ad3debc
feat: Add French caption detection with build and coroutine fixes
google-labs-jules[bot] Nov 5, 2025
fd18328
feat: Add French caption detection with build and test fixes
google-labs-jules[bot] Nov 5, 2025
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
3 changes: 3 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ android {
}
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs += "-Xskip-metadata-version-check"
}
buildFeatures {
buildConfig = true
Expand Down Expand Up @@ -363,6 +364,8 @@ dependencies {

//OSMDroid
implementation(libs.osmdroid.android)
implementation("com.google.mlkit:genai-prompt:1.0.0-alpha1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
constraints {
implementation(libs.kotlin.stdlib.jdk7) {
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-sdk tools:overrideLibrary="com.google.mlkit.genai.prompt, com.google.mlkit.genai.common, com.google.android.gms.basement, com.google.android.gms:play-services-basement, com.google.android.gms.common" />

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
Expand Down
81 changes: 81 additions & 0 deletions app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
import androidx.work.ExistingWorkPolicy
import androidx.lifecycle.lifecycleScope
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SessionManager
Expand Down Expand Up @@ -52,13 +53,18 @@ import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.PermissionUtils.PERMISSIONS_STORAGE
import fr.free.nrw.commons.utils.PermissionUtils.checkPermissionsAndPerformAction
import fr.free.nrw.commons.utils.PermissionUtils.hasPartialAccess
import com.google.mlkit.genai.prompt.GenerativeModel
import com.google.mlkit.genai.prompt.Generation
import com.google.mlkit.genai.prompt.TextPart
import com.google.mlkit.genai.prompt.generateContentRequest
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
import fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT
import fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE
import fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import javax.inject.Inject
Expand Down Expand Up @@ -91,6 +97,7 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
@Inject
var locationManager: LocationServiceManager? = null

private var model: GenerativeModel? = null
private var isTitleExpanded = true

private var progressDialog: ProgressDialog? = null
Expand Down Expand Up @@ -231,6 +238,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
clearAll()
}
checkStoragePermissions()
if (VERSION.SDK_INT >= VERSION_CODES.O) {
model = Generation.getClient()
}
}

private fun init() {
Expand Down Expand Up @@ -804,6 +814,54 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
}

override fun onNextButtonClicked(index: Int) {
val currentFragment = fragments!![index]
if (currentFragment is UploadMediaDetailFragment) {
if (VERSION.SDK_INT >= VERSION_CODES.O) {
val adapter = (currentFragment as UploadMediaDetailFragment).uploadMediaDetailAdapter
val uploadMediaDetail = adapter.items[0]
val languagesAdapter =
fr.free.nrw.commons.upload.LanguagesAdapter(this, mutableMapOf())
val defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(this)
val defaultLanguageCode = languagesAdapter.getLanguageCode(defaultLocaleIndex)

if (adapter.items.size == 1
&& uploadMediaDetail.languageCode == defaultLanguageCode
&& uploadMediaDetail.languageCode != "fr"
) {
showProgress(true)
isCaptionFrench(uploadMediaDetail.captionText) { isFrench ->
showProgress(false)
if (isFrench) {
showAlertDialog(
this,
getString(R.string.french_caption_title),
getString(R.string.french_caption_check),
getString(R.string.yes),
getString(R.string.no),
{
adapter.setFirstCaptionLanguageToFrench()
proceedToNextStep(index)
},
{
proceedToNextStep(index)
}
)
} else {
proceedToNextStep(index)
}
}
} else {
proceedToNextStep(index)
}
} else {
proceedToNextStep(index)
}
} else {
proceedToNextStep(index)
}
}

private fun proceedToNextStep(index: Int) {
if (index < fragments!!.size - 1) {
// Hide the keyboard before navigating to Media License screen
val isUploadCategoriesFragment = fragments!!.getOrNull(index)?.let {
Expand Down Expand Up @@ -831,6 +889,26 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
}
}

private fun isCaptionFrench(caption: String, callback: (Boolean) -> Unit) {
lifecycleScope.launch {
try {
val prompt = "Output FR if the following expression is in French: $caption"
Timber.tag("LanguageDetection").d("Prompt: %s", prompt)
val genRequest = generateContentRequest(
TextPart(prompt)
) {
temperature = 0f
}
val response = model!!.generateContent(genRequest)
Timber.tag("LanguageDetection").d("Response: %s", response.candidates.first().text)
callback(response.candidates.first().text.toString().trim() == "FR")
} catch (e: Exception) {
Timber.e(e, "Error detecting language")
callback(false)
}
}
}

override fun onPreviousButtonClicked(index: Int) {
if (index != 0) {
binding.vpUpload.setCurrentItem(index - 1, true)
Expand Down Expand Up @@ -897,6 +975,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
uploadCategoriesFragment!!.callback = null
}
onBackPressedCallback.remove()
if (VERSION.SDK_INT >= VERSION_CODES.O) {
model?.close()
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ class UploadMediaDetailAdapter : RecyclerView.Adapter<UploadMediaDetailAdapter.V
updateAddButtonVisibility()
}

fun setFirstCaptionLanguageToFrench() {
if (uploadMediaDetails.size == 1) {
uploadMediaDetails[0].languageCode = "fr"
notifyItemChanged(0)
}
}

inner class ViewHolder(val binding: RowItemDescriptionBinding) :
RecyclerView.ViewHolder(binding.root) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContra
private val keyForShowingAlertDialog = "isNoNetworkAlertDialogShowing"
internal var uploadableFile: UploadableFile? = null
internal var place: Place? = null
private lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter
lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter
var indexOfFragment = 0
var isExpanded = true
var fragmentCallback: UploadMediaDetailFragmentCallback? = null
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -887,4 +887,6 @@ Upload your first media by tapping on the add button.</string>
<string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string>
<string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string>
<string name="nominated_for_deletion_btn">Nominated for Deletion</string>
<string name="french_caption_check">Is this caption in language \"fr\"?</string>
<string name="french_caption_title">French caption</string>
</resources>
5 changes: 5 additions & 0 deletions app/src/test/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="com.google.mlkit.genai.prompt, com.google.mlkit.genai.common, com.google.android.gms.basement, com.google.android.gms:play-services-basement, com.google.android.gms.common" />
</manifest>
Loading