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" kaptTest "androidx.databinding:databinding-compiler:8.0.2"
kaptAndroidTest "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 //OSMDroid
implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION") implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION")
@ -226,7 +226,7 @@ android {
excludes += ['META-INF/androidx.*'] excludes += ['META-INF/androidx.*']
} }
resources { 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 compose true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion '1.3.2' kotlinCompilerExtensionVersion '1.5.8'
} }
namespace 'fr.free.nrw.commons' namespace 'fr.free.nrw.commons'
lint { lint {

View file

@ -105,7 +105,7 @@ class AboutActivityTest {
fun testLaunchTranslate() { fun testLaunchTranslate() {
Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click()) Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(android.R.id.button1)).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( Intents.intended(
CoreMatchers.allOf( CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasAction(Intent.ACTION_VIEW),

View file

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

View file

@ -13,7 +13,9 @@
android:maxSdkVersion="29"/> android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> <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.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
@ -97,7 +99,6 @@
android:exported="true" android:exported="true"
android:hardwareAccelerated="false" android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize">
<intent-filter android:label="@string/intent_share_upload_label"> <intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -120,7 +121,7 @@
android:name=".contributions.MainActivity" android:name=".contributions.MainActivity"
android:configChanges="screenSize|keyboard|orientation" android:configChanges="screenSize|keyboard|orientation"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" /> />
<activity <activity
android:name=".settings.SettingsActivity" android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" /> android:label="@string/title_activity_settings" />

View file

@ -46,7 +46,7 @@ class BaseMarker {
val drawable: Drawable = context.resources.getDrawable(drawableResId) val drawable: Drawable = context.resources.getDrawable(drawableResId)
icon = icon =
if (drawable is BitmapDrawable) { if (drawable is BitmapDrawable) {
(drawable as BitmapDrawable).bitmap drawable.bitmap
} else { } else {
val bitmap = val bitmap =
Bitmap.createBitmap( 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 android.annotation.SuppressLint
import static org.acra.ReportField.ANDROID_VERSION; import android.app.Activity
import static org.acra.ReportField.APP_VERSION_CODE; import android.app.NotificationChannel
import static org.acra.ReportField.APP_VERSION_NAME; import android.app.NotificationManager
import static org.acra.ReportField.PHONE_MODEL; import android.content.Context
import static org.acra.ReportField.STACK_TRACE; import android.content.Intent
import static org.acra.ReportField.USER_COMMENT; import android.database.sqlite.SQLiteException
import android.os.Build
import android.annotation.SuppressLint; import android.os.Process
import android.app.Activity; import android.util.Log
import android.app.NotificationChannel; import androidx.multidex.MultiDexApplication
import android.app.NotificationManager; import com.facebook.drawee.backends.pipeline.Fresco
import android.content.Context; import com.facebook.imagepipeline.core.ImagePipelineConfig
import android.content.Intent; import fr.free.nrw.commons.auth.LoginActivity
import android.database.sqlite.SQLiteDatabase; import fr.free.nrw.commons.auth.SessionManager
import android.database.sqlite.SQLiteException; import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
import android.os.Build; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import android.os.Process; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
import android.util.Log; import fr.free.nrw.commons.category.CategoryDao
import androidx.annotation.NonNull; import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler
import androidx.multidex.MultiDexApplication; import fr.free.nrw.commons.concurrency.ThreadPoolService
import com.facebook.drawee.backends.pipeline.Fresco; import fr.free.nrw.commons.contributions.ContributionDao
import com.facebook.imagepipeline.core.ImagePipeline; import fr.free.nrw.commons.data.DBOpenHelper
import com.facebook.imagepipeline.core.ImagePipelineConfig; import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.auth.LoginActivity; import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table; import fr.free.nrw.commons.logging.FileLoggingTree
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; import fr.free.nrw.commons.logging.LogUtils
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher
import fr.free.nrw.commons.category.CategoryDao; import fr.free.nrw.commons.settings.Prefs
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler; import fr.free.nrw.commons.upload.FileUtils
import fr.free.nrw.commons.concurrency.ThreadPoolService; import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.contributions.ContributionDao; import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import fr.free.nrw.commons.di.ApplicationlessInjection; import io.reactivex.Completable
import fr.free.nrw.commons.kvstore.JsonKvStore; import io.reactivex.android.schedulers.AndroidSchedulers
import fr.free.nrw.commons.language.AppLanguageLookUpTable; import io.reactivex.internal.functions.Functions
import fr.free.nrw.commons.logging.FileLoggingTree; import io.reactivex.plugins.RxJavaPlugins
import fr.free.nrw.commons.logging.LogUtils; import io.reactivex.schedulers.Schedulers
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher; import org.acra.ACRA.init
import fr.free.nrw.commons.settings.Prefs; import org.acra.ReportField
import fr.free.nrw.commons.upload.FileUtils; import org.acra.annotation.AcraCore
import fr.free.nrw.commons.utils.ConfigUtils; import org.acra.annotation.AcraDialog
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar; import org.acra.annotation.AcraMailSender
import io.reactivex.Completable; import org.acra.data.StringFormat
import io.reactivex.android.schedulers.AndroidSchedulers; import timber.log.Timber
import io.reactivex.internal.functions.Functions; import timber.log.Timber.DebugTree
import io.reactivex.plugins.RxJavaPlugins; import java.io.File
import io.reactivex.schedulers.Schedulers; import javax.inject.Inject
import java.io.File; import javax.inject.Named
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;
@AcraCore( @AcraCore(
buildConfigClass = BuildConfig.class, buildConfigClass = BuildConfig::class,
resReportSendSuccessToast = R.string.crash_dialog_ok_toast, resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
reportFormat = StringFormat.KEY_VALUE_LIST, reportFormat = StringFormat.KEY_VALUE_LIST,
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL, reportContent = [ReportField.USER_COMMENT, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE]
STACK_TRACE}
) )
@AcraMailSender( @AcraMailSender(mailTo = "commons-app-android-private@googlegroups.com", reportAsFile = false)
mailTo = "commons-app-android-private@googlegroups.com",
reportAsFile = false
)
@AcraDialog( @AcraDialog(
resTheme = R.style.Theme_AppCompat_Dialog, resTheme = R.style.Theme_AppCompat_Dialog,
@ -83,137 +67,100 @@ import timber.log.Timber;
resCommentPrompt = R.string.crash_dialog_comment_prompt resCommentPrompt = R.string.crash_dialog_comment_prompt
) )
public class CommonsApplication extends MultiDexApplication { class CommonsApplication : 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;
@Inject @Inject
@Named("default_preferences") lateinit var sessionManager: SessionManager
JsonKvStore defaultPrefs;
@Inject @Inject
CommonsCookieJar cookieJar; lateinit var dbOpenHelper: DBOpenHelper
@Inject @Inject
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher; @field:Named("default_preferences")
lateinit var defaultPrefs: JsonKvStore
/**
* 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;
}
@Inject @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 * Used to declare and initialize various components and dependencies
*/ */
@Override override fun onCreate() {
public void onCreate() { super.onCreate()
super.onCreate();
INSTANCE = this; instance = this
ACRA.init(this); init(this)
ApplicationlessInjection ApplicationlessInjection
.getInstance(this) .getInstance(this)
.getCommonsApplicationComponent() .commonsApplicationComponent
.inject(this); .inject(this)
initTimber(); initTimber()
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) { 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) { if (null == defaultExifTagsSet) {
defaultExifTagsSet = new HashSet<>(); defaultExifTagsSet = HashSet()
} }
defaultExifTagsSet.add(getString(R.string.exif_tag_location)); defaultExifTagsSet.add(getString(R.string.exif_tag_location))
defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet); defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet)
} }
// Set DownsampleEnabled to True to downsample the image in case it's heavy // 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) .setNetworkFetcher(customOkHttpNetworkFetcher)
.setDownsampleEnabled(true) .setDownsampleEnabled(true)
.build(); .build()
try { try {
Fresco.initialize(this, config); Fresco.initialize(this, config)
} catch (Exception e) { } catch (e: Exception) {
Timber.e(e); Timber.e(e)
// TODO: Remove when we're able to initialize Fresco in test builds. // 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, // This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler. // 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 // 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. * Plants debug and file logging tree. Timber lets you plant your own logging trees.
*/ */
private void initTimber() { private fun initTimber() {
boolean isBeta = ConfigUtils.isBetaFlavour(); val isBeta = isBetaFlavour
String logFileName = val logFileName =
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs"; if (isBeta) "CommonsBetaAppLogs" else "CommonsAppLogs"
String logDirectory = LogUtils.getLogDirectory(); val logDirectory = LogUtils.getLogDirectory()
//Delete stale logs if they have exceeded the specified size //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, Log.VERBOSE,
logFileName, logFileName,
logDirectory, logDirectory,
1000, 1000,
getFileLoggingThreadPool()); fileLoggingThreadPool
)
Timber.plant(tree); Timber.plant(tree)
Timber.plant(new Timber.DebugTree()); Timber.plant(DebugTree())
} }
/** /**
@ -223,48 +170,27 @@ public class CommonsApplication extends MultiDexApplication {
* @param logFileName * @param logFileName
* @param logDirectory * @param logDirectory
*/ */
private void deleteStaleLogs(String logFileName, String logDirectory) { private fun deleteStaleLogs(logFileName: String, logDirectory: String) {
try { try {
File file = new File(logDirectory + "/zip/" + logFileName + ".zip"); val file = File("$logDirectory/zip/$logFileName.zip")
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs if (file.exists() && file.totalSpace > 1000000) { // In Kbs
file.delete(); file.delete()
} }
} catch (Exception e) { } catch (e: Exception) {
Timber.e(e); Timber.e(e)
} }
} }
public static boolean isRoboUnitTest() { private val fileLoggingThreadPool: ThreadPoolService
return "robolectric".equals(Build.FINGERPRINT); get() = ThreadPoolService.Builder("file-logging-thread")
}
private ThreadPoolService getFileLoggingThreadPool() {
return new ThreadPoolService.Builder("file-logging-thread")
.setPriority(Process.THREAD_PRIORITY_LOWEST) .setPriority(Process.THREAD_PRIORITY_LOWEST)
.setPoolSize(1) .setPoolSize(1)
.setExceptionHandler(new BackgroundPoolExceptionHandler()) .setExceptionHandler(BackgroundPoolExceptionHandler())
.build(); .build()
}
public static void createNotificationChannel(@NonNull Context context) { val userAgent: String
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { get() = ("Commons/" + this.getVersionNameWithSha()
NotificationManager manager = (NotificationManager) context + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE)
.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;
}
/** /**
* clears data of current application * clears data of current application
@ -273,88 +199,88 @@ public class CommonsApplication extends MultiDexApplication {
* @param logoutListener Implementation of interface LogoutListener * @param logoutListener Implementation of interface LogoutListener
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
public void clearApplicationData(Context context, LogoutListener logoutListener) { fun clearApplicationData(context: Context, logoutListener: LogoutListener) {
File cacheDirectory = context.getCacheDir(); val cacheDirectory = context.cacheDir
File applicationDirectory = new File(cacheDirectory.getParent()); val applicationDirectory = File(cacheDirectory.parent)
if (applicationDirectory.exists()) { if (applicationDirectory.exists()) {
String[] fileNames = applicationDirectory.list(); val fileNames = applicationDirectory.list()
for (String fileName : fileNames) { for (fileName in fileNames) {
if (!fileName.equals("lib")) { if (fileName != "lib") {
FileUtils.deleteFile(new File(applicationDirectory, fileName)); FileUtils.deleteFile(File(applicationDirectory, fileName))
} }
} }
} }
sessionManager.logout() sessionManager.logout()
.andThen(Completable.fromAction(() -> cookieJar.clear())) .andThen(Completable.fromAction { cookieJar.clear() })
.andThen(Completable.fromAction(() -> { .andThen(Completable.fromAction {
Timber.d("All accounts have been removed"); Timber.d("All accounts have been removed")
clearImageCache(); clearImageCache()
//TODO: fix preference manager //TODO: fix preference manager
defaultPrefs.clearAll(); defaultPrefs.clearAll()
defaultPrefs.putBoolean("firstrun", false); defaultPrefs.putBoolean("firstrun", false)
updateAllDatabases(); updateAllDatabases()
} })
))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(logoutListener::onLogoutComplete, Timber::e); .subscribe({ logoutListener.onLogoutComplete() }, { t: Throwable? -> Timber.e(t) })
} }
/** /**
* Clear all images cache held by Fresco * Clear all images cache held by Fresco
*/ */
private void clearImageCache() { private fun clearImageCache() {
ImagePipeline imagePipeline = Fresco.getImagePipeline(); val imagePipeline = Fresco.getImagePipeline()
imagePipeline.clearCaches(); imagePipeline.clearCaches()
} }
/** /**
* Deletes all tables and re-creates them. * Deletes all tables and re-creates them.
*/ */
private void updateAllDatabases() { private fun updateAllDatabases() {
dbOpenHelper.getReadableDatabase().close(); dbOpenHelper.readableDatabase.close()
SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); val db = dbOpenHelper.writableDatabase
CategoryDao.Table.onDelete(db); CategoryDao.Table.onDelete(db)
dbOpenHelper.deleteTable(db, dbOpenHelper.deleteTable(
CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions db,
DBOpenHelper.CONTRIBUTIONS_TABLE
) //Delete the contributions table in the existing db on older versions
try { try {
contributionDao.deleteAll(); contributionDao.deleteAll()
} catch (SQLiteException e) { } catch (e: SQLiteException) {
Timber.e(e); Timber.e(e)
} }
BookmarkPicturesDao.Table.onDelete(db); BookmarkPicturesDao.Table.onDelete(db)
BookmarkLocationsDao.Table.onDelete(db); BookmarkLocationsDao.Table.onDelete(db)
Table.onDelete(db); BookmarkItemsDao.Table.onDelete(db)
} }
/** /**
* Interface used to get log-out events * Interface used to get log-out events
*/ */
public interface LogoutListener { interface LogoutListener {
fun onLogoutComplete()
void onLogoutComplete();
} }
/** /**
* This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity * 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. * with relevant intent parameters. It does not perform the actual logout operation.
*/ */
public static class BaseLogoutListener implements CommonsApplication.LogoutListener { open class BaseLogoutListener : LogoutListener {
var ctx: Context
Context ctx; var loginMessage: String? = null
String loginMessage, userName; var userName: String? = null
/** /**
* Constructor for BaseLogoutListener. * Constructor for BaseLogoutListener.
* *
* @param ctx Application context * @param ctx Application context
*/ */
public BaseLogoutListener(final Context ctx) { constructor(ctx: Context) {
this.ctx = ctx; this.ctx = ctx
} }
/** /**
@ -364,28 +290,29 @@ public class CommonsApplication extends MultiDexApplication {
* @param loginMessage Message to be displayed on the login page * @param loginMessage Message to be displayed on the login page
* @param loginUsername Username to be pre-filled on the login page * @param loginUsername Username to be pre-filled on the login page
*/ */
public BaseLogoutListener(final Context ctx, final String loginMessage, constructor(
final String loginUsername) { ctx: Context, loginMessage: String?,
this.ctx = ctx; loginUsername: String?
this.loginMessage = loginMessage; ) {
this.userName = loginUsername; this.ctx = ctx
this.loginMessage = loginMessage
this.userName = loginUsername
} }
@Override override fun onLogoutComplete() {
public void onLogoutComplete() { Timber.d("Logout complete callback received.")
Timber.d("Logout complete callback received."); val loginIntent = Intent(ctx, LoginActivity::class.java)
final Intent loginIntent = new Intent(ctx, LoginActivity.class);
loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (loginMessage != null) { if (loginMessage != null) {
loginIntent.putExtra(loginMessageIntentKey, loginMessage); loginIntent.putExtra(LOGIN_MESSAGE_INTENT_KEY, loginMessage)
} }
if (userName != null) { 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 * 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. * 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 { class ActivityLogoutListener : BaseLogoutListener {
var activity: Activity
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 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. * @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) { constructor(activity: Activity, ctx: Context) : super(ctx) {
super(ctx); this.activity = activity
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 loginMessage Message to be displayed on the login page after logout.
* @param loginUsername Username to be pre-filled 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, constructor(
final String loginMessage, final String loginUsername) { activity: Activity, ctx: Context?,
super(activity, loginMessage, loginUsername); loginMessage: String?, loginUsername: String?
this.activity = activity; ) : super(activity, loginMessage, loginUsername) {
this.activity = activity
} }
@Override override fun onLogoutComplete() {
public void onLogoutComplete() { super.onLogoutComplete()
super.onLogoutComplete(); activity.finish()
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.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import org.osmdroid.tileprovider.tilesource.TileSourceFactory; import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
@ -301,7 +302,8 @@ public class LocationPickerActivity extends BaseActivity implements
modifyLocationButton = findViewById(R.id.modify_location); modifyLocationButton = findViewById(R.id.modify_location);
removeLocationButton = findViewById(R.id.remove_location); removeLocationButton = findViewById(R.id.remove_location);
showInMapButton = findViewById(R.id.show_in_map); 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); 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 android.os.Parcelable
import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.wikidata.model.page.PageTitle import fr.free.nrw.commons.wikidata.model.page.PageTitle
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
@ -124,6 +125,7 @@ class Media constructor(
* Gets the categories the file falls under. * Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings * @return file categories as an ArrayList of Strings
*/ */
@IgnoredOnParcel
var addedCategories: List<String>? = null var addedCategories: List<String>? = null
// TODO added categories should be removed. It is added for a short fix. On category update, // TODO added categories should be removed. It is added for a short fix. On category update,
// categories should be re-fetched instead // categories should be re-fetched instead

View file

@ -32,7 +32,7 @@ class ThanksClient
revisionId.toString(), // Rev revisionId.toString(), // Rev
null, // Log null, // Log
csrfTokenClient.getTokenBlocking(), // Token csrfTokenClient.getTokenBlocking(), // Token
CommonsApplication.getInstance().userAgent, // Source CommonsApplication.instance.userAgent, // Source
).map { mwThankPostResponse -> ).map { mwThankPostResponse ->
mwThankPostResponse.result?.success == 1 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.KeyEvent.KEYCODE_ENTER;
import static android.view.View.VISIBLE; import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static fr.free.nrw.commons.CommonsApplication.loginMessageIntentKey; import static fr.free.nrw.commons.CommonsApplication.LOGIN_MESSAGE_INTENT_KEY;
import static fr.free.nrw.commons.CommonsApplication.loginUsernameIntentKey; import static fr.free.nrw.commons.CommonsApplication.LOGIN_USERNAME_INTENT_KEY;
public class LoginActivity extends AccountAuthenticatorActivity { public class LoginActivity extends AccountAuthenticatorActivity {
@ -94,8 +94,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
binding = ActivityLoginBinding.inflate(getLayoutInflater()); binding = ActivityLoginBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
String message = getIntent().getStringExtra(loginMessageIntentKey); String message = getIntent().getStringExtra(LOGIN_MESSAGE_INTENT_KEY);
String username = getIntent().getStringExtra(loginUsernameIntentKey); String username = getIntent().getStringExtra(LOGIN_USERNAME_INTENT_KEY);
binding.loginUsername.addTextChangedListener(textWatcher); binding.loginUsername.addTextChangedListener(textWatcher);
binding.loginPassword.addTextChangedListener(textWatcher); binding.loginPassword.addTextChangedListener(textWatcher);

View file

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

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.bookmarks.locations; package fr.free.nrw.commons.bookmarks.locations;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
@ -146,6 +147,7 @@ public class BookmarkLocationsDao {
return false; return false;
} }
@SuppressLint("Range")
@NonNull @NonNull
Place fromCursor(final Cursor cursor) { Place fromCursor(final Cursor cursor) {
final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)), 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.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -33,6 +34,23 @@ public class BookmarkLocationsFragment extends DaggerFragment {
@Inject BookmarkLocationsDao bookmarkLocationDao; @Inject BookmarkLocationsDao bookmarkLocationDao;
@Inject CommonPlaceClickActions commonPlaceClickActions; @Inject CommonPlaceClickActions commonPlaceClickActions;
private PlaceAdapter adapter; 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>>() { private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override @Override
public void onActivityResult(Map<String, Boolean> result) { public void onActivityResult(Map<String, Boolean> result) {
@ -45,7 +63,7 @@ public class BookmarkLocationsFragment extends DaggerFragment {
contributionController.locationPermissionCallback.onLocationPermissionGranted(); contributionController.locationPermissionCallback.onLocationPermissionGranted();
} else { } else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else { } else {
contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied)); 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; return Unit.INSTANCE;
}, },
commonPlaceClickActions, commonPlaceClickActions,
inAppCameraLocationPermissionLauncher inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
); );
binding.listView.setAdapter(adapter); 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 @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();

View file

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

View file

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

View file

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

View file

@ -7,6 +7,7 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
@ -64,10 +65,11 @@ public class ContributionController {
* Check for permissions and initiate camera click * Check for permissions and initiate camera click
*/ */
public void initiateCameraPick(Activity activity, public void initiateCameraPick(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true); boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true);
if (!useExtStorage) { if (!useExtStorage) {
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
return; return;
} }
@ -75,12 +77,12 @@ public class ContributionController {
() -> { () -> {
if (defaultKvStore.getBoolean("inAppCameraFirstRun")) { if (defaultKvStore.getBoolean("inAppCameraFirstRun")) {
defaultKvStore.putBoolean("inAppCameraFirstRun", false); defaultKvStore.putBoolean("inAppCameraFirstRun", false);
askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher); askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher, resultLauncher);
} else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) { } else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) {
createDialogsAndHandleLocationPermissions(activity, createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher); inAppCameraLocationPermissionLauncher, resultLauncher);
} else { } else {
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
} }
}, },
R.string.storage_permission_title, R.string.storage_permission_title,
@ -94,7 +96,8 @@ public class ContributionController {
* @param activity * @param activity
*/ */
private void createDialogsAndHandleLocationPermissions(Activity activity, private void createDialogsAndHandleLocationPermissions(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
locationPermissionCallback = new LocationPermissionCallback() { locationPermissionCallback = new LocationPermissionCallback() {
@Override @Override
public void onLocationPermissionDenied(String toastMessage) { public void onLocationPermissionDenied(String toastMessage) {
@ -103,16 +106,16 @@ public class ContributionController {
toastMessage, toastMessage,
Toast.LENGTH_LONG Toast.LENGTH_LONG
).show(); ).show();
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
} }
@Override @Override
public void onLocationPermissionGranted() { public void onLocationPermissionGranted() {
if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
showLocationOffDialog(activity, R.string.in_app_camera_needs_location, 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 { } else {
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
} }
} }
}; };
@ -135,9 +138,10 @@ public class ContributionController {
* @param activity Activity reference * @param activity Activity reference
* @param dialogTextResource Resource id of text to be shown in dialog * @param dialogTextResource Resource id of text to be shown in dialog
* @param toastTextResource Resource id of text to be shown in toast * @param toastTextResource Resource id of text to be shown in toast
* @param resultLauncher
*/ */
private void showLocationOffDialog(Activity activity, int dialogTextResource, private void showLocationOffDialog(Activity activity, int dialogTextResource,
int toastTextResource) { int toastTextResource, ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil DialogUtil
.showAlertDialog(activity, .showAlertDialog(activity,
activity.getString(R.string.ask_to_turn_location_on), activity.getString(R.string.ask_to_turn_location_on),
@ -148,20 +152,21 @@ public class ContributionController {
() -> { () -> {
Toast.makeText(activity, activity.getString(toastTextResource), Toast.makeText(activity, activity.getString(toastTextResource),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
} }
); );
} }
public void handleShowRationaleFlowCameraLocation(Activity activity, public void handleShowRationaleFlowCameraLocation(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title), DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title),
activity.getString(R.string.in_app_camera_location_permission_rationale), activity.getString(R.string.in_app_camera_location_permission_rationale),
activity.getString(android.R.string.ok), activity.getString(android.R.string.ok),
activity.getString(android.R.string.cancel), activity.getString(android.R.string.cancel),
() -> { () -> {
createDialogsAndHandleLocationPermissions(activity, createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher); inAppCameraLocationPermissionLauncher, resultLauncher);
}, },
() -> locationPermissionCallback.onLocationPermissionDenied( () -> locationPermissionCallback.onLocationPermissionDenied(
activity.getString(R.string.in_app_camera_location_permission_denied)), activity.getString(R.string.in_app_camera_location_permission_denied)),
@ -181,7 +186,8 @@ public class ContributionController {
* @param activity * @param activity
*/ */
private void askUserToAllowLocationAccess(Activity activity, private void askUserToAllowLocationAccess(Activity activity,
ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher,
ActivityResultLauncher<Intent> resultLauncher) {
DialogUtil.showAlertDialog(activity, DialogUtil.showAlertDialog(activity,
activity.getString(R.string.in_app_camera_location_permission_title), activity.getString(R.string.in_app_camera_location_permission_title),
activity.getString(R.string.in_app_camera_location_access_explanation), activity.getString(R.string.in_app_camera_location_access_explanation),
@ -190,12 +196,12 @@ public class ContributionController {
() -> { () -> {
defaultKvStore.putBoolean("inAppCameraLocationPref", true); defaultKvStore.putBoolean("inAppCameraLocationPref", true);
createDialogsAndHandleLocationPermissions(activity, createDialogsAndHandleLocationPermissions(activity,
inAppCameraLocationPermissionLauncher); inAppCameraLocationPermissionLauncher, resultLauncher);
}, },
() -> { () -> {
ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied); ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied);
defaultKvStore.putBoolean("inAppCameraLocationPref", false); defaultKvStore.putBoolean("inAppCameraLocationPref", false);
initiateCameraUpload(activity); initiateCameraUpload(activity, resultLauncher);
}, },
null, null,
true); true);
@ -204,18 +210,18 @@ public class ContributionController {
/** /**
* Initiate gallery picker * Initiate gallery picker
*/ */
public void initiateGalleryPick(final Activity activity, final boolean allowMultipleUploads) { public void initiateGalleryPick(final Activity activity, ActivityResultLauncher<Intent> resultLauncher, final boolean allowMultipleUploads) {
initiateGalleryUpload(activity, allowMultipleUploads); initiateGalleryUpload(activity, resultLauncher, allowMultipleUploads);
} }
/** /**
* Initiate gallery picker with permission * Initiate gallery picker with permission
*/ */
public void initiateCustomGalleryPickWithPermission(final Activity activity) { public void initiateCustomGalleryPickWithPermission(final Activity activity, ActivityResultLauncher<Intent> resultLauncher) {
setPickerConfiguration(activity, true); setPickerConfiguration(activity, true);
PermissionUtils.checkPermissionsAndPerformAction(activity, PermissionUtils.checkPermissionsAndPerformAction(activity,
() -> FilePicker.openCustomSelector(activity, 0), () -> FilePicker.openCustomSelector(activity, resultLauncher, 0),
R.string.storage_permission_title, R.string.storage_permission_title,
R.string.write_storage_permission_rationale, R.string.write_storage_permission_rationale,
PermissionUtils.PERMISSIONS_STORAGE); PermissionUtils.PERMISSIONS_STORAGE);
@ -225,12 +231,10 @@ public class ContributionController {
/** /**
* Open chooser for gallery uploads * Open chooser for gallery uploads
*/ */
private void initiateGalleryUpload(final Activity activity, private void initiateGalleryUpload(final Activity activity, ActivityResultLauncher<Intent> resultLauncher,
final boolean allowMultipleUploads) { final boolean allowMultipleUploads) {
setPickerConfiguration(activity, allowMultipleUploads); setPickerConfiguration(activity, allowMultipleUploads);
boolean openDocumentIntentPreferred = defaultKvStore.getBoolean( FilePicker.openGallery(activity, resultLauncher, 0, isDocumentPhotoPickerPreferred());
"openDocumentPhotoPickerPref", true);
FilePicker.openGallery(activity, 0, openDocumentIntentPreferred);
} }
/** /**
@ -247,22 +251,43 @@ public class ContributionController {
/** /**
* Initiate camera upload by opening camera * Initiate camera upload by opening camera
*/ */
private void initiateCameraUpload(Activity activity) { private void initiateCameraUpload(Activity activity, ActivityResultLauncher<Intent> resultLauncher) {
setPickerConfiguration(activity, false); setPickerConfiguration(activity, false);
if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) { if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) {
locationBeforeImageCapture = locationManager.getLastLocation(); locationBeforeImageCapture = locationManager.getLastLocation();
} }
isInAppCameraUpload = true; 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. * Attaches callback for file picker.
*/ */
public void handleActivityResult(Activity activity, int requestCode, int resultCode, public void handleActivityResultWithCallback(Activity activity, FilePicker.HandleActivityResult handleActivityResult) {
Intent data) {
FilePicker.handleActivityResult(requestCode, resultCode, data, activity, handleActivityResult.onHandleActivityResult(new DefaultCallback() {
new DefaultCallback() {
@Override @Override
public void onCanceled(final ImageSource source, final int type) { 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.Manifest.permission;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -20,6 +21,7 @@ import android.widget.LinearLayout;
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@ -96,6 +98,30 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
private int contributionsSize; private int contributionsSize;
private String userName; 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( private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new RequestMultiplePermissions(), new RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() { new ActivityResultCallback<Map<String, Boolean>>() {
@ -111,7 +137,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
} else { } else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller.handleShowRationaleFlowCameraLocation(getActivity(), controller.handleShowRationaleFlowCameraLocation(getActivity(),
inAppCameraLocationPermissionLauncher); inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else { } else {
controller.locationPermissionCallback.onLocationPermissionDenied( controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString( getActivity().getString(
@ -322,7 +348,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
private void setListeners() { private void setListeners() {
binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
binding.fabCamera.setOnClickListener(view -> { binding.fabCamera.setOnClickListener(view -> {
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher); controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
animateFAB(isFabOpen); animateFAB(isFabOpen);
}); });
binding.fabCamera.setOnLongClickListener(view -> { binding.fabCamera.setOnLongClickListener(view -> {
@ -330,7 +356,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
return true; return true;
}); });
binding.fabGallery.setOnClickListener(view -> { binding.fabGallery.setOnClickListener(view -> {
controller.initiateGalleryPick(getActivity(), true); controller.initiateGalleryPick(getActivity(), galleryPickLauncherForResult, true);
animateFAB(isFabOpen); animateFAB(isFabOpen);
}); });
binding.fabGallery.setOnLongClickListener(view -> { binding.fabGallery.setOnLongClickListener(view -> {
@ -343,7 +369,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* Launch Custom Selector. * Launch Custom Selector.
*/ */
protected void launchCustomSelector() { protected void launchCustomSelector() {
controller.initiateCustomGalleryPickWithPermission(getActivity()); controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult);
animateFAB(isFabOpen); 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 @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();

View file

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

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons.customselector.helper package fr.free.nrw.commons.customselector.helper
import android.app.Activity
import android.content.ContentUris import android.content.ContentUris
import android.content.Context import android.content.Context
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
@ -8,9 +7,10 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.provider.MediaStore import android.provider.MediaStore
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.filepicker.Constants
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -22,15 +22,20 @@ object FolderDeletionHelper {
* @param context The context used to show the confirmation dialog and manage deletion. * @param context The context used to show the confirmation dialog and manage deletion.
* @param folder The folder to be deleted. * @param folder The folder to be deleted.
* @param onDeletionComplete Callback invoked with `true` if the folder was * @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. * 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 itemCount = countItemsInFolder(context, folder)
val folderPath = folder.absolutePath val folderPath = folder.absolutePath
//don't show this dialog on API 30+, it's handled automatically using MediaStore //don't show this dialog on API 30+, it's handled automatically using MediaStore
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val success = deleteFolderMain(context, folder) val success = deleteFolderMain(context, folder, trashFolderLauncher)
onDeletionComplete(success) onDeletionComplete(success)
} else { } else {
@ -40,7 +45,7 @@ object FolderDeletionHelper {
.setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ -> .setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ ->
//proceed with deletion if user confirms //proceed with deletion if user confirms
val success = deleteFolderMain(context, folder) val success = deleteFolderMain(context, folder, trashFolderLauncher)
onDeletionComplete(success) onDeletionComplete(success)
} }
.setNegativeButton(context.getString(R.string.custom_selector_cancel)) { dialog, _ -> .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 context The context used to manage storage operations.
* @param folder The folder to delete. * @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. * @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 { return when {
//for API 30 and above, use MediaStore //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) //for API 29 ('requestLegacyExternalStorage' is set to true in Manifest)
// and below use file system // and below use file system
@ -75,9 +85,14 @@ object FolderDeletionHelper {
* *
* @param context The context used to access the content resolver. * @param context The context used to access the content resolver.
* @param folder The folder whose contents are to be moved to the trash. * @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. * @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 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
@ -111,11 +126,8 @@ object FolderDeletionHelper {
if (urisToTrash.isNotEmpty()) { if (urisToTrash.isNotEmpty()) {
try { try {
val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true) val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true)
(context as? Activity)?.startIntentSenderForResult( val intentSenderRequest = IntentSenderRequest.Builder(trashRequest.intentSender).build()
trashRequest.intentSender, trashFolderLauncher.launch(intentSenderRequest)
Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE,
null, 0, 0, 0
)
return true return true
} catch (e: SecurityException) { } catch (e: SecurityException) {
Timber.tag("DeleteFolder").e(context.getString(R.string.custom_selector_error_trashing_folder_contents, e.message)) 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.ImageButton
import android.widget.PopupMenu import android.widget.PopupMenu
import android.widget.TextView 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.BorderStroke
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth 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.NotForUploadStatus
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants 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.helper.FolderDeletionHelper
import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.FolderClickListener
import fr.free.nrw.commons.customselector.listeners.ImageSelectListener 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.ActivityCustomSelectorBinding
import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding
import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding 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.media.ZoomableActivity
import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper import fr.free.nrw.commons.upload.FileUtilsWrapper
@ -65,7 +66,6 @@ import java.io.File
import java.lang.Integer.max import java.lang.Integer.max
import javax.inject.Inject import javax.inject.Inject
/** /**
* Custom Selector Activity. * Custom Selector Activity.
*/ */
@ -155,7 +155,16 @@ class CustomSelectorActivity :
*/ */
private var showOverflowMenu = false 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)
}
/** /**
@ -184,9 +193,9 @@ class CustomSelectorActivity :
} }
}, },
modifier = modifier =
Modifier Modifier
.padding(vertical = 8.dp, horizontal = 4.dp) .padding(vertical = 8.dp, horizontal = 4.dp)
.fillMaxWidth(), .fillMaxWidth(),
) )
} }
val view = binding.root val view = binding.root
@ -215,7 +224,6 @@ class CustomSelectorActivity :
} }
} }
override fun onRequestPermissionsResult( override fun onRequestPermissionsResult(
requestCode: Int, requestCode: Int,
permissions: Array<out String>, 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 * When data will be send from full screen mode, it will be passed to fragment
*/ */
override fun onActivityResult( private fun onFullScreenDataReceived(result: ActivityResult){
requestCode: Int, if (result.resultCode == Activity.RESULT_OK) {
resultCode: Int,
data: Intent?,
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE &&
resultCode == Activity.RESULT_OK
) {
val selectedImages: ArrayList<Image> = val selectedImages: ArrayList<Image> =
data!! result.data!!
.getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!! .getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!!
val shouldRefresh = data.getBooleanExtra(SHOULD_REFRESH, false) viewModel?.selectedImages?.value = selectedImages
imageFragment?.passSelectedImages(selectedImages, shouldRefresh)
} }
}
if (requestCode == Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE && private fun onDeleteFolderResultReceived(result: ActivityResult){
resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK){
FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName)
navigateToCustomSelector() navigateToCustomSelector()
} }
} }
/** /**
* Show Custom Selector Welcome Dialog. * 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() { private fun setUpToolbar() {
val back: ImageButton = findViewById(R.id.back) val back: ImageButton = findViewById(R.id.back)
@ -489,9 +486,9 @@ class CustomSelectorActivity :
return return
} }
FolderDeletionHelper.confirmAndDeleteFolder(this, folder) { success -> FolderDeletionHelper.confirmAndDeleteFolder(this, folder, startForFolderDeletionResult) { success ->
if (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) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName)
navigateToCustomSelector() navigateToCustomSelector()
@ -542,9 +539,8 @@ class CustomSelectorActivity :
override fun onFolderClick( override fun onFolderClick(
folderId: Long, folderId: Long,
folderName: String, folderName: String,
lastItemId: Long lastItemId: Long,
) { ) {
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId)) .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. * override Selected Images Change, update view model selected images and change UI.
*/ */
@ -629,7 +624,7 @@ class CustomSelectorActivity :
selectedImages, selectedImages,
) )
intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId) intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId)
startActivityForResult(intent, Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE) startForResult.launch(intent)
} }
/** /**
@ -738,15 +733,13 @@ fun partialStorageAccessIndicator(
OutlinedCard( OutlinedCard(
modifier = modifier, modifier = modifier,
colors = colors =
CardDefaults.cardColors( CardDefaults.cardColors(
containerColor = colorResource(R.color.primarySuperLightColor), containerColor = colorResource(R.color.primarySuperLightColor),
), ),
border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)), border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
) { ) {
Row(modifier = Modifier Row(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
.padding(16.dp)
.fillMaxWidth()) {
Text( Text(
text = "You've given access to a select number of photos", text = "You've given access to a select number of photos",
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
@ -755,9 +748,9 @@ fun partialStorageAccessIndicator(
onClick = onManage, onClick = onManage,
modifier = Modifier.align(Alignment.Bottom), modifier = Modifier.align(Alignment.Bottom),
colors = colors =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(
containerColor = colorResource(R.color.primaryColor), containerColor = colorResource(R.color.primaryColor),
), ),
shape = RoundedCornerShape(8.dp), shape = RoundedCornerShape(8.dp),
) { ) {
Text( Text(
@ -779,9 +772,9 @@ fun partialStorageAccessIndicatorPreview() {
isVisible = true, isVisible = true,
onManage = {}, onManage = {},
modifier = modifier =
Modifier Modifier
.padding(vertical = 8.dp, horizontal = 4.dp) .padding(vertical = 8.dp, horizontal = 4.dp)
.fillMaxWidth(), .fillMaxWidth(),
) )
} }
} }

View file

@ -279,11 +279,17 @@ class ImageFragment :
filteredImages = ImageHelper.filterImages(images, bucketId) filteredImages = ImageHelper.filterImages(images, bucketId)
allImages = ArrayList(filteredImages) allImages = ArrayList(filteredImages)
imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions)
viewModel?.selectedImages?.value?.let { selectedImages ->
imageAdapter.setSelectedImages(selectedImages)
}
imageAdapter.notifyDataSetChanged()
selectorRV?.let { selectorRV?.let {
it.visibility = View.VISIBLE it.visibility = View.VISIBLE
lastItemId?.let { pos -> if (switch?.isChecked == false) {
(it.layoutManager as GridLayoutManager) lastItemId?.let { pos ->
.scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos)) (it.layoutManager as GridLayoutManager)
.scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos))
}
} }
} }
} else { } else {
@ -382,14 +388,6 @@ class ImageFragment :
selectedImages: ArrayList<Image>, selectedImages: ArrayList<Image>,
shouldRefresh: Boolean, 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 fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.checkWhetherFileExistsOnCommonsUsingSHA1
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Calendar import java.util.Calendar

View file

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

View file

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

View file

@ -22,6 +22,7 @@ import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ActivityUtils; import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
@ -112,13 +113,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
mobileRootFragment = new ExploreListRootFragment(mobileArguments); mobileRootFragment = new ExploreListRootFragment(mobileArguments);
mapRootFragment = new ExploreMapRootFragment(mapArguments); mapRootFragment = new ExploreMapRootFragment(mapArguments);
fragmentList.add(featuredRootFragment); 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); 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); 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(); ((MainActivity)getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);

View file

@ -28,6 +28,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
@ -95,11 +96,11 @@ public class SearchActivity extends BaseActivity
searchDepictionsFragment = new SearchDepictionsFragment(); searchDepictionsFragment = new SearchDepictionsFragment();
searchCategoryFragment= new SearchCategoryFragment(); searchCategoryFragment= new SearchCategoryFragment();
fragmentList.add(searchMediaFragment); 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); 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); 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.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();

View file

@ -18,6 +18,6 @@ class CategoriesMediaFragment : PageableMediaFragment() {
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
) { ) {
super.onViewCreated(view, savedInstanceState) 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?, savedInstanceState: Bundle?,
) { ) {
super.onViewCreated(view, savedInstanceState) 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?, savedInstanceState: Bundle?,
) { ) {
super.onViewCreated(view, savedInstanceState) 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 override val injectedPresenter
get() = presenter 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( override fun onViewCreated(
view: View, view: View,
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
) { ) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!) onQueryUpdated(requireArguments().getString("entityId")!!)
} }
} }

View file

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

View file

@ -13,13 +13,13 @@ class ParentDepictionsFragment : PageableDepictionsFragment() {
override val injectedPresenter override val injectedPresenter
get() = presenter 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( override fun onViewCreated(
view: View, view: View,
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
) { ) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
onQueryUpdated(arguments!!.getString("entityId")!!) onQueryUpdated(requireArguments().getString("entityId")!!)
} }
} }

View file

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

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.explore.recentsearches; package fr.free.nrw.commons.explore.recentsearches;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
@ -178,6 +179,7 @@ public class RecentSearchesDao {
* @return RecentSearch object * @return RecentSearch object
*/ */
@NonNull @NonNull
@SuppressLint("Range")
RecentSearch fromCursor(Cursor cursor) { RecentSearch fromCursor(Cursor cursor) {
// Hardcoding column positions! // Hardcoding column positions!
return new RecentSearch( 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.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.SearchActivity; import fr.free.nrw.commons.explore.SearchActivity;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
@ -90,7 +91,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
private void showDeleteAlertDialog(@NonNull final Context context, final int position) { private void showDeleteAlertDialog(@NonNull final Context context, final int position) {
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setMessage(R.string.delete_search_dialog) .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))) ((dialog, which) -> setDeletePositiveButton(context, dialog, position)))
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.create() .create()

View file

@ -4,23 +4,12 @@ public interface Constants {
String DEFAULT_FOLDER_NAME = "CommonsContributions"; String DEFAULT_FOLDER_NAME = "CommonsContributions";
/** /**
* Provides the request codes utilised by the FilePicker * Provides the request codes for permission handling
*/ */
interface RequestCodes { interface RequestCodes {
int LOCATION = 1; int LOCATION = 1;
int STORAGE = 2; 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.net.Uri;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultLauncher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager; 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 * @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); Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred);
activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY); resultLauncher.launch(intent);
} }
/** /**
* Opens Custom Selector * 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); 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 * 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); Intent intent = createCameraForImageIntent(activity, type);
activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE); resultLauncher.launch(intent);
} }
@Nullable @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) { public static List<UploadableFile> handleExternalImagesPicked(Intent data, Activity activity) {
try { try {
return getFilesFromGalleryPictures(data, activity); return getFilesFromGalleryPictures(data, activity);
@ -241,18 +202,22 @@ public class FilePicker implements Constants {
return intent; 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) {
try { if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){
Uri photoPath = data.getData(); try {
UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); Uri photoPath = result.getData().getData();
callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath);
callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) {
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
}
} catch (Exception e) {
e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
} }
} catch (Exception e) { } else {
e.printStackTrace(); callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity));
} }
} }
@ -260,14 +225,18 @@ public class FilePicker implements Constants {
* onPictureReturnedFromCustomSelector. * onPictureReturnedFromCustomSelector.
* Retrieve and forward the images to upload wizard through callback. * 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) {
try { if(result.getResultCode() == Activity.RESULT_OK){
List<UploadableFile> files = getFilesFromCustomSelector(data, activity); try {
callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); List<UploadableFile> files = getFilesFromCustomSelector(result.getData(), activity);
} catch (Exception e) { callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
e.printStackTrace(); } catch (Exception e) {
callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); e.printStackTrace();
} callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity));
}
} else {
callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity));
}
} }
/** /**
@ -290,13 +259,17 @@ public class FilePicker implements Constants {
return files; 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) {
try { if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){
List<UploadableFile> files = getFilesFromGalleryPictures(data, activity); try {
callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); List<UploadableFile> files = getFilesFromGalleryPictures(result.getData(), activity);
} catch (Exception e) { callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity));
e.printStackTrace(); } catch (Exception e) {
callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity));
}
} else{
callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity));
} }
} }
@ -322,69 +295,40 @@ public class FilePicker implements Constants {
return files; return files;
} }
private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) { public static void onPictureReturnedFromCamera(ActivityResult activityResult, Activity activity, @NonNull FilePicker.Callbacks callbacks) {
try { if(activityResult.getResultCode() == Activity.RESULT_OK){
String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); try {
if (!TextUtils.isEmpty(lastImageUri)) { String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null);
revokeWritePermission(activity, Uri.parse(lastImageUri)); if (!TextUtils.isEmpty(lastImageUri)) {
} revokeWritePermission(activity, Uri.parse(lastImageUri));
UploadableFile photoFile = FilePicker.takenCameraPicture(activity);
List<UploadableFile> files = new ArrayList<>();
files.add(photoFile);
if (photoFile == null) {
Exception e = new IllegalStateException("Unable to get the picture returned from camera");
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
} else {
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
} }
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); UploadableFile photoFile = FilePicker.takenCameraPicture(activity);
} List<UploadableFile> files = new ArrayList<>();
files.add(photoFile);
PreferenceManager.getDefaultSharedPreferences(activity) if (photoFile == null) {
Exception e = new IllegalStateException("Unable to get the picture returned from camera");
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
} else {
if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) {
PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile));
}
callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity));
}
PreferenceManager.getDefaultSharedPreferences(activity)
.edit() .edit()
.remove(KEY_LAST_CAMERA_PHOTO) .remove(KEY_LAST_CAMERA_PHOTO)
.remove(KEY_PHOTO_URI) .remove(KEY_PHOTO_URI)
.apply(); .apply();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); 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));
} }
} else {
UploadableFile photoFile = FilePicker.takenCameraVideo(activity); callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(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));
} }
} }
@ -404,4 +348,8 @@ public class FilePicker implements Constants {
void onCanceled(FilePicker.ImageSource source, int type); 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; 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.GONE;
import static android.view.View.VISIBLE; 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_NEEDING_CATEGORIES;
import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED; 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.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.description.EditDescriptionConstants.WIKITEXT;
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION;
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources; import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
@ -112,8 +109,6 @@ import timber.log.Timber;
public class MediaDetailFragment extends CommonsDaggerSupportFragment implements public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
CategoryEditHelper.Callback { 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"; private static final String IMAGE_BACKGROUND_COLOR = "image_background_color";
static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0; static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0;
@ -277,6 +272,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
if (!sessionManager.isUserLoggedIn()) { if (!sessionManager.isUserLoggedIn()) {
binding.categoryEditButton.setVisibility(GONE); 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")){ if(applicationKvStore.getBoolean("login_skipped")){
@ -405,7 +406,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
} }
); );
binding.progressBarEdit.setVisibility(GONE); binding.progressBarEdit.setVisibility(GONE);
binding.descriptionEdit.setVisibility(VISIBLE);
} }
@Override @Override
@ -605,8 +605,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
// Check if the presented category is about need of category // Check if the presented category is about need of category
if (categoriesPresent) { if (categoriesPresent) {
for (String category : media.getCategories()) { for (String category : media.getCategories()) {
if (category.toLowerCase().contains(CATEGORY_NEEDING_CATEGORIES) || if (category.toLowerCase(Locale.ROOT).contains(CATEGORY_NEEDING_CATEGORIES) ||
category.toLowerCase().contains(CATEGORY_UNCATEGORISED)) { category.toLowerCase(Locale.ROOT).contains(CATEGORY_UNCATEGORISED)) {
categoriesPresent = false; categoriesPresent = false;
} }
break; break;
@ -683,7 +683,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
// Stick in a filler element. // Stick in a filler element.
allCategories.add(getString(R.string.detail_panel_cats_none)); allCategories.add(getString(R.string.detail_panel_cats_none));
} }
binding.categoryEditButton.setVisibility(VISIBLE); if(sessionManager.isUserLoggedIn()) {
binding.categoryEditButton.setVisibility(VISIBLE);
}
rebuildCatList(allCategories); rebuildCatList(allCategories);
} }
@ -1065,81 +1067,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
return captionList; 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 * Adds caption to the map and updates captions
* @param mediaDetail UploadMediaDetail * @param mediaDetail UploadMediaDetail

View file

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

View file

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

View file

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

View file

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

View file

@ -4,7 +4,6 @@ import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.OnConflictStrategy; import androidx.room.OnConflictStrategy;
import androidx.room.Query; import androidx.room.Query;
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable; import io.reactivex.Completable;
/** /**
@ -38,8 +37,21 @@ public abstract class PlaceDao {
*/ */
public Completable save(final Place place) { public Completable save(final Place place) {
return Completable return Completable
.fromAction(() -> { .fromAction(() -> saveSynchronous(place));
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; package fr.free.nrw.commons.nearby;
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable; import io.reactivex.Completable;
import javax.inject.Inject; import javax.inject.Inject;
@ -36,4 +35,8 @@ public class PlacesLocalDataSource {
public Completable savePlace(Place place) { public Completable savePlace(Place place) {
return placeDao.save(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.contributions.Contribution;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Completable; import io.reactivex.Completable;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@ -38,4 +39,13 @@ public class PlacesRepository {
return localDataSource.fetchPlace(entityID); 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, lat,
lng, lng,
) )
} as Callable<SingleSource<Boolean?>>, },
).subscribeOn(Schedulers.io()) ).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ aBoolean: Boolean? -> .subscribe({ aBoolean: Boolean? ->

View file

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

View file

@ -43,15 +43,18 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions;
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; import androidx.activity.result.contract.ActivityResultContracts.RequestPermission;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog.Builder; import androidx.appcompat.app.AlertDialog.Builder;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.DividerItemDecoration; 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.SystemThemeUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import fr.free.nrw.commons.wikidata.WikidataEditListener; import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.Completable;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable; import io.reactivex.disposables.Disposable;
@ -218,9 +222,36 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private LatLng updatedLatLng; private LatLng updatedLatLng;
private boolean searchable; private boolean searchable;
private ConstraintLayout nearbyLegend;
private GridLayoutManager gridLayoutManager; private GridLayoutManager gridLayoutManager;
private List<BottomSheetItem> dataList; private List<BottomSheetItem> dataList;
private BottomSheetAdapter bottomSheetAdapter; 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( private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new RequestMultiplePermissions(), new RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() { new ActivityResultCallback<Map<String, Boolean>>() {
@ -236,7 +267,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} else { } else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller.handleShowRationaleFlowCameraLocation(getActivity(), controller.handleShowRationaleFlowCameraLocation(getActivity(),
inAppCameraLocationPermissionLauncher); inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else { } else {
controller.locationPermissionCallback.onLocationPermissionDenied( controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString( getActivity().getString(
@ -302,6 +333,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
progressDialog.setCancelable(false); progressDialog.setCancelable(false);
progressDialog.setMessage("Saving in progress..."); progressDialog.setMessage("Saving in progress...");
setHasOptionsMenu(true); setHasOptionsMenu(true);
// Inflate the layout for this fragment // Inflate the layout for this fragment
return view; return view;
@ -311,9 +343,21 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
public void onCreateOptionsMenu(@NonNull final Menu menu, public void onCreateOptionsMenu(@NonNull final Menu menu,
@NonNull final MenuInflater inflater) { @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.nearby_fragment_menu, menu); 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 listMenu = menu.findItem(R.id.list_sheet);
MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx);
MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); 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() { listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@Override @Override
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
@ -362,6 +406,16 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} }
locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager,
this); 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); presenter.attachView(this);
isPermissionDenied = false; isPermissionDenied = false;
recenterToUserLocation = false; recenterToUserLocation = false;
@ -555,7 +609,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return Unit.INSTANCE; return Unit.INSTANCE;
}, },
commonPlaceClickActions, commonPlaceClickActions,
inAppCameraLocationPermissionLauncher inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
); );
binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter); 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() { private void savePlacesAsKML() {
final Observable<String> savePlacesObservable = Observable final Observable<String> savePlacesObservable = Observable
.fromCallable(() -> nearbyController .fromCallable(() -> nearbyController
@ -2186,7 +2284,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCamera.isShown()) { if (binding.fabCamera.isShown()) {
Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); Timber.d("Camera button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace); 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()); Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace); storeSharedPrefs(selectedPlace);
controller.initiateGalleryPick(getActivity(), controller.initiateGalleryPick(getActivity(),
galleryPickLauncherForResult,
false); false);
} }
}); });
@ -2203,7 +2302,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabCustomGallery.isShown()) { if (binding.fabCustomGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace); storeSharedPrefs(selectedPlace);
controller.initiateCustomGalleryPickWithPermission(getActivity()); controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult);
} }
}); });
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.nearby.fragments package fr.free.nrw.commons.nearby.fragments
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
@ -12,6 +13,8 @@ class PlaceAdapter(
onBookmarkClicked: (Place, Boolean) -> Unit, onBookmarkClicked: (Place, Boolean) -> Unit,
commonPlaceClickActions: CommonPlaceClickActions, commonPlaceClickActions: CommonPlaceClickActions,
inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>, inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>,
galleryPickLauncherForResult: ActivityResultLauncher<Intent>,
cameraPickLauncherForResult: ActivityResultLauncher<Intent>
) : BaseDelegateAdapter<Place>( ) : BaseDelegateAdapter<Place>(
placeAdapterDelegate( placeAdapterDelegate(
bookmarkLocationsDao, bookmarkLocationsDao,
@ -27,6 +30,8 @@ class PlaceAdapter(
commonPlaceClickActions.onDirectionsClicked(), commonPlaceClickActions.onDirectionsClicked(),
commonPlaceClickActions.onDirectionsLongPressed(), commonPlaceClickActions.onDirectionsLongPressed(),
inAppCameraLocationPermissionLauncher, inAppCameraLocationPermissionLauncher,
cameraPickLauncherForResult,
galleryPickLauncherForResult
), ),
areItemsTheSame = { oldItem, newItem -> oldItem.wikiDataEntityId == newItem.wikiDataEntityId }, 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.SessionManager;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException; import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.models.Notification; 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.theme.BaseActivity;
import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.NetworkUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
@ -148,7 +149,11 @@ public class NotificationActivity extends BaseActivity {
} }
adapter = new NotificatinAdapter(item -> { adapter = new NotificatinAdapter(item -> {
Timber.d("Notification clicked %s", item.getLink()); Timber.d("Notification clicked %s", item.getLink());
handleUrl(item.getLink()); if (item.getNotificationType() == NotificationType.EMAIL){
ViewUtil.showLongSnackbar(binding.container,getString(R.string.check_your_email_inbox));
} else {
handleUrl(item.getLink());
}
removeNotification(item); removeNotification(item);
return Unit.INSTANCE; return Unit.INSTANCE;
}); });

View file

@ -51,13 +51,23 @@ class NotificationClient
} }
} }
private fun WikimediaNotification.toCommonsNotification() = private fun WikimediaNotification.toCommonsNotification() :
Notification( Notification {
notificationType = NotificationType.UNKNOWN, val notificationText = contents?.compactHeader ?: ""
notificationText = contents?.compactHeader ?: "", val notificationType =
date = DateUtil.getMonthOnlyDateString(timestamp), if (notificationText.contains("Sent you an email", ignoreCase = true)) {
link = contents?.links?.primary?.url ?: "", NotificationType.EMAIL
iconUrl = "", } else {
notificationId = id().toString(), 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"), THANK_YOU_EDIT("thank-you-edit"),
EDIT_USER_TALK("edit-user-talk"), EDIT_USER_TALK("edit-user-talk"),
MENTION("mention"), MENTION("mention"),
EMAIL("email"),
WELCOME("welcome"), WELCOME("welcome"),
UNKNOWN("unknown"); UNKNOWN("unknown");
private String type; private String type;

View file

@ -32,6 +32,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
/** /**
@ -139,14 +140,14 @@ public class ProfileActivity extends BaseActivity {
leaderboardFragment.setArguments(leaderBoardBundle); leaderboardFragment.setArguments(leaderBoardBundle);
fragmentList.add(leaderboardFragment); 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(); contributionsFragment = new ContributionsFragment();
Bundle contributionsListBundle = new Bundle(); Bundle contributionsListBundle = new Bundle();
contributionsListBundle.putString(KEY_USERNAME, userName); contributionsListBundle.putString(KEY_USERNAME, userName);
contributionsFragment.setArguments(contributionsListBundle); contributionsFragment.setArguments(contributionsListBundle);
fragmentList.add(contributionsFragment); 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.setTabData(fragmentList, titleList);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();

View file

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

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.recentlanguages; package fr.free.nrw.commons.recentlanguages;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
@ -117,6 +118,7 @@ public class RecentLanguagesDao {
* @return Language object * @return Language object
*/ */
@NonNull @NonNull
@SuppressLint("Range")
Language fromCursor(final Cursor cursor) { Language fromCursor(final Cursor cursor) {
// Hardcoding column positions! // Hardcoding column positions!
final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); 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.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
public class ReviewActivity extends BaseActivity { public class ReviewActivity extends BaseActivity {
@ -241,7 +242,7 @@ public class ReviewActivity extends BaseActivity {
public void showSkipImageInfo(){ public void showSkipImageInfo(){
DialogUtil.showAlertDialog(ReviewActivity.this, 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(R.string.skip_image_explanation),
getString(android.R.string.ok), getString(android.R.string.ok),
"", "",

View file

@ -22,6 +22,7 @@ import android.widget.TextView;
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference; import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
@ -88,6 +89,15 @@ public class SettingsFragment extends PreferenceFragmentCompat {
private View separator; private View separator;
private ListView languageHistoryListView; private ListView languageHistoryListView;
private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"; 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>>() { private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override @Override
public void onActivityResult(Map<String, Boolean> result) { public void onActivityResult(Map<String, Boolean> result) {
@ -96,7 +106,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
areAllGranted = areAllGranted && b; areAllGranted = areAllGranted && b;
} }
if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { 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)) { if (StringUtils.isEmpty(userName)) {
userName = sessionManager!!.getUserName() userName = sessionManager.getUserName()
} }
} }
@ -96,8 +96,8 @@ class FailedUploadsFragment :
fun initRecyclerView() { fun initRecyclerView() {
binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.failedUploadsRecyclerView.adapter = adapter binding.failedUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.getFailedContributions() pendingUploadsPresenter.getFailedContributions()
pendingUploadsPresenter!!.failedContributionList.observe( pendingUploadsPresenter.failedContributionList.observe(
viewLifecycleOwner, viewLifecycleOwner,
) { list: PagedList<Contribution?> -> ) { list: PagedList<Contribution?> ->
adapter.submitList(list) adapter.submitList(list)

View file

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

View file

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

View file

@ -320,6 +320,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
finish(); 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 * Show/Hide the progress dialog
*/ */
@ -433,14 +441,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
super.onRequestPermissionsResult(requestCode, permissions, grantResults); 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. * Sets the flag indicating whether the upload is of a specific place.
* *

View file

@ -18,6 +18,13 @@ public interface UploadContract {
void returnToMainActivity(); 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(); void askUserToLogIn();
/** /**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -18,6 +18,9 @@ import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Toast; 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface; import androidx.exifinterface.media.ExifInterface;
@ -58,9 +61,24 @@ import timber.log.Timber;
public class UploadMediaDetailFragment extends UploadBaseFragment implements public class UploadMediaDetailFragment extends UploadBaseFragment implements
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
private static final int REQUEST_CODE = 1211; private UploadMediaDetailAdapter uploadMediaDetailAdapter;
private static final int REQUEST_CODE_FOR_EDIT_ACTIVITY = 1212;
private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213; 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 ; public static Activity activity ;
@ -84,8 +102,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
private boolean hasUserRemovedLocation; private boolean hasUserRemovedLocation;
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
@Inject @Inject
UploadMediaDetailsContract.UserActionListener presenter; UploadMediaDetailsContract.UserActionListener presenter;
@ -279,7 +295,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
*/ */
private void initRecyclerView() { private void initRecyclerView() {
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this, uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this,
defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao); defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao, voiceInputResultLauncher);
uploadMediaDetailAdapter.setCallback(this::showInfoAlert); uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
uploadMediaDetailAdapter.setEventListener(this); uploadMediaDetailAdapter.setEventListener(this);
binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext())); 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. * 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 * 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 * 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 @Override
public void showEditActivity(UploadItem uploadItem) { public void showEditActivity(UploadItem uploadItem) {
editableUploadItem = uploadItem; editableUploadItem = uploadItem;
Intent intent = new Intent(getContext(), EditActivity.class); Intent intent = new Intent(getContext(), EditActivity.class);
intent.putExtra("image", uploadableFile.getFilePath().toString()); 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 defaultLongitude = -122.431297;
double defaultZoom = 16.0; double defaultZoom = 16.0;
final Intent locationPickerIntent;
/* Retrieve image location from EXIF if present or /* Retrieve image location from EXIF if present or
check if user has provided location while using the in-app camera. check if user has provided location while using the in-app camera.
Use location of last UploadItem if none of them is available */ Use location of last UploadItem if none of them is available */
@ -624,10 +642,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
.getDecLatitude(); .getDecLatitude();
defaultLongitude = uploadItem.getGpsCoords().getDecLongitude(); defaultLongitude = uploadItem.getGpsCoords().getDecLongitude();
defaultZoom = uploadItem.getGpsCoords().getZoomLevel(); defaultZoom = uploadItem.getGpsCoords().getZoomLevel();
startActivityForResult(new LocationPicker.IntentBuilder()
locationPickerIntent = new LocationPicker.IntentBuilder()
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
.activityKey("UploadActivity") .activityKey("UploadActivity")
.build(getActivity()), REQUEST_CODE); .build(getActivity());
} else { } else {
if (defaultKvStore.getString(LAST_LOCATION) != null) { if (defaultKvStore.getString(LAST_LOCATION) != null) {
final String[] locationLatLng final String[] locationLatLng
@ -638,27 +657,20 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
if (defaultKvStore.getString(LAST_ZOOM) != null) { if (defaultKvStore.getString(LAST_ZOOM) != null) {
defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM)); defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM));
} }
startActivityForResult(new LocationPicker.IntentBuilder()
locationPickerIntent = new LocationPicker.IntentBuilder()
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
.activityKey("NoLocationUploadActivity") .activityKey("NoLocationUploadActivity")
.build(getActivity()), REQUEST_CODE); .build(getActivity());
} }
startForResult.launch(locationPickerIntent);
} }
/** private void onCameraPosition(ActivityResult result){
* Get the coordinates and update the existing coordinates. if (result.getResultCode() == RESULT_OK) {
* @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) {
assert data != null; assert result.getData() != null;
final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data); final CameraPosition cameraPosition = LocationPicker.getCameraPosition(result.getData());
if (cameraPosition != null) { if (cameraPosition != null) {
@ -678,8 +690,21 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
removeLocation(); 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")) { if (Objects.equals(result, "Error")) {
Timber.e("Error in rotating image"); Timber.e("Error in rotating image");
@ -687,24 +712,15 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
} }
try { try {
if (binding != null){ 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, callback.changeThumbnail(indexOfFragment,
result); path);
} catch (Exception e) { } catch (Exception e) {
Timber.e(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 @Override
public void displayAddLocationDialog(final Runnable onSkipClicked) { public void displayAddLocationDialog(final Runnable onSkipClicked) {
isMissingLocationDialog = true; isMissingLocationDialog = true;
DialogUtil.showAlertDialog(Objects.requireNonNull(getActivity()), DialogUtil.showAlertDialog(requireActivity(),
getString(R.string.no_location_found_title), getString(R.string.no_location_found_title),
getString(R.string.no_location_found_message), getString(R.string.no_location_found_message),
getString(R.string.add_location), getString(R.string.add_location),

View file

@ -129,9 +129,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
if (place.location != null) { if (place.location != null) {
final String countryCode = reverseGeoCode(place.location); final String countryCode = reverseGeoCode(place.location);
if (countryCode != null && WLM_SUPPORTED_COUNTRIES if (countryCode != null && WLM_SUPPORTED_COUNTRIES
.contains(countryCode.toLowerCase())) { .contains(countryCode.toLowerCase(Locale.ROOT))) {
uploadItem.setWLMUpload(true); 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.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.BuildConfig.HOME_URL
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R 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.customselector.database.UploadedStatusDao
import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.media.MediaClient 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.theme.BaseActivity
import fr.free.nrw.commons.upload.FileUtilsWrapper import fr.free.nrw.commons.upload.FileUtilsWrapper
import fr.free.nrw.commons.upload.StashUploadResult 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.UploadProgressActivity
import fr.free.nrw.commons.upload.UploadResult import fr.free.nrw.commons.upload.UploadResult
import fr.free.nrw.commons.wikidata.WikidataEditService import fr.free.nrw.commons.wikidata.WikidataEditService
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
@ -74,6 +77,9 @@ class UploadWorker(
@Inject @Inject
lateinit var fileUtilsWrapper: FileUtilsWrapper lateinit var fileUtilsWrapper: FileUtilsWrapper
@Inject
lateinit var placesRepository: PlacesRepository
private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag" private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag"
private val processingUploadsNotificationId = 101 private val processingUploadsNotificationId = 101
@ -379,7 +385,7 @@ class UploadWorker(
saveCompletedContribution(contribution, uploadResult) saveCompletedContribution(contribution, uploadResult)
} else { } else {
Timber.d( Timber.d(
"WikiDataEdit not required, making wikidata edit", "WikiDataEdit required, making wikidata edit",
) )
makeWikiDataEdit(uploadResult, contribution) makeWikiDataEdit(uploadResult, contribution)
} }
@ -432,7 +438,7 @@ class UploadWorker(
username, username,
) )
CommonsApplication CommonsApplication
.getInstance() .instance!!
.clearApplicationData(appContext, logoutListener) .clearApplicationData(appContext, logoutListener)
} }
} }
@ -471,6 +477,16 @@ class UploadWorker(
contribution.media.captions, contribution.media.captions,
) )
if (null != revisionID) { 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) showSuccessNotification(contribution)
} }
} catch (exception: Exception) { } catch (exception: Exception) {
@ -518,7 +534,7 @@ class UploadWorker(
contribution.contentUri?.let { contribution.contentUri?.let {
val imageSha1 = contribution.imageSHA1.toString() val imageSha1 = contribution.imageSHA1.toString()
val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path)) val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path))
MainScope().launch { CoroutineScope(Dispatchers.IO).launch {
uploadedStatusDao.insertUploaded( uploadedStatusDao.insertUploaded(
UploadedStatus( UploadedStatus(
imageSha1, imageSha1,

View file

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

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

View file

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

View file

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

View file

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

View file

@ -124,6 +124,33 @@
app:srcCompat="@drawable/ic_my_location_black_24dp" app:srcCompat="@drawable/ic_my_location_black_24dp"
app:useCompatPadding="true" /> 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> </RelativeLayout>
<FrameLayout <FrameLayout

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

@ -527,6 +527,7 @@
<string name="no_notification">ليس لديك أي إشعارات غير مقروءة</string> <string name="no_notification">ليس لديك أي إشعارات غير مقروءة</string>
<string name="no_read_notification">ليس لديك أي إشعاراتٍ غير مقروءة</string> <string name="no_read_notification">ليس لديك أي إشعاراتٍ غير مقروءة</string>
<string name="share_logs_using">مشاركة السجلات باستخدام</string> <string name="share_logs_using">مشاركة السجلات باستخدام</string>
<string name="check_your_email_inbox">تحقق من صندوق بريدك الإلكتروني</string>
<string name="menu_option_read">عرض المقروءة</string> <string name="menu_option_read">عرض المقروءة</string>
<string name="menu_option_unread">عرض غير المقروءة</string> <string name="menu_option_unread">عرض غير المقروءة</string>
<string name="error_occurred_in_picking_images">حدث خطأ أثناء التقاط الصور</string> <string name="error_occurred_in_picking_images">حدث خطأ أثناء التقاط الصور</string>
@ -690,6 +691,7 @@
<string name="leaderboard_nearby">مجاور</string> <string name="leaderboard_nearby">مجاور</string>
<string name="leaderboard_used">مستخدَم</string> <string name="leaderboard_used">مستخدَم</string>
<string name="leaderboard_my_rank_button_text">ترتيبي</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_enabled">وضع الاتصال المحدود مُمَكَّن!</string>
<string name="limited_connection_disabled">وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن.</string> <string name="limited_connection_disabled">وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن.</string>
<string name="limited_connection_mode">وضع الاتصال المحدود</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_dismiss_limit_warning_button_text">رفض</string>
<string name="custom_selector_button_limit_text">الحد الأقصى: %1$d</string> <string name="custom_selector_button_limit_text">الحد الأقصى: %1$d</string>
<string name="custom_selector_limit_error_desc">خطأ: تجاوز حد التحميل</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="wlm_upload_info">سيتم إدخال هذه الصورة في مسابقة Wiki Loves Monuments</string>
<string name="display_monuments">عرض الآثار</string> <string name="display_monuments">عرض الآثار</string>
<string name="wlm_month_message">إنه شهر Wiki Loves Monuments!</string> <string name="wlm_month_message">إنه شهر Wiki Loves Monuments!</string>
@ -788,16 +791,66 @@
<string name="image_selected">تم تحديد الصورة</string> <string name="image_selected">تم تحديد الصورة</string>
<string name="image_marked_as_not_for_upload">تم وضع علامة على الصورة على أنها ليست للتحميل</string> <string name="image_marked_as_not_for_upload">تم وضع علامة على الصورة على أنها ليست للتحميل</string>
<string name="menu_view_report">تقرير</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_violation">تبليغ عن عنف</string>
<string name="report_user">أخطر عن هذا المستخدم</string> <string name="report_user">أخطر عن هذا المستخدم</string>
<string name="report_content">الإبلاغ عن هذا المحتوى</string> <string name="report_content">الإبلاغ عن هذا المحتوى</string>
<string name="request_user_block">طلب منع هذا المستخدم</string> <string name="request_user_block">طلب منع هذا المستخدم</string>
<string name="welcome_to_full_screen_mode_text">مرحبًا بك في وضع التحديد بملء الشاشة</string> <string name="welcome_to_full_screen_mode_text">مرحبًا بك في وضع التحديد بملء الشاشة</string>
<string name="full_screen_mode_zoom_info">استخدم إصبعين للتكبير والتصغير.</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="storage_permissions_denied">رُفض إذن التخزين</string>
<string name="unable_to_share_upload_item">تعذر مشاركة هذا العنصر</string> <string name="unable_to_share_upload_item">تعذر مشاركة هذا العنصر</string>
<string name="permissions_are_required_for_functionality">الإذن مطلوب لهذه الوظيفة</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="send_thanks_to_author">اشكر المؤلف</string>
<string name="error_sending_thanks">حدث خطأ أثناء إرسال الشكر للمؤلف.</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> </resources>

View file

@ -2,10 +2,11 @@
<!-- Authors: <!-- Authors:
* Dağlı95 * Dağlı95
* Khan27 * Khan27
* Nemoralis
--> -->
<resources> <resources>
<string name="crash_dialog_title">Nasazlıq</string> <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_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_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ür!</string> <string name="crash_dialog_ok_toast">Təşəkkürlər!</string>
</resources> </resources>

View file

@ -12,34 +12,60 @@
* Şeyx Şamil * Şeyx Şamil
--> -->
<resources> <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"> <plurals name="uploads_pending_notification_indicator">
<item quantity="one">%1$d fayl yüklənir</item> <item quantity="one">%1$d fayl yüklənir</item>
<item quantity="other">%1$d fayllar yüklənir</item> <item quantity="other">%1$d fayllar yüklənir</item>
</plurals> </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_general">Ümumi</string>
<string name="preference_category_privacy">Məxfilik</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="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="password">Parol</string>
<string name="login">Daxil ol</string> <string name="login">Daxil ol</string>
<string name="signup">Qeydiyyatdan keç</string> <string name="signup">Qeydiyyatdan keç</string>
<string name="logging_in_title">Giriş edilir</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_success" fuzzy="true">Daxil oldunuz!</string>
<string name="login_failed">Giriş baş tutmadı!</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="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="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="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_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_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_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_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_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="upload_failed_notification_subtitle">Baxmaq üçün toxun</string>
<string name="title_activity_contributions">Yükləmələrim</string> <string name="title_activity_contributions">Son Yükləmələrim</string>
<string name="contribution_state_queued">Sırada</string> <string name="contribution_state_queued">Növbəyə alındı</string>
<string name="contribution_state_failed">Uğursuz</string> <string name="contribution_state_failed">Uğursuz</string>
<string name="contribution_state_in_progress">%1$d%% tamamlandı</string> <string name="contribution_state_in_progress">%1$d%% tamamlandı</string>
<string name="contribution_state_starting">Yüklənir</string> <string name="contribution_state_starting">Yüklənir</string>
@ -52,15 +78,15 @@
<string name="share_description_hint">ıqlama</string> <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_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_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_blocked">Bağışlayın, bu istifadəçi Vikianbardan bloklanıb</string>
<string name="login_failed_2fa_needed">İki faktorlu giriş doğrulama kodunu verməlisiniz.</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="login_failed_generic" fuzzy="true">Daxil olma uğursuz oldu</string>
<string name="share_upload_button">Yüklə</string> <string name="share_upload_button">Yüklə</string>
<string name="multiple_share_base_title">Bu dəsti adlandırın</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="menu_upload_single">Yüklə</string>
<string name="categories_search_text_hint">Kateqoriyaları axtar</string> <string name="categories_search_text_hint">Kateqoriyalarda axtar</string>
<string name="menu_save_categories">Qeyd et</string> <string name="menu_save_categories">Yadda saxla</string>
<string name="refresh_button">Yenilə</string> <string name="refresh_button">Yenilə</string>
<string name="display_list_button">Siyahı</string> <string name="display_list_button">Siyahı</string>
<string name="contributions_subtitle_zero">(Hələ yükləmə yoxdur)</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="please_wait">Моля, изчакайте...</string>
<string name="delete_helper_ask_spam_blurry">напълно размазано</string> <string name="delete_helper_ask_spam_blurry">напълно размазано</string>
<string name="leaderboard_nearby">Наблизо</string> <string name="leaderboard_nearby">Наблизо</string>
<string name="read_help_link">Прочетете повече</string>
</resources> </resources>

View file

@ -242,8 +242,8 @@
<string name="nominated_for_deletion">Meneget eo bet ar skeudenn evit lemel.</string> <string name="nominated_for_deletion">Meneget eo bet ar skeudenn evit lemel.</string>
<string name="skip_login">Lezel a-gostez</string> <string name="skip_login">Lezel a-gostez</string>
<string name="navigation_item_login">Kevreañ</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_title">Ha n\'ho peus ket c\'hoant, 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_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="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="copy_wikicode">Eilañ an destenn wiki er golver</string>
<string name="wikicode_copied">Testenn wiki eilet 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="menu_view_file_page">Хьажа файлан агӀоне</string>
<string name="share_title_hint">Куьг йазор (ТIедилина ду)</string> <string name="share_title_hint">Куьг йазор (ТIедилина ду)</string>
<string name="add_caption_toast">Дехар ду, хӀокху файлан ц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="share_caption_hint">Куьг</string>
<string name="login_failed_network">Чувала(йала) тара цало — сетан гӀалат</string> <string name="login_failed_network">Чувала(йала) тара цало — сетан гӀалат</string>
<string name="login_failed_throttled">ТӀех дукха кхиаме боцу гӀертарш. Дехар ду масех минот йаьлча йуха а хьажа.</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_notification">Du har ingen ulæste notifikationer</string>
<string name="no_read_notification">Du har ingen læ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="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_read">Vis læste</string>
<string name="menu_option_unread">Vis ulæ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> <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="pending">Afventer</string>
<string name="failed">Mislykkedes</string> <string name="failed">Mislykkedes</string>
<string name="could_not_load_place_data">Kunne ikke indlæse steddata</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> </resources>

View file

@ -29,6 +29,7 @@
* Sujan * Sujan
* Sushi * Sushi
* Tacsipacsi * Tacsipacsi
* TheRabbit22
* ThisCarthing * ThisCarthing
* Tobi 406 * Tobi 406
* TomatoCake * 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">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="multiple_files_depiction_header">Hinweis zu Mehrfach-Uploads</string>
<string name="nearby_wikitalk">Melde ein Problem mit diesem Datenobjekt an Wikidata</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> </resources>

View file

@ -22,6 +22,7 @@
* JenyxGym * JenyxGym
* KATRINE1992 * KATRINE1992
* Koreller * Koreller
* Mahabarata
* McDutchie * McDutchie
* Melissadeba95 * Melissadeba95
* Metroitendo * Metroitendo
@ -516,6 +517,7 @@
<string name="no_notification">Vous navez aucune notification non lue</string> <string name="no_notification">Vous navez aucune notification non lue</string>
<string name="no_read_notification">Vous navez aucune notification 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="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_read">Afficher les lus</string>
<string name="menu_option_unread">Afficher les non 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> <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="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="please_enter_some_comments">Merci de saisir vos commentaires</string>
<string name="talk">Discussion</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="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="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> <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="uploads">Téléversements</string>
<string name="pending">En attente</string> <string name="pending">En attente</string>
<string name="failed">Échec</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> </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="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="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="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">Kampányok megjelenítése</string>
<string name="display_campaigns_explanation">Folyamatban lévő 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> <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_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="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="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="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="getting_edit_token">Szerkesztő token beszerzése</string>
<string name="check_category_adding_template">Kategóriaellenőrző sablon felhelyezé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_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_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_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_selfie">egy szelfi, amely egyetlen cikkben sem szerepel</string>
<string name="delete_helper_ask_spam_blurry" fuzzy="true">Homályos</string> <string name="delete_helper_ask_spam_blurry">teljesen homályos</string>
<string name="delete_helper_ask_spam_nonsense" fuzzy="true">Nonszensz</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_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_internet_photo">Random fénykép az Internetről</string>
<string name="delete_helper_ask_reason_copyright_logo">Logó</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_state_needs_photo">Fénykép szükséges</string>
<string name="place_type">Hely típusa:</string> <string name="place_type">Hely típusa:</string>
<string name="nearby_search_hint">Híd, múzeum, szálloda, stb.</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_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="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> <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="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="please_enter_some_comments">Per favor insere alcun commentos</string>
<string name="talk">Discussion</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="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="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="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="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> </resources>

View file

@ -760,4 +760,7 @@
<item quantity="one">%d immagine selezionata</item> <item quantity="one">%d immagine selezionata</item>
<item quantity="other">%d immagini selezionate</item> <item quantity="other">%d immagini selezionate</item>
</plurals> </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> </resources>

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