mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 21:33:53 +01:00
Merge branch 'main' into main
This commit is contained in:
commit
83bef3a6b3
41 changed files with 556 additions and 220 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -55,6 +55,10 @@
|
|||
android:theme="@style/LightAppTheme"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:replace="android:appComponentFactory">
|
||||
<activity
|
||||
android:name=".activity.SingleWebViewActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_single_web_view" />
|
||||
<activity
|
||||
android:name=".nearby.WikidataFeedback"
|
||||
android:exported="false" />
|
||||
|
|
|
|||
|
|
@ -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<WebView?>(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
|
||||
public class BackgroundPoolExceptionHandler implements ExceptionHandler {
|
||||
/**
|
||||
* If an exception occurs on a background thread, this handler will crash for debug builds
|
||||
* but fail silently for release builds.
|
||||
* @param t
|
||||
*/
|
||||
@Override
|
||||
public void onException(@NonNull final Throwable t) {
|
||||
//Crash for debug build
|
||||
if (BuildConfig.DEBUG) {
|
||||
Thread thread = new Thread(() -> {
|
||||
throw new RuntimeException(t);
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package fr.free.nrw.commons.concurrency
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
|
||||
|
||||
class BackgroundPoolExceptionHandler : ExceptionHandler {
|
||||
/**
|
||||
* If an exception occurs on a background thread, this handler will crash for debug builds
|
||||
* but fail silently for release builds.
|
||||
* @param t
|
||||
*/
|
||||
override fun onException(t: Throwable) {
|
||||
// Crash for debug build
|
||||
if (BuildConfig.DEBUG) {
|
||||
val thread = Thread {
|
||||
throw RuntimeException(t)
|
||||
}
|
||||
thread.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
class ExceptionAwareThreadPoolExecutor extends ScheduledThreadPoolExecutor {
|
||||
|
||||
private final ExceptionHandler exceptionHandler;
|
||||
|
||||
public ExceptionAwareThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory,
|
||||
ExceptionHandler exceptionHandler) {
|
||||
super(corePoolSize, threadFactory);
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
if (t == null && r instanceof Future<?>) {
|
||||
try {
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone()) future.get();
|
||||
} catch (CancellationException | InterruptedException e) {
|
||||
//ignore
|
||||
} catch (ExecutionException e) {
|
||||
t = e.getCause() != null ? e.getCause() : e;
|
||||
} catch (Exception e) {
|
||||
t = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (t != null) {
|
||||
exceptionHandler.onException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package fr.free.nrw.commons.concurrency
|
||||
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import java.util.concurrent.ThreadFactory
|
||||
|
||||
|
||||
class ExceptionAwareThreadPoolExecutor(
|
||||
corePoolSize: Int,
|
||||
threadFactory: ThreadFactory,
|
||||
private val exceptionHandler: ExceptionHandler?
|
||||
) : ScheduledThreadPoolExecutor(corePoolSize, threadFactory) {
|
||||
|
||||
override fun afterExecute(r: Runnable, t: Throwable?) {
|
||||
super.afterExecute(r, t)
|
||||
var throwable = t
|
||||
|
||||
if (throwable == null && r is Future<*>) {
|
||||
try {
|
||||
if (r.isDone) {
|
||||
r.get()
|
||||
}
|
||||
} catch (e: CancellationException) {
|
||||
// ignore
|
||||
} catch (e: InterruptedException) {
|
||||
// ignore
|
||||
} catch (e: ExecutionException) {
|
||||
throwable = e.cause ?: e
|
||||
} catch (e: Exception) {
|
||||
throwable = e
|
||||
}
|
||||
}
|
||||
|
||||
throwable?.let {
|
||||
exceptionHandler?.onException(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface ExceptionHandler {
|
||||
void onException(@NonNull Throwable t);
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package fr.free.nrw.commons.concurrency
|
||||
|
||||
interface ExceptionHandler {
|
||||
|
||||
fun onException(t: Throwable)
|
||||
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* This class is a thread pool which provides some additional features:
|
||||
* - it sets the thread priority to a value lower than foreground priority by default, or you can
|
||||
* supply your own priority
|
||||
* - it gives you a way to handle exceptions thrown in the thread pool
|
||||
*/
|
||||
|
||||
public class ThreadPoolService implements Executor {
|
||||
private final ScheduledThreadPoolExecutor backgroundPool;
|
||||
|
||||
private ThreadPoolService(final Builder b) {
|
||||
backgroundPool = new ExceptionAwareThreadPoolExecutor(b.poolSize,
|
||||
new ThreadFactory() {
|
||||
int count = 0;
|
||||
@Override
|
||||
public Thread newThread(@NonNull Runnable r) {
|
||||
count++;
|
||||
Thread t = new Thread(r, String.format("%s-%s", b.name, count));
|
||||
//If the priority is specified out of range, we set the thread priority to Thread.MIN_PRIORITY
|
||||
//It's done prevent IllegalArgumentException and to prevent setting of improper high priority for a less priority task
|
||||
t.setPriority(b.priority > Thread.MAX_PRIORITY || b.priority < Thread.MIN_PRIORITY ?
|
||||
Thread.MIN_PRIORITY : b.priority);
|
||||
return t;
|
||||
}
|
||||
}, b.exceptionHandler);
|
||||
}
|
||||
|
||||
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long time, TimeUnit timeUnit) {
|
||||
return backgroundPool.schedule(callable, time, timeUnit);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> schedule(Runnable runnable) {
|
||||
return schedule(runnable, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> schedule(Runnable runnable, long time, TimeUnit timeUnit) {
|
||||
return backgroundPool.schedule(runnable, time, timeUnit);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> scheduleAtFixedRate(final Runnable task, long initialDelay,
|
||||
long period, final TimeUnit timeUnit) {
|
||||
return backgroundPool.scheduleAtFixedRate(task, initialDelay, period, timeUnit);
|
||||
}
|
||||
|
||||
public ScheduledThreadPoolExecutor executor() {
|
||||
return backgroundPool;
|
||||
}
|
||||
|
||||
public void shutdown(){
|
||||
backgroundPool.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
backgroundPool.execute(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link ThreadPoolService}
|
||||
*/
|
||||
public static class Builder {
|
||||
//Required
|
||||
private final String name;
|
||||
|
||||
//Optional
|
||||
private int poolSize = 1;
|
||||
private int priority = Thread.MIN_PRIORITY;
|
||||
private ExceptionHandler exceptionHandler = null;
|
||||
|
||||
/**
|
||||
* @param name the name of the threads in the service. if there are N threads,
|
||||
* the thread names will be like name-1, name-2, name-3,...,name-N
|
||||
*/
|
||||
public Builder(@NonNull String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param poolSize the number of threads to keep in the pool
|
||||
* @throws IllegalArgumentException if size of pool <=0
|
||||
*/
|
||||
public Builder setPoolSize(int poolSize) throws IllegalArgumentException {
|
||||
if (poolSize <= 0) {
|
||||
throw new IllegalArgumentException("Pool size must be grater than 0");
|
||||
}
|
||||
this.poolSize = poolSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param priority Priority of the threads in the service. You can supply a constant from
|
||||
* {@link java.lang.Thread} or
|
||||
* specify your own priority in the range 1(MIN_PRIORITY) to 10(MAX_PRIORITY)
|
||||
* By default, the priority is set to {@link java.lang.Thread#MIN_PRIORITY}
|
||||
*/
|
||||
public Builder setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param handler The handler to use to handle exceptions in the service
|
||||
*/
|
||||
public Builder setExceptionHandler(ExceptionHandler handler) {
|
||||
this.exceptionHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolService build() {
|
||||
return new ThreadPoolService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
package fr.free.nrw.commons.concurrency
|
||||
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import java.util.concurrent.ThreadFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
/**
|
||||
* This class is a thread pool which provides some additional features:
|
||||
* - it sets the thread priority to a value lower than foreground priority by default, or you can
|
||||
* supply your own priority
|
||||
* - it gives you a way to handle exceptions thrown in the thread pool
|
||||
*/
|
||||
class ThreadPoolService private constructor(builder: Builder) : Executor {
|
||||
private val backgroundPool: ScheduledThreadPoolExecutor = ExceptionAwareThreadPoolExecutor(
|
||||
builder.poolSize,
|
||||
object : ThreadFactory {
|
||||
private var count = 0
|
||||
override fun newThread(r: Runnable): Thread {
|
||||
count++
|
||||
val t = Thread(r, "${builder.name}-$count")
|
||||
// If the priority is specified out of range, we set the thread priority to
|
||||
// Thread.MIN_PRIORITY
|
||||
// It's done to prevent IllegalArgumentException and to prevent setting of
|
||||
// improper high priority for a less priority task
|
||||
t.priority =
|
||||
if (
|
||||
builder.priority > Thread.MAX_PRIORITY
|
||||
||
|
||||
builder.priority < Thread.MIN_PRIORITY
|
||||
) {
|
||||
Thread.MIN_PRIORITY
|
||||
} else {
|
||||
builder.priority
|
||||
}
|
||||
return t
|
||||
}
|
||||
},
|
||||
builder.exceptionHandler
|
||||
)
|
||||
|
||||
fun <V> schedule(callable: Callable<V>, time: Long, timeUnit: TimeUnit): ScheduledFuture<V> {
|
||||
return backgroundPool.schedule(callable, time, timeUnit)
|
||||
}
|
||||
|
||||
fun schedule(runnable: Runnable): ScheduledFuture<*> {
|
||||
return schedule(runnable, 0, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
fun schedule(runnable: Runnable, time: Long, timeUnit: TimeUnit): ScheduledFuture<*> {
|
||||
return backgroundPool.schedule(runnable, time, timeUnit)
|
||||
}
|
||||
|
||||
fun scheduleAtFixedRate(
|
||||
task: Runnable,
|
||||
initialDelay: Long,
|
||||
period: Long,
|
||||
timeUnit: TimeUnit
|
||||
): ScheduledFuture<*> {
|
||||
return backgroundPool.scheduleWithFixedDelay(task, initialDelay, period, timeUnit)
|
||||
}
|
||||
|
||||
fun executor(): ScheduledThreadPoolExecutor {
|
||||
return backgroundPool
|
||||
}
|
||||
|
||||
fun shutdown() {
|
||||
backgroundPool.shutdown()
|
||||
}
|
||||
|
||||
override fun execute(command: Runnable) {
|
||||
backgroundPool.execute(command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for [ThreadPoolService]
|
||||
*/
|
||||
class Builder(val name: String) {
|
||||
var poolSize: Int = 1
|
||||
var priority: Int = Thread.MIN_PRIORITY
|
||||
var exceptionHandler: ExceptionHandler? = null
|
||||
|
||||
/**
|
||||
* @param poolSize the number of threads to keep in the pool
|
||||
* @throws IllegalArgumentException if size of pool <= 0
|
||||
*/
|
||||
fun setPoolSize(poolSize: Int): Builder {
|
||||
if (poolSize <= 0) {
|
||||
throw IllegalArgumentException("Pool size must be greater than 0")
|
||||
}
|
||||
this.poolSize = poolSize
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param priority Priority of the threads in the service. You can supply a constant from
|
||||
* [java.lang.Thread] or
|
||||
* specify your own priority in the range 1(MIN_PRIORITY)
|
||||
* to 10(MAX_PRIORITY)
|
||||
* By default, the priority is set to [java.lang.Thread.MIN_PRIORITY]
|
||||
*/
|
||||
fun setPriority(priority: Int): Builder {
|
||||
this.priority = priority
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param handler The handler to use to handle exceptions in the service
|
||||
*/
|
||||
fun setExceptionHandler(handler: ExceptionHandler): Builder {
|
||||
exceptionHandler = handler
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): ThreadPoolService {
|
||||
return ThreadPoolService(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
package fr.free.nrw.commons.settings
|
||||
|
||||
object Prefs {
|
||||
const val GLOBAL_PREFS = "fr.free.nrw.commons.preferences"
|
||||
|
||||
const val TRACKING_ENABLED = "eventLogging"
|
||||
const val DEFAULT_LICENSE = "defaultLicense"
|
||||
const val UPLOADS_SHOWING = "uploadsShowing"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import fr.free.nrw.commons.theme.BaseActivity
|
|||
class SettingsActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivitySettingsBinding
|
||||
// private var settingsDelegate: AppCompatDelegate? = null
|
||||
|
||||
/**
|
||||
* to be called when the activity starts
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package fr.free.nrw.commons.settings
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
|
|
@ -11,7 +10,6 @@ import android.net.Uri
|
|||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.Button
|
||||
|
|
@ -21,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
|
||||
|
|
@ -36,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
|
||||
|
|
@ -50,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
|
||||
|
|
@ -73,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
|
||||
|
|
@ -81,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<Array<String>>
|
||||
private val GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"
|
||||
|
||||
|
|
@ -116,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
|
||||
)
|
||||
|
|
@ -131,7 +154,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
inAppCameraLocationPref?.setOnPreferenceChangeListener { _, newValue ->
|
||||
val isInAppCameraLocationTurnedOn = newValue as Boolean
|
||||
if (isInAppCameraLocationTurnedOn) {
|
||||
createDialogsAndHandleLocationPermissions(requireActivity())
|
||||
createDialogsAndHandleLocationPermissions()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -256,7 +279,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
*
|
||||
* @param activity
|
||||
*/
|
||||
private fun createDialogsAndHandleLocationPermissions(activity: Activity) {
|
||||
private fun createDialogsAndHandleLocationPermissions() {
|
||||
inAppCameraLocationPermissionLauncher.launch(arrayOf(permission.ACCESS_FINE_LOCATION))
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +307,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
return object : PreferenceGroupAdapter(preferenceScreen) {
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
|
||||
super.onBindViewHolder(holder, position)
|
||||
val preference = getItem(position)
|
||||
val iconFrame: View? = holder.itemView.findViewById(R.id.icon_frame)
|
||||
iconFrame?.visibility = View.GONE
|
||||
}
|
||||
|
|
@ -487,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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -13,6 +16,7 @@ import androidx.paging.PagedListAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.facebook.imagepipeline.request.ImageRequest
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import java.io.File
|
||||
|
|
@ -51,6 +55,24 @@ class FailedUploadsAdapter(
|
|||
position: Int,
|
||||
) {
|
||||
val item: Contribution? = getItem(position)
|
||||
val itemView = holder.itemView
|
||||
val clipboardManager =
|
||||
itemView.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
itemView.setOnLongClickListener {
|
||||
val clip = ClipData.newPlainText(
|
||||
itemView.context.getString(R.string.caption),
|
||||
item?.media?.displayTitle
|
||||
)
|
||||
clipboardManager.setPrimaryClip(clip)
|
||||
Snackbar.make(
|
||||
itemView,
|
||||
itemView.context.getString(R.string.caption_copied_to_clipboard),
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
}
|
||||
|
||||
if (item != null) {
|
||||
holder.titleTextView.setText(item.media.displayTitle)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,9 +49,10 @@ class FileUtilsWrapper @Inject constructor(private val context: Context) {
|
|||
while ((bis.read(buffer).also { size = it }) > 0) {
|
||||
buffers.add(
|
||||
writeToFile(
|
||||
buffer.copyOf(size),
|
||||
buffer,
|
||||
file.name ?: "",
|
||||
getFileExt(file.name)
|
||||
getFileExt(file.name),
|
||||
size
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -67,7 +68,7 @@ class FileUtilsWrapper @Inject constructor(private val context: Context) {
|
|||
* Create a temp file containing the passed byte data.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun writeToFile(data: ByteArray, fileName: String, fileExtension: String): File {
|
||||
private fun writeToFile(data: ByteArray, fileName: String, fileExtension: String, size: Int): File {
|
||||
val file = File.createTempFile(fileName, fileExtension, context.cacheDir)
|
||||
try {
|
||||
if (!file.exists()) {
|
||||
|
|
@ -75,7 +76,7 @@ class FileUtilsWrapper @Inject constructor(private val context: Context) {
|
|||
}
|
||||
|
||||
FileOutputStream(file).use { fos ->
|
||||
fos.write(data)
|
||||
fos.write(data, 0, size)
|
||||
}
|
||||
} catch (throwable: Exception) {
|
||||
Timber.e(throwable, "Failed to create file")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -13,6 +16,7 @@ import androidx.paging.PagedListAdapter
|
|||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.facebook.imagepipeline.request.ImageRequest
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import java.io.File
|
||||
|
|
@ -99,6 +103,22 @@ class PendingUploadsAdapter(
|
|||
|
||||
fun bind(contribution: Contribution) {
|
||||
titleTextView.text = contribution.media.displayTitle
|
||||
val clipboardManager =
|
||||
itemView.context.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
itemView.setOnLongClickListener {
|
||||
val clip = ClipData.newPlainText(
|
||||
itemView.context.getString(R.string.caption),
|
||||
titleTextView.text
|
||||
)
|
||||
clipboardManager.setPrimaryClip(clip)
|
||||
Snackbar.make(
|
||||
itemView,
|
||||
itemView.context.getString(R.string.caption_copied_to_clipboard),
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
}
|
||||
|
||||
val imageSource: String = contribution.localUri.toString()
|
||||
var imageRequest: ImageRequest? = null
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
android:layout_width="@dimen/landscape_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/small_gap">
|
||||
android:layout_marginTop="@dimen/login_gap">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="@dimen/landscape_width"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fillViewport="true"
|
||||
android:orientation="vertical">
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="center_vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -875,4 +875,6 @@
|
|||
<string name="usages_on_commons_heading">كومنز</string>
|
||||
<string name="usages_on_other_wikis_heading">مواقع ويكي أخرى</string>
|
||||
<string name="file_usages_container_heading">حالات استخدام الملف</string>
|
||||
<string name="caption">الشرح</string>
|
||||
<string name="caption_copied_to_clipboard">تم نسخ التسمية التوضيحية إلى الحافظة</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@
|
|||
<string name="login_success">Uğurlu giriş!</string>
|
||||
<string name="login_failed">Giriş baş tutmadı!</string>
|
||||
<string name="upload_failed">Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin.</string>
|
||||
<string name="retry_limit_reached">Maksimum təkrar cəhd limitinə çatdınız! Yükləməni ləğv edin və yenidən cəhd edin</string>
|
||||
<string name="unrestricted_battery_mode">Batareya optimallaşdırılmasını söndürmək?</string>
|
||||
<string name="retry_limit_reached">Maksimum təkrar cəhd limitinə çatdınız! Zəhmət olmasa, yükləməni ləğv edin və yenidən cəhd edin</string>
|
||||
<string name="unrestricted_battery_mode">Batareya optimallaşdırılması söndürülsün?</string>
|
||||
<string name="authentication_failed" fuzzy="true">Doğrulama alınmadı, xahiş edirəm yenidən daxil olun</string>
|
||||
<string name="uploading_started">Yükləmə başladı!</string>
|
||||
<string name="upload_completed_notification_title">%1$s yükləndi!</string>
|
||||
|
|
|
|||
|
|
@ -529,4 +529,6 @@
|
|||
<string name="uploads">আপলোড</string>
|
||||
<string name="pending">অমীমাংসিত</string>
|
||||
<string name="failed">ব্যর্থ হয়েছে</string>
|
||||
<string name="caption">ক্যাপশন</string>
|
||||
<string name="caption_copied_to_clipboard">ক্যাপশন ক্লিপবোর্ডে অনুলিপি করা হয়েছে</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -814,4 +814,6 @@
|
|||
<string name="usages_on_other_wikis_heading">Andre wikier</string>
|
||||
<string name="bullet_point">•</string>
|
||||
<string name="file_usages_container_heading">Filanvendelser</string>
|
||||
<string name="caption">Billedtekst</string>
|
||||
<string name="caption_copied_to_clipboard">Billedtekst kopieret til udklipsholder</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
* Orikrin1998
|
||||
* Paraboule
|
||||
* Patrick Star
|
||||
* Pols12
|
||||
* Robins7
|
||||
* Sherbrooke
|
||||
* SleaY
|
||||
|
|
@ -852,4 +853,6 @@
|
|||
<string name="usages_on_other_wikis_heading">Autres wikis</string>
|
||||
<string name="bullet_point">•</string>
|
||||
<string name="file_usages_container_heading">Utilisations du fichier</string>
|
||||
<string name="caption">Légende</string>
|
||||
<string name="caption_copied_to_clipboard">Légende copiée dans le presse-papier</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -358,4 +358,5 @@
|
|||
<string name="custom_selector_delete_folder">फ़ोल्डर हटाएँ</string>
|
||||
<string name="custom_selector_delete">हटाएँ</string>
|
||||
<string name="custom_selector_cancel">रद्द करें</string>
|
||||
<string name="caption">कैप्शन</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -100,6 +100,8 @@
|
|||
<string name="menu_from_camera">Prender photo</string>
|
||||
<string name="menu_nearby">A proximitate</string>
|
||||
<string name="provider_contributions">Mi incargamentos</string>
|
||||
<string name="menu_copy_link">Copiar ligamine</string>
|
||||
<string name="menu_link_copied">Le ligamine ha essite copiate al area de transferentia</string>
|
||||
<string name="menu_share">Condivider</string>
|
||||
<string name="menu_view_file_page">Visitar le pagina del file</string>
|
||||
<string name="share_title_hint">Legenda (obligatori)</string>
|
||||
|
|
@ -118,6 +120,7 @@
|
|||
<string name="categories_search_text_hint">Cercar categorias</string>
|
||||
<string name="depicts_search_text_hint">Cerca elementos que tu file representa (montania, Taj Mahal, etc.)</string>
|
||||
<string name="menu_save_categories">Salveguardar</string>
|
||||
<string name="menu_overflow_desc">Menu de disbordamento</string>
|
||||
<string name="refresh_button">Refrescar</string>
|
||||
<string name="display_list_button">Listar</string>
|
||||
<string name="contributions_subtitle_zero">(Nihil incargate ancora)</string>
|
||||
|
|
@ -269,6 +272,7 @@
|
|||
<string name="copy_wikicode">Copiar le wikitexto al area de transferentia</string>
|
||||
<string name="wikicode_copied">Le wikitexto ha essite copiate al area de transferentia</string>
|
||||
<string name="nearby_location_not_available">“A proximitate” poterea non functionar perque le localisation non es disponibile.</string>
|
||||
<string name="nearby_showing_pins_offline">Internet indisponibile. Appare solmente le locos in cache.</string>
|
||||
<string name="upload_location_access_denied">Le accesso al localisation ha essite refusate. Per favor indica tu localisation manualmente pro usar iste function.</string>
|
||||
<string name="location_permission_rationale_nearby">Permission necessari pro monstrar un lista de locos a proximitate</string>
|
||||
<string name="location_permission_rationale_explore">Permission necessari pro monstrar un lista de imagines a proximitate</string>
|
||||
|
|
@ -352,11 +356,13 @@
|
|||
<string name="delete">Deler</string>
|
||||
<string name="Achievements">Realisationes</string>
|
||||
<string name="Profile">Profilo</string>
|
||||
<string name="badges">Insignias</string>
|
||||
<string name="statistics">Statisticas</string>
|
||||
<string name="statistics_thanks">Regratiamentos recipite</string>
|
||||
<string name="statistics_featured">Imagines eminente</string>
|
||||
<string name="statistics_wikidata_edits">Imagines via “Locos a proximitate”</string>
|
||||
<string name="level" fuzzy="true">Nivello</string>
|
||||
<string name="level">Nivello %d</string>
|
||||
<string name="profileLevel">%s (Nivello %s)</string>
|
||||
<string name="images_uploaded">Imagines incargate</string>
|
||||
<string name="image_reverts">Imagines non revertite</string>
|
||||
<string name="images_used_by_wiki">Imagines usate</string>
|
||||
|
|
@ -388,6 +394,7 @@
|
|||
<string name="map_application_missing">Necun application cartographic compatibile pote esser trovate sur tu apparato. Per favor installa un application cartographic pro usar iste function.</string>
|
||||
<string name="title_page_bookmarks_pictures">Imagines</string>
|
||||
<string name="title_page_bookmarks_locations">Locos</string>
|
||||
<string name="title_page_bookmarks_categories">Categorias</string>
|
||||
<string name="menu_bookmark">Adder al/Remover del marcapaginas</string>
|
||||
<string name="provider_bookmarks">Marcapaginas</string>
|
||||
<string name="bookmark_empty">Tu non ha addite alcun marcapagina</string>
|
||||
|
|
@ -469,6 +476,7 @@
|
|||
<string name="no_notification">Tu non ha notificationes non legite</string>
|
||||
<string name="no_read_notification">Tu non ha notificationes legite</string>
|
||||
<string name="share_logs_using">Condivider registros usante</string>
|
||||
<string name="check_your_email_inbox">Consulta tu cassa de entrata</string>
|
||||
<string name="menu_option_read">Vider legites</string>
|
||||
<string name="menu_option_unread">Vider non legites</string>
|
||||
<string name="error_occurred_in_picking_images">Un error ha occurrite durante le selection de imagines</string>
|
||||
|
|
@ -776,4 +784,21 @@
|
|||
<string name="pending">Pendente</string>
|
||||
<string name="failed">Fallite</string>
|
||||
<string name="could_not_load_place_data">Non poteva cargar le datos del loco</string>
|
||||
<string name="custom_selector_delete_folder">Deler dossier</string>
|
||||
<string name="custom_selector_confirm_deletion_title">Confirmar deletion</string>
|
||||
<string name="custom_selector_confirm_deletion_message">Es tu secur de voler deler le dossier %1$s que contine %2$d objectos?</string>
|
||||
<string name="custom_selector_delete">Deler</string>
|
||||
<string name="custom_selector_cancel">Cancellar</string>
|
||||
<string name="custom_selector_folder_deleted_success">Le dossier %1$s ha essite delite</string>
|
||||
<string name="custom_selector_folder_deleted_failure">Le dossier %1$s non ha potite esser delite</string>
|
||||
<string name="custom_selector_error_trashing_folder_contents">Error durante le elimination del contento del dossier: %1$s</string>
|
||||
<string name="custom_selector_folder_not_found_error">Non poteva recuperar le percurso al dossier pro le “bucket ID” %1$d</string>
|
||||
<string name="red_pin">Iste loco non ha ancora un photo. Proque non prender un?</string>
|
||||
<string name="green_pin">Iste loco ja ha un photo.</string>
|
||||
<string name="grey_pin">Ora se verifica si iste loco ha un photo.</string>
|
||||
<string name="error_while_loading">Error durante le cargamento</string>
|
||||
<string name="no_usages_found">Necun uso trovate</string>
|
||||
<string name="usages_on_commons_heading">Commons</string>
|
||||
<string name="usages_on_other_wikis_heading">Altere wikis</string>
|
||||
<string name="file_usages_container_heading">Usos del file</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -773,4 +773,6 @@
|
|||
<string name="usages_on_commons_heading">Commons</string>
|
||||
<string name="usages_on_other_wikis_heading">Altri wiki</string>
|
||||
<string name="file_usages_container_heading">Utilizzi del file</string>
|
||||
<string name="caption">Didascalia</string>
|
||||
<string name="caption_copied_to_clipboard">Didascalia copiata negli appunti</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Authors:
|
||||
* GilPe
|
||||
* Les Meloures
|
||||
* Robby
|
||||
* Soued031
|
||||
|
|
@ -532,4 +533,6 @@
|
|||
<string name="error_while_loading">Feeler beim Lueden</string>
|
||||
<string name="usages_on_commons_heading">Commons</string>
|
||||
<string name="usages_on_other_wikis_heading">Aner Wikien</string>
|
||||
<string name="caption">Beschrëftung</string>
|
||||
<string name="caption_copied_to_clipboard">Text an den Tëschespäicher kopéiert</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -810,4 +810,6 @@
|
|||
<string name="usages_on_other_wikis_heading">Други викија</string>
|
||||
<string name="bullet_point">•</string>
|
||||
<string name="file_usages_container_heading">Употреби на податотеката</string>
|
||||
<string name="caption">Толкување</string>
|
||||
<string name="caption_copied_to_clipboard">Толкувањето е ставено во меѓускладот</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Authors:
|
||||
* A67-A67
|
||||
* ABPMAB
|
||||
* Bouman4
|
||||
* Ciell
|
||||
* Dutchy45
|
||||
|
|
@ -299,6 +300,7 @@
|
|||
<string name="copy_wikicode">Kopieer de wikitekst naar het klembord</string>
|
||||
<string name="wikicode_copied">De wikitekst is gekopieerd naar het klembord</string>
|
||||
<string name="nearby_location_not_available">In de buurt werkt mogelijk niet naar behoren. Locatie niet beschikbaar.</string>
|
||||
<string name="nearby_showing_pins_offline">Internet niet beschikbaar. Alleen gecachte plaatsen worden getoond.</string>
|
||||
<string name="upload_location_access_denied">Locatietoegang geweigerd. Stel uw locatie handmatig in om deze functie te gebruiken.</string>
|
||||
<string name="location_permission_rationale_nearby">Toestemming vereist om een lijst van nabije plaatsen weer te geven</string>
|
||||
<string name="location_permission_rationale_explore">Toestemming vereist om een lijst van nabije afbeeldingen weer te geven</string>
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@
|
|||
<string name="license_name_cc_by_4_0">CC BY 4.0</string>
|
||||
<string name="tutorial_1_text">ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਜ਼ਿਆਦਾਤਰ ਉਹ ਤਸਵੀਰਾਂ ਦਾ ਭੰਡਾਰ ਹੈ ਜੋ ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।</string>
|
||||
<string name="tutorial_1_subtext">ਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਵਿਸ਼ਵ ਦੇ ਬਾਕੀ ਲੋਕਾਂ ਨੂੰ ਸਿੱਖਿਅਤ ਕਰਨ ਲਈ ਸਹਾਈ ਹਨ!</string>
|
||||
<string name="tutorial_2_text">ਕਿਰਪਾ ਕਰਕੇ ਉਹ ਤਸਵੀਰਾਂ ਅਪਲੋਡ ਕਰੋ ਜੋ ਤੁਹਾਡੇ ਦੁਆਰਾ ਲਈਆਂ ਗਈਆਂ ਹਨ ਜਾਂ ਬਣਾਈਆਂ ਗਈਆਂ ਹਨ:</string>
|
||||
<string name="tutorial_2_text">ਕਿਰਪਾ ਕਰਕੇ ਉਹ ਤਸਵੀਰਾਂ ਨੂੰ ਚੜ੍ਹਾਉ ਜੋ ਤੁਹਾਡੇ ਵੱਲੋਂ ਲਈਆਂ ਗਈਆਂ ਹਨ ਜਾਂ ਬਣਾਈਆਂ ਗਈਆਂ ਹਨ:</string>
|
||||
<string name="tutorial_3_text">ਕਿਰਪਾ ਕਰਕੇ ਅਪਲੋਡ ਨਾ ਕਰੋ:</string>
|
||||
<string name="tutorial_4_text">ਉਦਾਹਰਣ ਵਜੋਂ ਇਹ ਅਪਲੋਡ:</string>
|
||||
<string name="welcome_wikipedia_text">ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਯੋਗਦਾਨ ਪਾਓ। ਵਿਕੀਪੀਡੀਆ ਲੇਖਾਂ ਨੂੰ ਸੁਰਜੀਤ ਕਰ ਦਿਓ!</string>
|
||||
|
|
@ -160,7 +160,7 @@
|
|||
<string name="navigation_item_upload">ਚੜ੍ਹਾਉ</string>
|
||||
<string name="navigation_item_nearby">ਨੇੜੇ-ਤੇੜੇ</string>
|
||||
<string name="navigation_item_about">ਬਾਰੇ</string>
|
||||
<string name="navigation_item_settings">ਸੈਟਿੰਗਾਂ</string>
|
||||
<string name="navigation_item_settings">ਤਰਜੀਹਾਂ</string>
|
||||
<string name="navigation_item_feedback">ਸੁਝਾਅ</string>
|
||||
<string name="navigation_item_logout">ਬਾਹਰ ਆਉ</string>
|
||||
<string name="navigation_item_info">ਸਿਖਲਾਈ</string>
|
||||
|
|
|
|||
|
|
@ -806,4 +806,6 @@
|
|||
<string name="usages_on_other_wikis_heading">Àutre wiki</string>
|
||||
<string name="bullet_point">•</string>
|
||||
<string name="file_usages_container_heading">Usagi dl\'archivi</string>
|
||||
<string name="caption">Legenda</string>
|
||||
<string name="caption_copied_to_clipboard">Legenda copià an sla taulëtta</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@
|
|||
<string name="welcome_help_button_text">Lassedieđut</string>
|
||||
<string name="detail_panel_cats_label">Kategoriijat</string>
|
||||
<string name="detail_panel_cats_loading">Luđeme...</string>
|
||||
<string name="detail_panel_cats_none">Ii guhtege válljejuvvon</string>
|
||||
<string name="detail_panel_cats_none">Ii oktage válljejuvvon</string>
|
||||
<string name="detail_caption_empty">Ii leat govvateaksta</string>
|
||||
<string name="detail_description_empty">Ii gávdno govvádus</string>
|
||||
<string name="detail_discussion_empty">Ii digaštallojuvvo</string>
|
||||
|
|
@ -217,6 +217,9 @@
|
|||
<string name="next">Čuovvovaš</string>
|
||||
<string name="title_page_bookmarks_pictures">Govat</string>
|
||||
<string name="title_page_bookmarks_locations">Sajit</string>
|
||||
<string name="title_page_bookmarks_categories">Kategoriijat</string>
|
||||
<string name="provider_bookmarks">Girjemearkkat</string>
|
||||
<string name="provider_bookmarks_location">Girjemearkkat</string>
|
||||
<string name="no_categories_selected">Ii oktage kategoriija leat válljejuvvon</string>
|
||||
<string name="no_depictions_selected">Ii oktage govvádus leat válljejuvvon</string>
|
||||
<string name="back_button_warning">Gaskkalduhte fiilla vurkema</string>
|
||||
|
|
@ -232,9 +235,11 @@
|
|||
<string name="caption_edit_helper_edit_message_else">Govvateavstta lasiheapmi ii lihkostuvvan.</string>
|
||||
<string name="description_activity_title">Rievdat govvádusaid ja govvateavsttaid</string>
|
||||
<string name="nearby_search_hint">Šaldi, musea, hotealla jna.</string>
|
||||
<string name="title_app_shortcut_bookmark">Girjemearkkat</string>
|
||||
<string name="load_more">Luđe lasi</string>
|
||||
<string name="add_picture_to_wikipedia_article_title">Lasit gova Wikipediai</string>
|
||||
<string name="resume">joatkke</string>
|
||||
<string name="bookmarks">Girjemearkkat</string>
|
||||
<string name="leaderboard_column_user">Geavaheaddji</string>
|
||||
<string name="cancelling_upload">Fiilla vurkema gaskkalduhttomin…</string>
|
||||
<string name="license_step_title">Medialiseansa</string>
|
||||
|
|
|
|||
|
|
@ -277,4 +277,5 @@
|
|||
<string name="custom_selector_delete">مٹاؤ</string>
|
||||
<string name="custom_selector_cancel">منسوخ</string>
|
||||
<string name="usages_on_commons_heading">کامنز</string>
|
||||
<string name="caption">عنوان</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -743,4 +743,6 @@
|
|||
<string name="failed">Није успело</string>
|
||||
<string name="green_pin">Ово место већ има слику</string>
|
||||
<string name="grey_pin">Проверавање да ли ово место има слику.</string>
|
||||
<string name="caption">Поднапис</string>
|
||||
<string name="caption_copied_to_clipboard">Поднапис копиран</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@
|
|||
<string name="menu_from_camera">拍照</string>
|
||||
<string name="menu_nearby">附近</string>
|
||||
<string name="provider_contributions">我的上传</string>
|
||||
<string name="menu_copy_link">复制链接</string>
|
||||
<string name="menu_link_copied">链接已复制到剪贴板。</string>
|
||||
<string name="menu_share">分享</string>
|
||||
<string name="menu_view_file_page">查看文件页面</string>
|
||||
<string name="share_title_hint">说明(必需)</string>
|
||||
|
|
@ -329,6 +331,7 @@
|
|||
<string name="copy_wikicode">复制wikitext到剪贴板</string>
|
||||
<string name="wikicode_copied">wikitext已经复制到剪贴板</string>
|
||||
<string name="nearby_location_not_available">附近可能无法正常工作,位置不可用。</string>
|
||||
<string name="nearby_showing_pins_offline">互联网不可用。仅显示缓存的位置。</string>
|
||||
<string name="upload_location_access_denied">位置权限被拒绝。请手动设置您的位置以使用此功能。</string>
|
||||
<string name="location_permission_rationale_nearby">需要权限以显示附近地点列表</string>
|
||||
<string name="location_permission_rationale_explore">需要权限以显示附近图片列表</string>
|
||||
|
|
@ -412,11 +415,13 @@
|
|||
<string name="delete">删除</string>
|
||||
<string name="Achievements">成就</string>
|
||||
<string name="Profile">个人资料</string>
|
||||
<string name="badges">徽章</string>
|
||||
<string name="statistics">统计</string>
|
||||
<string name="statistics_thanks">已收到感谢</string>
|
||||
<string name="statistics_featured">特色图片</string>
|
||||
<string name="statistics_wikidata_edits">来自“附近地点”的图片</string>
|
||||
<string name="level" fuzzy="true">等级</string>
|
||||
<string name="level">等级%d</string>
|
||||
<string name="profileLevel">%s(%s級)</string>
|
||||
<string name="images_uploaded">已上传图片</string>
|
||||
<string name="image_reverts">未还原图片</string>
|
||||
<string name="images_used_by_wiki">使用过的图片</string>
|
||||
|
|
@ -448,6 +453,7 @@
|
|||
<string name="map_application_missing">您的设备上无法找到兼容的地图应用程序。请安装地图应用程序来使用此功能。</string>
|
||||
<string name="title_page_bookmarks_pictures">图片</string>
|
||||
<string name="title_page_bookmarks_locations">方位</string>
|
||||
<string name="title_page_bookmarks_categories">分类</string>
|
||||
<string name="menu_bookmark">添加/删除书签</string>
|
||||
<string name="provider_bookmarks">书签</string>
|
||||
<string name="bookmark_empty">您还未加入任何书签</string>
|
||||
|
|
@ -850,4 +856,8 @@
|
|||
<string name="red_pin">这个地点还没有照片,快去拍一张吧!</string>
|
||||
<string name="green_pin">这个地点已经有照片了。</string>
|
||||
<string name="grey_pin">现在检查这个地点是否有照片。</string>
|
||||
<string name="error_while_loading">加载时出错</string>
|
||||
<string name="usages_on_commons_heading">维基共享资源</string>
|
||||
<string name="usages_on_other_wikis_heading">其它wiki</string>
|
||||
<string name="file_usages_container_heading">文件用途</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
<dimen name="gigantic_gap">64dp</dimen>
|
||||
<dimen name="standard_gap">16dp</dimen>
|
||||
<dimen name="small_gap">8dp</dimen>
|
||||
<dimen name="login_gap">20dp</dimen>
|
||||
<dimen name="small_height">7dp</dimen>
|
||||
<dimen name="tiny_gap">4dp</dimen>
|
||||
<dimen name="very_tiny_gap">2dp</dimen>
|
||||
|
|
|
|||
|
|
@ -860,5 +860,12 @@ Upload your first media by tapping on the add button.</string>
|
|||
<string name="usages_on_other_wikis_heading">Other wikis</string>
|
||||
<string name="bullet_point">•</string>
|
||||
<string name="file_usages_container_heading">File usages</string>
|
||||
<string name="title_activity_single_web_view">SingleWebViewActivity</string>
|
||||
<string name="account">Account</string>
|
||||
<string name="vanish_account">Vanish Account</string>
|
||||
<string name="account_vanish_request_confirm_title">Vanish account warning</string>
|
||||
<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>
|
||||
<string name="caption">Caption</string>
|
||||
<string name="caption_copied_to_clipboard">Caption copied to clipboard</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -129,4 +129,14 @@
|
|||
android:title="@string/send_log_file" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/account">
|
||||
|
||||
<Preference
|
||||
android:key="vanishAccount"
|
||||
app:singleLineTitle="false"
|
||||
android:title="@string/vanish_account"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue