Issue 5811: fixes merge conflicts, replaces used function onActivityResult with an ActivityResultLauncher

This commit is contained in:
why-lab 2024-11-12 16:49:57 +01:00
parent 40bc11ee61
commit 4663c78953
138 changed files with 1602 additions and 1126 deletions

View file

@ -174,7 +174,7 @@ dependencies {
kaptTest "androidx.databinding:databinding-compiler:8.0.2"
kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2"
implementation("io.github.coordinates2country:coordinates2country-android:1.3") { exclude group: 'com.google.android', module: 'android' }
implementation("io.github.coordinates2country:coordinates2country-android:1.8") { exclude group: 'com.google.android', module: 'android' }
//OSMDroid
implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION")
@ -226,7 +226,7 @@ android {
excludes += ['META-INF/androidx.*']
}
resources {
excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro']
excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro', '/META-INF/LICENSE.md', '/META-INF/LICENSE-notice.md']
}
}
@ -380,7 +380,7 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.3.2'
kotlinCompilerExtensionVersion '1.5.8'
}
namespace 'fr.free.nrw.commons'
lint {

View file

@ -105,7 +105,7 @@ class AboutActivityTest {
fun testLaunchTranslate() {
Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click())
val langCode = CommonsApplication.getInstance().languageLookUpTable.codes[0]
val langCode = CommonsApplication.instance.languageLookUpTable!!.codes[0]
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),

View file

@ -17,6 +17,8 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.CoreMatchers.equalTo
@LargeTest
@RunWith(AndroidJUnit4::class)
@ -59,7 +61,7 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.finishTutorialButton))
.perform(ViewActions.click())
assert(activityRule.activity.isDestroyed)
assertThat(activityRule.activity.isDestroyed, equalTo(true))
}
}
@ -69,10 +71,10 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
assert(true)
assertThat(true, equalTo(true))
}
@Test
@ -84,13 +86,13 @@ class WelcomeActivityTest {
.perform(ViewActions.swipeLeft())
.perform(ViewActions.swipeLeft())
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
assert(true)
assertThat(true, equalTo(true))
}
@Test
@ -101,10 +103,10 @@ class WelcomeActivityTest {
if (viewPager.currentItem == 3) {
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
assert(false)
assertThat(true, equalTo(true))
}
}
}
@ -119,7 +121,7 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.finishTutorialButton))
.perform(ViewActions.click())
assert(activityRule.activity.isDestroyed)
assertThat(activityRule.activity.isDestroyed, equalTo(true))
}
}
}

View file

@ -13,7 +13,9 @@
android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Permission needed up to Android 5.1, see https://github.com/commons-app/apps-android-commons/pull/5863 -->
<uses-permission android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
@ -97,7 +99,6 @@
android:exported="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND" />
@ -120,7 +121,7 @@
android:name=".contributions.MainActivity"
android:configChanges="screenSize|keyboard|orientation"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" />
/>
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" />

View file

@ -46,7 +46,7 @@ class BaseMarker {
val drawable: Drawable = context.resources.getDrawable(drawableResId)
icon =
if (drawable is BitmapDrawable) {
(drawable as BitmapDrawable).bitmap
drawable.bitmap
} else {
val bitmap =
Bitmap.createBitmap(

View file

@ -1,80 +1,64 @@
package fr.free.nrw.commons;
package fr.free.nrw.commons
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_COMMENT;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.multidex.MultiDexApplication;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import fr.free.nrw.commons.auth.LoginActivity;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
import fr.free.nrw.commons.concurrency.ThreadPoolService;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.language.AppLanguageLookUpTable;
import fr.free.nrw.commons.logging.FileLoggingTree;
import fr.free.nrw.commons.logging.LogUtils;
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.functions.Functions;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraMailSender;
import org.acra.data.StringFormat;
import timber.log.Timber;
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.os.Build
import android.os.Process
import android.util.Log
import androidx.multidex.MultiDexApplication
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
import fr.free.nrw.commons.category.CategoryDao
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler
import fr.free.nrw.commons.concurrency.ThreadPoolService
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.logging.FileLoggingTree
import fr.free.nrw.commons.logging.LogUtils
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher
import fr.free.nrw.commons.settings.Prefs
import fr.free.nrw.commons.upload.FileUtils
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.internal.functions.Functions
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.acra.ACRA.init
import org.acra.ReportField
import org.acra.annotation.AcraCore
import org.acra.annotation.AcraDialog
import org.acra.annotation.AcraMailSender
import org.acra.data.StringFormat
import timber.log.Timber
import timber.log.Timber.DebugTree
import java.io.File
import javax.inject.Inject
import javax.inject.Named
@AcraCore(
buildConfigClass = BuildConfig.class,
buildConfigClass = BuildConfig::class,
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
reportFormat = StringFormat.KEY_VALUE_LIST,
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL,
STACK_TRACE}
reportContent = [ReportField.USER_COMMENT, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE]
)
@AcraMailSender(
mailTo = "commons-app-android-private@googlegroups.com",
reportAsFile = false
)
@AcraMailSender(mailTo = "commons-app-android-private@googlegroups.com", reportAsFile = false)
@AcraDialog(
resTheme = R.style.Theme_AppCompat_Dialog,
@ -83,137 +67,100 @@ import timber.log.Timber;
resCommentPrompt = R.string.crash_dialog_comment_prompt
)
public class CommonsApplication extends MultiDexApplication {
public static final String loginMessageIntentKey = "loginMessage";
public static final String loginUsernameIntentKey = "loginUsername";
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
@Inject
SessionManager sessionManager;
@Inject
DBOpenHelper dbOpenHelper;
class CommonsApplication : MultiDexApplication() {
@Inject
@Named("default_preferences")
JsonKvStore defaultPrefs;
lateinit var sessionManager: SessionManager
@Inject
CommonsCookieJar cookieJar;
lateinit var dbOpenHelper: DBOpenHelper
@Inject
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
/**
* Constants begin
*/
public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001;
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]";
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App Feedback";
public static final String REPORT_EMAIL = "commons-app-android-private@googlegroups.com";
public static final String REPORT_EMAIL_SUBJECT = "Report a violation";
public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll";
public static final String FEEDBACK_EMAIL_TEMPLATE_HEADER = "-- Technical information --";
/**
* Constants End
*/
private static CommonsApplication INSTANCE;
public static CommonsApplication getInstance() {
return INSTANCE;
}
private AppLanguageLookUpTable languageLookUpTable;
public AppLanguageLookUpTable getLanguageLookUpTable() {
return languageLookUpTable;
}
@field:Named("default_preferences")
lateinit var defaultPrefs: JsonKvStore
@Inject
ContributionDao contributionDao;
lateinit var cookieJar: CommonsCookieJar
public static Boolean isPaused = false;
@Inject
lateinit var customOkHttpNetworkFetcher: CustomOkHttpNetworkFetcher
var languageLookUpTable: AppLanguageLookUpTable? = null
private set
@Inject
lateinit var contributionDao: ContributionDao
/**
* Used to declare and initialize various components and dependencies
*/
@Override
public void onCreate() {
super.onCreate();
override fun onCreate() {
super.onCreate()
INSTANCE = this;
ACRA.init(this);
instance = this
init(this)
ApplicationlessInjection
.getInstance(this)
.getCommonsApplicationComponent()
.inject(this);
.commonsApplicationComponent
.inject(this)
initTimber();
initTimber()
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS);
var defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS)
if (null == defaultExifTagsSet) {
defaultExifTagsSet = new HashSet<>();
defaultExifTagsSet = HashSet()
}
defaultExifTagsSet.add(getString(R.string.exif_tag_location));
defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet);
defaultExifTagsSet.add(getString(R.string.exif_tag_location))
defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet)
}
// Set DownsampleEnabled to True to downsample the image in case it's heavy
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
val config = ImagePipelineConfig.newBuilder(this)
.setNetworkFetcher(customOkHttpNetworkFetcher)
.setDownsampleEnabled(true)
.build();
.build()
try {
Fresco.initialize(this, config);
} catch (Exception e) {
Timber.e(e);
Fresco.initialize(this, config)
} catch (e: Exception) {
Timber.e(e)
// TODO: Remove when we're able to initialize Fresco in test builds.
}
createNotificationChannel(this);
createNotificationChannel(this)
languageLookUpTable = new AppLanguageLookUpTable(this);
languageLookUpTable = AppLanguageLookUpTable(this)
// This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler.
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer());
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer())
// Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0")
}
/**
* Plants debug and file logging tree. Timber lets you plant your own logging trees.
*/
private void initTimber() {
boolean isBeta = ConfigUtils.isBetaFlavour();
String logFileName =
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
String logDirectory = LogUtils.getLogDirectory();
private fun initTimber() {
val isBeta = isBetaFlavour
val logFileName =
if (isBeta) "CommonsBetaAppLogs" else "CommonsAppLogs"
val logDirectory = LogUtils.getLogDirectory()
//Delete stale logs if they have exceeded the specified size
deleteStaleLogs(logFileName, logDirectory);
deleteStaleLogs(logFileName, logDirectory)
FileLoggingTree tree = new FileLoggingTree(
val tree = FileLoggingTree(
Log.VERBOSE,
logFileName,
logDirectory,
1000,
getFileLoggingThreadPool());
fileLoggingThreadPool
)
Timber.plant(tree);
Timber.plant(new Timber.DebugTree());
Timber.plant(tree)
Timber.plant(DebugTree())
}
/**
@ -223,48 +170,27 @@ public class CommonsApplication extends MultiDexApplication {
* @param logFileName
* @param logDirectory
*/
private void deleteStaleLogs(String logFileName, String logDirectory) {
private fun deleteStaleLogs(logFileName: String, logDirectory: String) {
try {
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
file.delete();
val file = File("$logDirectory/zip/$logFileName.zip")
if (file.exists() && file.totalSpace > 1000000) { // In Kbs
file.delete()
}
} catch (Exception e) {
Timber.e(e);
} catch (e: Exception) {
Timber.e(e)
}
}
public static boolean isRoboUnitTest() {
return "robolectric".equals(Build.FINGERPRINT);
}
private ThreadPoolService getFileLoggingThreadPool() {
return new ThreadPoolService.Builder("file-logging-thread")
private val fileLoggingThreadPool: ThreadPoolService
get() = ThreadPoolService.Builder("file-logging-thread")
.setPriority(Process.THREAD_PRIORITY_LOWEST)
.setPoolSize(1)
.setExceptionHandler(new BackgroundPoolExceptionHandler())
.build();
}
.setExceptionHandler(BackgroundPoolExceptionHandler())
.build()
public static void createNotificationChannel(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = manager
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
if (channel == null) {
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL,
context.getString(R.string.notifications_channel_name_all),
NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
}
}
public String getUserAgent() {
return "Commons/" + ConfigUtils.getVersionNameWithSha(this)
+ " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
}
val userAgent: String
get() = ("Commons/" + this.getVersionNameWithSha()
+ " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE)
/**
* clears data of current application
@ -273,88 +199,88 @@ public class CommonsApplication extends MultiDexApplication {
* @param logoutListener Implementation of interface LogoutListener
*/
@SuppressLint("CheckResult")
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
fun clearApplicationData(context: Context, logoutListener: LogoutListener) {
val cacheDirectory = context.cacheDir
val applicationDirectory = File(cacheDirectory.parent)
if (applicationDirectory.exists()) {
String[] fileNames = applicationDirectory.list();
for (String fileName : fileNames) {
if (!fileName.equals("lib")) {
FileUtils.deleteFile(new File(applicationDirectory, fileName));
val fileNames = applicationDirectory.list()
for (fileName in fileNames) {
if (fileName != "lib") {
FileUtils.deleteFile(File(applicationDirectory, fileName))
}
}
}
sessionManager.logout()
.andThen(Completable.fromAction(() -> cookieJar.clear()))
.andThen(Completable.fromAction(() -> {
Timber.d("All accounts have been removed");
clearImageCache();
.andThen(Completable.fromAction { cookieJar.clear() })
.andThen(Completable.fromAction {
Timber.d("All accounts have been removed")
clearImageCache()
//TODO: fix preference manager
defaultPrefs.clearAll();
defaultPrefs.putBoolean("firstrun", false);
updateAllDatabases();
}
))
defaultPrefs.clearAll()
defaultPrefs.putBoolean("firstrun", false)
updateAllDatabases()
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(logoutListener::onLogoutComplete, Timber::e);
.subscribe({ logoutListener.onLogoutComplete() }, { t: Throwable? -> Timber.e(t) })
}
/**
* Clear all images cache held by Fresco
*/
private void clearImageCache() {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.clearCaches();
private fun clearImageCache() {
val imagePipeline = Fresco.getImagePipeline()
imagePipeline.clearCaches()
}
/**
* Deletes all tables and re-creates them.
*/
private void updateAllDatabases() {
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
private fun updateAllDatabases() {
dbOpenHelper.readableDatabase.close()
val db = dbOpenHelper.writableDatabase
CategoryDao.Table.onDelete(db);
dbOpenHelper.deleteTable(db,
CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
CategoryDao.Table.onDelete(db)
dbOpenHelper.deleteTable(
db,
DBOpenHelper.CONTRIBUTIONS_TABLE
) //Delete the contributions table in the existing db on older versions
try {
contributionDao.deleteAll();
} catch (SQLiteException e) {
Timber.e(e);
contributionDao.deleteAll()
} catch (e: SQLiteException) {
Timber.e(e)
}
BookmarkPicturesDao.Table.onDelete(db);
BookmarkLocationsDao.Table.onDelete(db);
Table.onDelete(db);
BookmarkPicturesDao.Table.onDelete(db)
BookmarkLocationsDao.Table.onDelete(db)
BookmarkItemsDao.Table.onDelete(db)
}
/**
* Interface used to get log-out events
*/
public interface LogoutListener {
void onLogoutComplete();
interface LogoutListener {
fun onLogoutComplete()
}
/**
* This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity
* with relevant intent parameters. It does not perform the actual logout operation.
*/
public static class BaseLogoutListener implements CommonsApplication.LogoutListener {
Context ctx;
String loginMessage, userName;
open class BaseLogoutListener : LogoutListener {
var ctx: Context
var loginMessage: String? = null
var userName: String? = null
/**
* Constructor for BaseLogoutListener.
*
* @param ctx Application context
*/
public BaseLogoutListener(final Context ctx) {
this.ctx = ctx;
constructor(ctx: Context) {
this.ctx = ctx
}
/**
@ -364,28 +290,29 @@ public class CommonsApplication extends MultiDexApplication {
* @param loginMessage Message to be displayed on the login page
* @param loginUsername Username to be pre-filled on the login page
*/
public BaseLogoutListener(final Context ctx, final String loginMessage,
final String loginUsername) {
this.ctx = ctx;
this.loginMessage = loginMessage;
this.userName = loginUsername;
constructor(
ctx: Context, loginMessage: String?,
loginUsername: String?
) {
this.ctx = ctx
this.loginMessage = loginMessage
this.userName = loginUsername
}
@Override
public void onLogoutComplete() {
Timber.d("Logout complete callback received.");
final Intent loginIntent = new Intent(ctx, LoginActivity.class);
override fun onLogoutComplete() {
Timber.d("Logout complete callback received.")
val loginIntent = Intent(ctx, LoginActivity::class.java)
loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (loginMessage != null) {
loginIntent.putExtra(loginMessageIntentKey, loginMessage);
loginIntent.putExtra(LOGIN_MESSAGE_INTENT_KEY, loginMessage)
}
if (userName != null) {
loginIntent.putExtra(loginUsernameIntentKey, userName);
loginIntent.putExtra(LOGIN_USERNAME_INTENT_KEY, userName)
}
ctx.startActivity(loginIntent);
ctx.startActivity(loginIntent)
}
}
@ -393,9 +320,8 @@ public class CommonsApplication extends MultiDexApplication {
* This class is an extension of BaseLogoutListener, providing additional functionality or customization
* for the logout process. It includes specific actions to be taken during logout, such as handling redirection to the login screen.
*/
public static class ActivityLogoutListener extends BaseLogoutListener {
Activity activity;
class ActivityLogoutListener : BaseLogoutListener {
var activity: Activity
/**
@ -404,9 +330,8 @@ public class CommonsApplication extends MultiDexApplication {
* @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity.
* @param ctx The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process.
*/
public ActivityLogoutListener(final Activity activity, final Context ctx) {
super(ctx);
this.activity = activity;
constructor(activity: Activity, ctx: Context) : super(ctx) {
this.activity = activity
}
/**
@ -417,16 +342,72 @@ public class CommonsApplication extends MultiDexApplication {
* @param loginMessage Message to be displayed on the login page after logout.
* @param loginUsername Username to be pre-filled on the login page after logout.
*/
public ActivityLogoutListener(final Activity activity, final Context ctx,
final String loginMessage, final String loginUsername) {
super(activity, loginMessage, loginUsername);
this.activity = activity;
constructor(
activity: Activity, ctx: Context?,
loginMessage: String?, loginUsername: String?
) : super(activity, loginMessage, loginUsername) {
this.activity = activity
}
@Override
public void onLogoutComplete() {
super.onLogoutComplete();
activity.finish();
override fun onLogoutComplete() {
super.onLogoutComplete()
activity.finish()
}
}
companion object {
const val LOGIN_MESSAGE_INTENT_KEY: String = "loginMessage"
const val LOGIN_USERNAME_INTENT_KEY: String = "loginUsername"
const val IS_LIMITED_CONNECTION_MODE_ENABLED: String = "is_limited_connection_mode_enabled"
/**
* Constants begin
*/
const val OPEN_APPLICATION_DETAIL_SETTINGS: Int = 1001
const val DEFAULT_EDIT_SUMMARY: String = "Uploaded using [[COM:MOA|Commons Mobile App]]"
const val FEEDBACK_EMAIL: String = "commons-app-android@googlegroups.com"
const val FEEDBACK_EMAIL_SUBJECT: String = "Commons Android App Feedback"
const val REPORT_EMAIL: String = "commons-app-android-private@googlegroups.com"
const val REPORT_EMAIL_SUBJECT: String = "Report a violation"
const val NOTIFICATION_CHANNEL_ID_ALL: String = "CommonsNotificationAll"
const val FEEDBACK_EMAIL_TEMPLATE_HEADER: String = "-- Technical information --"
/**
* Constants End
*/
@JvmStatic
lateinit var instance: CommonsApplication
private set
@JvmField
var isPaused: Boolean = false
@JvmStatic
fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = context
.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
var channel = manager
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL)
if (channel == null) {
channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID_ALL,
context.getString(R.string.notifications_channel_name_all),
NotificationManager.IMPORTANCE_DEFAULT
)
manager.createNotificationChannel(channel)
}
}
}
}
}

View file

@ -53,6 +53,7 @@ import fr.free.nrw.commons.utils.SystemThemeUtils;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
@ -301,7 +302,8 @@ public class LocationPickerActivity extends BaseActivity implements
modifyLocationButton = findViewById(R.id.modify_location);
removeLocationButton = findViewById(R.id.remove_location);
showInMapButton = findViewById(R.id.show_in_map);
showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase());
showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase(
Locale.ROOT));
shadow = findViewById(R.id.location_picker_image_view_shadow);
}

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons
import android.os.Parcelable
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.wikidata.model.page.PageTitle
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import java.util.Date
import java.util.Locale
@ -124,6 +125,7 @@ class Media constructor(
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
@IgnoredOnParcel
var addedCategories: List<String>? = null
// TODO added categories should be removed. It is added for a short fix. On category update,
// categories should be re-fetched instead

View file

@ -32,7 +32,7 @@ class ThanksClient
revisionId.toString(), // Rev
null, // Log
csrfTokenClient.getTokenBlocking(), // Token
CommonsApplication.getInstance().userAgent, // Source
CommonsApplication.instance.userAgent, // Source
).map { mwThankPostResponse ->
mwThankPostResponse.result?.success == 1
}

View file

@ -50,8 +50,8 @@ import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static fr.free.nrw.commons.CommonsApplication.loginMessageIntentKey;
import static fr.free.nrw.commons.CommonsApplication.loginUsernameIntentKey;
import static fr.free.nrw.commons.CommonsApplication.LOGIN_MESSAGE_INTENT_KEY;
import static fr.free.nrw.commons.CommonsApplication.LOGIN_USERNAME_INTENT_KEY;
public class LoginActivity extends AccountAuthenticatorActivity {
@ -94,8 +94,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
String message = getIntent().getStringExtra(loginMessageIntentKey);
String username = getIntent().getStringExtra(loginUsernameIntentKey);
String message = getIntent().getStringExtra(LOGIN_MESSAGE_INTENT_KEY);
String username = getIntent().getStringExtra(LOGIN_USERNAME_INTENT_KEY);
binding.loginUsername.addTextChangedListener(textWatcher);
binding.loginPassword.addTextChangedListener(textWatcher);

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.bookmarks.items;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -134,6 +135,7 @@ public class BookmarkItemsDao {
* @param cursor : Object for storing database data
* @return DepictedItem
*/
@SuppressLint("Range")
DepictedItem fromCursor(final Cursor cursor) {
final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));
final String description

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.bookmarks.locations;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -146,6 +147,7 @@ public class BookmarkLocationsDao {
return false;
}
@SuppressLint("Range")
@NonNull
Place fromCursor(final Cursor cursor) {
final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),

View file

@ -9,6 +9,7 @@ import android.view.ViewGroup;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -33,6 +34,23 @@ public class BookmarkLocationsFragment extends DaggerFragment {
@Inject BookmarkLocationsDao bookmarkLocationDao;
@Inject CommonPlaceClickActions commonPlaceClickActions;
private PlaceAdapter adapter;
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
@ -45,7 +63,7 @@ public class BookmarkLocationsFragment extends DaggerFragment {
contributionController.locationPermissionCallback.onLocationPermissionGranted();
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher);
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else {
contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied));
}
@ -83,7 +101,9 @@ public class BookmarkLocationsFragment extends DaggerFragment {
return Unit.INSTANCE;
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
);
binding.listView.setAdapter(adapter);
}
@ -109,11 +129,6 @@ public class BookmarkLocationsFragment extends DaggerFragment {
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
contributionController.handleActivityResult(getActivity(), requestCode, resultCode, data);
}
@Override
public void onDestroy() {
super.onDestroy();

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.bookmarks.pictures;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -150,6 +151,7 @@ public class BookmarkPicturesDao {
return false;
}
@SuppressLint("Range")
@NonNull
Bookmark fromCursor(Cursor cursor) {
String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME));

View file

@ -124,7 +124,7 @@ class CategoryClient
}.map {
it
.filter { page ->
page.categoryInfo() == null || !page.categoryInfo().isHidden
!page.categoryInfo().isHidden
}.map {
CategoryItem(
it.title().replace(CATEGORY_PREFIX, ""),

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.category;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -111,6 +112,7 @@ public class CategoryDao {
}
@NonNull
@SuppressLint("Range")
Category fromCursor(Cursor cursor) {
// Hardcoding column positions!
return new Category(

View file

@ -7,6 +7,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
@ -64,10 +65,11 @@ public class ContributionController {
* Check for permissions and initiate camera click
*/
public void initiateCameraPick(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) {
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
if (!useExtStorage) {
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
return;
}
@ -75,12 +77,12 @@ public class ContributionController {
() -> {
if (defaultKvStore.getBoolean("inAppCameraFirstRun")) {
defaultKvStore.putBoolean("inAppCameraFirstRun", false);
askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher);
askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher, resultLauncher);
} else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) {
createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher);
inAppCameraLocationPermissionLauncher, resultLauncher);
} else {
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
}
},
R.string.storage_permission_title,
@ -94,7 +96,8 @@ public class ContributionController {
* @param activity
*/
private void createDialogsAndHandleLocationPermissions(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) {
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
locationPermissionCallback = new LocationPermissionCallback() {
@Override
public void onLocationPermissionDenied(String toastMessage) {
@ -103,16 +106,16 @@ public class ContributionController {
toastMessage,
Toast.LENGTH_LONG
).show();
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
}
@Override
public void onLocationPermissionGranted() {
if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
showLocationOffDialog(activity, R.string.in_app_camera_needs_location,
R.string.in_app_camera_location_unavailable);
R.string.in_app_camera_location_unavailable, resultLauncher);
} else {
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
}
}
};
@ -135,9 +138,10 @@ public class ContributionController {
* @param activity Activity reference
* @param dialogTextResource Resource id of text to be shown in dialog
* @param toastTextResource Resource id of text to be shown in toast
* @param resultLauncher
*/
private void showLocationOffDialog(Activity activity, int dialogTextResource,
int toastTextResource) {
int toastTextResource, ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil
.showAlertDialog(activity,
activity.getString(R.string.ask_to_turn_location_on),
@ -148,20 +152,21 @@ public class ContributionController {
() -> {
Toast.makeText(activity, activity.getString(toastTextResource),
Toast.LENGTH_LONG).show();
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
}
);
}
public void handleShowRationaleFlowCameraLocation(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) {
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title),
activity.getString(R.string.in_app_camera_location_permission_rationale),
activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel),
() -> {
createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher);
inAppCameraLocationPermissionLauncher, resultLauncher);
},
() -> locationPermissionCallback.onLocationPermissionDenied(
activity.getString(R.string.in_app_camera_location_permission_denied)),
@ -181,7 +186,8 @@ public class ContributionController {
* @param activity
*/
private void askUserToAllowLocationAccess(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) {
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil.showAlertDialog(activity,
activity.getString(R.string.in_app_camera_location_permission_title),
activity.getString(R.string.in_app_camera_location_access_explanation),
@ -190,12 +196,12 @@ public class ContributionController {
() -> {
defaultKvStore.putBoolean("inAppCameraLocationPref", true);
createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher);
inAppCameraLocationPermissionLauncher, resultLauncher);
},
() -> {
ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied);
defaultKvStore.putBoolean("inAppCameraLocationPref", false);
initiateCameraUpload(activity);
initiateCameraUpload(activity, resultLauncher);
},
null,
true);
@ -204,18 +210,18 @@ public class ContributionController {
/**
* Initiate gallery picker
*/
public void initiateGalleryPick(final Activity activity, final boolean allowMultipleUploads) {
initiateGalleryUpload(activity, allowMultipleUploads);
public void initiateGalleryPick(final Activity activity, ActivityResultLauncher<Intent> resultLauncher, final boolean allowMultipleUploads) {
initiateGalleryUpload(activity, resultLauncher, allowMultipleUploads);
}
/**
* Initiate gallery picker with permission
*/
public void initiateCustomGalleryPickWithPermission(final Activity activity) {
public void initiateCustomGalleryPickWithPermission(final Activity activity, ActivityResultLauncher<Intent> resultLauncher) {
setPickerConfiguration(activity, true);
PermissionUtils.checkPermissionsAndPerformAction(activity,
() -> FilePicker.openCustomSelector(activity, 0),
() -> FilePicker.openCustomSelector(activity, resultLauncher, 0),
R.string.storage_permission_title,
R.string.write_storage_permission_rationale,
PermissionUtils.PERMISSIONS_STORAGE);
@ -225,12 +231,10 @@ public class ContributionController {
/**
* Open chooser for gallery uploads
*/
private void initiateGalleryUpload(final Activity activity,
private void initiateGalleryUpload(final Activity activity, ActivityResultLauncher<Intent> resultLauncher,
final boolean allowMultipleUploads) {
setPickerConfiguration(activity, allowMultipleUploads);
boolean openDocumentIntentPreferred = defaultKvStore.getBoolean(
"openDocumentPhotoPickerPref", true);
FilePicker.openGallery(activity, 0, openDocumentIntentPreferred);
FilePicker.openGallery(activity, resultLauncher, 0, isDocumentPhotoPickerPreferred());
}
/**
@ -247,22 +251,43 @@ public class ContributionController {
/**
* Initiate camera upload by opening camera
*/
private void initiateCameraUpload(Activity activity) {
private void initiateCameraUpload(Activity activity, ActivityResultLauncher<Intent> resultLauncher) {
setPickerConfiguration(activity, false);
if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) {
locationBeforeImageCapture = locationManager.getLastLocation();
}
isInAppCameraUpload = true;
FilePicker.openCameraForImage(activity, 0);
FilePicker.openCameraForImage(activity, resultLauncher, 0);
}
private boolean isDocumentPhotoPickerPreferred(){
return defaultKvStore.getBoolean(
"openDocumentPhotoPickerPref", true);
}
public void onPictureReturnedFromGallery(ActivityResult result, Activity activity, FilePicker.Callbacks callbacks){
if(isDocumentPhotoPickerPreferred()){
FilePicker.onPictureReturnedFromDocuments(result, activity, callbacks);
} else {
FilePicker.onPictureReturnedFromGallery(result, activity, callbacks);
}
}
public void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
FilePicker.onPictureReturnedFromCustomSelector(result, activity, callbacks);
}
public void onPictureReturnedFromCamera(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
FilePicker.onPictureReturnedFromCamera(result, activity, callbacks);
}
/**
* Attaches callback for file picker.
*/
public void handleActivityResult(Activity activity, int requestCode, int resultCode,
Intent data) {
FilePicker.handleActivityResult(requestCode, resultCode, data, activity,
new DefaultCallback() {
public void handleActivityResultWithCallback(Activity activity, FilePicker.HandleActivityResult handleActivityResult) {
handleActivityResult.onHandleActivityResult(new DefaultCallback() {
@Override
public void onCanceled(final ImageSource source, final int type) {

View file

@ -6,6 +6,7 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_
import android.Manifest.permission;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
@ -20,6 +21,7 @@ import android.widget.LinearLayout;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -96,6 +98,30 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
private int contributionsSize;
private String userName;
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> customSelectorLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@ -111,7 +137,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller.handleShowRationaleFlowCameraLocation(getActivity(),
inAppCameraLocationPermissionLauncher);
inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else {
controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString(
@ -322,7 +348,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
private void setListeners() {
binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
binding.fabCamera.setOnClickListener(view -> {
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher);
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
animateFAB(isFabOpen);
});
binding.fabCamera.setOnLongClickListener(view -> {
@ -330,7 +356,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
return true;
});
binding.fabGallery.setOnClickListener(view -> {
controller.initiateGalleryPick(getActivity(), true);
controller.initiateGalleryPick(getActivity(), galleryPickLauncherForResult, true);
animateFAB(isFabOpen);
});
binding.fabGallery.setOnLongClickListener(view -> {
@ -343,7 +369,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* Launch Custom Selector.
*/
protected void launchCustomSelector() {
controller.initiateCustomGalleryPickWithPermission(getActivity());
controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult);
animateFAB(isFabOpen);
}

View file

@ -438,13 +438,6 @@ public class MainActivity extends BaseActivity
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Timber.d(data != null ? data.toString() : "onActivityResult data is null");
super.onActivityResult(requestCode, resultCode, data);
controller.handleActivityResult(this, requestCode, resultCode, data);
}
@Override
protected void onResume() {
super.onResume();

View file

@ -22,7 +22,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() {
) = DialogAddToWikipediaInstructionsBinding
.inflate(inflater, container, false)
.apply {
val contribution: Contribution? = arguments!!.getParcelable(ARG_CONTRIBUTION)
val contribution: Contribution? = requireArguments().getParcelable(ARG_CONTRIBUTION)
tvWikicode.setText(contribution?.media?.wikiCode)
instructionsCancel.setOnClickListener { dismiss() }
instructionsConfirm.setOnClickListener {

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.customselector.helper
import android.app.Activity
import android.content.ContentUris
import android.content.Context
import android.media.MediaScannerConnection
@ -8,9 +7,10 @@ import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.appcompat.app.AlertDialog
import fr.free.nrw.commons.R
import fr.free.nrw.commons.filepicker.Constants
import timber.log.Timber
import java.io.File
@ -22,15 +22,20 @@ object FolderDeletionHelper {
* @param context The context used to show the confirmation dialog and manage deletion.
* @param folder The folder to be deleted.
* @param onDeletionComplete Callback invoked with `true` if the folder was
* @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request.
* successfully deleted, `false` otherwise.
*/
fun confirmAndDeleteFolder(context: Context, folder: File, onDeletionComplete: (Boolean) -> Unit) {
fun confirmAndDeleteFolder(
context: Context,
folder: File,
trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>,
onDeletionComplete: (Boolean) -> Unit) {
val itemCount = countItemsInFolder(context, folder)
val folderPath = folder.absolutePath
//don't show this dialog on API 30+, it's handled automatically using MediaStore
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val success = deleteFolderMain(context, folder)
val success = deleteFolderMain(context, folder, trashFolderLauncher)
onDeletionComplete(success)
} else {
@ -40,7 +45,7 @@ object FolderDeletionHelper {
.setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ ->
//proceed with deletion if user confirms
val success = deleteFolderMain(context, folder)
val success = deleteFolderMain(context, folder, trashFolderLauncher)
onDeletionComplete(success)
}
.setNegativeButton(context.getString(R.string.custom_selector_cancel)) { dialog, _ ->
@ -56,12 +61,17 @@ object FolderDeletionHelper {
*
* @param context The context used to manage storage operations.
* @param folder The folder to delete.
* @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request.
* @return `true` if the folder deletion was successful, `false` otherwise.
*/
private fun deleteFolderMain(context: Context, folder: File): Boolean {
private fun deleteFolderMain(
context: Context,
folder: File,
trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>): Boolean
{
return when {
//for API 30 and above, use MediaStore
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder)
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder, trashFolderLauncher)
//for API 29 ('requestLegacyExternalStorage' is set to true in Manifest)
// and below use file system
@ -75,9 +85,14 @@ object FolderDeletionHelper {
*
* @param context The context used to access the content resolver.
* @param folder The folder whose contents are to be moved to the trash.
* @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request.
* @return `true` if the trash request was initiated successfully, `false` otherwise.
*/
private fun trashFolderContents(context: Context, folder: File): Boolean {
private fun trashFolderContents(
context: Context,
folder: File,
trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>): Boolean
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false
val contentResolver = context.contentResolver
@ -111,11 +126,8 @@ object FolderDeletionHelper {
if (urisToTrash.isNotEmpty()) {
try {
val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true)
(context as? Activity)?.startIntentSenderForResult(
trashRequest.intentSender,
Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE,
null, 0, 0, 0
)
val intentSenderRequest = IntentSenderRequest.Builder(trashRequest.intentSender).build()
trashFolderLauncher.launch(intentSenderRequest)
return true
} catch (e: SecurityException) {
Timber.tag("DeleteFolder").e(context.getString(R.string.custom_selector_error_trashing_folder_contents, e.message))

View file

@ -14,6 +14,9 @@ import android.widget.Button
import android.widget.ImageButton
import android.widget.PopupMenu
import android.widget.TextView
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
@ -42,7 +45,6 @@ import fr.free.nrw.commons.R
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.SHOULD_REFRESH
import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper
import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener
@ -50,7 +52,6 @@ import fr.free.nrw.commons.customselector.model.Image
import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding
import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding
import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding
import fr.free.nrw.commons.filepicker.Constants
import fr.free.nrw.commons.media.ZoomableActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper
@ -65,7 +66,6 @@ import java.io.File
import java.lang.Integer.max
import javax.inject.Inject
/**
* Custom Selector Activity.
*/
@ -155,7 +155,16 @@ class CustomSelectorActivity :
*/
private var showOverflowMenu = false
/**
* Waits for confirmation of delete folder
*/
private val startForFolderDeletionResult = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){
result -> onDeleteFolderResultReceived(result)
}
private val startForResult = registerForActivityResult(StartActivityForResult()){ result ->
onFullScreenDataReceived(result)
}
/**
@ -215,7 +224,6 @@ class CustomSelectorActivity :
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
@ -237,35 +245,24 @@ class CustomSelectorActivity :
/**
* When data will be send from full screen mode, it will be passed to fragment
*/
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE &&
resultCode == Activity.RESULT_OK
) {
private fun onFullScreenDataReceived(result: ActivityResult){
if (result.resultCode == Activity.RESULT_OK) {
val selectedImages: ArrayList<Image> =
data!!
result.data!!
.getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!!
val shouldRefresh = data.getBooleanExtra(SHOULD_REFRESH, false)
imageFragment?.passSelectedImages(selectedImages, shouldRefresh)
viewModel?.selectedImages?.value = selectedImages
}
}
if (requestCode == Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE &&
resultCode == Activity.RESULT_OK) {
private fun onDeleteFolderResultReceived(result: ActivityResult){
if (result.resultCode == Activity.RESULT_OK){
FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName)
navigateToCustomSelector()
}
}
/**
* Show Custom Selector Welcome Dialog.
*/
@ -438,7 +435,7 @@ class CustomSelectorActivity :
}
/**
* Set up the toolbar, back listener, done listener, overflow menu listener.
* Set up the toolbar, back listener, done listener.
*/
private fun setUpToolbar() {
val back: ImageButton = findViewById(R.id.back)
@ -489,9 +486,9 @@ class CustomSelectorActivity :
return
}
FolderDeletionHelper.confirmAndDeleteFolder(this, folder) { success ->
FolderDeletionHelper.confirmAndDeleteFolder(this, folder, startForFolderDeletionResult) { success ->
if (success) {
// For API 30+, navigation is handled in 'onActivityResult'
//for API 30+, navigation is handled in 'onDeleteFolderResultReceived'
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName)
navigateToCustomSelector()
@ -542,9 +539,8 @@ class CustomSelectorActivity :
override fun onFolderClick(
folderId: Long,
folderName: String,
lastItemId: Long
lastItemId: Long,
) {
supportFragmentManager
.beginTransaction()
.add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId))
@ -563,7 +559,6 @@ class CustomSelectorActivity :
}
/**
* override Selected Images Change, update view model selected images and change UI.
*/
@ -629,7 +624,7 @@ class CustomSelectorActivity :
selectedImages,
)
intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId)
startActivityForResult(intent, Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE)
startForResult.launch(intent)
}
/**
@ -744,9 +739,7 @@ fun partialStorageAccessIndicator(
border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)),
shape = RoundedCornerShape(8.dp),
) {
Row(modifier = Modifier
.padding(16.dp)
.fillMaxWidth()) {
Row(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
Text(
text = "You've given access to a select number of photos",
modifier = Modifier.weight(1f),

View file

@ -279,13 +279,19 @@ class ImageFragment :
filteredImages = ImageHelper.filterImages(images, bucketId)
allImages = ArrayList(filteredImages)
imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions)
viewModel?.selectedImages?.value?.let { selectedImages ->
imageAdapter.setSelectedImages(selectedImages)
}
imageAdapter.notifyDataSetChanged()
selectorRV?.let {
it.visibility = View.VISIBLE
if (switch?.isChecked == false) {
lastItemId?.let { pos ->
(it.layoutManager as GridLayoutManager)
.scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos))
}
}
}
} else {
filteredImages = ArrayList()
allImages = filteredImages
@ -382,14 +388,6 @@ class ImageFragment :
selectedImages: ArrayList<Image>,
shouldRefresh: Boolean,
) {
imageAdapter.setSelectedImages(selectedImages)
val uploadingContributions = getUploadingContributions()
if (!showAlreadyActionedImages && shouldRefresh) {
imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions)
imageAdapter.setSelectedImages(selectedImages)
}
}
/**

View file

@ -17,6 +17,7 @@ import fr.free.nrw.commons.utils.CustomSelectorUtils
import fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.checkWhetherFileExistsOnCommonsUsingSHA1
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import java.util.Calendar

View file

@ -6,6 +6,8 @@ import android.os.Bundle
import android.os.Parcelable
import android.speech.RecognizerIntent
import android.view.View
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.free.nrw.commons.CommonsApplication
@ -70,10 +72,14 @@ class DescriptionEditActivity :
private lateinit var binding: ActivityDescriptionEditBinding
private val requestCodeForVoiceInput = 1213
private var descriptionAndCaptions: ArrayList<UploadMediaDetail>? = null
private val voiceInputResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result: ActivityResult ->
onVoiceInput(result)
}
@Inject lateinit var descriptionEditHelper: DescriptionEditHelper
@Inject lateinit var sessionManager: SessionManager
@ -115,6 +121,7 @@ class DescriptionEditActivity :
savedLanguageValue,
descriptionAndCaptions,
recentLanguagesDao,
voiceInputResultLauncher
)
uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int ->
showInfoAlert(
@ -149,6 +156,15 @@ class DescriptionEditActivity :
override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {}
private fun onVoiceInput(result: ActivityResult) {
if (result.resultCode == RESULT_OK && result.data != null) {
val resultData = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
uploadMediaDetailAdapter.handleSpeechResult(resultData!![0])
} else {
Timber.e("Error %s", result.resultCode)
}
}
/**
* Adds new language item to RecyclerView
*/
@ -221,7 +237,7 @@ class DescriptionEditActivity :
) {
try {
descriptionEditHelper
?.addDescription(
.addDescription(
applicationContext,
media,
updatedWikiText,
@ -234,7 +250,7 @@ class DescriptionEditActivity :
)
}
} catch (e: InvalidLoginTokenException) {
val username: String? = sessionManager?.userName
val username: String? = sessionManager.userName
val logoutListener =
CommonsApplication.BaseLogoutListener(
this,
@ -242,7 +258,7 @@ class DescriptionEditActivity :
username,
)
val commonsApplication = CommonsApplication.getInstance()
val commonsApplication = CommonsApplication.instance
if (commonsApplication != null) {
commonsApplication.clearApplicationData(this, logoutListener)
}
@ -252,7 +268,7 @@ class DescriptionEditActivity :
for (mediaDetail in uploadMediaDetails) {
try {
compositeDisposable.add(
descriptionEditHelper!!
descriptionEditHelper
.addCaption(
applicationContext,
media,
@ -275,7 +291,7 @@ class DescriptionEditActivity :
username,
)
val commonsApplication = CommonsApplication.getInstance()
val commonsApplication = CommonsApplication.instance
if (commonsApplication != null) {
commonsApplication.clearApplicationData(this, logoutListener)
}
@ -292,22 +308,6 @@ class DescriptionEditActivity :
progressDialog!!.show()
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?,
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == requestCodeForVoiceInput) {
if (resultCode == RESULT_OK && data != null) {
val result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)
uploadMediaDetailAdapter.handleSpeechResult(result!![0])
} else {
Timber.e("Error %s", resultCode)
}
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)

View file

@ -65,7 +65,6 @@ class TransformImageImpl : TransformImage {
} catch (e: LLJTranException) {
Timber.tag("Error").d(e)
return null
false
}
if (rotated) {

View file

@ -22,6 +22,7 @@ import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
@ -112,13 +113,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
mobileRootFragment = new ExploreListRootFragment(mobileArguments);
mapRootFragment = new ExploreMapRootFragment(mapArguments);
fragmentList.add(featuredRootFragment);
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase());
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT));
fragmentList.add(mobileRootFragment);
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase());
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT));
fragmentList.add(mapRootFragment);
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase());
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT));
((MainActivity)getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);

View file

@ -28,6 +28,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import timber.log.Timber;
@ -95,11 +96,11 @@ public class SearchActivity extends BaseActivity
searchDepictionsFragment = new SearchDepictionsFragment();
searchCategoryFragment= new SearchCategoryFragment();
fragmentList.add(searchMediaFragment);
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase());
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT));
fragmentList.add(searchCategoryFragment);
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase());
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT));
fragmentList.add(searchDepictionsFragment);
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase());
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT));
viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged();

View file

@ -18,6 +18,6 @@ class CategoriesMediaFragment : PageableMediaFragment() {
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}")
}
}

View file

@ -21,6 +21,6 @@ class ParentCategoriesFragment : PageableCategoryFragment() {
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}")
}
}

View file

@ -20,6 +20,6 @@ class SubCategoriesFragment : PageableCategoryFragment() {
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}")
onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}")
}
}

View file

@ -13,13 +13,13 @@ class ChildDepictionsFragment : PageableDepictionsFragment() {
override val injectedPresenter
get() = presenter
override fun getEmptyText(query: String) = getString(R.string.no_child_classes, arguments!!.getString("wikidataItemName")!!)
override fun getEmptyText(query: String) = getString(R.string.no_child_classes, requireArguments().getString("wikidataItemName")!!)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!)
onQueryUpdated(requireArguments().getString("entityId")!!)
}
}

View file

@ -17,6 +17,6 @@ class DepictedImagesFragment : PageableMediaFragment() {
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!)
onQueryUpdated(requireArguments().getString("entityId")!!)
}
}

View file

@ -13,13 +13,13 @@ class ParentDepictionsFragment : PageableDepictionsFragment() {
override val injectedPresenter
get() = presenter
override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, arguments!!.getString("wikidataItemName")!!)
override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, requireArguments().getString("wikidataItemName")!!)
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!)
onQueryUpdated(requireArguments().getString("entityId")!!)
}
}

View file

@ -40,7 +40,7 @@ class MediaConverter
metadata.licenseShortName(),
metadata.prefixedLicenseUrl,
getAuthor(metadata),
imageInfo.user,
getAuthor(metadata),
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories),
metadata.latLng,
entity.labels().mapValues { it.value.value() },

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.explore.recentsearches;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -178,6 +179,7 @@ public class RecentSearchesDao {
* @return RecentSearch object
*/
@NonNull
@SuppressLint("Range")
RecentSearch fromCursor(Cursor cursor) {
// Hardcoding column positions!
return new RecentSearch(

View file

@ -15,6 +15,7 @@ import fr.free.nrw.commons.databinding.FragmentSearchHistoryBinding;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.SearchActivity;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
@ -90,7 +91,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
private void showDeleteAlertDialog(@NonNull final Context context, final int position) {
new AlertDialog.Builder(context)
.setMessage(R.string.delete_search_dialog)
.setPositiveButton(getString(R.string.delete).toUpperCase(),
.setPositiveButton(getString(R.string.delete).toUpperCase(Locale.ROOT),
((dialog, which) -> setDeletePositiveButton(context, dialog, position)))
.setNegativeButton(android.R.string.cancel, null)
.create()

View file

@ -4,23 +4,12 @@ public interface Constants {
String DEFAULT_FOLDER_NAME = "CommonsContributions";
/**
* Provides the request codes utilised by the FilePicker
* Provides the request codes for permission handling
*/
interface RequestCodes {
int LOCATION = 1;
int STORAGE = 2;
int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876
int SOURCE_CHOOSER = 1 << 15;
int PICK_PICTURE_FROM_CUSTOM_SELECTOR = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 10);
int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11);
int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12);
int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13);
int CAPTURE_VIDEO = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 14);
int RECEIVE_DATA_FROM_FULL_SCREEN_MODE = 1 << 9;
int DELETE_FOLDER_REQUEST_CODE = 1 << 16;
}
/**

View file

@ -12,6 +12,8 @@ import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.provider.MediaStore;
import android.text.TextUtils;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
@ -107,25 +109,25 @@ public class FilePicker implements Constants {
*
* @param type Custom type of your choice, which will be returned with the images
*/
public static void openGallery(Activity activity, int type, boolean openDocumentIntentPreferred) {
public static void openGallery(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type, boolean openDocumentIntentPreferred) {
Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred);
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY);
resultLauncher.launch(intent);
}
/**
* Opens Custom Selector
*/
public static void openCustomSelector(Activity activity, int type) {
public static void openCustomSelector(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type) {
Intent intent = createCustomSelectorIntent(activity, type);
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR);
resultLauncher.launch(intent);
}
/**
* Opens the camera app to pick image clicked by user
*/
public static void openCameraForImage(Activity activity, int type) {
public static void openCameraForImage(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type) {
Intent intent = createCameraForImageIntent(activity, type);
activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE);
resultLauncher.launch(intent);
}
@Nullable
@ -148,47 +150,6 @@ public class FilePicker implements Constants {
}
}
/**
* Any activity can use this method to attach their callback to the file picker
*/
public static void handleActivityResult(int requestCode, int resultCode, Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
boolean isHandledPickedFile = (requestCode & RequestCodes.FILE_PICKER_IMAGE_IDENTIFICATOR) > 0;
if (isHandledPickedFile) {
requestCode &= ~RequestCodes.SOURCE_CHOOSER;
if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY ||
requestCode == RequestCodes.TAKE_PICTURE ||
requestCode == RequestCodes.CAPTURE_VIDEO ||
requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS ||
requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) {
onPictureReturnedFromDocuments(data, activity, callbacks);
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) {
onPictureReturnedFromGallery(data, activity, callbacks);
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) {
onPictureReturnedFromCustomSelector(data, activity, callbacks);
} else if (requestCode == RequestCodes.TAKE_PICTURE) {
onPictureReturnedFromCamera(activity, callbacks);
} else if (requestCode == RequestCodes.CAPTURE_VIDEO) {
onVideoReturnedFromCamera(activity, callbacks);
} else if (isPhoto(data)) {
onPictureReturnedFromCamera(activity, callbacks);
} else {
onPictureReturnedFromDocuments(data, activity, callbacks);
}
} else {
if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) {
callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
} else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY) {
callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity));
} else {
callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
}
}
}
}
}
public static List<UploadableFile> handleExternalImagesPicked(Intent data, Activity activity) {
try {
return getFilesFromGalleryPictures(data, activity);
@ -241,9 +202,10 @@ public class FilePicker implements Constants {
return intent;
}
private static void onPictureReturnedFromDocuments(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
public static void onPictureReturnedFromDocuments(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){
try {
Uri photoPath = data.getData();
Uri photoPath = result.getData().getData();
UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath);
callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
@ -254,20 +216,27 @@ public class FilePicker implements Constants {
e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
}
} else {
callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
}
}
/**
* onPictureReturnedFromCustomSelector.
* Retrieve and forward the images to upload wizard through callback.
*/
private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
public static void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
if(result.getResultCode() == Activity.RESULT_OK){
try {
List<UploadableFile> files = getFilesFromCustomSelector(data, activity);
List<UploadableFile> files = getFilesFromCustomSelector(result.getData(), activity);
callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
} catch (Exception e) {
e.printStackTrace();
callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
}
} else {
callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity));
}
}
/**
@ -290,14 +259,18 @@ public class FilePicker implements Constants {
return files;
}
private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
public static void onPictureReturnedFromGallery(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){
try {
List<UploadableFile> files = getFilesFromGalleryPictures(data, activity);
List<UploadableFile> files = getFilesFromGalleryPictures(result.getData(), activity);
callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity));
} catch (Exception e) {
e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity));
}
} else{
callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity));
}
}
private static List<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException {
@ -322,7 +295,8 @@ public class FilePicker implements Constants {
return files;
}
private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
public static void onPictureReturnedFromCamera(ActivityResult activityResult, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
if(activityResult.getResultCode() == Activity.RESULT_OK){
try {
String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null);
if (!TextUtils.isEmpty(lastImageUri)) {
@ -353,38 +327,8 @@ public class FilePicker implements Constants {
e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
}
}
private static void onVideoReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try {
String lastVideoUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_VIDEO_URI, null);
if (!TextUtils.isEmpty(lastVideoUri)) {
revokeWritePermission(activity, Uri.parse(lastVideoUri));
}
UploadableFile photoFile = FilePicker.takenCameraVideo(activity);
List<UploadableFile> files = new ArrayList<>();
files.add(photoFile);
if (photoFile == null) {
Exception e = new IllegalStateException("Unable to get the video returned from camera");
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
} else {
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
}
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
}
PreferenceManager.getDefaultSharedPreferences(activity)
.edit()
.remove(KEY_LAST_CAMERA_VIDEO)
.remove(KEY_VIDEO_URI)
.apply();
} catch (Exception e) {
e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity));
callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
}
}
@ -404,4 +348,8 @@ public class FilePicker implements Constants {
void onCanceled(FilePicker.ImageSource source, int type);
}
public interface HandleActivityResult{
void onHandleActivityResult(FilePicker.Callbacks callbacks);
}
}

View file

@ -1,13 +1,10 @@
package fr.free.nrw.commons.media;
import static android.app.Activity.RESULT_CANCELED;
import static android.app.Activity.RESULT_OK;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED;
import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION;
import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT;
import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT;
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION;
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
@ -112,8 +109,6 @@ import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
CategoryEditHelper.Callback {
private static final int REQUEST_CODE = 1001;
private static final int REQUEST_CODE_EDIT_DESCRIPTION = 1002;
private static final String IMAGE_BACKGROUND_COLOR = "image_background_color";
static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0;
@ -277,6 +272,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
if (!sessionManager.isUserLoggedIn()) {
binding.categoryEditButton.setVisibility(GONE);
binding.descriptionEdit.setVisibility(GONE);
binding.depictionsEditButton.setVisibility(GONE);
} else {
binding.categoryEditButton.setVisibility(VISIBLE);
binding.descriptionEdit.setVisibility(VISIBLE);
binding.depictionsEditButton.setVisibility(VISIBLE);
}
if(applicationKvStore.getBoolean("login_skipped")){
@ -405,7 +406,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
}
);
binding.progressBarEdit.setVisibility(GONE);
binding.descriptionEdit.setVisibility(VISIBLE);
}
@Override
@ -605,8 +605,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
// Check if the presented category is about need of category
if (categoriesPresent) {
for (String category : media.getCategories()) {
if (category.toLowerCase().contains(CATEGORY_NEEDING_CATEGORIES) ||
category.toLowerCase().contains(CATEGORY_UNCATEGORISED)) {
if (category.toLowerCase(Locale.ROOT).contains(CATEGORY_NEEDING_CATEGORIES) ||
category.toLowerCase(Locale.ROOT).contains(CATEGORY_UNCATEGORISED)) {
categoriesPresent = false;
}
break;
@ -683,7 +683,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
// Stick in a filler element.
allCategories.add(getString(R.string.detail_panel_cats_none));
}
if(sessionManager.isUserLoggedIn()) {
binding.categoryEditButton.setVisibility(VISIBLE);
}
rebuildCatList(allCategories);
}
@ -1065,81 +1067,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
return captionList;
}
/**
* Get the result from another activity and act accordingly.
* @param requestCode
* @param resultCode
* @param data
*/
@Override
public void onActivityResult(final int requestCode, final int resultCode,
@Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) {
final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT);
try {
compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media,
updatedWikiText)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
Timber.d("Descriptions are added.");
}));
} catch (Exception e) {
if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
}
final ArrayList<UploadMediaDetail> uploadMediaDetails
= data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION);
LinkedHashMap<String, String> updatedCaptions = new LinkedHashMap<>();
for (UploadMediaDetail mediaDetail:
uploadMediaDetails) {
try {
compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media,
mediaDetail.getLanguageCode(), mediaDetail.getCaptionText())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
updateCaptions(mediaDetail, updatedCaptions);
Timber.d("Caption is added.");
}));
} catch (Exception e) {
if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
getActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
}
}
binding.progressBarEdit.setVisibility(GONE);
binding.descriptionEdit.setVisibility(VISIBLE);
} else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_CANCELED) {
binding.progressBarEdit.setVisibility(GONE);
binding.descriptionEdit.setVisibility(VISIBLE);
}
}
/**
* Adds caption to the map and updates captions
* @param mediaDetail UploadMediaDetail

View file

@ -219,7 +219,7 @@ class ZoomableActivity : BaseActivity() {
onSwipe()
}
}
binding.zoomProgressBar?.let {
binding.zoomProgressBar.let {
it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE
}
}
@ -234,7 +234,7 @@ class ZoomableActivity : BaseActivity() {
sharedPreferences.getBoolean(ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true)
if (!images.isNullOrEmpty()) {
binding.zoomable!!.setOnTouchListener(
binding.zoomable.setOnTouchListener(
object : OnSwipeTouchListener(this) {
// Swipe left to view next image in the folder. (if available)
override fun onSwipeLeft() {
@ -271,7 +271,7 @@ class ZoomableActivity : BaseActivity() {
* Handles down swipe action
*/
private fun onDownSwiped() {
if (binding.zoomable?.zoomableController?.isIdentity == false) {
if (binding.zoomable.zoomableController?.isIdentity == false) {
return
}
@ -341,7 +341,7 @@ class ZoomableActivity : BaseActivity() {
* Handles up swipe action
*/
private fun onUpSwiped() {
if (binding.zoomable?.zoomableController?.isIdentity == false) {
if (binding.zoomable.zoomableController?.isIdentity == false) {
return
}
@ -414,7 +414,7 @@ class ZoomableActivity : BaseActivity() {
* Handles right swipe action
*/
private fun onRightSwiped(showAlreadyActionedImages: Boolean) {
if (binding.zoomable?.zoomableController?.isIdentity == false) {
if (binding.zoomable.zoomableController?.isIdentity == false) {
return
}
@ -451,7 +451,7 @@ class ZoomableActivity : BaseActivity() {
* Handles left swipe action
*/
private fun onLeftSwiped(showAlreadyActionedImages: Boolean) {
if (binding.zoomable?.zoomableController?.isIdentity == false) {
if (binding.zoomable.zoomableController?.isIdentity == false) {
return
}
@ -646,7 +646,7 @@ class ZoomableActivity : BaseActivity() {
.setProgressBarImage(ProgressBarDrawable())
.setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.build()
with(binding.zoomable!!) {
with(binding.zoomable) {
setHierarchy(hierarchy)
setAllowTouchInterceptionWhileZoomed(true)
setIsLongpressEnabled(false)
@ -658,10 +658,10 @@ class ZoomableActivity : BaseActivity() {
.setUri(imageUri)
.setControllerListener(loadingListener)
.build()
binding.zoomable!!.controller = controller
binding.zoomable.controller = controller
if (photoBackgroundColor != null) {
binding.zoomable!!.setBackgroundColor(photoBackgroundColor!!)
binding.zoomable.setBackgroundColor(photoBackgroundColor!!)
}
if (!images.isNullOrEmpty()) {

View file

@ -285,7 +285,7 @@ public class OkHttpJsonApiClient {
throws Exception {
Timber.d("Fetching nearby items at radius %s", radius);
Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null));
Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (customQuery != null) {
wikidataQuery = customQuery;
@ -344,7 +344,7 @@ public class OkHttpJsonApiClient {
final boolean shouldQueryForMonuments, final String customQuery)
throws Exception {
Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null));
Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null));
final String wikidataQuery;
if (customQuery != null) {

View file

@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import fr.free.nrw.commons.R;
import java.util.Locale;
public class NearbyFilterSearchRecyclerViewAdapter
extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder>
@ -121,11 +122,11 @@ public class NearbyFilterSearchRecyclerViewAdapter
results.count = labels.size();
results.values = labels;
} else {
constraint = constraint.toString().toLowerCase();
constraint = constraint.toString().toLowerCase(Locale.ROOT);
for (Label label : labels) {
String data = label.toString();
if (data.toLowerCase().startsWith(constraint.toString())) {
if (data.toLowerCase(Locale.ROOT).startsWith(constraint.toString())) {
filteredArrayList.add(Label.fromText(label.getText()));
}
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby
import android.content.Intent
import android.view.View
import android.view.View.GONE
import android.view.View.INVISIBLE
@ -17,9 +18,9 @@ import fr.free.nrw.commons.databinding.ItemPlaceBinding
fun placeAdapterDelegate(
bookmarkLocationDao: BookmarkLocationsDao,
onItemClick: ((Place) -> Unit)? = null,
onCameraClicked: (Place, ActivityResultLauncher<Array<String>>) -> Unit,
onCameraClicked: (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit,
onCameraLongPressed: () -> Boolean,
onGalleryClicked: (Place) -> Unit,
onGalleryClicked: (Place, ActivityResultLauncher<Intent>) -> Unit,
onGalleryLongPressed: () -> Boolean,
onBookmarkClicked: (Place, Boolean) -> Unit,
onBookmarkLongPressed: () -> Boolean,
@ -28,6 +29,8 @@ fun placeAdapterDelegate(
onDirectionsClicked: (Place) -> Unit,
onDirectionsLongPressed: () -> Boolean,
inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>,
cameraPickLauncherForResult: ActivityResultLauncher<Intent>,
galleryPickLauncherForResult: ActivityResultLauncher<Intent>
) = adapterDelegateViewBinding<Place, Place, ItemPlaceBinding>({ layoutInflater, parent ->
ItemPlaceBinding.inflate(layoutInflater, parent, false)
}) {
@ -44,10 +47,10 @@ fun placeAdapterDelegate(
onItemClick?.invoke(item)
}
}
nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher) }
nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult) }
nearbyButtonLayout.cameraButton.setOnLongClickListener { onCameraLongPressed() }
nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item) }
nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) }
nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() }
bookmarkButtonImage.setOnClickListener {
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)

View file

@ -4,7 +4,6 @@ import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable;
/**
@ -38,8 +37,21 @@ public abstract class PlaceDao {
*/
public Completable save(final Place place) {
return Completable
.fromAction(() -> {
saveSynchronous(place);
});
.fromAction(() -> saveSynchronous(place));
}
/**
* Deletes all Place objects from the database.
*/
@Query("DELETE FROM place")
public abstract void deleteAllSynchronous();
/**
* Deletes all Place objects from the database.
*
* @return A Completable that completes once the deletion operation is done.
*/
public Completable deleteAll() {
return Completable.fromAction(this::deleteAllSynchronous);
}
}

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.nearby;
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable;
import javax.inject.Inject;
@ -36,4 +35,8 @@ public class PlacesLocalDataSource {
public Completable savePlace(Place place) {
return placeDao.save(place);
}
public Completable clearCache() {
return placeDao.deleteAll();
}
}

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
/**
@ -38,4 +39,13 @@ public class PlacesRepository {
return localDataSource.fetchPlace(entityID);
}
/**
* Clears the Nearby cache on an IO thread.
*
* @return A Completable that completes once the cache has been successfully cleared.
*/
public Completable clearCache() {
return localDataSource.clearCache()
.subscribeOn(Schedulers.io()); // Ensure it runs on IO thread
}
}

View file

@ -87,7 +87,7 @@ class WikidataFeedback : BaseActivity() {
lat,
lng,
)
} as Callable<SingleSource<Boolean?>>,
},
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ aBoolean: Boolean? ->

View file

@ -28,14 +28,14 @@ class CommonPlaceClickActions
private val activity: Activity,
private val contributionController: ContributionController,
) {
fun onCameraClicked(): (Place, ActivityResultLauncher<Array<String>>) -> Unit =
{ place, launcher ->
fun onCameraClicked(): (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit =
{ place, launcher, resultLauncher ->
if (applicationKvStore.getBoolean("login_skipped", false)) {
showLoginDialog()
} else {
Timber.d("Camera button tapped. Image title: ${place.getName()}Image desc: ${place.longDescription}")
storeSharedPrefs(place)
contributionController.initiateCameraPick(activity, launcher)
contributionController.initiateCameraPick(activity, launcher, resultLauncher)
}
}
@ -72,14 +72,14 @@ class CommonPlaceClickActions
true
}
fun onGalleryClicked(): (Place) -> Unit =
{
fun onGalleryClicked(): (Place, ActivityResultLauncher<Intent>) -> Unit =
{place, galleryPickLauncherForResult ->
if (applicationKvStore.getBoolean("login_skipped", false)) {
showLoginDialog()
} else {
Timber.d("Gallery button tapped. Image title: ${it.getName()}Image desc: ${it.getLongDescription()}")
storeSharedPrefs(it)
contributionController.initiateGalleryPick(activity, false)
Timber.d("Gallery button tapped. Image title: ${place.getName()}Image desc: ${place.getLongDescription()}")
storeSharedPrefs(place)
contributionController.initiateGalleryPick(activity, galleryPickLauncherForResult, false)
}
}

View file

@ -43,15 +43,18 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
@ -105,6 +108,7 @@ import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
@ -218,9 +222,36 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private LatLng updatedLatLng;
private boolean searchable;
private ConstraintLayout nearbyLegend;
private GridLayoutManager gridLayoutManager;
private List<BottomSheetItem> dataList;
private BottomSheetAdapter bottomSheetAdapter;
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> customSelectorLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@ -236,7 +267,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller.handleShowRationaleFlowCameraLocation(getActivity(),
inAppCameraLocationPermissionLauncher);
inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else {
controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString(
@ -302,6 +333,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
progressDialog.setCancelable(false);
progressDialog.setMessage("Saving in progress...");
setHasOptionsMenu(true);
// Inflate the layout for this fragment
return view;
@ -311,9 +343,21 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.nearby_fragment_menu, menu);
MenuItem refreshButton = menu.findItem(R.id.item_refresh);
MenuItem listMenu = menu.findItem(R.id.list_sheet);
MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx);
MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml);
refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
try {
emptyCache();
} catch (Exception e) {
throw new RuntimeException(e);
}
return false;
}
});
listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
@ -362,6 +406,16 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager,
this);
// Set up the floating activity button to toggle the visibility of the legend
binding.fabLegend.setOnClickListener(v -> {
if (binding.nearbyLegendLayout.getRoot().getVisibility() == View.VISIBLE) {
binding.nearbyLegendLayout.getRoot().setVisibility(View.GONE);
} else {
binding.nearbyLegendLayout.getRoot().setVisibility(View.VISIBLE);
}
});
presenter.attachView(this);
isPermissionDenied = false;
recenterToUserLocation = false;
@ -555,7 +609,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return Unit.INSTANCE;
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
);
binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter);
}
@ -1115,6 +1171,48 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
}
/**
* Reloads the Nearby map
* Clears all location markers, refreshes them, reinserts them into the map.
*
*/
private void reloadMap() {
clearAllMarkers(); // Clear the list of markers
binding.map.getController().setZoom(ZOOM_LEVEL); // Reset the zoom level
binding.map.getController().setCenter(lastMapFocus); // Recenter the focus
if (locationPermissionsHelper.checkLocationPermission(getActivity())) {
locationPermissionGranted(); // Reload map with user's location
} else {
startMapWithoutPermission(); // Reload map without user's location
}
binding.map.invalidate(); // Invalidate the map
presenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); // Restart the map
Timber.d("Reloaded Map Successfully");
}
/**
* Clears the Nearby local cache and then calls for the map to be reloaded
*
*/
private void emptyCache() {
// reload the map once the cache is cleared
compositeDisposable.add(
placesRepository.clearCache()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.andThen(Completable.fromAction(this::reloadMap))
.subscribe(
() -> {
Timber.d("Nearby Cache cleared successfully.");
},
throwable -> {
Timber.e(throwable, "Failed to clear the Nearby Cache");
}
)
);
}
private void savePlacesAsKML() {
final Observable<String> savePlacesObservable = Observable
.fromCallable(() -> nearbyController
@ -2186,7 +2284,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCamera.isShown()) {
Timber.d("Camera button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace);
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher);
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
}
});
@ -2195,6 +2293,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace);
controller.initiateGalleryPick(getActivity(),
galleryPickLauncherForResult,
false);
}
});
@ -2203,7 +2302,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCustomGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace);
controller.initiateCustomGalleryPickWithPermission(getActivity());
controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult);
}
});
}

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby.fragments
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.nearby.Place
@ -12,6 +13,8 @@ class PlaceAdapter(
onBookmarkClicked: (Place, Boolean) -> Unit,
commonPlaceClickActions: CommonPlaceClickActions,
inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>,
galleryPickLauncherForResult: ActivityResultLauncher<Intent>,
cameraPickLauncherForResult: ActivityResultLauncher<Intent>
) : BaseDelegateAdapter<Place>(
placeAdapterDelegate(
bookmarkLocationsDao,
@ -27,6 +30,8 @@ class PlaceAdapter(
commonPlaceClickActions.onDirectionsClicked(),
commonPlaceClickActions.onDirectionsLongPressed(),
inAppCameraLocationPermissionLauncher,
cameraPickLauncherForResult,
galleryPickLauncherForResult
),
areItemsTheSame = { oldItem, newItem -> oldItem.wikiDataEntityId == newItem.wikiDataEntityId },
)

View file

@ -19,6 +19,7 @@ import fr.free.nrw.commons.databinding.ActivityNotificationBinding;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.models.Notification;
import fr.free.nrw.commons.notification.models.NotificationType;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil;
@ -148,7 +149,11 @@ public class NotificationActivity extends BaseActivity {
}
adapter = new NotificatinAdapter(item -> {
Timber.d("Notification clicked %s", item.getLink());
if (item.getNotificationType() == NotificationType.EMAIL){
ViewUtil.showLongSnackbar(binding.container,getString(R.string.check_your_email_inbox));
} else {
handleUrl(item.getLink());
}
removeNotification(item);
return Unit.INSTANCE;
});

View file

@ -51,13 +51,23 @@ class NotificationClient
}
}
private fun WikimediaNotification.toCommonsNotification() =
Notification(
notificationType = NotificationType.UNKNOWN,
notificationText = contents?.compactHeader ?: "",
private fun WikimediaNotification.toCommonsNotification() :
Notification {
val notificationText = contents?.compactHeader ?: ""
val notificationType =
if (notificationText.contains("Sent you an email", ignoreCase = true)) {
NotificationType.EMAIL
} else {
NotificationType.UNKNOWN
}
return Notification(
notificationType = notificationType,
notificationText = notificationText,
date = DateUtil.getMonthOnlyDateString(timestamp),
link = contents?.links?.primary?.url ?: "",
iconUrl = "",
notificationId = id().toString(),
)
}
}

