Skip to content

Commit 39b513d

Browse files
feat : Account Vanishing (commons-app#6098)
* feat : Account Vanishing * Added Comment for SingleWebViewActivity --------- Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
1 parent 18f599b commit 39b513d

File tree

8 files changed

+234
-1
lines changed

8 files changed

+234
-1
lines changed

app/build.gradle

+4
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ dependencies {
5050
implementation "com.google.android.material:material:1.12.0"
5151
implementation 'com.karumi:dexter:5.0.0'
5252
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
53+
implementation 'androidx.compose.ui:ui-tooling-preview'
54+
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
5355

5456
// Jetpack Compose
5557
def composeBom = platform('androidx.compose:compose-bom:2024.11.00')
@@ -87,6 +89,8 @@ dependencies {
8789
// Dependency injector
8890
implementation "com.google.dagger:dagger-android:$DAGGER_VERSION"
8991
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
92+
debugImplementation 'androidx.compose.ui:ui-tooling'
93+
debugImplementation 'androidx.compose.ui:ui-test-manifest'
9094
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
9195
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
9296
annotationProcessor "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"

app/src/main/AndroidManifest.xml

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@
5555
android:theme="@style/LightAppTheme"
5656
tools:ignore="GoogleAppIndexingWarning"
5757
tools:replace="android:appComponentFactory">
58+
<activity
59+
android:name=".activity.SingleWebViewActivity"
60+
android:exported="false"
61+
android:label="@string/title_activity_single_web_view" />
5862
<activity
5963
android:name=".nearby.WikidataFeedback"
6064
android:exported="false" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package fr.free.nrw.commons.activity
2+
3+
import android.annotation.SuppressLint
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.os.Bundle
7+
import android.webkit.ConsoleMessage
8+
import android.webkit.WebChromeClient
9+
import android.webkit.WebResourceRequest
10+
import android.webkit.WebView
11+
import android.webkit.WebViewClient
12+
import androidx.activity.ComponentActivity
13+
import androidx.activity.compose.setContent
14+
import androidx.activity.enableEdgeToEdge
15+
import androidx.compose.foundation.layout.fillMaxSize
16+
import androidx.compose.foundation.layout.padding
17+
import androidx.compose.material.icons.Icons
18+
import androidx.compose.material.icons.automirrored.filled.ArrowBack
19+
import androidx.compose.material3.ExperimentalMaterial3Api
20+
import androidx.compose.material3.Icon
21+
import androidx.compose.material3.IconButton
22+
import androidx.compose.material3.Scaffold
23+
import androidx.compose.material3.Text
24+
import androidx.compose.material3.TopAppBar
25+
import androidx.compose.runtime.Composable
26+
import androidx.compose.runtime.mutableStateOf
27+
import androidx.compose.runtime.remember
28+
import androidx.compose.ui.Modifier
29+
import androidx.compose.ui.viewinterop.AndroidView
30+
import fr.free.nrw.commons.R
31+
import timber.log.Timber
32+
33+
/**
34+
* SingleWebViewActivity is a reusable activity webView based on a given url(initial url) and
35+
* closes itself when a specified success URL is reached to success url.
36+
*/
37+
class SingleWebViewActivity : ComponentActivity() {
38+
@OptIn(ExperimentalMaterial3Api::class)
39+
override fun onCreate(savedInstanceState: Bundle?) {
40+
super.onCreate(savedInstanceState)
41+
val url = intent.getStringExtra(VANISH_ACCOUNT_URL)
42+
val successUrl = intent.getStringExtra(VANISH_ACCOUNT_SUCCESS_URL)
43+
if (url == null || successUrl == null) {
44+
finish()
45+
return
46+
}
47+
enableEdgeToEdge()
48+
setContent {
49+
Scaffold(
50+
topBar = {
51+
TopAppBar(
52+
modifier = Modifier,
53+
title = { Text(getString(R.string.vanish_account)) },
54+
navigationIcon = {
55+
IconButton(
56+
onClick = {
57+
// Close the WebView Activity if the user taps the back button
58+
finish()
59+
},
60+
) {
61+
Icon(
62+
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
63+
// TODO("Add contentDescription)
64+
contentDescription = ""
65+
)
66+
}
67+
}
68+
)
69+
},
70+
content = {
71+
WebViewComponent(
72+
url = url,
73+
successUrl = successUrl,
74+
onSuccess = {
75+
// TODO Redirect the user to login screen like we do when the user logout's
76+
finish()
77+
},
78+
modifier = Modifier
79+
.fillMaxSize()
80+
.padding(it)
81+
)
82+
}
83+
)
84+
}
85+
}
86+
87+
88+
/**
89+
* @param url The initial URL which we are loading in the WebView.
90+
* @param successUrl The URL that, when reached, triggers the `onSuccess` callback.
91+
* @param onSuccess A callback that is invoked when the current url of webView is successUrl.
92+
* This is used when we want to close when the webView once a success url is hit.
93+
* @param modifier An optional [Modifier] to customize the layout or appearance of the WebView.
94+
*/
95+
@SuppressLint("SetJavaScriptEnabled")
96+
@Composable
97+
private fun WebViewComponent(
98+
url: String,
99+
successUrl: String,
100+
onSuccess: () -> Unit,
101+
modifier: Modifier = Modifier
102+
) {
103+
val webView = remember { mutableStateOf<WebView?>(null) }
104+
AndroidView(
105+
modifier = modifier,
106+
factory = {
107+
WebView(it).apply {
108+
settings.apply {
109+
javaScriptEnabled = true
110+
domStorageEnabled = true
111+
javaScriptCanOpenWindowsAutomatically = true
112+
113+
}
114+
webViewClient = object : WebViewClient() {
115+
override fun shouldOverrideUrlLoading(
116+
view: WebView?,
117+
request: WebResourceRequest?
118+
): Boolean {
119+
120+
request?.url?.let { url ->
121+
Timber.d("URL Loading: $url")
122+
if (url.toString() == successUrl) {
123+
Timber.d("Success URL detected. Closing WebView.")
124+
onSuccess() // Close the activity
125+
return true
126+
}
127+
return false
128+
}
129+
return false
130+
}
131+
132+
override fun onPageFinished(view: WebView?, url: String?) {
133+
super.onPageFinished(view, url)
134+
}
135+
136+
}
137+
138+
webChromeClient = object : WebChromeClient() {
139+
override fun onConsoleMessage(message: ConsoleMessage): Boolean {
140+
Timber.d("Console: ${message.message()} -- From line ${message.lineNumber()} of ${message.sourceId()}")
141+
return true
142+
}
143+
}
144+
145+
loadUrl(url)
146+
}
147+
},
148+
update = {
149+
webView.value = it
150+
}
151+
)
152+
153+
}
154+
155+
companion object {
156+
private const val VANISH_ACCOUNT_URL = "VanishAccountUrl"
157+
private const val VANISH_ACCOUNT_SUCCESS_URL = "vanishAccountSuccessUrl"
158+
159+
/**
160+
* Launch the WebViewActivity with the specified URL and success URL.
161+
* @param context The context from which the activity is launched.
162+
* @param url The initial URL to load in the WebView.
163+
* @param successUrl The URL that triggers the WebView to close when matched.
164+
*/
165+
fun showWebView(
166+
context: Context,
167+
url: String,
168+
successUrl: String
169+
) {
170+
val intent = Intent(
171+
context,
172+
SingleWebViewActivity::class.java
173+
).apply {
174+
putExtra(VANISH_ACCOUNT_URL, url)
175+
putExtra(VANISH_ACCOUNT_SUCCESS_URL, successUrl)
176+
}
177+
context.startActivity(intent)
178+
}
179+
}
180+
}
181+

app/src/main/java/fr/free/nrw/commons/settings/Prefs.kt

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fr.free.nrw.commons.settings
33
object Prefs {
44
const val DEFAULT_LICENSE = "defaultLicense"
55
const val MANAGED_EXIF_TAGS = "managed_exif_tags"
6+
const val VANISHED_ACCOUNT = "vanishAccount"
67
const val DESCRIPTION_LANGUAGE = "languageDescription"
78
const val APP_UI_LANGUAGE = "appUiLanguage"
89
const val KEY_THEME_VALUE = "appThemePref"

app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt

+28
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import android.widget.TextView
1919
import androidx.activity.result.ActivityResultLauncher
2020
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
2121
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
22+
import androidx.appcompat.app.AlertDialog
2223
import androidx.preference.ListPreference
2324
import androidx.preference.MultiSelectListPreference
2425
import androidx.preference.Preference
@@ -34,6 +35,7 @@ import com.karumi.dexter.listener.PermissionRequest
3435
import com.karumi.dexter.listener.multi.MultiplePermissionsListener
3536
import fr.free.nrw.commons.R
3637
import fr.free.nrw.commons.Utils
38+
import fr.free.nrw.commons.activity.SingleWebViewActivity
3739
import fr.free.nrw.commons.campaigns.CampaignView
3840
import fr.free.nrw.commons.contributions.ContributionController
3941
import fr.free.nrw.commons.contributions.MainActivity
@@ -48,6 +50,7 @@ import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
4850
import fr.free.nrw.commons.upload.LanguagesAdapter
4951
import fr.free.nrw.commons.utils.DialogUtil
5052
import fr.free.nrw.commons.utils.PermissionUtils
53+
import fr.free.nrw.commons.utils.StringUtil
5154
import fr.free.nrw.commons.utils.ViewUtil
5255
import java.util.Locale
5356
import javax.inject.Inject
@@ -71,6 +74,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
7174
@Inject
7275
lateinit var locationManager: LocationServiceManager
7376

77+
private var vanishAccountPreference: Preference? = null
7478
private var themeListPreference: ListPreference? = null
7579
private var descriptionLanguageListPreference: Preference? = null
7680
private var appUiLanguageListPreference: Preference? = null
@@ -79,6 +83,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
7983
private var recentLanguagesTextView: TextView? = null
8084
private var separator: View? = null
8185
private var languageHistoryListView: ListView? = null
86+
8287
private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>
8388
private val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"
8489

@@ -114,6 +119,26 @@ class SettingsFragment : PreferenceFragmentCompat() {
114119
themeListPreference = findPreference(Prefs.KEY_THEME_VALUE)
115120
prepareTheme()
116121

122+
vanishAccountPreference = findPreference(Prefs.VANISHED_ACCOUNT)
123+
vanishAccountPreference?.setOnPreferenceClickListener {
124+
AlertDialog.Builder(requireContext())
125+
.setTitle(R.string.account_vanish_request_confirm_title)
126+
.setMessage(StringUtil.fromHtml(getString(R.string.account_vanish_request_confirm)))
127+
.setNegativeButton(R.string.cancel){ dialog,_ ->
128+
dialog.dismiss()
129+
}
130+
.setPositiveButton(R.string.vanish_account) { dialog, _ ->
131+
SingleWebViewActivity.showWebView(
132+
context = requireActivity(),
133+
url = VANISH_ACCOUNT_URL,
134+
successUrl = VANISH_ACCOUNT_SUCCESS_URL
135+
)
136+
dialog.dismiss()
137+
}
138+
.show()
139+
true
140+
}
141+
117142
val multiSelectListPref: MultiSelectListPreference? = findPreference(
118143
Prefs.MANAGED_EXIF_TAGS
119144
)
@@ -484,7 +509,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
484509
editor.apply()
485510
}
486511

512+
@Suppress("LongLine")
487513
companion object {
514+
private const val VANISH_ACCOUNT_URL = "https://meta.m.wikimedia.org/wiki/Special:Contact/accountvanishapps"
515+
private const val VANISH_ACCOUNT_SUCCESS_URL = "https://meta.m.wikimedia.org/wiki/Special:GlobalVanishRequest/vanished"
488516
/**
489517
* Create Locale based on different types of language codes
490518
* @param languageCode

app/src/main/res/values/strings.xml

+5
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,11 @@ Upload your first media by tapping on the add button.</string>
860860
<string name="usages_on_other_wikis_heading">Other wikis</string>
861861
<string name="bullet_point">•</string>
862862
<string name="file_usages_container_heading">File usages</string>
863+
<string name="title_activity_single_web_view">SingleWebViewActivity</string>
864+
<string name="account">Account</string>
865+
<string name="vanish_account">Vanish Account</string>
866+
<string name="account_vanish_request_confirm_title">Vanish account warning</string>
867+
<string name="account_vanish_request_confirm"><![CDATA[Vanishing is a <b>last resort</b> and should <b>only be used when you wish to stop editing forever</b> and also to hide as many of your past associations as possible.<br/><br/>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. <b>Vanishing does not guarantee complete anonymity or remove contributions to the projects</b>.]]></string>
863868
<string name="caption">Caption</string>
864869
<string name="caption_copied_to_clipboard">Caption copied to clipboard</string>
865870

app/src/main/res/xml/preferences.xml

+10
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,14 @@
129129
android:title="@string/send_log_file" />
130130

131131
</PreferenceCategory>
132+
133+
<PreferenceCategory
134+
android:title="@string/account">
135+
136+
<Preference
137+
android:key="vanishAccount"
138+
app:singleLineTitle="false"
139+
android:title="@string/vanish_account"/>
140+
141+
</PreferenceCategory>
132142
</PreferenceScreen>

gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ KOTLIN_VERSION=1.9.22
2121
LEAK_CANARY_VERSION=2.10
2222
DAGGER_VERSION=2.23
2323
ROOM_VERSION=2.6.1
24-
PREFERENCE_VERSION=1.1.0
24+
PREFERENCE_VERSION=1.2.1
2525
CORE_KTX_VERSION=1.9.0
2626
ADAPTER_DELEGATES_VERSION=4.3.0
2727
PAGING_VERSION=2.1.2

0 commit comments

Comments
 (0)