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/settings/Prefs.kt b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt
index 05c850a03..1bd60efd7 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt
+++ b/app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.settings
object Prefs {
const val DEFAULT_LICENSE = "defaultLicense"
const val MANAGED_EXIF_TAGS = "managed_exif_tags"
+ const val VANISHED_ACCOUNT = "vanishAccount"
const val DESCRIPTION_LANGUAGE = "languageDescription"
const val APP_UI_LANGUAGE = "appUiLanguage"
const val KEY_THEME_VALUE = "appThemePref"
diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
index 166cb1e97..2f293937c 100644
--- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
+++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt
@@ -19,6 +19,7 @@ import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
+import androidx.appcompat.app.AlertDialog
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
@@ -34,6 +35,7 @@ import com.karumi.dexter.listener.PermissionRequest
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
import fr.free.nrw.commons.R
import fr.free.nrw.commons.Utils
+import fr.free.nrw.commons.activity.SingleWebViewActivity
import fr.free.nrw.commons.campaigns.CampaignView
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.contributions.MainActivity
@@ -48,6 +50,7 @@ import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
import fr.free.nrw.commons.upload.LanguagesAdapter
import fr.free.nrw.commons.utils.DialogUtil
import fr.free.nrw.commons.utils.PermissionUtils
+import fr.free.nrw.commons.utils.StringUtil
import fr.free.nrw.commons.utils.ViewUtil
import java.util.Locale
import javax.inject.Inject
@@ -71,6 +74,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var locationManager: LocationServiceManager
+ private var vanishAccountPreference: Preference? = null
private var themeListPreference: ListPreference? = null
private var descriptionLanguageListPreference: Preference? = null
private var appUiLanguageListPreference: Preference? = null
@@ -79,6 +83,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
private var recentLanguagesTextView: TextView? = null
private var separator: View? = null
private var languageHistoryListView: ListView? = null
+
private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher>
private val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"
@@ -114,6 +119,26 @@ class SettingsFragment : PreferenceFragmentCompat() {
themeListPreference = findPreference(Prefs.KEY_THEME_VALUE)
prepareTheme()
+ vanishAccountPreference = findPreference(Prefs.VANISHED_ACCOUNT)
+ vanishAccountPreference?.setOnPreferenceClickListener {
+ AlertDialog.Builder(requireContext())
+ .setTitle(R.string.account_vanish_request_confirm_title)
+ .setMessage(StringUtil.fromHtml(getString(R.string.account_vanish_request_confirm)))
+ .setNegativeButton(R.string.cancel){ dialog,_ ->
+ dialog.dismiss()
+ }
+ .setPositiveButton(R.string.vanish_account) { dialog, _ ->
+ SingleWebViewActivity.showWebView(
+ context = requireActivity(),
+ url = VANISH_ACCOUNT_URL,
+ successUrl = VANISH_ACCOUNT_SUCCESS_URL
+ )
+ dialog.dismiss()
+ }
+ .show()
+ true
+ }
+
val multiSelectListPref: MultiSelectListPreference? = findPreference(
Prefs.MANAGED_EXIF_TAGS
)
@@ -484,7 +509,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
editor.apply()
}
+ @Suppress("LongLine")
companion object {
+ private const val VANISH_ACCOUNT_URL = "https://meta.m.wikimedia.org/wiki/Special:Contact/accountvanishapps"
+ private const val VANISH_ACCOUNT_SUCCESS_URL = "https://meta.m.wikimedia.org/wiki/Special:GlobalVanishRequest/vanished"
/**
* Create Locale based on different types of language codes
* @param languageCode
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 7f5cbf814..e7504df98 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -860,6 +860,11 @@ Upload your first media by tapping on the add button.
Other wikis
•
File usages
+ SingleWebViewActivity
+ Account
+ Vanish Account
+ Vanish account warning
+ last resort and should only be used when you wish to stop editing forever and also to hide as many of your past associations as possible.
Account deletion on Wikimedia Commons is done by changing your account name to make it so others cannot recognize your contributions in a process called account vanishing. Vanishing does not guarantee complete anonymity or remove contributions to the projects.]]>
Caption
Caption copied to clipboard
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 5d2dabb59..fb3cb0ca1 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -129,4 +129,14 @@
android:title="@string/send_log_file" />
+
+
+
+
+
+
diff --git a/gradle.properties b/gradle.properties
index 0aee97f4e..090289fa6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -21,7 +21,7 @@ KOTLIN_VERSION=1.9.22
LEAK_CANARY_VERSION=2.10
DAGGER_VERSION=2.23
ROOM_VERSION=2.6.1
-PREFERENCE_VERSION=1.1.0
+PREFERENCE_VERSION=1.2.1
CORE_KTX_VERSION=1.9.0
ADAPTER_DELEGATES_VERSION=4.3.0
PAGING_VERSION=2.1.2