View file

@ -4,6 +4,7 @@ public enum NotificationType {
THANK_YOU_EDIT("thank-you-edit"),
EDIT_USER_TALK("edit-user-talk"),
MENTION("mention"),
EMAIL("email"),
WELCOME("welcome"),
UNKNOWN("unknown");
private String type;

View file

@ -32,6 +32,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
/**
@ -139,14 +140,14 @@ public class ProfileActivity extends BaseActivity {
leaderboardFragment.setArguments(leaderBoardBundle);
fragmentList.add(leaderboardFragment);
titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase());
titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase(Locale.ROOT));
contributionsFragment = new ContributionsFragment();
Bundle contributionsListBundle = new Bundle();
contributionsListBundle.putString(KEY_USERNAME, userName);
contributionsFragment.setArguments(contributionsListBundle);
fragmentList.add(contributionsFragment);
titleList.add(getString(R.string.contributions_fragment).toUpperCase());
titleList.add(getString(R.string.contributions_fragment).toUpperCase(Locale.ROOT));
viewPagerAdapter.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged();

View file

@ -27,6 +27,7 @@ import fr.free.nrw.commons.profile.ProfileActivity;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.Locale;
import java.util.Objects;
import javax.inject.Inject;
import org.apache.commons.lang3.StringUtils;
@ -361,7 +362,7 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment {
+ levelInfo.getMaxUniqueImages());
binding.imageFeatured.setText(String.valueOf(achievements.getFeaturedImages()));
binding.qualityImages.setText(String.valueOf(achievements.getQualityImages()));
String levelUpInfoString = getString(R.string.level).toUpperCase();
String levelUpInfoString = getString(R.string.level).toUpperCase(Locale.ROOT);
levelUpInfoString += " " + levelInfo.getLevelNumber();
binding.achievementLevel.setText(levelUpInfoString);
binding.achievementBadgeImage.setImageDrawable(VectorDrawableCompat.create(getResources(), R.drawable.badge,

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.recentlanguages;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
@ -117,6 +118,7 @@ public class RecentLanguagesDao {
* @return Language object
*/
@NonNull
@SuppressLint("Range")
Language fromCursor(final Cursor cursor) {
// Hardcoding column positions!
final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));

