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