diff --git a/app/build.gradle b/app/build.gradle
index 14bf5f3b7..f50a4e6dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -50,6 +50,8 @@ dependencies {
implementation "com.google.android.material:material:1.12.0"
implementation 'com.karumi:dexter:5.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
// Jetpack Compose
def composeBom = platform('androidx.compose:compose-bom:2024.11.00')
@@ -87,6 +89,8 @@ dependencies {
// Dependency injector
implementation "com.google.dagger:dagger-android:$DAGGER_VERSION"
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
annotationProcessor "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ab2edf719..e7c64f929 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,6 +55,10 @@
android:theme="@style/LightAppTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:appComponentFactory">
+
diff --git a/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt b/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt
new file mode 100644
index 000000000..284c84caf
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/activity/SingleWebViewActivity.kt
@@ -0,0 +1,181 @@
+package fr.free.nrw.commons.activity
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.webkit.ConsoleMessage
+import android.webkit.WebChromeClient
+import android.webkit.WebResourceRequest
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+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.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import fr.free.nrw.commons.R
+import timber.log.Timber
+
+/**
+ * SingleWebViewActivity is a reusable activity webView based on a given url(initial url) and
+ * closes itself when a specified success URL is reached to success url.
+ */
+class SingleWebViewActivity : ComponentActivity() {
+ @OptIn(ExperimentalMaterial3Api::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val url = intent.getStringExtra(VANISH_ACCOUNT_URL)
+ val successUrl = intent.getStringExtra(VANISH_ACCOUNT_SUCCESS_URL)
+ if (url == null || successUrl == null) {
+ finish()
+ return
+ }
+ enableEdgeToEdge()
+ setContent {
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ modifier = Modifier,
+ title = { Text(getString(R.string.vanish_account)) },
+ navigationIcon = {
+ IconButton(
+ onClick = {
+ // Close the WebView Activity if the user taps the back button
+ finish()
+ },
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ // TODO("Add contentDescription)
+ contentDescription = ""
+ )
+ }
+ }
+ )
+ },
+ content = {
+ WebViewComponent(
+ url = url,
+ successUrl = successUrl,
+ onSuccess = {
+ // TODO Redirect the user to login screen like we do when the user logout's
+ finish()
+ },
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(it)
+ )
+ }
+ )
+ }
+ }
+
+
+ /**
+ * @param url The initial URL which we are loading in the WebView.
+ * @param successUrl The URL that, when reached, triggers the `onSuccess` callback.
+ * @param onSuccess A callback that is invoked when the current url of webView is successUrl.
+ * This is used when we want to close when the webView once a success url is hit.
+ * @param modifier An optional [Modifier] to customize the layout or appearance of the WebView.
+ */
+ @SuppressLint("SetJavaScriptEnabled")
+ @Composable
+ private fun WebViewComponent(
+ url: String,
+ successUrl: String,
+ onSuccess: () -> Unit,
+ modifier: Modifier = Modifier
+ ) {
+ val webView = remember { mutableStateOf(null) }
+ AndroidView(
+ modifier = modifier,
+ factory = {
+ WebView(it).apply {
+ settings.apply {
+ javaScriptEnabled = true
+ domStorageEnabled = true
+ javaScriptCanOpenWindowsAutomatically = true
+
+ }
+ webViewClient = object : WebViewClient() {
+ override fun shouldOverrideUrlLoading(
+ view: WebView?,
+ request: WebResourceRequest?
+ ): Boolean {
+
+ request?.url?.let { url ->
+ Timber.d("URL Loading: $url")
+ if (url.toString() == successUrl) {
+ Timber.d("Success URL detected. Closing WebView.")
+ onSuccess() // Close the activity
+ return true
+ }
+ return false
+ }
+ return false
+ }
+
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+ }
+
+ }
+
+ webChromeClient = object : WebChromeClient() {
+ override fun onConsoleMessage(message: ConsoleMessage): Boolean {
+ Timber.d("Console: ${message.message()} -- From line ${message.lineNumber()} of ${message.sourceId()}")
+ return true
+ }
+ }
+
+ loadUrl(url)
+ }
+ },
+ update = {
+ webView.value = it
+ }
+ )
+
+ }
+
+ companion object {
+ private const val VANISH_ACCOUNT_URL = "VanishAccountUrl"
+ private const val VANISH_ACCOUNT_SUCCESS_URL = "vanishAccountSuccessUrl"
+
+ /**
+ * Launch the WebViewActivity with the specified URL and success URL.
+ * @param context The context from which the activity is launched.
+ * @param url The initial URL to load in the WebView.
+ * @param successUrl The URL that triggers the WebView to close when matched.
+ */
+ fun showWebView(
+ context: Context,
+ url: String,
+ successUrl: String
+ ) {
+ val intent = Intent(
+ context,
+ SingleWebViewActivity::class.java
+ ).apply {
+ putExtra(VANISH_ACCOUNT_URL, url)
+ putExtra(VANISH_ACCOUNT_SUCCESS_URL, successUrl)
+ }
+ context.startActivity(intent)
+ }
+ }
+}
+
diff --git a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt
index 3095497c3..1bad2e2a5 100644
--- a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt
+++ b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt
@@ -61,16 +61,16 @@ class CoordinateEditHelper @Inject constructor(
/**
* Replaces new coordinates
* @param media to be added
- * @param Latitude to be added
- * @param Longitude to be added
- * @param Accuracy to be added
+ * @param latitude to be added
+ * @param longitude to be added
+ * @param accuracy to be added
* @return Observable
*/
private fun addCoordinates(
media: Media,
- Latitude: String,
- Longitude: String,
- Accuracy: String
+ latitude: String,
+ longitude: String,
+ accuracy: String
): Observable? {
Timber.d("thread is coordinates adding %s", Thread.currentThread().getName())
val summary = "Adding Coordinates"
@@ -83,9 +83,9 @@ class CoordinateEditHelper @Inject constructor(
.blockingGet()
}
- if (Latitude != null) {
- buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
- .append("|").append(Accuracy).append("}}")
+ if (latitude != null) {
+ buffer.append("\n{{Location|").append(latitude).append("|").append(longitude)
+ .append("|").append(accuracy).append("}}")
}
val editedLocation = buffer.toString()
@@ -141,7 +141,7 @@ class CoordinateEditHelper @Inject constructor(
* @param media to be added
* @param latitude to be added
* @param longitude to be added
- * @param Accuracy to be added
+ * @param accuracy to be added
* @param result to be added
* @return boolean
*/
@@ -150,7 +150,7 @@ class CoordinateEditHelper @Inject constructor(
media: Media,
latitude: String,
longitude: String,
- Accuracy: String,
+ accuracy: String,
result: Boolean
): Boolean {
val message: String
@@ -160,7 +160,7 @@ class CoordinateEditHelper @Inject constructor(
media.coordinates = fr.free.nrw.commons.location.LatLng(
latitude.toDouble(),
longitude.toDouble(),
- Accuracy.toFloat()
+ accuracy.toFloat()
)
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title_success)
val coordinatesInMessage = StringBuilder()
diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
index 52b615175..4e2d58bab 100644
--- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
+++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt
@@ -271,7 +271,7 @@ class CustomSelectorActivity :
dialog.setCancelable(false)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setContentView(R.layout.custom_selector_info_dialog)
- (dialog.findViewById(R.id.btn_ok) as Button).setOnClickListener { dialog.dismiss() }
+ (dialog.findViewById