View file

@ -25,6 +25,7 @@ import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.util.Locale;
import javax.inject.Inject;
public class ReviewActivity extends BaseActivity {
@ -241,7 +242,7 @@ public class ReviewActivity extends BaseActivity {
public void showSkipImageInfo(){
DialogUtil.showAlertDialog(ReviewActivity.this,
getString(R.string.skip_image).toUpperCase(),
getString(R.string.skip_image).toUpperCase(Locale.ROOT),
getString(R.string.skip_image_explanation),
getString(android.R.string.ok),
"",

View file

@ -22,6 +22,7 @@ import android.widget.TextView;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
@ -88,6 +89,15 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private View separator;
private ListView languageHistoryListView;
private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content";
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
@ -96,7 +106,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
areAllGranted = areAllGranted && b;
}
if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher);
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
}
}
});

View file

@ -63,7 +63,7 @@ class FailedUploadsFragment :
}
if (StringUtils.isEmpty(userName)) {
userName = sessionManager!!.getUserName()
userName = sessionManager.getUserName()
}
}
@ -96,8 +96,8 @@ class FailedUploadsFragment :
fun initRecyclerView() {
binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.failedUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.getFailedContributions()
pendingUploadsPresenter!!.failedContributionList.observe(
pendingUploadsPresenter.getFailedContributions()
pendingUploadsPresenter.failedContributionList.observe(
viewLifecycleOwner,
) { list: PagedList<Contribution?> ->
adapter.submitList(list)

View file

@ -19,6 +19,7 @@ import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
import timber.log.Timber;
public class FileUtils {
@ -139,7 +140,7 @@ public class FileUtils {
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
.toString());
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
fileExtension.toLowerCase());
fileExtension.toLowerCase(Locale.getDefault()));
}
return mimeType;
}

View file

@ -74,8 +74,8 @@ class PendingUploadsFragment :
fun initRecyclerView() {
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.pendingUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.setup()
pendingUploadsPresenter!!.totalContributionList.observe(
pendingUploadsPresenter.setup()
pendingUploadsPresenter.totalContributionList.observe(
viewLifecycleOwner,
) { list: PagedList<Contribution?> ->
contributionsSize = list.size

View file

@ -320,6 +320,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
finish();
}
/**
* go to the uploadProgress activity to check the status of uploading
*/
@Override
public void goToUploadProgressActivity() {
startActivity(new Intent(this, UploadProgressActivity.class));
}
/**
* Show/Hide the progress dialog
*/
@ -433,14 +441,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) {
//TODO: Confirm if handling manual permission enabled is required
}
}
/**
* Sets the flag indicating whether the upload is of a specific place.
*

View file

@ -18,6 +18,13 @@ public interface UploadContract {
void returnToMainActivity();
/**
* When submission successful, go to the loadProgressActivity to hint the user this
* submission is valid. And the user will see the upload progress in this activity;
* Fixes: <a href="https://github.com/commons-app/apps-android-commons/issues/5846">Issue</a>
*/
void goToUploadProgressActivity();
void askUserToLogIn();
/**

View file

@ -20,6 +20,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
@ -57,27 +58,29 @@ public class UploadMediaDetailAdapter extends
private int currentPosition;
private Fragment fragment;
private Activity activity;
private final ActivityResultLauncher<Intent> voiceInputResultLauncher;
private SelectedVoiceIcon selectedVoiceIcon;
private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213;
private RowItemDescriptionBinding binding;
public UploadMediaDetailAdapter(Fragment fragment, String savedLanguageValue,
RecentLanguagesDao recentLanguagesDao) {
RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) {
uploadMediaDetails = new ArrayList<>();
selectedLanguages = new HashMap<>();
this.savedLanguageValue = savedLanguageValue;
this.recentLanguagesDao = recentLanguagesDao;
this.fragment = fragment;
this.voiceInputResultLauncher = voiceInputResultLauncher;
}
public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue,
List<UploadMediaDetail> uploadMediaDetails, RecentLanguagesDao recentLanguagesDao) {
List<UploadMediaDetail> uploadMediaDetails, RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) {
this.uploadMediaDetails = uploadMediaDetails;
selectedLanguages = new HashMap<>();
this.savedLanguageValue = savedLanguageValue;
this.recentLanguagesDao = recentLanguagesDao;
this.activity = activity;
this.voiceInputResultLauncher = voiceInputResultLauncher;
}
public void setCallback(Callback callback) {
@ -150,11 +153,7 @@ public class UploadMediaDetailAdapter extends
);
try {
if (activity == null) {
fragment.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT);
} else {
activity.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT);
}
voiceInputResultLauncher.launch(intent);
} catch (Exception e) {
Timber.e(e.getMessage());
}
@ -407,7 +406,7 @@ public class UploadMediaDetailAdapter extends
recentLanguagesDao
.addRecentLanguage(new Language(languageName, languageCode));
selectedLanguages.remove(position);
selectedLanguages.clear();
selectedLanguages.put(position, languageCode);
((LanguagesAdapter) adapterView
.getAdapter()).setSelectedLangCode(languageCode);
@ -497,7 +496,7 @@ public class UploadMediaDetailAdapter extends
}
recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode));
selectedLanguages.remove(position);
selectedLanguages.clear();
selectedLanguages.put(position, languageCode);
((RecentLanguagesAdapter) adapterView
.getAdapter()).setSelectedLangCode(languageCode);

View file

@ -123,6 +123,9 @@ public class UploadPresenter implements UploadContract.UserActionListener {
view.returnToMainActivity();
compositeDisposable.clear();
Timber.e("failed to upload: " + e.getMessage());
//is submission error, not need to go to the uploadActivity
//not start the uploading progress
}
@Override
@ -131,6 +134,10 @@ public class UploadPresenter implements UploadContract.UserActionListener {
repository.cleanup();
view.returnToMainActivity();
compositeDisposable.clear();
//after finish the uploadActivity, if successful,
//directly go to the upload progress activity
view.goToUploadProgressActivity();
}
});
} else {

View file

@ -10,15 +10,13 @@ abstract class BaseDelegateAdapter<T>(
areContentsTheSame: (T, T) -> Boolean = { old, new -> old == new },
) : AsyncListDifferDelegationAdapter<T>(
object : DiffUtil.ItemCallback<T>() {
override fun areItemsTheSame(
oldItem: T,
newItem: T,
) = areItemsTheSame(oldItem, newItem)
override fun areItemsTheSame(oldItem: T & Any, newItem: T & Any): Boolean {
return areItemsTheSame(oldItem, newItem)
}
override fun areContentsTheSame(
oldItem: T,
newItem: T,
) = areContentsTheSame(oldItem, newItem)
override fun areContentsTheSame(oldItem: T & Any, newItem: T & Any): Boolean {
return areContentsTheSame(oldItem, newItem)
}
},
*delegates,
) {

View file

@ -372,7 +372,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
return false;
});
Objects.requireNonNull(getView()).setFocusableInTouchMode(true);
requireView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
@ -387,7 +387,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
});
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
((AppCompatActivity) requireActivity()).getSupportActionBar())
.hide();
if (getParentFragment().getParentFragment().getParentFragment()
@ -407,7 +407,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
super.onStop();
if (media != null) {
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
((AppCompatActivity) requireActivity()).getSupportActionBar())
.show();
}
}

View file

@ -36,7 +36,7 @@ abstract class DepictsDao {
/**
* Gets all Depicts objects from the database, ordered by lastUsed in descending order.
*
* @return A list of Depicts objects.
* @return Deferred list of Depicts objects.
*/
fun depictsList(): Deferred<List<Depicts>> =
CoroutineScope(Dispatchers.IO).async {
@ -48,7 +48,7 @@ abstract class DepictsDao {
*
* @param depictedItem The Depicts object to insert.
*/
private fun insertDepict(depictedItem: Depicts) =
fun insertDepict(depictedItem: Depicts) =
CoroutineScope(Dispatchers.IO).launch {
insert(depictedItem)
}
@ -59,7 +59,7 @@ abstract class DepictsDao {
* @param n The number of depicts to delete.
* @return A list of Depicts objects to delete.
*/
private suspend fun depictsForDeletion(n: Int): Deferred<List<Depicts>> =
fun depictsForDeletion(n: Int): Deferred<List<Depicts>> =
CoroutineScope(Dispatchers.IO).async {
getDepictsForDeletion(n)
}
@ -69,7 +69,7 @@ abstract class DepictsDao {
*
* @param depicts The Depicts object to delete.
*/
private suspend fun deleteDepicts(depicts: Depicts) =
fun deleteDepicts(depicts: Depicts) =
CoroutineScope(Dispatchers.IO).launch {
delete(depicts)
}

View file

@ -398,7 +398,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
return false;
});
Objects.requireNonNull(getView()).setFocusableInTouchMode(true);
requireView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
@ -411,7 +411,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
});
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
((AppCompatActivity) requireActivity()).getSupportActionBar())
.hide();
if (getParentFragment().getParentFragment().getParentFragment()
@ -431,7 +431,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
super.onStop();
if (media != null) {
Objects.requireNonNull(
((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar())
((AppCompatActivity) requireActivity()).getSupportActionBar())
.show();
}
}

View file

@ -18,6 +18,9 @@ import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
@ -58,9 +61,24 @@ import timber.log.Timber;
public class UploadMediaDetailFragment extends UploadBaseFragment implements
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
private static final int REQUEST_CODE = 1211;
private static final int REQUEST_CODE_FOR_EDIT_ACTIVITY = 1212;
private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213;
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
private final ActivityResultLauncher<Intent> startForResult = registerForActivityResult(
new StartActivityForResult(), result -> {
onCameraPosition(result);
});
private final ActivityResultLauncher<Intent> startForEditActivityResult = registerForActivityResult(
new StartActivityForResult(), result -> {
onEditActivityResult(result);
}
);
private final ActivityResultLauncher<Intent> voiceInputResultLauncher = registerForActivityResult(
new StartActivityForResult(), result -> {
onVoiceInput(result);
}
);
public static Activity activity ;
@ -84,8 +102,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
private boolean hasUserRemovedLocation;
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
@Inject
UploadMediaDetailsContract.UserActionListener presenter;
@ -279,7 +295,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
*/
private void initRecyclerView() {
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this,
defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao);
defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao, voiceInputResultLauncher);
uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
uploadMediaDetailAdapter.setEventListener(this);
binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
@ -593,14 +609,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
* This method is called to start the image editing activity for a specific UploadItem.
* It sets the UploadItem as the currently editable item, creates an intent to launch the
* EditActivity, and passes the image file path as an extra in the intent. The activity
* is started with a request code, allowing the result to be handled in onActivityResult.
* is started using resultLauncher that handles the result in respective callback.
*/
@Override
public void showEditActivity(UploadItem uploadItem) {
editableUploadItem = uploadItem;
Intent intent = new Intent(getContext(), EditActivity.class);
intent.putExtra("image", uploadableFile.getFilePath().toString());
startActivityForResult(intent, REQUEST_CODE_FOR_EDIT_ACTIVITY);
startForEditActivityResult.launch(intent);
}
/**
@ -615,6 +631,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
double defaultLongitude = -122.431297;
double defaultZoom = 16.0;
final Intent locationPickerIntent;
/* Retrieve image location from EXIF if present or
check if user has provided location while using the in-app camera.
Use location of last UploadItem if none of them is available */
@ -624,10 +642,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
.getDecLatitude();
defaultLongitude = uploadItem.getGpsCoords().getDecLongitude();
defaultZoom = uploadItem.getGpsCoords().getZoomLevel();
startActivityForResult(new LocationPicker.IntentBuilder()
locationPickerIntent = new LocationPicker.IntentBuilder()
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
.activityKey("UploadActivity")
.build(getActivity()), REQUEST_CODE);
.build(getActivity());
} else {
if (defaultKvStore.getString(LAST_LOCATION) != null) {
final String[] locationLatLng
@ -638,27 +657,20 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
if (defaultKvStore.getString(LAST_ZOOM) != null) {
defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM));
}
startActivityForResult(new LocationPicker.IntentBuilder()
locationPickerIntent = new LocationPicker.IntentBuilder()
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
.activityKey("NoLocationUploadActivity")
.build(getActivity()), REQUEST_CODE);
.build(getActivity());
}
startForResult.launch(locationPickerIntent);
}
/**
* Get the coordinates and update the existing coordinates.
* @param requestCode code of request
* @param resultCode code of result
* @param data intent
*/
@Override
public void onActivityResult(final int requestCode, final int resultCode,
@Nullable final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
private void onCameraPosition(ActivityResult result){
if (result.getResultCode() == RESULT_OK) {
assert data != null;
final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data);
assert result.getData() != null;
final CameraPosition cameraPosition = LocationPicker.getCameraPosition(result.getData());
if (cameraPosition != null) {
@ -678,8 +690,21 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
removeLocation();
}
}
if (requestCode == REQUEST_CODE_FOR_EDIT_ACTIVITY && resultCode == RESULT_OK) {
String result = data.getStringExtra("editedImageFilePath");
}
private void onVoiceInput(ActivityResult result) {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
ArrayList<String> resultData = result.getData().getStringArrayListExtra(
RecognizerIntent.EXTRA_RESULTS);
uploadMediaDetailAdapter.handleSpeechResult(resultData.get(0));
}else {
Timber.e("Error %s", result.getResultCode());
}
}
private void onEditActivityResult(ActivityResult result){
if (result.getResultCode() == RESULT_OK) {
String path = result.getData().getStringExtra("editedImageFilePath");
if (Objects.equals(result, "Error")) {
Timber.e("Error in rotating image");
@ -687,24 +712,15 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
}
try {
if (binding != null){
binding.backgroundImage.setImageURI(Uri.fromFile(new File(result)));
binding.backgroundImage.setImageURI(Uri.fromFile(new File(path)));
}
editableUploadItem.setContentUri(Uri.fromFile(new File(result)));
editableUploadItem.setContentUri(Uri.fromFile(new File(path)));
callback.changeThumbnail(indexOfFragment,
result);
path);
} catch (Exception e) {
Timber.e(e);
}
}
else if (requestCode == REQUEST_CODE_FOR_VOICE_INPUT) {
if (resultCode == RESULT_OK && data != null) {
ArrayList<String> result = data.getStringArrayListExtra(
RecognizerIntent.EXTRA_RESULTS);
uploadMediaDetailAdapter.handleSpeechResult(result.get(0));
}else {
Timber.e("Error %s", resultCode);
}
}
}
/**
@ -809,7 +825,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
@Override
public void displayAddLocationDialog(final Runnable onSkipClicked) {
isMissingLocationDialog = true;
DialogUtil.showAlertDialog(Objects.requireNonNull(getActivity()),
DialogUtil.showAlertDialog(requireActivity(),
getString(R.string.no_location_found_title),
getString(R.string.no_location_found_message),
getString(R.string.add_location),

View file

@ -129,9 +129,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
if (place.location != null) {
final String countryCode = reverseGeoCode(place.location);
if (countryCode != null && WLM_SUPPORTED_COUNTRIES
.contains(countryCode.toLowerCase())) {
.contains(countryCode.toLowerCase(Locale.ROOT))) {
uploadItem.setWLMUpload(true);
uploadItem.setCountryCode(countryCode.toLowerCase());
uploadItem.setCountryCode(countryCode.toLowerCase(Locale.ROOT));
}
}
}

View file

@ -17,6 +17,7 @@ import androidx.work.Data
import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters
import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.BuildConfig.HOME_URL
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
@ -30,6 +31,7 @@ import fr.free.nrw.commons.customselector.database.UploadedStatus
import fr.free.nrw.commons.customselector.database.UploadedStatusDao
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.nearby.PlacesRepository
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.upload.StashUploadResult
@ -38,8 +40,9 @@ import fr.free.nrw.commons.upload.UploadClient
import fr.free.nrw.commons.upload.UploadProgressActivity
import fr.free.nrw.commons.upload.UploadResult
import fr.free.nrw.commons.wikidata.WikidataEditService
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
@ -74,6 +77,9 @@ class UploadWorker(
@Inject
lateinit var fileUtilsWrapper: FileUtilsWrapper
@Inject
lateinit var placesRepository: PlacesRepository
private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag"
private val processingUploadsNotificationId = 101
@ -379,7 +385,7 @@ class UploadWorker(
saveCompletedContribution(contribution, uploadResult)
} else {
Timber.d(
"WikiDataEdit not required, making wikidata edit",
"WikiDataEdit required, making wikidata edit",
)
makeWikiDataEdit(uploadResult, contribution)
}
@ -432,7 +438,7 @@ class UploadWorker(
username,
)
CommonsApplication
.getInstance()
.instance!!
.clearApplicationData(appContext, logoutListener)
}
}
@ -471,6 +477,16 @@ class UploadWorker(
contribution.media.captions,
)
if (null != revisionID) {
withContext(Dispatchers.IO) {
val place = placesRepository.fetchPlace(wikiDataPlace.id);
place.name = wikiDataPlace.name;
place.pic = HOME_URL + uploadResult.createCanonicalFileName()
placesRepository
.save(place)
.subscribeOn(Schedulers.io())
.blockingAwait()
Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}")
}
showSuccessNotification(contribution)
}
} catch (exception: Exception) {
@ -518,7 +534,7 @@ class UploadWorker(
contribution.contentUri?.let {
val imageSha1 = contribution.imageSHA1.toString()
val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path))
MainScope().launch {
CoroutineScope(Dispatchers.IO).launch {
uploadedStatusDao.insertUploaded(
UploadedStatus(
imageSha1,

View file

@ -59,8 +59,7 @@ public class PermissionUtils {
final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
final Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent,
CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS);
activity.startActivity(intent);
}
/**

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="@dimen/half_standard_height"
android:height="@dimen/half_standard_height"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<group
android:scaleX="1.0"
android:scaleY="1.0"
android:translateX="-0.0"
android:translateY="-0.0">
<path
android:fillColor="?attr/menu_item_tint"
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</group>
</vector>

View file

@ -36,11 +36,11 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:contentDescription="@string/exit_location_picker"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_arrow_back_white" />
app:srcCompat="@drawable/ic_arrow_back_white"
app:tint="@color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -69,7 +69,7 @@
android:id="@+id/btn_edit_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:text="@string/submit"
android:textColor="@android:color/white" />
</RelativeLayout>

View file

@ -31,7 +31,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:layout_marginRight="50dp"
android:layout_marginEnd="50dp"
android:maxLines="2"
android:ellipsize="end"
/>
@ -58,6 +58,7 @@
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:focusable="true"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
@ -69,8 +70,7 @@
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_directions_black_24dp"
android:tint="?attr/rowButtonColor"
/>
app:tint="?attr/rowButtonColor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -89,6 +89,7 @@
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:focusable="true"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
@ -118,6 +119,7 @@
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:focusable="true"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
@ -153,8 +155,8 @@
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/large_height"
android:layout_marginRight="@dimen/standard_gap"
android:layout_marginStart="@dimen/large_height"
android:layout_marginEnd="@dimen/standard_gap"
android:layout_marginBottom="@dimen/standard_gap"
android:textSize="16sp" />

View file

@ -1,11 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bookmarkButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:background="@drawable/button_background_selector"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
@ -14,7 +16,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:tint="?attr/rowButtonColor" />
app:tint="?attr/rowButtonColor" />
<TextView
android:id="@+id/buttonText"

View file

@ -28,7 +28,6 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/toolbar"
android:background="?attr/achievementBackground"
android:orientation="vertical">
@ -36,7 +35,6 @@
style="?android:textAppearanceLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:text="@string/level"
@ -48,13 +46,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/activity_margin_vertical"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/black"
android:layout_marginVertical="@dimen/activity_margin_vertical" />
android:layout_marginVertical="@dimen/activity_margin_vertical"
app:tint="@color/black" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/badge_layout"
@ -108,7 +104,6 @@
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:id="@+id/images_upload_text_param"
android:layout_marginTop="@dimen/achievements_activity_margin_vertical"
@ -120,12 +115,10 @@
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
android:layout_toRightOf="@+id/images_upload_text_param"
android:layout_toEndOf="@+id/images_upload_text_param"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"/>
android:layout_marginStart="@dimen/activity_margin_horizontal"
app:tint="@color/primaryLightColor" />
</LinearLayout>
@ -189,7 +182,6 @@
style="?android:textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:id="@+id/images_reverted_text"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:text="@string/image_reverts" />
@ -200,24 +192,19 @@
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
android:layout_toRightOf="@+id/images_reverted_text"
android:layout_toEndOf="@+id/images_reverted_text"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"/>
android:layout_marginStart="@dimen/activity_margin_horizontal" app:tint="@color/primaryLightColor" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/achievements_revert_limit_message"
android:textSize="@dimen/small_text"
android:id="@+id/images_revert_limit_text"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_below="@id/images_reverted_info"/>
@ -278,7 +265,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/images_used_by_wiki_text"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_marginTop="@dimen/achievements_activity_margin_vertical"
android:text="@string/images_used_by_wiki" />
@ -289,12 +275,10 @@
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
android:layout_toRightOf="@+id/images_used_by_wiki_text"
android:layout_toEndOf="@+id/images_used_by_wiki_text"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"/>
android:layout_marginStart="@dimen/activity_margin_horizontal"
app:tint="@color/primaryLightColor" />
</LinearLayout>
@ -353,7 +337,6 @@
android:layout_height="wrap_content"
android:text="@string/statistics"
style="?android:textAppearanceLarge"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_marginTop="@dimen/activity_margin_vertical"
android:textAllCaps="true"/>
@ -373,9 +356,7 @@
android:id="@+id/images_nearby_info"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@+id/wikidata_edits"
android:layout_toLeftOf="@+id/wikidata_edits"
android:orientation="horizontal"
android:gravity="center_vertical">
@ -407,14 +388,13 @@
android:layout_height="@dimen/medium_height"
android:id="@+id/images_nearby_info_icon"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
android:layout_gravity="top"
app:layout_constraintLeft_toRightOf="@id/images_nearby_data"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor" />
app:tint="@color/primaryLightColor" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -423,16 +403,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceMedium"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/half_standard_height"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_centerVertical="true"
tools:text="2"
android:id="@+id/wikidata_edits"
android:layout_marginRight="@dimen/half_standard_height" />
/>
</RelativeLayout>
@ -451,9 +429,7 @@
android:id="@+id/images_featured_info"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@+id/image_featured"
android:layout_toLeftOf="@+id/image_featured"
android:orientation="horizontal"
android:gravity="center_vertical">
@ -486,14 +462,13 @@
android:layout_height="@dimen/medium_height"
android:id="@+id/images_featured_info_icon"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
app:layout_constraintLeft_toRightOf="@id/images_featured_data"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_gravity="top"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor" />
app:tint="@color/primaryLightColor" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -501,16 +476,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceMedium"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_centerVertical="true"
tools:text="2"
android:id="@+id/image_featured"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/half_standard_height"
android:layout_marginRight="@dimen/half_standard_height" />
/>
</RelativeLayout>
@ -529,9 +502,7 @@
android:id="@+id/quality_images_info"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@+id/quality_images"
android:layout_toLeftOf="@+id/quality_images"
android:orientation="horizontal"
android:gravity="center_vertical">
@ -564,14 +535,13 @@
android:layout_height="@dimen/medium_height"
android:id="@+id/quality_images_info_icon"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
app:layout_constraintLeft_toRightOf="@id/quality_images_data"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_gravity="top"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor" />
app:tint="@color/primaryLightColor" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -579,7 +549,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceMedium"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
@ -587,9 +556,8 @@
tools:text="2"
android:text="0"
android:id="@+id/quality_images"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/half_standard_height"
android:layout_marginRight="@dimen/half_standard_height" />
/>
</RelativeLayout>
@ -608,9 +576,7 @@
android:id="@+id/thanks_received_info"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_toStartOf="@+id/thanks_received"
android:layout_toLeftOf="@+id/thanks_received"
android:orientation="horizontal"
android:gravity="center_vertical">
@ -643,14 +609,13 @@
android:layout_height="@dimen/medium_height"
android:id="@+id/thanks_received_info_icon"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
android:layout_marginEnd="@dimen/activity_margin_horizontal"
app:layout_constraintLeft_toRightOf="@id/thanks_received_data"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_gravity="top"
app:srcCompat="@drawable/ic_info_outline_24dp"
android:tint="@color/primaryLightColor" />
app:tint="@color/primaryLightColor" />
</androidx.constraintlayout.widget.ConstraintLayout>
@ -658,16 +623,14 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:textAppearanceMedium"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginStart="@dimen/activity_margin_horizontal"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_centerVertical="true"
tools:text="2"
android:id="@+id/thanks_received"
android:layout_marginEnd="@dimen/half_standard_height"
android:layout_marginRight="@dimen/half_standard_height" />
/>
</RelativeLayout>

View file

@ -124,6 +124,33 @@
app:srcCompat="@drawable/ic_my_location_black_24dp"
app:useCompatPadding="true" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_legend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/fab_recenter"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:clickable="true"
android:visibility="visible"
app:backgroundTint="@color/main_background_light"
app:elevation="@dimen/dimen_6"
app:fabSize="normal"
app:layout_anchorGravity="top|right|end"
app:srcCompat="@drawable/ic_info_outline_24dp"
app:useCompatPadding="true" />
<include
android:id="@+id/nearby_legend_layout"
layout="@layout/nearby_legend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/rl_container_wlm_month_message"
android:visibility="gone"
android:layout_marginTop="30dp"
android:layout_marginStart="5dp"
/>
</RelativeLayout>
<FrameLayout

View file

@ -11,8 +11,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/standard_gap"
android:tint="?attr/rowButtonColor"
app:srcCompat="@drawable/ic_round_star_border_24px" />
app:srcCompat="@drawable/ic_round_star_border_24px"
app:tint="?attr/rowButtonColor" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/icon"
@ -30,7 +30,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginRight="@dimen/standard_gap"
android:layout_marginTop="@dimen/standard_gap"
@ -43,8 +42,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginRight="@dimen/standard_gap"
android:layout_marginEnd="@dimen/standard_gap"
android:layout_marginTop="@dimen/large_gap"
/>
@ -54,11 +52,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignTop="@id/distance"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:layout_toEndOf="@id/icon"
android:layout_toLeftOf="@id/distance"
android:layout_toRightOf="@id/icon"
android:layout_toStartOf="@id/distance"
android:ellipsize="end"
android:maxLines="2"
@ -71,8 +66,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignEnd="@id/distance"
android:layout_alignLeft="@id/tvName"
android:layout_alignRight="@id/distance"
android:layout_alignStart="@id/tvName"
android:layout_below="@id/tvName"
android:layout_marginBottom="@dimen/standard_gap"

View file

@ -19,16 +19,13 @@
android:id="@+id/iv_campaign"
android:layout_width="@dimen/dimen_40"
android:layout_height="@dimen/dimen_40"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_campaign"
android:tint="?attr/card_item_color"
/>
app:tint="?attr/card_item_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
@ -37,15 +34,13 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginRight="@dimen/tiny_margin"
android:layout_centerInParent="true"
>
android:layout_marginStart="@dimen/standard_gap"
android:layout_marginEnd="@dimen/tiny_margin">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:textColor="?attr/card_item_color"
android:textStyle="bold"
tools:text="Campaign Title"
@ -55,7 +50,7 @@
android:id="@+id/tv_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:gravity="start"
android:paddingTop="@dimen/miniscule_margin"
android:textAlignment="textStart"
@ -69,7 +64,7 @@
android:id="@+id/tv_dates"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:layout_weight="1"
android:paddingTop="@dimen/miniscule_margin"
android:text="@string/ends_on"

View file

@ -113,9 +113,9 @@
android:background="@android:color/transparent"
android:padding="@dimen/activity_margin_horizontal"
android:src="@drawable/ic_wikipedia"
android:tint="?attr/contributionsListTextSecondary"
android:text="@string/menu_cancel_upload"
android:visibility="visible" />
android:visibility="visible"
app:tint="?attr/contributionsListTextSecondary" />
</RelativeLayout>

View file

@ -14,7 +14,6 @@
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_marginLeft="@dimen/activity_margin_horizontal"
android:layout_marginTop="@dimen/activity_margin_horizontal"
android:layout_marginRight="@dimen/activity_margin_horizontal"
@ -30,33 +29,27 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/content_layout"
android:layout_centerInParent="true"
android:orientation="horizontal"
>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_centerInParent="true"
/>
android:id="@+id/progressBar" />
<ImageView
android:id="@+id/nearby_icon"
android:layout_width="@dimen/dimen_40"
android:layout_height="@dimen/dimen_40"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginStart="@dimen/standard_gap"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_location_white_24dp"
android:tint="?attr/card_item_color"
/>
app:tint="?attr/card_item_color" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="horizontal"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
@ -68,7 +61,6 @@
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="3"
android:layout_centerInParent="true"
android:layout_marginLeft="@dimen/standard_gap"
android:layout_marginRight="@dimen/standard_gap"
tools:text="test distance"

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
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:background="#BFFFFFFF"
android:orientation="vertical">
<ImageView
android:id="@+id/imageRed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toTopOf="@+id/imageGreen"
app:layout_constraintEnd_toEndOf="@+id/imageGreen"
app:layout_constraintStart_toStartOf="@+id/imageGreen"
app:srcCompat="@drawable/ic_custom_map_marker_red" />
<TextView
android:id="@+id/textRed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:text="@string/red_pin"
android:textColor="#F74D4D"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/imageRed"
app:layout_constraintStart_toEndOf="@+id/imageRed"
app:layout_constraintTop_toTopOf="@+id/imageRed" />
<ImageView
android:id="@+id/imageGreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="0dp"
app:layout_constraintBottom_toTopOf="@+id/imageGrey"
app:layout_constraintEnd_toEndOf="@+id/imageGrey"
app:layout_constraintStart_toStartOf="@+id/imageGrey"
app:srcCompat="@drawable/ic_custom_map_marker_green" />
<TextView
android:id="@+id/textGreen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:text="@string/green_pin"
android:textColor="#1F7123"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/imageGreen"
app:layout_constraintStart_toEndOf="@+id/imageGreen"
app:layout_constraintTop_toTopOf="@+id/imageGreen" />
<ImageView
android:id="@+id/imageGrey"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_custom_map_marker_grey" />
<TextView
android:id="@+id/textGrey"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/grey_pin"
android:textColor="#454547"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/imageGrey"
app:layout_constraintStart_toEndOf="@+id/imageGrey"
app:layout_constraintTop_toTopOf="@+id/imageGrey" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -17,6 +17,7 @@
android:layout_weight="1"
android:background="@drawable/button_background_selector"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
@ -24,8 +25,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:tint="?attr/bookmarkButtonColor"
app:srcCompat="@drawable/ic_photo_camera_white_24dp" />
app:srcCompat="@drawable/ic_photo_camera_white_24dp"
app:tint="?attr/bookmarkButtonColor" />
<TextView
android:id="@+id/cameraButtonText"
@ -45,6 +46,7 @@
android:background="@drawable/button_background_selector"
android:clickable="true"
android:contentDescription="@string/nearby_row_image"
android:focusable="true"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
@ -53,8 +55,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
android:tint="?attr/bookmarkButtonColor"
app:srcCompat="@drawable/ic_photo_white_24dp" />
app:srcCompat="@drawable/ic_photo_white_24dp"
app:tint="?attr/bookmarkButtonColor" />
<TextView
android:id="@+id/galleryButtonText"
@ -72,6 +74,7 @@
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:focusable="true"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:orientation="vertical"
@ -82,8 +85,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_directions_black_24dp"
android:tint="?attr/bookmarkButtonColor"
android:duplicateParentState="true"/>
android:duplicateParentState="true"
app:tint="?attr/bookmarkButtonColor" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@ -102,6 +105,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:clickable="true"
android:focusable="true"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
@ -110,8 +114,8 @@
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
android:tint="?attr/bookmarkButtonColor"
app:srcCompat="@drawable/ic_overflow" />
app:srcCompat="@drawable/ic_overflow"
app:tint="?attr/bookmarkButtonColor" />
<TextView
android:id="@+id/iconOverflowText"

View file

@ -42,10 +42,10 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:contentDescription="@string/exit_location_picker"
android:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_arrow_back_white"/>
app:srcCompat="@drawable/ic_arrow_back_white"
app:tint="@color/white" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,17 +1,25 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/item_refresh"
android:title="Refresh"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_refresh_24dp_nearby" />
<item android:id="@+id/list_sheet"
android:title="@string/list_sheet"
app:showAsAction="ifRoom|withText"
android:icon="@drawable/ic_list_white_24dp"
/>
<item android:id="@+id/list_item_gpx"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="Save as GPX file" />
<item android:id="@+id/list_item_kml"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:title="Save as KML file" />
</menu>

View file

@ -527,6 +527,7 @@
<string name="no_notification">ليس لديك أي إشعارات غير مقروءة</string>
<string name="no_read_notification">ليس لديك أي إشعاراتٍ غير مقروءة</string>
<string name="share_logs_using">مشاركة السجلات باستخدام</string>
<string name="check_your_email_inbox">تحقق من صندوق بريدك الإلكتروني</string>
<string name="menu_option_read">عرض المقروءة</string>
<string name="menu_option_unread">عرض غير المقروءة</string>
<string name="error_occurred_in_picking_images">حدث خطأ أثناء التقاط الصور</string>
@ -690,6 +691,7 @@
<string name="leaderboard_nearby">مجاور</string>
<string name="leaderboard_used">مستخدَم</string>
<string name="leaderboard_my_rank_button_text">ترتيبي</string>
<string name="map_attribution">&amp;#169; &lt;a href=\"https://www.openstreetmap.org/copyright\"&gt;خريطة الشارع المفتوحة&lt;/a&gt;</string>
<string name="limited_connection_enabled">وضع الاتصال المحدود مُمَكَّن!</string>
<string name="limited_connection_disabled">وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن.</string>
<string name="limited_connection_mode">وضع الاتصال المحدود</string>
@ -739,6 +741,7 @@
<string name="custom_selector_dismiss_limit_warning_button_text">رفض</string>
<string name="custom_selector_button_limit_text">الحد الأقصى: %1$d</string>
<string name="custom_selector_limit_error_desc">خطأ: تجاوز حد التحميل</string>
<string name="place_state_wlm">دبليو إل إم</string>
<string name="wlm_upload_info">سيتم إدخال هذه الصورة في مسابقة Wiki Loves Monuments</string>
<string name="display_monuments">عرض الآثار</string>
<string name="wlm_month_message">إنه شهر Wiki Loves Monuments!</string>
@ -788,16 +791,66 @@
<string name="image_selected">تم تحديد الصورة</string>
<string name="image_marked_as_not_for_upload">تم وضع علامة على الصورة على أنها ليست للتحميل</string>
<string name="menu_view_report">تقرير</string>
<string name="menu_view_set_white_background">تعيين الخلفية البيضاء</string>
<string name="menu_view_set_black_background">تعيين خلفية سوداء</string>
<string name="report_violation">تبليغ عن عنف</string>
<string name="report_user">أخطر عن هذا المستخدم</string>
<string name="report_content">الإبلاغ عن هذا المحتوى</string>
<string name="request_user_block">طلب منع هذا المستخدم</string>
<string name="welcome_to_full_screen_mode_text">مرحبًا بك في وضع التحديد بملء الشاشة</string>
<string name="full_screen_mode_zoom_info">استخدم إصبعين للتكبير والتصغير.</string>
<string name="full_screen_mode_features_info" fuzzy="true">مرر سريعًا وطويلًا لتنفيذ هذه الإجراءات:! N! - يسار / يمين: انتقل إلى السابق / التالي! N! - لأعلى: حدد! N! - أسفل: وضع علامة على أنه ليس للتحميل.</string>
<string name="full_screen_mode_features_info">مرر سريعًا وطويلًا لأداء هذه الإجراءات: \n- يسار/يمين: الانتقال إلى السابق/التالي \n- أعلى: تحديد\n- أسفل: وضع علامة على عدم التحميل.</string>
<string name="set_up_avatar_toast_string">لإعداد صورتك الرمزية في قائمة المتصدرين، اضغط على \"تعيين كصورة رمزية\" في قائمة النقاط الثلاث لأي صورة.</string>
<string name="similar_coordinate_description_auto_set">الإحداثيات ليست إحداثيات دقيقة، لكن الشخص الذي قام بتحميل هذه الصورة يعتقد أنها قريبة بما فيه الكفاية.</string>
<string name="storage_permissions_denied">رُفض إذن التخزين</string>
<string name="unable_to_share_upload_item">تعذر مشاركة هذا العنصر</string>
<string name="permissions_are_required_for_functionality">الإذن مطلوب لهذه الوظيفة</string>
<string name="learn_how_to_write_a_useful_description">تعلم كيفية كتابة وصف مفيد</string>
<string name="learn_how_to_write_a_useful_caption">تعلم كيفية كتابة تعليق مفيد</string>
<string name="see_your_achievements">شاهد إنجازاتك</string>
<string name="edit_image">تعديل الصورة</string>
<string name="edit_location">تعديل الموقع</string>
<string name="location_updated">تم تحديث الموقع!</string>
<string name="remove_location">إزالة الموقع</string>
<string name="remove_location_warning_title">إزالة تحذير الموقع</string>
<string name="remove_location_warning_desc">يجعل تحديد الموقع الصور أكثر فائدة وسهولة في العثور عليها. هل ترغب حقًا في إزالة تحديد الموقع من هذه الصورة؟</string>
<string name="location_removed">تمت إزالة الموقع!</string>
<string name="send_thanks_to_author">اشكر المؤلف</string>
<string name="error_sending_thanks">حدث خطأ أثناء إرسال الشكر للمؤلف.</string>
<string name="invalid_login_message">لقد انتهت صلاحية تسجيل الدخول الخاص بك. يرجى تسجيل الدخول مرة أخرى.</string>
<string name="no_application_available_to_open_gpx_files">لا يوجد تطبيق متاح لفتح ملفات GPX</string>
<string name="file_saved_successfully">تم حفظ الملف بنجاح</string>
<string name="do_you_want_to_open_gpx_file">هل تريد فتح ملف GPX؟</string>
<string name="do_you_want_to_open_kml_file">هل تريد فتح ملف KML؟</string>
<string name="failed_to_save_kml_file">فشل في حفظ ملف KML.</string>
<string name="failed_to_save_gpx_file">فشل في حفظ ملف GPX.</string>
<string name="saving_kml_file">حفظ ملف KML</string>
<string name="saving_gpx_file">حفظ ملف GPX</string>
<plurals name="custom_picker_images_selected_title_appendix">
<item quantity="zero">لا صور تم اختيارها</item>
<item quantity="one">%d صورة تم اختيارها</item>
<item quantity="two">صورتان تم اختيارهما</item>
<item quantity="few">صور قليلة تم اختيارها</item>
<item quantity="many">صور كثيرة تم اختيارها</item>
<item quantity="other">%d صور تم اختيارها</item>
</plurals>
<string name="multiple_files_depiction">يرجى تذكر أن جميع الصور في التحميل المتعدد تحصل على نفس الفئات والأوصاف. إذا لم تتشارك الصور في الأوصاف والفئات، فيرجى إجراء عدة عمليات تحميل منفصلة.</string>
<string name="multiple_files_depiction_header">ملاحظة حول التحميلات المتعددة</string>
<string name="nearby_wikitalk">الإبلاغ عن مشكلة حول هذا العنصر إلى Wikidata</string>
<string name="please_enter_some_comments">الرجاء إدخال بعض التعليقات</string>
<string name="talk">نقاش</string>
<string name="write_something_about_the_item">اكتب شيئًا عن العنصر \'%1$s\'. سيكون مرئيًا للعامة.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' لم يعد موجودًا، ولا يمكن التقاط صورة له أبدًا.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' موجود في مكان مختلف. يرجى تحديد المكان الصحيح أدناه، وإذا أمكن، اكتب خط العرض وخط الطول الصحيحين.</string>
<string name="other_problem_or_information_please_explain_below">مشكلة أو معلومات أخرى (يرجى التوضيح أدناه).</string>
<string name="feedback_destination_note">سيتم نشر تعليقاتك على صفحة الويكي التالية: &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\"&gt;Commons:Mobile app/Feedback&lt;/a&gt;</string>
<string name="are_you_sure_that_you_want_cancel_all_the_uploads">هل أنت متأكد أنك تريد إلغاء كافة التحميلات؟</string>
<string name="cancelling_all_the_uploads">إلغاء كافة التحميلات...</string>
<string name="uploads">المرفوعات</string>
<string name="pending">قيد الانتظار</string>
<string name="failed">فشل</string>
<string name="could_not_load_place_data">تعذر تحميل بيانات المكان</string>
<string name="red_pin">هذا المكان ليس له صورة بعد، اذهب والتقط واحدة!</string>
<string name="green_pin">هذا المكان لديه صورة بالفعل.</string>
<string name="grey_pin">الآن التحقق ما إذا كان هذا المكان لديه صورة.</string>
</resources>

View file

@ -2,10 +2,11 @@
<!-- Authors:
* Dağlı95
* Khan27
* Nemoralis
-->
<resources>
<string name="crash_dialog_title">Nasazlıq</string>
<string name="crash_dialog_text">Uups. Nəsə düzgün çalışmır!</string>
<string name="crash_dialog_comment_prompt">Nə etdiyinizi dəqiqləşdirib, bizə bildirin və sonra e-poçtla bizə göndərin. Bu problemi həll etməyə bizə kömək edin.</string>
<string name="crash_dialog_ok_toast">Təşəkkür!</string>
<string name="crash_dialog_comment_prompt">Nə etdiyinizi bizə deyin, sonra e-poçt vasitəsilə bizimlə paylaşın. Bu, bizə bunu düzəltməyə kömək edəcək!</string>
<string name="crash_dialog_ok_toast">Təşəkkürlər!</string>
</resources>

View file

@ -12,34 +12,60 @@
* Şeyx Şamil
-->
<resources>
<string name="commons_facebook">Commons Facebook səhifəsi</string>
<string name="commons_github">Commons Github Mənbə Kodu</string>
<string name="commons_logo">Commons Loqotipi</string>
<string name="commons_website">Commons Veb-saytı</string>
<string name="exit_location_picker">Məkan seçicidən çıxın</string>
<string name="submit">Göndər</string>
<string name="add_another_description">Başqa təsvir əlavə et</string>
<string name="add_new_contribution">Yeni töhfə</string>
<string name="add_contribution_from_camera">Kamera ilə töhfə ver</string>
<string name="add_contribution_from_photos">Fotolar ilə töhfə ver</string>
<string name="add_contribution_from_contributions_gallery">Əvvəlki töhfələr qalereyasından töhfə əlavə et</string>
<string name="show_captions">Başlıqlar</string>
<string name="row_item_language_description">Dil təsviri</string>
<string name="row_item_caption">Başlıq</string>
<string name="show_captions_description">Təsvir</string>
<string name="nearby_row_image">Şəkil</string>
<string name="nearby_all">Hamısı</string>
<string name="nearby_filter_toggle">Aç/Bağla</string>
<string name="nearby_filter_search">Axtarış Görünüşü</string>
<string name="nearby_filter_state">Məkanın Vəziyyəti</string>
<string name="appwidget_img">Günün Şəkli</string>
<plurals name="uploads_pending_notification_indicator">
<item quantity="one">%1$d fayl yüklənir</item>
<item quantity="other">%1$d fayllar yüklənir</item>
</plurals>
<plurals name="contributions_subtitle">
<item quantity="one">(%1$d)</item>
<item quantity="other">(%1$d)</item>
</plurals>
<string name="starting_uploads">Yükləmələrə Başlanılır</string>
<string name="preference_category_general">Ümumi</string>
<string name="preference_category_privacy">Məxfilik</string>
<string name="app_name">Vikimedia Commons</string>
<string name="app_name">Vikianbar</string>
<string name="menu_settings">Tənzimləmələr</string>
<string name="username">Ləqəb</string>
<string name="username">İstifadəçi adı</string>
<string name="password">Parol</string>
<string name="login">Daxil ol</string>
<string name="signup">Qeydiyyatdan keç</string>
<string name="logging_in_title">Giriş edilir</string>
<string name="logging_in_message">Lütfən gözləyin…</string>
<string name="logging_in_message">Zəhmət olmasa, gözləyin…</string>
<string name="login_success" fuzzy="true">Daxil oldunuz!</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="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>
<string name="upload_completed_notification_text">Yüklədiyini izlə</string>
<string name="upload_completed_notification_text">Yükləmənizə baxmaq üçün toxunun</string>
<string name="upload_progress_notification_title_start" fuzzy="true">%1$s yüklənməsi başlanır</string>
<string name="upload_progress_notification_title_in_progress">%1$s yüklənir</string>
<string name="upload_progress_notification_title_finishing">%1$s yüklənməsi başa çatdı</string>
<string name="upload_failed_notification_title" fuzzy="true">%1$s faylının yüklənməsi alınmadı</string>
<string name="upload_failed_notification_subtitle">Baxmaq üçün toxunun</string>
<string name="title_activity_contributions">Yükləmələrim</string>
<string name="contribution_state_queued">Sırada</string>
<string name="upload_failed_notification_subtitle">Baxmaq üçün toxun</string>
<string name="title_activity_contributions">Son Yükləmələrim</string>
<string name="contribution_state_queued">Növbəyə alındı</string>
<string name="contribution_state_failed">Uğursuz</string>
<string name="contribution_state_in_progress">%1$d%% tamamlandı</string>
<string name="contribution_state_starting">Yüklənir</string>
@ -52,15 +78,15 @@
<string name="share_description_hint">ıqlama</string>
<string name="login_failed_network" fuzzy="true">Daxil olmaq olmur — şəbəkə xətası</string>
<string name="login_failed_throttled">Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin.</string>
<string name="login_failed_blocked">Bağışlayın, bu istifadəçi Commons-da bloklanmışdır.</string>
<string name="login_failed_2fa_needed">İki faktorlu giriş doğrulama kodunu verməlisiniz.</string>
<string name="login_failed_blocked">Bağışlayın, bu istifadəçi Vikianbardan bloklanıb</string>
<string name="login_failed_2fa_needed">Siz iki faktorlu autentifikasiya kodunuzu təqdim etməlisiniz.</string>
<string name="login_failed_generic" fuzzy="true">Daxil olma uğursuz oldu</string>
<string name="share_upload_button">Yüklə</string>
<string name="multiple_share_base_title">Bu dəsti adlandırın</string>
<string name="provider_modifications">Bildirişlər</string>
<string name="provider_modifications">Dəyişikliklər</string>
<string name="menu_upload_single">Yüklə</string>
<string name="categories_search_text_hint">Kateqoriyaları axtar</string>
<string name="menu_save_categories">Qeyd et</string>
<string name="categories_search_text_hint">Kateqoriyalarda axtar</string>
<string name="menu_save_categories">Yadda saxla</string>
<string name="refresh_button">Yenilə</string>
<string name="display_list_button">Siyahı</string>
<string name="contributions_subtitle_zero">(Hələ yükləmə yoxdur)</string>

View file

@ -307,4 +307,5 @@
<string name="please_wait">Моля, изчакайте...</string>
<string name="delete_helper_ask_spam_blurry">напълно размазано</string>
<string name="leaderboard_nearby">Наблизо</string>
<string name="read_help_link">Прочетете повече</string>
</resources>

View file

@ -242,8 +242,8 @@
<string name="nominated_for_deletion">Meneget eo bet ar skeudenn evit lemel.</string>
<string name="skip_login">Lezel a-gostez</string>
<string name="navigation_item_login">Kevreañ</string>
<string name="skip_login_title" fuzzy="true">Ha c\'hoant ho peus, evit gwir, da gevreañ ?</string>
<string name="skip_login_message" fuzzy="true">Da gevreañ ho po en amzer-da-zont evit pellgargañ skeudennoù.</string>
<string name="skip_login_title">Ha n\'ho peus ket c\'hoant, evit gwir, da gevreañ ?</string>
<string name="skip_login_message">Ret e vo deoc\'h kevreañ en amzer-da-zont evit pellgargañ skeudennoù.</string>
<string name="login_alert_message">Kevreit, mar plij, evit implijout an arc\'hwel-mañ</string>
<string name="copy_wikicode">Eilañ an destenn wiki er golver</string>
<string name="wikicode_copied">Testenn wiki eilet er golver</string>

View file

@ -106,7 +106,7 @@
<string name="menu_view_file_page">Хьажа файлан агӀоне</string>
<string name="share_title_hint">Куьг йазор (ТIедилина ду)</string>
<string name="add_caption_toast">Дехар ду, хӀокху файлан цIе гайта</string>
<string name="share_description_hint">Цунах лаьцна</string>
<string name="share_description_hint">Цуьнах лаьцна</string>
<string name="share_caption_hint">Куьг</string>
<string name="login_failed_network">Чувала(йала) тара цало — сетан гӀалат</string>
<string name="login_failed_throttled">ТӀех дукха кхиаме боцу гӀертарш. Дехар ду масех минот йаьлча йуха а хьажа.</string>

View file

@ -481,6 +481,7 @@
<string name="no_notification">Du har ingen ulæste notifikationer</string>
<string name="no_read_notification">Du har ingen læste notifikationer</string>
<string name="share_logs_using">Del logs ved hjælp af</string>
<string name="check_your_email_inbox">Tjek din e-mail-indbakke</string>
<string name="menu_option_read">Vis læste</string>
<string name="menu_option_unread">Vis ulæste</string>
<string name="error_occurred_in_picking_images">Der opstod en fejl under udvælgelse af billeder</string>
@ -788,4 +789,7 @@
<string name="pending">Afventer</string>
<string name="failed">Mislykkedes</string>
<string name="could_not_load_place_data">Kunne ikke indlæse steddata</string>
<string name="red_pin">Dette sted har endnu ikke noget billede, så gå hen og tag et!</string>
<string name="green_pin">Dette sted har allerede et billede.</string>
<string name="grey_pin">Tjekker nu, om dette sted har et billede.</string>
</resources>

View file

@ -29,6 +29,7 @@
* Sujan
* Sushi
* Tacsipacsi
* TheRabbit22
* ThisCarthing
* Tobi 406
* TomatoCake
@ -802,4 +803,17 @@
<string name="multiple_files_depiction">Bitte beachte, dass bei einem Multiupload alle Bilder die gleichen Kategorien und Bezeichnungen erhalten. Sollten die Bilder keine gemeinsamen Bezeichnungen und Kategorien haben, führe bitte mehrere separate Uploads durch.</string>
<string name="multiple_files_depiction_header">Hinweis zu Mehrfach-Uploads</string>
<string name="nearby_wikitalk">Melde ein Problem mit diesem Datenobjekt an Wikidata</string>
<string name="please_enter_some_comments">Bitte gib einige Kommentare ein</string>
<string name="talk">Diskussion</string>
<string name="write_something_about_the_item">Schreibe etwas über das Objekt %1$s. Deine Beschreibung wird öffentlich sichtbar sein.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">%1$s existiert nicht mehr, es kann kein Foto mehr davon gemacht werden.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">%1$s ist jetzt an einem anderen Ort. Bitte gib den richtigen Ort und, wenn möglich, den Breiten- und Längengrad an.</string>
<string name="other_problem_or_information_please_explain_below">Sonstiges Problem oder Information (bitte unten erläutern).</string>
<string name="feedback_destination_note">Dein Feedback wird auf der folgenden Wiki-Seite veröffentlicht werden: &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\"&gt;Commons:Mobile app/Feedback&lt;/a&gt;</string>
<string name="are_you_sure_that_you_want_cancel_all_the_uploads">Möchtest du wirklich alle Uploads abbrechen?</string>
<string name="cancelling_all_the_uploads">Alle Uploads werden abgebrochen…</string>
<string name="uploads">Hochgeladene Dateien</string>
<string name="pending">Ausstehend</string>
<string name="failed">Fehlgeschlagen</string>
<string name="could_not_load_place_data">Ortsdaten konnten nicht geladen werden</string>
</resources>

View file

@ -22,6 +22,7 @@
* JenyxGym
* KATRINE1992
* Koreller
* Mahabarata
* McDutchie
* Melissadeba95
* Metroitendo
@ -516,6 +517,7 @@
<string name="no_notification">Vous navez aucune notification non lue</string>
<string name="no_read_notification">Vous navez aucune notification lue</string>
<string name="share_logs_using">Partager les journaux en utilisant</string>
<string name="check_your_email_inbox">Vérifiez votre boîte de réception</string>
<string name="menu_option_read">Afficher les lus</string>
<string name="menu_option_unread">Afficher les non lus</string>
<string name="error_occurred_in_picking_images">Une erreur est survenue lors de la sélection des images</string>
@ -814,7 +816,7 @@
<string name="nearby_wikitalk">Signaler un problème concernant cet élément à Wikidata</string>
<string name="please_enter_some_comments">Merci de saisir vos commentaires</string>
<string name="talk">Discussion</string>
<string name="write_something_about_the_item">Ecrivez quelque chose sur l\'article \"%1$s\". Il sera visible par le public\nAjouter une définition terminologique pour ce terme</string>
<string name="write_something_about_the_item">Écrivez quelque chose à propos de lélément « %1$s ». Il sera visible publiquement.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' n\'existe plus, aucune photo ne pourra jamais en être prise.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' se trouve à un endroit différent. Veuillez indiquer l\'endroit correct ci-dessous et, si possible, indiquez la latitude et la longitude correctes.</string>
<string name="other_problem_or_information_please_explain_below">Autre problème ou information (merci d\'expliquer ci-dessous).</string>
@ -824,5 +826,8 @@
<string name="uploads">Téléversements</string>
<string name="pending">En attente</string>
<string name="failed">Échec</string>
<string name="could_not_load_place_data">Ne peut pas supporter les données</string>
<string name="could_not_load_place_data">Les données du lieu n\'ont pas pu être chargées</string>
<string name="red_pin">Cet endroit n\'a pas encore de photo, allez en prendre une !</string>
<string name="green_pin">Cet endroit a déjà une photo.</string>
<string name="grey_pin">Je vérifie maintenant si cet endroit a une photo.</string>
</resources>

View file

@ -374,7 +374,7 @@
<string name="unable_to_display_nearest_place">A helymeghatározás nélkül nem használható ez a funkció.</string>
<string name="never_ask_again">Ne kérdezd meg többször</string>
<string name="display_location_permission_title">Helymeghatározási engedély</string>
<string name="achievements_fetch_failed" fuzzy="true">Valami hiba történt, nem sikerült az eredményeid betöltése</string>
<string name="achievements_fetch_failed">Valami hiba történt, nem sikerült az eredményeid betöltése</string>
<string name="display_campaigns">Kampányok megjelenítése</string>
<string name="display_campaigns_explanation">Folyamatban lévő kampányok megjelenítése</string>
<string name="in_app_camera_location_access_explanation">Engedélyezd az alkalmazás számára a helyszín lekérését, ha a kamera nem rögzíti azt! Egyes eszközök kamerái nem rögzítik a helyszínt. Közreműködésed hasznosabb, ha ilyen esetekben hagyod, hogy az alkalmazás lekérje és hozzárendelje a helyszínt. Ezt bármikor módosíthatod a Beállításokban</string>
@ -385,7 +385,7 @@
<string name="in_app_camera_location_permission_denied">Az alkalmazás helymeghatározási engedély hiányában nem rögzíti a helyszínt a felvételekkel együtt</string>
<string name="in_app_camera_location_unavailable">Az alkalmazás nem rögzít helyszínt a felvételekkel együtt, mivel a GPS ki van kapcsolva</string>
<string name="nearby_campaign_dismiss_message">Többé nem lesznek láthatók a kampányok. Ha akarod, visszakapcsolható a Beállításoknál.</string>
<string name="this_function_needs_network_connection" fuzzy="true">Ehhez a funkcióhoz hálózati kapcsolat szükséges, kérlek ellenőrizd az internetbeállításaidat.</string>
<string name="this_function_needs_network_connection">Ehhez a funkcióhoz hálózati kapcsolat szükséges. Kérlek ellenőrizd az internetbeállításaidat!</string>
<string name="error_processing_image">Hiba történt a kép feltöltése során. Próbáld meg újra!</string>
<string name="getting_edit_token">Szerkesztő token beszerzése</string>
<string name="check_category_adding_template">Kategóriaellenőrző sablon felhelyezése</string>
@ -454,9 +454,9 @@
<string name="delete_helper_show_deletion_message_if">Törlésre jelölve: %1$s.</string>
<string name="delete_helper_show_deletion_title_failed">Sikertelen</string>
<string name="delete_helper_show_deletion_message_else">Nem sikerült a törlés kérése.</string>
<string name="delete_helper_ask_spam_selfie" fuzzy="true">Egy szelfi</string>
<string name="delete_helper_ask_spam_blurry" fuzzy="true">Homályos</string>
<string name="delete_helper_ask_spam_nonsense" fuzzy="true">Nonszensz</string>
<string name="delete_helper_ask_spam_selfie">egy szelfi, amely egyetlen cikkben sem szerepel</string>
<string name="delete_helper_ask_spam_blurry">teljesen homályos</string>
<string name="delete_helper_ask_spam_nonsense">nonszensz, abszolút használhatatlan bármely cikkben is</string>
<string name="delete_helper_ask_reason_copyright_press_photo">Sajtófotó</string>
<string name="delete_helper_ask_reason_copyright_internet_photo">Random fénykép az Internetről</string>
<string name="delete_helper_ask_reason_copyright_logo">Logó</string>
@ -472,7 +472,7 @@
<string name="place_state_needs_photo">Fénykép szükséges</string>
<string name="place_type">Hely típusa:</string>
<string name="nearby_search_hint">Híd, múzeum, szálloda, stb.</string>
<string name="you_must_reset_your_passsword" fuzzy="true">A belépés nem sikerült, kérj új jelszót.</string>
<string name="you_must_reset_your_passsword">A belépés nem sikerült. Kérj új jelszót!</string>
<string name="setting_wallpaper_dialog_title">Beállítás háttérképnek</string>
<string name="setting_wallpaper_dialog_message">Beállítás háttérképnek. Kérem várjon...</string>
<string name="theme_default_name">Rendszerbeállítás követése</string>

View file

@ -765,8 +765,15 @@
<string name="nearby_wikitalk">Signalar a Wikidata un problema sur iste elemento</string>
<string name="please_enter_some_comments">Per favor insere alcun commentos</string>
<string name="talk">Discussion</string>
<string name="write_something_about_the_item">Scribe qualcosa sur le elemento %1$s. Isto essera visibile publicamente.</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">%1$s non existe plus, necun imagine pote jammais esser prendite de illo.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">%1$s es in un altere loco. Per favor specifica le loco correcte hic infra, e si possibile, indica le latitude e longitude correcte.</string>
<string name="other_problem_or_information_please_explain_below">Altere problema o information (per favor explica hic infra).</string>
<string name="feedback_destination_note">Tu retroaction apparera sur le sequente pagina wiki: &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\"&gt;Commons:Mobile app/Feedback&lt;/a&gt;</string>
<string name="are_you_sure_that_you_want_cancel_all_the_uploads">Es tu secur de voler cancellar tote le incargamentos?</string>
<string name="cancelling_all_the_uploads">Cancella tote le incargamentos…</string>
<string name="uploads">Incargamentos</string>
<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>
</resources>

View file

@ -760,4 +760,7 @@
<item quantity="one">%d immagine selezionata</item>
<item quantity="other">%d immagini selezionate</item>
</plurals>
<string name="red_pin">Questo posto non ha ancora una foto, scattane una!</string>
<string name="green_pin">Questo posto ha già una foto.</string>
<string name="grey_pin">Ora controlliamo se questo posto ha una foto.</string>
</resources>

Some files were not shown because too many files have changed in this diff Show more