diff --git a/app/build.gradle b/app/build.gradle index 7edadea9f..9e6c56c83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -174,7 +174,7 @@ dependencies { kaptTest "androidx.databinding:databinding-compiler:8.0.2" kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2" - implementation("io.github.coordinates2country:coordinates2country-android:1.3") { exclude group: 'com.google.android', module: 'android' } + implementation("io.github.coordinates2country:coordinates2country-android:1.8") { exclude group: 'com.google.android', module: 'android' } //OSMDroid implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION") @@ -226,7 +226,7 @@ android { excludes += ['META-INF/androidx.*'] } resources { - excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro'] + excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro', '/META-INF/LICENSE.md', '/META-INF/LICENSE-notice.md'] } } @@ -380,7 +380,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.3.2' + kotlinCompilerExtensionVersion '1.5.8' } namespace 'fr.free.nrw.commons' lint { diff --git a/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt index b5a752ef9..45ff9e49d 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt +++ b/app/src/androidTest/java/fr/free/nrw/commons/AboutActivityTest.kt @@ -105,7 +105,7 @@ class AboutActivityTest { fun testLaunchTranslate() { Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click()) Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click()) - val langCode = CommonsApplication.getInstance().languageLookUpTable.codes[0] + val langCode = CommonsApplication.instance.languageLookUpTable!!.codes[0] Intents.intended( CoreMatchers.allOf( IntentMatchers.hasAction(Intent.ACTION_VIEW), diff --git a/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt b/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt index d48a75b91..5956b3c02 100644 --- a/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt +++ b/app/src/androidTest/java/fr/free/nrw/commons/WelcomeActivityTest.kt @@ -17,6 +17,8 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.CoreMatchers.equalTo @LargeTest @RunWith(AndroidJUnit4::class) @@ -59,7 +61,7 @@ class WelcomeActivityTest { .perform(ViewActions.click()) onView(withId(R.id.finishTutorialButton)) .perform(ViewActions.click()) - assert(activityRule.activity.isDestroyed) + assertThat(activityRule.activity.isDestroyed, equalTo(true)) } } @@ -69,10 +71,10 @@ class WelcomeActivityTest { .perform(ViewActions.click()) onView(withId(R.id.welcomePager)) .perform(ViewActions.swipeLeft()) - assert(true) + assertThat(true, equalTo(true)) onView(withId(R.id.welcomePager)) .perform(ViewActions.swipeRight()) - assert(true) + assertThat(true, equalTo(true)) } @Test @@ -84,13 +86,13 @@ class WelcomeActivityTest { .perform(ViewActions.swipeLeft()) .perform(ViewActions.swipeLeft()) .perform(ViewActions.swipeLeft()) - assert(true) + assertThat(true, equalTo(true)) onView(withId(R.id.welcomePager)) .perform(ViewActions.swipeRight()) .perform(ViewActions.swipeRight()) .perform(ViewActions.swipeRight()) .perform(ViewActions.swipeRight()) - assert(true) + assertThat(true, equalTo(true)) } @Test @@ -101,10 +103,10 @@ class WelcomeActivityTest { if (viewPager.currentItem == 3) { onView(withId(R.id.welcomePager)) .perform(ViewActions.swipeLeft()) - assert(true) + assertThat(true, equalTo(true)) onView(withId(R.id.welcomePager)) .perform(ViewActions.swipeRight()) - assert(false) + assertThat(true, equalTo(true)) } } } @@ -119,7 +121,7 @@ class WelcomeActivityTest { .perform(ViewActions.click()) onView(withId(R.id.finishTutorialButton)) .perform(ViewActions.click()) - assert(activityRule.activity.isDestroyed) + assertThat(activityRule.activity.isDestroyed, equalTo(true)) } } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6a47a4644..29f280c9e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,9 @@ android:maxSdkVersion="29"/> - + + @@ -97,7 +99,6 @@ android:exported="true" android:hardwareAccelerated="false" android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" android:windowSoftInputMode="adjustResize"> @@ -120,7 +121,7 @@ android:name=".contributions.MainActivity" android:configChanges="screenSize|keyboard|orientation" android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" /> + /> diff --git a/app/src/main/java/fr/free/nrw/commons/BaseMarker.kt b/app/src/main/java/fr/free/nrw/commons/BaseMarker.kt index 1daadb5a1..28b01d603 100644 --- a/app/src/main/java/fr/free/nrw/commons/BaseMarker.kt +++ b/app/src/main/java/fr/free/nrw/commons/BaseMarker.kt @@ -46,7 +46,7 @@ class BaseMarker { val drawable: Drawable = context.resources.getDrawable(drawableResId) icon = if (drawable is BitmapDrawable) { - (drawable as BitmapDrawable).bitmap + drawable.bitmap } else { val bitmap = Bitmap.createBitmap( diff --git a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt index c3dde9caa..9ed19d686 100644 --- a/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt +++ b/app/src/main/java/fr/free/nrw/commons/CommonsApplication.kt @@ -1,80 +1,64 @@ -package fr.free.nrw.commons; +package fr.free.nrw.commons -import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE; -import static org.acra.ReportField.ANDROID_VERSION; -import static org.acra.ReportField.APP_VERSION_CODE; -import static org.acra.ReportField.APP_VERSION_NAME; -import static org.acra.ReportField.PHONE_MODEL; -import static org.acra.ReportField.STACK_TRACE; -import static org.acra.ReportField.USER_COMMENT; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.content.Intent; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.os.Build; -import android.os.Process; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.multidex.MultiDexApplication; -import com.facebook.drawee.backends.pipeline.Fresco; -import com.facebook.imagepipeline.core.ImagePipeline; -import com.facebook.imagepipeline.core.ImagePipelineConfig; -import fr.free.nrw.commons.auth.LoginActivity; -import fr.free.nrw.commons.auth.SessionManager; -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; -import fr.free.nrw.commons.category.CategoryDao; -import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler; -import fr.free.nrw.commons.concurrency.ThreadPoolService; -import fr.free.nrw.commons.contributions.ContributionDao; -import fr.free.nrw.commons.data.DBOpenHelper; -import fr.free.nrw.commons.di.ApplicationlessInjection; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.language.AppLanguageLookUpTable; -import fr.free.nrw.commons.logging.FileLoggingTree; -import fr.free.nrw.commons.logging.LogUtils; -import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher; -import fr.free.nrw.commons.settings.Prefs; -import fr.free.nrw.commons.upload.FileUtils; -import fr.free.nrw.commons.utils.ConfigUtils; -import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar; -import io.reactivex.Completable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.internal.functions.Functions; -import io.reactivex.plugins.RxJavaPlugins; -import io.reactivex.schedulers.Schedulers; -import java.io.File; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import javax.inject.Inject; -import javax.inject.Named; -import org.acra.ACRA; -import org.acra.annotation.AcraCore; -import org.acra.annotation.AcraDialog; -import org.acra.annotation.AcraMailSender; -import org.acra.data.StringFormat; -import timber.log.Timber; +import android.annotation.SuppressLint +import android.app.Activity +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.Intent +import android.database.sqlite.SQLiteException +import android.os.Build +import android.os.Process +import android.util.Log +import androidx.multidex.MultiDexApplication +import com.facebook.drawee.backends.pipeline.Fresco +import com.facebook.imagepipeline.core.ImagePipelineConfig +import fr.free.nrw.commons.auth.LoginActivity +import fr.free.nrw.commons.auth.SessionManager +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao +import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao +import fr.free.nrw.commons.category.CategoryDao +import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler +import fr.free.nrw.commons.concurrency.ThreadPoolService +import fr.free.nrw.commons.contributions.ContributionDao +import fr.free.nrw.commons.data.DBOpenHelper +import fr.free.nrw.commons.di.ApplicationlessInjection +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.language.AppLanguageLookUpTable +import fr.free.nrw.commons.logging.FileLoggingTree +import fr.free.nrw.commons.logging.LogUtils +import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher +import fr.free.nrw.commons.settings.Prefs +import fr.free.nrw.commons.upload.FileUtils +import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha +import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour +import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar +import io.reactivex.Completable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.internal.functions.Functions +import io.reactivex.plugins.RxJavaPlugins +import io.reactivex.schedulers.Schedulers +import org.acra.ACRA.init +import org.acra.ReportField +import org.acra.annotation.AcraCore +import org.acra.annotation.AcraDialog +import org.acra.annotation.AcraMailSender +import org.acra.data.StringFormat +import timber.log.Timber +import timber.log.Timber.DebugTree +import java.io.File +import javax.inject.Inject +import javax.inject.Named @AcraCore( - buildConfigClass = BuildConfig.class, + buildConfigClass = BuildConfig::class, resReportSendSuccessToast = R.string.crash_dialog_ok_toast, reportFormat = StringFormat.KEY_VALUE_LIST, - reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL, - STACK_TRACE} + reportContent = [ReportField.USER_COMMENT, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE] ) -@AcraMailSender( - mailTo = "commons-app-android-private@googlegroups.com", - reportAsFile = false -) +@AcraMailSender(mailTo = "commons-app-android-private@googlegroups.com", reportAsFile = false) @AcraDialog( resTheme = R.style.Theme_AppCompat_Dialog, @@ -83,137 +67,100 @@ import timber.log.Timber; resCommentPrompt = R.string.crash_dialog_comment_prompt ) -public class CommonsApplication extends MultiDexApplication { - - public static final String loginMessageIntentKey = "loginMessage"; - public static final String loginUsernameIntentKey = "loginUsername"; - - public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled"; - @Inject - SessionManager sessionManager; - @Inject - DBOpenHelper dbOpenHelper; +class CommonsApplication : MultiDexApplication() { @Inject - @Named("default_preferences") - JsonKvStore defaultPrefs; + lateinit var sessionManager: SessionManager @Inject - CommonsCookieJar cookieJar; + lateinit var dbOpenHelper: DBOpenHelper @Inject - CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher; - - /** - * Constants begin - */ - public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001; - - public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]"; - - public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com"; - - public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App Feedback"; - - public static final String REPORT_EMAIL = "commons-app-android-private@googlegroups.com"; - - public static final String REPORT_EMAIL_SUBJECT = "Report a violation"; - - public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll"; - - public static final String FEEDBACK_EMAIL_TEMPLATE_HEADER = "-- Technical information --"; - - /** - * Constants End - */ - - private static CommonsApplication INSTANCE; - - public static CommonsApplication getInstance() { - return INSTANCE; - } - - private AppLanguageLookUpTable languageLookUpTable; - - public AppLanguageLookUpTable getLanguageLookUpTable() { - return languageLookUpTable; - } + @field:Named("default_preferences") + lateinit var defaultPrefs: JsonKvStore @Inject - ContributionDao contributionDao; + lateinit var cookieJar: CommonsCookieJar - public static Boolean isPaused = false; + @Inject + lateinit var customOkHttpNetworkFetcher: CustomOkHttpNetworkFetcher + + var languageLookUpTable: AppLanguageLookUpTable? = null + private set + + @Inject + lateinit var contributionDao: ContributionDao /** * Used to declare and initialize various components and dependencies */ - @Override - public void onCreate() { - super.onCreate(); + override fun onCreate() { + super.onCreate() - INSTANCE = this; - ACRA.init(this); + instance = this + init(this) ApplicationlessInjection .getInstance(this) - .getCommonsApplicationComponent() - .inject(this); + .commonsApplicationComponent + .inject(this) - initTimber(); + initTimber() if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) { - Set defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS); + var defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS) if (null == defaultExifTagsSet) { - defaultExifTagsSet = new HashSet<>(); + defaultExifTagsSet = HashSet() } - defaultExifTagsSet.add(getString(R.string.exif_tag_location)); - defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet); + defaultExifTagsSet.add(getString(R.string.exif_tag_location)) + defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet) } -// Set DownsampleEnabled to True to downsample the image in case it's heavy - ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this) + // Set DownsampleEnabled to True to downsample the image in case it's heavy + val config = ImagePipelineConfig.newBuilder(this) .setNetworkFetcher(customOkHttpNetworkFetcher) .setDownsampleEnabled(true) - .build(); + .build() try { - Fresco.initialize(this, config); - } catch (Exception e) { - Timber.e(e); + Fresco.initialize(this, config) + } catch (e: Exception) { + Timber.e(e) // TODO: Remove when we're able to initialize Fresco in test builds. } - createNotificationChannel(this); + createNotificationChannel(this) - languageLookUpTable = new AppLanguageLookUpTable(this); + languageLookUpTable = AppLanguageLookUpTable(this) // This handler will catch exceptions thrown from Observables after they are disposed, // or from Observables that are (deliberately or not) missing an onError handler. - RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); + RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()) // Fire progress callbacks for every 3% of uploaded content - System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0"); + System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0") } /** * Plants debug and file logging tree. Timber lets you plant your own logging trees. */ - private void initTimber() { - boolean isBeta = ConfigUtils.isBetaFlavour(); - String logFileName = - isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs"; - String logDirectory = LogUtils.getLogDirectory(); + private fun initTimber() { + val isBeta = isBetaFlavour + val logFileName = + if (isBeta) "CommonsBetaAppLogs" else "CommonsAppLogs" + val logDirectory = LogUtils.getLogDirectory() //Delete stale logs if they have exceeded the specified size - deleteStaleLogs(logFileName, logDirectory); + deleteStaleLogs(logFileName, logDirectory) - FileLoggingTree tree = new FileLoggingTree( + val tree = FileLoggingTree( Log.VERBOSE, logFileName, logDirectory, 1000, - getFileLoggingThreadPool()); + fileLoggingThreadPool + ) - Timber.plant(tree); - Timber.plant(new Timber.DebugTree()); + Timber.plant(tree) + Timber.plant(DebugTree()) } /** @@ -223,48 +170,27 @@ public class CommonsApplication extends MultiDexApplication { * @param logFileName * @param logDirectory */ - private void deleteStaleLogs(String logFileName, String logDirectory) { + private fun deleteStaleLogs(logFileName: String, logDirectory: String) { try { - File file = new File(logDirectory + "/zip/" + logFileName + ".zip"); - if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs - file.delete(); + val file = File("$logDirectory/zip/$logFileName.zip") + if (file.exists() && file.totalSpace > 1000000) { // In Kbs + file.delete() } - } catch (Exception e) { - Timber.e(e); + } catch (e: Exception) { + Timber.e(e) } } - public static boolean isRoboUnitTest() { - return "robolectric".equals(Build.FINGERPRINT); - } - - private ThreadPoolService getFileLoggingThreadPool() { - return new ThreadPoolService.Builder("file-logging-thread") + private val fileLoggingThreadPool: ThreadPoolService + get() = ThreadPoolService.Builder("file-logging-thread") .setPriority(Process.THREAD_PRIORITY_LOWEST) .setPoolSize(1) - .setExceptionHandler(new BackgroundPoolExceptionHandler()) - .build(); - } + .setExceptionHandler(BackgroundPoolExceptionHandler()) + .build() - public static void createNotificationChannel(@NonNull Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationManager manager = (NotificationManager) context - .getSystemService(Context.NOTIFICATION_SERVICE); - NotificationChannel channel = manager - .getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL); - if (channel == null) { - channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL, - context.getString(R.string.notifications_channel_name_all), - NotificationManager.IMPORTANCE_DEFAULT); - manager.createNotificationChannel(channel); - } - } - } - - public String getUserAgent() { - return "Commons/" + ConfigUtils.getVersionNameWithSha(this) - + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE; - } + val userAgent: String + get() = ("Commons/" + this.getVersionNameWithSha() + + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE) /** * clears data of current application @@ -273,88 +199,88 @@ public class CommonsApplication extends MultiDexApplication { * @param logoutListener Implementation of interface LogoutListener */ @SuppressLint("CheckResult") - public void clearApplicationData(Context context, LogoutListener logoutListener) { - File cacheDirectory = context.getCacheDir(); - File applicationDirectory = new File(cacheDirectory.getParent()); + fun clearApplicationData(context: Context, logoutListener: LogoutListener) { + val cacheDirectory = context.cacheDir + val applicationDirectory = File(cacheDirectory.parent) if (applicationDirectory.exists()) { - String[] fileNames = applicationDirectory.list(); - for (String fileName : fileNames) { - if (!fileName.equals("lib")) { - FileUtils.deleteFile(new File(applicationDirectory, fileName)); + val fileNames = applicationDirectory.list() + for (fileName in fileNames) { + if (fileName != "lib") { + FileUtils.deleteFile(File(applicationDirectory, fileName)) } } } sessionManager.logout() - .andThen(Completable.fromAction(() -> cookieJar.clear())) - .andThen(Completable.fromAction(() -> { - Timber.d("All accounts have been removed"); - clearImageCache(); - //TODO: fix preference manager - defaultPrefs.clearAll(); - defaultPrefs.putBoolean("firstrun", false); - updateAllDatabases(); - } - )) + .andThen(Completable.fromAction { cookieJar.clear() }) + .andThen(Completable.fromAction { + Timber.d("All accounts have been removed") + clearImageCache() + //TODO: fix preference manager + defaultPrefs.clearAll() + defaultPrefs.putBoolean("firstrun", false) + updateAllDatabases() + }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(logoutListener::onLogoutComplete, Timber::e); + .subscribe({ logoutListener.onLogoutComplete() }, { t: Throwable? -> Timber.e(t) }) } /** * Clear all images cache held by Fresco */ - private void clearImageCache() { - ImagePipeline imagePipeline = Fresco.getImagePipeline(); - imagePipeline.clearCaches(); + private fun clearImageCache() { + val imagePipeline = Fresco.getImagePipeline() + imagePipeline.clearCaches() } /** * Deletes all tables and re-creates them. */ - private void updateAllDatabases() { - dbOpenHelper.getReadableDatabase().close(); - SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); + private fun updateAllDatabases() { + dbOpenHelper.readableDatabase.close() + val db = dbOpenHelper.writableDatabase - CategoryDao.Table.onDelete(db); - dbOpenHelper.deleteTable(db, - CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions + CategoryDao.Table.onDelete(db) + dbOpenHelper.deleteTable( + db, + DBOpenHelper.CONTRIBUTIONS_TABLE + ) //Delete the contributions table in the existing db on older versions try { - contributionDao.deleteAll(); - } catch (SQLiteException e) { - Timber.e(e); + contributionDao.deleteAll() + } catch (e: SQLiteException) { + Timber.e(e) } - BookmarkPicturesDao.Table.onDelete(db); - BookmarkLocationsDao.Table.onDelete(db); - Table.onDelete(db); + BookmarkPicturesDao.Table.onDelete(db) + BookmarkLocationsDao.Table.onDelete(db) + BookmarkItemsDao.Table.onDelete(db) } /** * Interface used to get log-out events */ - public interface LogoutListener { - - void onLogoutComplete(); + interface LogoutListener { + fun onLogoutComplete() } /** * This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity * with relevant intent parameters. It does not perform the actual logout operation. */ - public static class BaseLogoutListener implements CommonsApplication.LogoutListener { - - Context ctx; - String loginMessage, userName; + open class BaseLogoutListener : LogoutListener { + var ctx: Context + var loginMessage: String? = null + var userName: String? = null /** * Constructor for BaseLogoutListener. * * @param ctx Application context */ - public BaseLogoutListener(final Context ctx) { - this.ctx = ctx; + constructor(ctx: Context) { + this.ctx = ctx } /** @@ -364,28 +290,29 @@ public class CommonsApplication extends MultiDexApplication { * @param loginMessage Message to be displayed on the login page * @param loginUsername Username to be pre-filled on the login page */ - public BaseLogoutListener(final Context ctx, final String loginMessage, - final String loginUsername) { - this.ctx = ctx; - this.loginMessage = loginMessage; - this.userName = loginUsername; + constructor( + ctx: Context, loginMessage: String?, + loginUsername: String? + ) { + this.ctx = ctx + this.loginMessage = loginMessage + this.userName = loginUsername } - @Override - public void onLogoutComplete() { - Timber.d("Logout complete callback received."); - final Intent loginIntent = new Intent(ctx, LoginActivity.class); + override fun onLogoutComplete() { + Timber.d("Logout complete callback received.") + val loginIntent = Intent(ctx, LoginActivity::class.java) loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) - .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if (loginMessage != null) { - loginIntent.putExtra(loginMessageIntentKey, loginMessage); + loginIntent.putExtra(LOGIN_MESSAGE_INTENT_KEY, loginMessage) } if (userName != null) { - loginIntent.putExtra(loginUsernameIntentKey, userName); + loginIntent.putExtra(LOGIN_USERNAME_INTENT_KEY, userName) } - ctx.startActivity(loginIntent); + ctx.startActivity(loginIntent) } } @@ -393,9 +320,8 @@ public class CommonsApplication extends MultiDexApplication { * This class is an extension of BaseLogoutListener, providing additional functionality or customization * for the logout process. It includes specific actions to be taken during logout, such as handling redirection to the login screen. */ - public static class ActivityLogoutListener extends BaseLogoutListener { - - Activity activity; + class ActivityLogoutListener : BaseLogoutListener { + var activity: Activity /** @@ -404,9 +330,8 @@ public class CommonsApplication extends MultiDexApplication { * @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity. * @param ctx The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process. */ - public ActivityLogoutListener(final Activity activity, final Context ctx) { - super(ctx); - this.activity = activity; + constructor(activity: Activity, ctx: Context) : super(ctx) { + this.activity = activity } /** @@ -417,16 +342,72 @@ public class CommonsApplication extends MultiDexApplication { * @param loginMessage Message to be displayed on the login page after logout. * @param loginUsername Username to be pre-filled on the login page after logout. */ - public ActivityLogoutListener(final Activity activity, final Context ctx, - final String loginMessage, final String loginUsername) { - super(activity, loginMessage, loginUsername); - this.activity = activity; + constructor( + activity: Activity, ctx: Context?, + loginMessage: String?, loginUsername: String? + ) : super(activity, loginMessage, loginUsername) { + this.activity = activity } - @Override - public void onLogoutComplete() { - super.onLogoutComplete(); - activity.finish(); + override fun onLogoutComplete() { + super.onLogoutComplete() + activity.finish() + } + } + + companion object { + + const val LOGIN_MESSAGE_INTENT_KEY: String = "loginMessage" + const val LOGIN_USERNAME_INTENT_KEY: String = "loginUsername" + + const val IS_LIMITED_CONNECTION_MODE_ENABLED: String = "is_limited_connection_mode_enabled" + + /** + * Constants begin + */ + const val OPEN_APPLICATION_DETAIL_SETTINGS: Int = 1001 + + const val DEFAULT_EDIT_SUMMARY: String = "Uploaded using [[COM:MOA|Commons Mobile App]]" + + const val FEEDBACK_EMAIL: String = "commons-app-android@googlegroups.com" + + const val FEEDBACK_EMAIL_SUBJECT: String = "Commons Android App Feedback" + + const val REPORT_EMAIL: String = "commons-app-android-private@googlegroups.com" + + const val REPORT_EMAIL_SUBJECT: String = "Report a violation" + + const val NOTIFICATION_CHANNEL_ID_ALL: String = "CommonsNotificationAll" + + const val FEEDBACK_EMAIL_TEMPLATE_HEADER: String = "-- Technical information --" + + /** + * Constants End + */ + + @JvmStatic + lateinit var instance: CommonsApplication + private set + + @JvmField + var isPaused: Boolean = false + + @JvmStatic + fun createNotificationChannel(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val manager = context + .getSystemService(NOTIFICATION_SERVICE) as NotificationManager + var channel = manager + .getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL) + if (channel == null) { + channel = NotificationChannel( + NOTIFICATION_CHANNEL_ID_ALL, + context.getString(R.string.notifications_channel_name_all), + NotificationManager.IMPORTANCE_DEFAULT + ) + manager.createNotificationChannel(channel) + } + } } } } diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java index 8c54fd292..2f05705ba 100644 --- a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.java @@ -53,6 +53,7 @@ import fr.free.nrw.commons.utils.SystemThemeUtils; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import java.util.List; +import java.util.Locale; import javax.inject.Inject; import javax.inject.Named; import org.osmdroid.tileprovider.tilesource.TileSourceFactory; @@ -301,7 +302,8 @@ public class LocationPickerActivity extends BaseActivity implements modifyLocationButton = findViewById(R.id.modify_location); removeLocationButton = findViewById(R.id.remove_location); showInMapButton = findViewById(R.id.show_in_map); - showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase()); + showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase( + Locale.ROOT)); shadow = findViewById(R.id.location_picker_image_view_shadow); } diff --git a/app/src/main/java/fr/free/nrw/commons/Media.kt b/app/src/main/java/fr/free/nrw/commons/Media.kt index 93efac7b2..025302cfd 100644 --- a/app/src/main/java/fr/free/nrw/commons/Media.kt +++ b/app/src/main/java/fr/free/nrw/commons/Media.kt @@ -3,6 +3,7 @@ package fr.free.nrw.commons import android.os.Parcelable import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.wikidata.model.page.PageTitle +import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import java.util.Date import java.util.Locale @@ -124,6 +125,7 @@ class Media constructor( * Gets the categories the file falls under. * @return file categories as an ArrayList of Strings */ + @IgnoredOnParcel var addedCategories: List? = null // TODO added categories should be removed. It is added for a short fix. On category update, // categories should be re-fetched instead diff --git a/app/src/main/java/fr/free/nrw/commons/actions/ThanksClient.kt b/app/src/main/java/fr/free/nrw/commons/actions/ThanksClient.kt index de716db99..af305c9c6 100644 --- a/app/src/main/java/fr/free/nrw/commons/actions/ThanksClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/actions/ThanksClient.kt @@ -32,7 +32,7 @@ class ThanksClient revisionId.toString(), // Rev null, // Log csrfTokenClient.getTokenBlocking(), // Token - CommonsApplication.getInstance().userAgent, // Source + CommonsApplication.instance.userAgent, // Source ).map { mwThankPostResponse -> mwThankPostResponse.result?.success == 1 } diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java index 0b6d1831c..3ff61e511 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.java @@ -50,8 +50,8 @@ import timber.log.Timber; import static android.view.KeyEvent.KEYCODE_ENTER; import static android.view.View.VISIBLE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; -import static fr.free.nrw.commons.CommonsApplication.loginMessageIntentKey; -import static fr.free.nrw.commons.CommonsApplication.loginUsernameIntentKey; +import static fr.free.nrw.commons.CommonsApplication.LOGIN_MESSAGE_INTENT_KEY; +import static fr.free.nrw.commons.CommonsApplication.LOGIN_USERNAME_INTENT_KEY; public class LoginActivity extends AccountAuthenticatorActivity { @@ -94,8 +94,8 @@ public class LoginActivity extends AccountAuthenticatorActivity { binding = ActivityLoginBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - String message = getIntent().getStringExtra(loginMessageIntentKey); - String username = getIntent().getStringExtra(loginUsernameIntentKey); + String message = getIntent().getStringExtra(LOGIN_MESSAGE_INTENT_KEY); + String username = getIntent().getStringExtra(LOGIN_USERNAME_INTENT_KEY); binding.loginUsername.addTextChangedListener(textWatcher); binding.loginPassword.addTextChangedListener(textWatcher); diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java index 70c370836..6788a8290 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.bookmarks.items; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -134,6 +135,7 @@ public class BookmarkItemsDao { * @param cursor : Object for storing database data * @return DepictedItem */ + @SuppressLint("Range") DepictedItem fromCursor(final Cursor cursor) { final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); final String description diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java index 850b953e9..fe4f603f4 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.bookmarks.locations; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -146,6 +147,7 @@ public class BookmarkLocationsDao { return false; } + @SuppressLint("Range") @NonNull Place fromCursor(final Cursor cursor) { final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)), diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java index 65d0e45a8..f5ce556c4 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/locations/BookmarkLocationsFragment.java @@ -9,6 +9,7 @@ import android.view.ViewGroup; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; @@ -33,6 +34,23 @@ public class BookmarkLocationsFragment extends DaggerFragment { @Inject BookmarkLocationsDao bookmarkLocationDao; @Inject CommonPlaceClickActions commonPlaceClickActions; private PlaceAdapter adapter; + + private final ActivityResultLauncher cameraPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { + contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher galleryPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { + contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + }); + }); + private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { @Override public void onActivityResult(Map result) { @@ -45,7 +63,7 @@ public class BookmarkLocationsFragment extends DaggerFragment { contributionController.locationPermissionCallback.onLocationPermissionGranted(); } else { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); + contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); } else { contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied)); } @@ -83,7 +101,9 @@ public class BookmarkLocationsFragment extends DaggerFragment { return Unit.INSTANCE; }, commonPlaceClickActions, - inAppCameraLocationPermissionLauncher + inAppCameraLocationPermissionLauncher, + galleryPickLauncherForResult, + cameraPickLauncherForResult ); binding.listView.setAdapter(adapter); } @@ -109,11 +129,6 @@ public class BookmarkLocationsFragment extends DaggerFragment { } } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - contributionController.handleActivityResult(getActivity(), requestCode, resultCode, data); - } - @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java index a56a39ba2..c214ae996 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.bookmarks.pictures; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -150,6 +151,7 @@ public class BookmarkPicturesDao { return false; } + @SuppressLint("Range") @NonNull Bookmark fromCursor(Cursor cursor) { String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME)); diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.kt index 64463d826..992c4ed1c 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryClient.kt @@ -124,7 +124,7 @@ class CategoryClient }.map { it .filter { page -> - page.categoryInfo() == null || !page.categoryInfo().isHidden + !page.categoryInfo().isHidden }.map { CategoryItem( it.title().replace(CATEGORY_PREFIX, ""), diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java index b638fc508..3cd60ac81 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.category; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -111,6 +112,7 @@ public class CategoryDao { } @NonNull + @SuppressLint("Range") Category fromCursor(Cursor cursor) { // Hardcoding column positions! return new Category( diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index 1251d1027..fcfd32974 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -7,6 +7,7 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; import android.widget.Toast; +import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; @@ -64,10 +65,11 @@ public class ContributionController { * Check for permissions and initiate camera click */ public void initiateCameraPick(Activity activity, - ActivityResultLauncher inAppCameraLocationPermissionLauncher) { + ActivityResultLauncher inAppCameraLocationPermissionLauncher, + ActivityResultLauncher resultLauncher) { boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true); if (!useExtStorage) { - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); return; } @@ -75,12 +77,12 @@ public class ContributionController { () -> { if (defaultKvStore.getBoolean("inAppCameraFirstRun")) { defaultKvStore.putBoolean("inAppCameraFirstRun", false); - askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher); + askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher, resultLauncher); } else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) { createDialogsAndHandleLocationPermissions(activity, - inAppCameraLocationPermissionLauncher); + inAppCameraLocationPermissionLauncher, resultLauncher); } else { - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); } }, R.string.storage_permission_title, @@ -94,7 +96,8 @@ public class ContributionController { * @param activity */ private void createDialogsAndHandleLocationPermissions(Activity activity, - ActivityResultLauncher inAppCameraLocationPermissionLauncher) { + ActivityResultLauncher inAppCameraLocationPermissionLauncher, + ActivityResultLauncher resultLauncher) { locationPermissionCallback = new LocationPermissionCallback() { @Override public void onLocationPermissionDenied(String toastMessage) { @@ -103,16 +106,16 @@ public class ContributionController { toastMessage, Toast.LENGTH_LONG ).show(); - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); } @Override public void onLocationPermissionGranted() { if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { showLocationOffDialog(activity, R.string.in_app_camera_needs_location, - R.string.in_app_camera_location_unavailable); + R.string.in_app_camera_location_unavailable, resultLauncher); } else { - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); } } }; @@ -135,9 +138,10 @@ public class ContributionController { * @param activity Activity reference * @param dialogTextResource Resource id of text to be shown in dialog * @param toastTextResource Resource id of text to be shown in toast + * @param resultLauncher */ private void showLocationOffDialog(Activity activity, int dialogTextResource, - int toastTextResource) { + int toastTextResource, ActivityResultLauncher resultLauncher) { DialogUtil .showAlertDialog(activity, activity.getString(R.string.ask_to_turn_location_on), @@ -148,20 +152,21 @@ public class ContributionController { () -> { Toast.makeText(activity, activity.getString(toastTextResource), Toast.LENGTH_LONG).show(); - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); } ); } public void handleShowRationaleFlowCameraLocation(Activity activity, - ActivityResultLauncher inAppCameraLocationPermissionLauncher) { + ActivityResultLauncher inAppCameraLocationPermissionLauncher, + ActivityResultLauncher resultLauncher) { DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title), activity.getString(R.string.in_app_camera_location_permission_rationale), activity.getString(android.R.string.ok), activity.getString(android.R.string.cancel), () -> { createDialogsAndHandleLocationPermissions(activity, - inAppCameraLocationPermissionLauncher); + inAppCameraLocationPermissionLauncher, resultLauncher); }, () -> locationPermissionCallback.onLocationPermissionDenied( activity.getString(R.string.in_app_camera_location_permission_denied)), @@ -181,7 +186,8 @@ public class ContributionController { * @param activity */ private void askUserToAllowLocationAccess(Activity activity, - ActivityResultLauncher inAppCameraLocationPermissionLauncher) { + ActivityResultLauncher inAppCameraLocationPermissionLauncher, + ActivityResultLauncher resultLauncher) { DialogUtil.showAlertDialog(activity, activity.getString(R.string.in_app_camera_location_permission_title), activity.getString(R.string.in_app_camera_location_access_explanation), @@ -190,12 +196,12 @@ public class ContributionController { () -> { defaultKvStore.putBoolean("inAppCameraLocationPref", true); createDialogsAndHandleLocationPermissions(activity, - inAppCameraLocationPermissionLauncher); + inAppCameraLocationPermissionLauncher, resultLauncher); }, () -> { ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied); defaultKvStore.putBoolean("inAppCameraLocationPref", false); - initiateCameraUpload(activity); + initiateCameraUpload(activity, resultLauncher); }, null, true); @@ -204,18 +210,18 @@ public class ContributionController { /** * Initiate gallery picker */ - public void initiateGalleryPick(final Activity activity, final boolean allowMultipleUploads) { - initiateGalleryUpload(activity, allowMultipleUploads); + public void initiateGalleryPick(final Activity activity, ActivityResultLauncher resultLauncher, final boolean allowMultipleUploads) { + initiateGalleryUpload(activity, resultLauncher, allowMultipleUploads); } /** * Initiate gallery picker with permission */ - public void initiateCustomGalleryPickWithPermission(final Activity activity) { + public void initiateCustomGalleryPickWithPermission(final Activity activity, ActivityResultLauncher resultLauncher) { setPickerConfiguration(activity, true); PermissionUtils.checkPermissionsAndPerformAction(activity, - () -> FilePicker.openCustomSelector(activity, 0), + () -> FilePicker.openCustomSelector(activity, resultLauncher, 0), R.string.storage_permission_title, R.string.write_storage_permission_rationale, PermissionUtils.PERMISSIONS_STORAGE); @@ -225,12 +231,10 @@ public class ContributionController { /** * Open chooser for gallery uploads */ - private void initiateGalleryUpload(final Activity activity, + private void initiateGalleryUpload(final Activity activity, ActivityResultLauncher resultLauncher, final boolean allowMultipleUploads) { setPickerConfiguration(activity, allowMultipleUploads); - boolean openDocumentIntentPreferred = defaultKvStore.getBoolean( - "openDocumentPhotoPickerPref", true); - FilePicker.openGallery(activity, 0, openDocumentIntentPreferred); + FilePicker.openGallery(activity, resultLauncher, 0, isDocumentPhotoPickerPreferred()); } /** @@ -247,22 +251,43 @@ public class ContributionController { /** * Initiate camera upload by opening camera */ - private void initiateCameraUpload(Activity activity) { + private void initiateCameraUpload(Activity activity, ActivityResultLauncher resultLauncher) { setPickerConfiguration(activity, false); if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) { locationBeforeImageCapture = locationManager.getLastLocation(); } isInAppCameraUpload = true; - FilePicker.openCameraForImage(activity, 0); + FilePicker.openCameraForImage(activity, resultLauncher, 0); + } + + private boolean isDocumentPhotoPickerPreferred(){ + return defaultKvStore.getBoolean( + "openDocumentPhotoPickerPref", true); + } + + public void onPictureReturnedFromGallery(ActivityResult result, Activity activity, FilePicker.Callbacks callbacks){ + + if(isDocumentPhotoPickerPreferred()){ + FilePicker.onPictureReturnedFromDocuments(result, activity, callbacks); + } else { + FilePicker.onPictureReturnedFromGallery(result, activity, callbacks); + } + } + + public void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + FilePicker.onPictureReturnedFromCustomSelector(result, activity, callbacks); + } + + public void onPictureReturnedFromCamera(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + FilePicker.onPictureReturnedFromCamera(result, activity, callbacks); } /** * Attaches callback for file picker. */ - public void handleActivityResult(Activity activity, int requestCode, int resultCode, - Intent data) { - FilePicker.handleActivityResult(requestCode, resultCode, data, activity, - new DefaultCallback() { + public void handleActivityResultWithCallback(Activity activity, FilePicker.HandleActivityResult handleActivityResult) { + + handleActivityResult.onHandleActivityResult(new DefaultCallback() { @Override public void onCanceled(final ImageSource source, final int type) { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index 53c91534e..509d1eb95 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -6,6 +6,7 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_ import android.Manifest.permission; import android.content.Context; +import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; @@ -20,6 +21,7 @@ import android.widget.LinearLayout; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -96,6 +98,30 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl private int contributionsSize; private String userName; + private final ActivityResultLauncher galleryPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher customSelectorLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher cameraPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); + }); + private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult( new RequestMultiplePermissions(), new ActivityResultCallback>() { @@ -111,7 +137,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl } else { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { controller.handleShowRationaleFlowCameraLocation(getActivity(), - inAppCameraLocationPermissionLauncher); + inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); } else { controller.locationPermissionCallback.onLocationPermissionDenied( getActivity().getString( @@ -322,7 +348,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl private void setListeners() { binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); binding.fabCamera.setOnClickListener(view -> { - controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher); + controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); animateFAB(isFabOpen); }); binding.fabCamera.setOnLongClickListener(view -> { @@ -330,7 +356,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl return true; }); binding.fabGallery.setOnClickListener(view -> { - controller.initiateGalleryPick(getActivity(), true); + controller.initiateGalleryPick(getActivity(), galleryPickLauncherForResult, true); animateFAB(isFabOpen); }); binding.fabGallery.setOnLongClickListener(view -> { @@ -343,7 +369,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl * Launch Custom Selector. */ protected void launchCustomSelector() { - controller.initiateCustomGalleryPickWithPermission(getActivity()); + controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); animateFAB(isFabOpen); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java index 54d3e9681..a9e9ee5c6 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.java @@ -438,13 +438,6 @@ public class MainActivity extends BaseActivity }); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - Timber.d(data != null ? data.toString() : "onActivityResult data is null"); - super.onActivityResult(requestCode, resultCode, data); - controller.handleActivityResult(this, requestCode, resultCode, data); - } - @Override protected void onResume() { super.onResume(); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt index 77e52e1db..86cda2cf3 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/WikipediaInstructionsDialogFragment.kt @@ -22,7 +22,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() { ) = DialogAddToWikipediaInstructionsBinding .inflate(inflater, container, false) .apply { - val contribution: Contribution? = arguments!!.getParcelable(ARG_CONTRIBUTION) + val contribution: Contribution? = requireArguments().getParcelable(ARG_CONTRIBUTION) tvWikicode.setText(contribution?.media?.wikiCode) instructionsCancel.setOnClickListener { dismiss() } instructionsConfirm.setOnClickListener { diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/helper/FolderDeletionHelper.kt b/app/src/main/java/fr/free/nrw/commons/customselector/helper/FolderDeletionHelper.kt index 01f3196f0..0ead4c289 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/helper/FolderDeletionHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/helper/FolderDeletionHelper.kt @@ -1,6 +1,5 @@ package fr.free.nrw.commons.customselector.helper -import android.app.Activity import android.content.ContentUris import android.content.Context import android.media.MediaScannerConnection @@ -8,9 +7,10 @@ import android.net.Uri import android.os.Build import android.provider.MediaStore import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest import androidx.appcompat.app.AlertDialog import fr.free.nrw.commons.R -import fr.free.nrw.commons.filepicker.Constants import timber.log.Timber import java.io.File @@ -22,15 +22,20 @@ object FolderDeletionHelper { * @param context The context used to show the confirmation dialog and manage deletion. * @param folder The folder to be deleted. * @param onDeletionComplete Callback invoked with `true` if the folder was + * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. * successfully deleted, `false` otherwise. */ - fun confirmAndDeleteFolder(context: Context, folder: File, onDeletionComplete: (Boolean) -> Unit) { + fun confirmAndDeleteFolder( + context: Context, + folder: File, + trashFolderLauncher: ActivityResultLauncher, + onDeletionComplete: (Boolean) -> Unit) { val itemCount = countItemsInFolder(context, folder) val folderPath = folder.absolutePath //don't show this dialog on API 30+, it's handled automatically using MediaStore if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - val success = deleteFolderMain(context, folder) + val success = deleteFolderMain(context, folder, trashFolderLauncher) onDeletionComplete(success) } else { @@ -40,7 +45,7 @@ object FolderDeletionHelper { .setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ -> //proceed with deletion if user confirms - val success = deleteFolderMain(context, folder) + val success = deleteFolderMain(context, folder, trashFolderLauncher) onDeletionComplete(success) } .setNegativeButton(context.getString(R.string.custom_selector_cancel)) { dialog, _ -> @@ -56,12 +61,17 @@ object FolderDeletionHelper { * * @param context The context used to manage storage operations. * @param folder The folder to delete. + * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. * @return `true` if the folder deletion was successful, `false` otherwise. */ - private fun deleteFolderMain(context: Context, folder: File): Boolean { + private fun deleteFolderMain( + context: Context, + folder: File, + trashFolderLauncher: ActivityResultLauncher): Boolean + { return when { //for API 30 and above, use MediaStore - Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder) + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder, trashFolderLauncher) //for API 29 ('requestLegacyExternalStorage' is set to true in Manifest) // and below use file system @@ -75,9 +85,14 @@ object FolderDeletionHelper { * * @param context The context used to access the content resolver. * @param folder The folder whose contents are to be moved to the trash. + * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. * @return `true` if the trash request was initiated successfully, `false` otherwise. */ - private fun trashFolderContents(context: Context, folder: File): Boolean { + private fun trashFolderContents( + context: Context, + folder: File, + trashFolderLauncher: ActivityResultLauncher): Boolean + { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false val contentResolver = context.contentResolver @@ -111,11 +126,8 @@ object FolderDeletionHelper { if (urisToTrash.isNotEmpty()) { try { val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true) - (context as? Activity)?.startIntentSenderForResult( - trashRequest.intentSender, - Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE, - null, 0, 0, 0 - ) + val intentSenderRequest = IntentSenderRequest.Builder(trashRequest.intentSender).build() + trashFolderLauncher.launch(intentSenderRequest) return true } catch (e: SecurityException) { Timber.tag("DeleteFolder").e(context.getString(R.string.custom_selector_error_trashing_folder_contents, e.message)) diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt index a85d48ed1..6c6d7e53f 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/CustomSelectorActivity.kt @@ -14,6 +14,9 @@ import android.widget.Button import android.widget.ImageButton import android.widget.PopupMenu import android.widget.TextView +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -42,7 +45,6 @@ import fr.free.nrw.commons.R import fr.free.nrw.commons.customselector.database.NotForUploadStatus import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants -import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.SHOULD_REFRESH import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper import fr.free.nrw.commons.customselector.listeners.FolderClickListener import fr.free.nrw.commons.customselector.listeners.ImageSelectListener @@ -50,7 +52,6 @@ import fr.free.nrw.commons.customselector.model.Image import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding -import fr.free.nrw.commons.filepicker.Constants import fr.free.nrw.commons.media.ZoomableActivity import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.FileUtilsWrapper @@ -65,7 +66,6 @@ import java.io.File import java.lang.Integer.max import javax.inject.Inject - /** * Custom Selector Activity. */ @@ -155,7 +155,16 @@ class CustomSelectorActivity : */ private var showOverflowMenu = false + /** + * Waits for confirmation of delete folder + */ + private val startForFolderDeletionResult = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){ + result -> onDeleteFolderResultReceived(result) + } + private val startForResult = registerForActivityResult(StartActivityForResult()){ result -> + onFullScreenDataReceived(result) + } /** @@ -184,9 +193,9 @@ class CustomSelectorActivity : } }, modifier = - Modifier - .padding(vertical = 8.dp, horizontal = 4.dp) - .fillMaxWidth(), + Modifier + .padding(vertical = 8.dp, horizontal = 4.dp) + .fillMaxWidth(), ) } val view = binding.root @@ -215,7 +224,6 @@ class CustomSelectorActivity : } } - override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, @@ -237,35 +245,24 @@ class CustomSelectorActivity : /** * When data will be send from full screen mode, it will be passed to fragment */ - override fun onActivityResult( - requestCode: Int, - resultCode: Int, - data: Intent?, - ) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE && - resultCode == Activity.RESULT_OK - ) { + private fun onFullScreenDataReceived(result: ActivityResult){ + if (result.resultCode == Activity.RESULT_OK) { val selectedImages: ArrayList = - data!! + result.data!! .getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!! - val shouldRefresh = data.getBooleanExtra(SHOULD_REFRESH, false) - imageFragment?.passSelectedImages(selectedImages, shouldRefresh) + viewModel?.selectedImages?.value = selectedImages } + } - if (requestCode == Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE && - resultCode == Activity.RESULT_OK) { - + private fun onDeleteFolderResultReceived(result: ActivityResult){ + if (result.resultCode == Activity.RESULT_OK){ FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) navigateToCustomSelector() } - } - - /** * Show Custom Selector Welcome Dialog. */ @@ -438,7 +435,7 @@ class CustomSelectorActivity : } /** - * Set up the toolbar, back listener, done listener, overflow menu listener. + * Set up the toolbar, back listener, done listener. */ private fun setUpToolbar() { val back: ImageButton = findViewById(R.id.back) @@ -489,9 +486,9 @@ class CustomSelectorActivity : return } - FolderDeletionHelper.confirmAndDeleteFolder(this, folder) { success -> + FolderDeletionHelper.confirmAndDeleteFolder(this, folder, startForFolderDeletionResult) { success -> if (success) { - // For API 30+, navigation is handled in 'onActivityResult' + //for API 30+, navigation is handled in 'onDeleteFolderResultReceived' if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) navigateToCustomSelector() @@ -542,9 +539,8 @@ class CustomSelectorActivity : override fun onFolderClick( folderId: Long, folderName: String, - lastItemId: Long + lastItemId: Long, ) { - supportFragmentManager .beginTransaction() .add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId)) @@ -563,7 +559,6 @@ class CustomSelectorActivity : } - /** * override Selected Images Change, update view model selected images and change UI. */ @@ -629,7 +624,7 @@ class CustomSelectorActivity : selectedImages, ) intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId) - startActivityForResult(intent, Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE) + startForResult.launch(intent) } /** @@ -738,15 +733,13 @@ fun partialStorageAccessIndicator( OutlinedCard( modifier = modifier, colors = - CardDefaults.cardColors( - containerColor = colorResource(R.color.primarySuperLightColor), - ), + CardDefaults.cardColors( + containerColor = colorResource(R.color.primarySuperLightColor), + ), border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)), shape = RoundedCornerShape(8.dp), ) { - Row(modifier = Modifier - .padding(16.dp) - .fillMaxWidth()) { + Row(modifier = Modifier.padding(16.dp).fillMaxWidth()) { Text( text = "You've given access to a select number of photos", modifier = Modifier.weight(1f), @@ -755,9 +748,9 @@ fun partialStorageAccessIndicator( onClick = onManage, modifier = Modifier.align(Alignment.Bottom), colors = - ButtonDefaults.buttonColors( - containerColor = colorResource(R.color.primaryColor), - ), + ButtonDefaults.buttonColors( + containerColor = colorResource(R.color.primaryColor), + ), shape = RoundedCornerShape(8.dp), ) { Text( @@ -779,9 +772,9 @@ fun partialStorageAccessIndicatorPreview() { isVisible = true, onManage = {}, modifier = - Modifier - .padding(vertical = 8.dp, horizontal = 4.dp) - .fillMaxWidth(), + Modifier + .padding(vertical = 8.dp, horizontal = 4.dp) + .fillMaxWidth(), ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt index 7e522f681..dbab629ff 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt @@ -279,11 +279,17 @@ class ImageFragment : filteredImages = ImageHelper.filterImages(images, bucketId) allImages = ArrayList(filteredImages) imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) + viewModel?.selectedImages?.value?.let { selectedImages -> + imageAdapter.setSelectedImages(selectedImages) + } + imageAdapter.notifyDataSetChanged() selectorRV?.let { it.visibility = View.VISIBLE - lastItemId?.let { pos -> - (it.layoutManager as GridLayoutManager) - .scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos)) + if (switch?.isChecked == false) { + lastItemId?.let { pos -> + (it.layoutManager as GridLayoutManager) + .scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos)) + } } } } else { @@ -382,14 +388,6 @@ class ImageFragment : selectedImages: ArrayList, shouldRefresh: Boolean, ) { - imageAdapter.setSelectedImages(selectedImages) - - val uploadingContributions = getUploadingContributions() - - if (!showAlreadyActionedImages && shouldRefresh) { - imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) - imageAdapter.setSelectedImages(selectedImages) - } } /** diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt index 1fb5c5953..ddfcf341e 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageLoader.kt @@ -17,6 +17,7 @@ import fr.free.nrw.commons.utils.CustomSelectorUtils import fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.checkWhetherFileExistsOnCommonsUsingSHA1 import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import java.util.Calendar diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index 0dbdf71ae..fa4349dbf 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -6,6 +6,8 @@ import android.os.Bundle import android.os.Parcelable import android.speech.RecognizerIntent import android.view.View +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import fr.free.nrw.commons.CommonsApplication @@ -70,10 +72,14 @@ class DescriptionEditActivity : private lateinit var binding: ActivityDescriptionEditBinding - private val requestCodeForVoiceInput = 1213 - private var descriptionAndCaptions: ArrayList? = null + private val voiceInputResultLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result: ActivityResult -> + onVoiceInput(result) + } + @Inject lateinit var descriptionEditHelper: DescriptionEditHelper @Inject lateinit var sessionManager: SessionManager @@ -115,6 +121,7 @@ class DescriptionEditActivity : savedLanguageValue, descriptionAndCaptions, recentLanguagesDao, + voiceInputResultLauncher ) uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int -> showInfoAlert( @@ -149,6 +156,15 @@ class DescriptionEditActivity : override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {} + private fun onVoiceInput(result: ActivityResult) { + if (result.resultCode == RESULT_OK && result.data != null) { + val resultData = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) + uploadMediaDetailAdapter.handleSpeechResult(resultData!![0]) + } else { + Timber.e("Error %s", result.resultCode) + } + } + /** * Adds new language item to RecyclerView */ @@ -221,7 +237,7 @@ class DescriptionEditActivity : ) { try { descriptionEditHelper - ?.addDescription( + .addDescription( applicationContext, media, updatedWikiText, @@ -234,7 +250,7 @@ class DescriptionEditActivity : ) } } catch (e: InvalidLoginTokenException) { - val username: String? = sessionManager?.userName + val username: String? = sessionManager.userName val logoutListener = CommonsApplication.BaseLogoutListener( this, @@ -242,7 +258,7 @@ class DescriptionEditActivity : username, ) - val commonsApplication = CommonsApplication.getInstance() + val commonsApplication = CommonsApplication.instance if (commonsApplication != null) { commonsApplication.clearApplicationData(this, logoutListener) } @@ -252,7 +268,7 @@ class DescriptionEditActivity : for (mediaDetail in uploadMediaDetails) { try { compositeDisposable.add( - descriptionEditHelper!! + descriptionEditHelper .addCaption( applicationContext, media, @@ -275,7 +291,7 @@ class DescriptionEditActivity : username, ) - val commonsApplication = CommonsApplication.getInstance() + val commonsApplication = CommonsApplication.instance if (commonsApplication != null) { commonsApplication.clearApplicationData(this, logoutListener) } @@ -292,22 +308,6 @@ class DescriptionEditActivity : progressDialog!!.show() } - override fun onActivityResult( - requestCode: Int, - resultCode: Int, - data: Intent?, - ) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == requestCodeForVoiceInput) { - if (resultCode == RESULT_OK && data != null) { - val result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) - uploadMediaDetailAdapter.handleSpeechResult(result!![0]) - } else { - Timber.e("Error %s", resultCode) - } - } - } - override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) diff --git a/app/src/main/java/fr/free/nrw/commons/edit/TransformImageImpl.kt b/app/src/main/java/fr/free/nrw/commons/edit/TransformImageImpl.kt index b59619691..c3db1a5a0 100644 --- a/app/src/main/java/fr/free/nrw/commons/edit/TransformImageImpl.kt +++ b/app/src/main/java/fr/free/nrw/commons/edit/TransformImageImpl.kt @@ -65,7 +65,6 @@ class TransformImageImpl : TransformImage { } catch (e: LLJTranException) { Timber.tag("Error").d(e) return null - false } if (rotated) { diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java index c66cd5163..26c8dd82b 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java @@ -22,6 +22,7 @@ import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.utils.ActivityUtils; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.inject.Inject; import javax.inject.Named; @@ -112,13 +113,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { mobileRootFragment = new ExploreListRootFragment(mobileArguments); mapRootFragment = new ExploreMapRootFragment(mapArguments); fragmentList.add(featuredRootFragment); - titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase()); + titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT)); fragmentList.add(mobileRootFragment); - titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase()); + titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT)); fragmentList.add(mapRootFragment); - titleList.add(getString(R.string.explore_tab_title_map).toUpperCase()); + titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT)); ((MainActivity)getActivity()).showTabs(); ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java index 7717f2deb..abb27184f 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java @@ -28,6 +28,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.inject.Inject; import timber.log.Timber; @@ -95,11 +96,11 @@ public class SearchActivity extends BaseActivity searchDepictionsFragment = new SearchDepictionsFragment(); searchCategoryFragment= new SearchCategoryFragment(); fragmentList.add(searchMediaFragment); - titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase()); + titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT)); fragmentList.add(searchCategoryFragment); - titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase()); + titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT)); fragmentList.add(searchDepictionsFragment); - titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase()); + titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT)); viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/media/CategoriesMediaFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/media/CategoriesMediaFragment.kt index 6de1248b4..765abd698 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/categories/media/CategoriesMediaFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/media/CategoriesMediaFragment.kt @@ -18,6 +18,6 @@ class CategoriesMediaFragment : PageableMediaFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") + onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/parent/ParentCategoriesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/parent/ParentCategoriesFragment.kt index 6ceccf607..c43e1c6bd 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/categories/parent/ParentCategoriesFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/parent/ParentCategoriesFragment.kt @@ -21,6 +21,6 @@ class ParentCategoriesFragment : PageableCategoryFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") + onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/categories/sub/SubCategoriesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/categories/sub/SubCategoriesFragment.kt index 19fe52beb..8fbc83039 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/categories/sub/SubCategoriesFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/categories/sub/SubCategoriesFragment.kt @@ -20,6 +20,6 @@ class SubCategoriesFragment : PageableCategoryFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") + onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/child/ChildDepictionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/child/ChildDepictionsFragment.kt index 527536299..4f13b1be8 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/child/ChildDepictionsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/child/ChildDepictionsFragment.kt @@ -13,13 +13,13 @@ class ChildDepictionsFragment : PageableDepictionsFragment() { override val injectedPresenter get() = presenter - override fun getEmptyText(query: String) = getString(R.string.no_child_classes, arguments!!.getString("wikidataItemName")!!) + override fun getEmptyText(query: String) = getString(R.string.no_child_classes, requireArguments().getString("wikidataItemName")!!) override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated(arguments!!.getString("entityId")!!) + onQueryUpdated(requireArguments().getString("entityId")!!) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/media/DepictedImagesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/media/DepictedImagesFragment.kt index cc1b664b2..4cdb0e461 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/media/DepictedImagesFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/media/DepictedImagesFragment.kt @@ -17,6 +17,6 @@ class DepictedImagesFragment : PageableMediaFragment() { savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated(arguments!!.getString("entityId")!!) + onQueryUpdated(requireArguments().getString("entityId")!!) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/parent/ParentDepictionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/parent/ParentDepictionsFragment.kt index 52a5aff5d..cf739a07d 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/parent/ParentDepictionsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/parent/ParentDepictionsFragment.kt @@ -13,13 +13,13 @@ class ParentDepictionsFragment : PageableDepictionsFragment() { override val injectedPresenter get() = presenter - override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, arguments!!.getString("wikidataItemName")!!) + override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, requireArguments().getString("wikidataItemName")!!) override fun onViewCreated( view: View, savedInstanceState: Bundle?, ) { super.onViewCreated(view, savedInstanceState) - onQueryUpdated(arguments!!.getString("entityId")!!) + onQueryUpdated(requireArguments().getString("entityId")!!) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/media/MediaConverter.kt b/app/src/main/java/fr/free/nrw/commons/explore/media/MediaConverter.kt index 70b46b129..a3103d41a 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/media/MediaConverter.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/media/MediaConverter.kt @@ -40,7 +40,7 @@ class MediaConverter metadata.licenseShortName(), metadata.prefixedLicenseUrl, getAuthor(metadata), - imageInfo.user, + getAuthor(metadata), MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories), metadata.latLng, entity.labels().mapValues { it.value.value() }, diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java index 9f12639dd..cee8a25ae 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.explore.recentsearches; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -178,6 +179,7 @@ public class RecentSearchesDao { * @return RecentSearch object */ @NonNull + @SuppressLint("Range") RecentSearch fromCursor(Cursor cursor) { // Hardcoding column positions! return new RecentSearch( diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java index cd98651f0..0db1e5539 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.java @@ -15,6 +15,7 @@ import fr.free.nrw.commons.databinding.FragmentSearchHistoryBinding; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.explore.SearchActivity; import java.util.List; +import java.util.Locale; import javax.inject.Inject; @@ -90,7 +91,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment { private void showDeleteAlertDialog(@NonNull final Context context, final int position) { new AlertDialog.Builder(context) .setMessage(R.string.delete_search_dialog) - .setPositiveButton(getString(R.string.delete).toUpperCase(), + .setPositiveButton(getString(R.string.delete).toUpperCase(Locale.ROOT), ((dialog, which) -> setDeletePositiveButton(context, dialog, position))) .setNegativeButton(android.R.string.cancel, null) .create() diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java index 503d385ac..611755aa1 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/Constants.java @@ -4,23 +4,12 @@ public interface Constants { String DEFAULT_FOLDER_NAME = "CommonsContributions"; /** - * Provides the request codes utilised by the FilePicker + * Provides the request codes for permission handling */ interface RequestCodes { int LOCATION = 1; int STORAGE = 2; - int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876 - int SOURCE_CHOOSER = 1 << 15; - int PICK_PICTURE_FROM_CUSTOM_SELECTOR = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 10); - int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11); - int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12); - int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13); - int CAPTURE_VIDEO = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 14); - - int RECEIVE_DATA_FROM_FULL_SCREEN_MODE = 1 << 9; - - int DELETE_FOLDER_REQUEST_CODE = 1 << 16; } /** diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java index 08453a95d..b64db24c5 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.java @@ -12,6 +12,8 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.provider.MediaStore; import android.text.TextUtils; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; @@ -107,25 +109,25 @@ public class FilePicker implements Constants { * * @param type Custom type of your choice, which will be returned with the images */ - public static void openGallery(Activity activity, int type, boolean openDocumentIntentPreferred) { + public static void openGallery(Activity activity, ActivityResultLauncher resultLauncher, int type, boolean openDocumentIntentPreferred) { Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); - activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY); + resultLauncher.launch(intent); } /** * Opens Custom Selector */ - public static void openCustomSelector(Activity activity, int type) { + public static void openCustomSelector(Activity activity, ActivityResultLauncher resultLauncher, int type) { Intent intent = createCustomSelectorIntent(activity, type); - activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR); + resultLauncher.launch(intent); } /** * Opens the camera app to pick image clicked by user */ - public static void openCameraForImage(Activity activity, int type) { + public static void openCameraForImage(Activity activity, ActivityResultLauncher resultLauncher, int type) { Intent intent = createCameraForImageIntent(activity, type); - activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE); + resultLauncher.launch(intent); } @Nullable @@ -148,47 +150,6 @@ public class FilePicker implements Constants { } } - /** - * Any activity can use this method to attach their callback to the file picker - */ - public static void handleActivityResult(int requestCode, int resultCode, Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - boolean isHandledPickedFile = (requestCode & RequestCodes.FILE_PICKER_IMAGE_IDENTIFICATOR) > 0; - if (isHandledPickedFile) { - requestCode &= ~RequestCodes.SOURCE_CHOOSER; - if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY || - requestCode == RequestCodes.TAKE_PICTURE || - requestCode == RequestCodes.CAPTURE_VIDEO || - requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS || - requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) { - if (resultCode == Activity.RESULT_OK) { - if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) { - onPictureReturnedFromDocuments(data, activity, callbacks); - } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) { - onPictureReturnedFromGallery(data, activity, callbacks); - } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) { - onPictureReturnedFromCustomSelector(data, activity, callbacks); - } else if (requestCode == RequestCodes.TAKE_PICTURE) { - onPictureReturnedFromCamera(activity, callbacks); - } else if (requestCode == RequestCodes.CAPTURE_VIDEO) { - onVideoReturnedFromCamera(activity, callbacks); - } else if (isPhoto(data)) { - onPictureReturnedFromCamera(activity, callbacks); - } else { - onPictureReturnedFromDocuments(data, activity, callbacks); - } - } else { - if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) { - callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); - } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY) { - callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); - } else { - callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); - } - } - } - } - } - public static List handleExternalImagesPicked(Intent data, Activity activity) { try { return getFilesFromGalleryPictures(data, activity); @@ -241,18 +202,22 @@ public class FilePicker implements Constants { return intent; } - private static void onPictureReturnedFromDocuments(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - try { - Uri photoPath = data.getData(); - UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); - callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); + public static void onPictureReturnedFromDocuments(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ + try { + Uri photoPath = result.getData().getData(); + UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); + callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); - if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); + if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { + PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); + } + } catch (Exception e) { + e.printStackTrace(); + callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); } - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); + } else { + callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); } } @@ -260,14 +225,18 @@ public class FilePicker implements Constants { * onPictureReturnedFromCustomSelector. * Retrieve and forward the images to upload wizard through callback. */ - private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - try { - List files = getFilesFromCustomSelector(data, activity); - callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); - } + public static void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + if(result.getResultCode() == Activity.RESULT_OK){ + try { + List files = getFilesFromCustomSelector(result.getData(), activity); + callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); + } catch (Exception e) { + e.printStackTrace(); + callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); + } + } else { + callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity)); + } } /** @@ -290,13 +259,17 @@ public class FilePicker implements Constants { return files; } - private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { - try { - List files = getFilesFromGalleryPictures(data, activity); - callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); + public static void onPictureReturnedFromGallery(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ + try { + List files = getFilesFromGalleryPictures(result.getData(), activity); + callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); + } catch (Exception e) { + e.printStackTrace(); + callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); + } + } else{ + callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); } } @@ -322,69 +295,40 @@ public class FilePicker implements Constants { return files; } - private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) { - try { - String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); - if (!TextUtils.isEmpty(lastImageUri)) { - revokeWritePermission(activity, Uri.parse(lastImageUri)); - } - - UploadableFile photoFile = FilePicker.takenCameraPicture(activity); - List 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)); + public static void onPictureReturnedFromCamera(ActivityResult activityResult, Activity activity, @NonNull FilePicker.Callbacks callbacks) { + if(activityResult.getResultCode() == Activity.RESULT_OK){ + try { + String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); + if (!TextUtils.isEmpty(lastImageUri)) { + revokeWritePermission(activity, Uri.parse(lastImageUri)); } - callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); - } + UploadableFile photoFile = FilePicker.takenCameraPicture(activity); + List 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() .remove(KEY_LAST_CAMERA_PHOTO) .remove(KEY_PHOTO_URI) .apply(); - } catch (Exception e) { - e.printStackTrace(); - callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); - } - } - - private static void onVideoReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) { - try { - String lastVideoUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_VIDEO_URI, null); - if (!TextUtils.isEmpty(lastVideoUri)) { - revokeWritePermission(activity, Uri.parse(lastVideoUri)); + } catch (Exception e) { + e.printStackTrace(); + callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); } - - UploadableFile photoFile = FilePicker.takenCameraVideo(activity); - List 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)); + } else { + callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); } } @@ -404,4 +348,8 @@ public class FilePicker implements Constants { void onCanceled(FilePicker.ImageSource source, int type); } + + public interface HandleActivityResult{ + void onHandleActivityResult(FilePicker.Callbacks callbacks); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java index 7336c1b40..ee905a5c5 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.java @@ -1,13 +1,10 @@ package fr.free.nrw.commons.media; -import static android.app.Activity.RESULT_CANCELED; -import static android.app.Activity.RESULT_OK; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES; import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED; import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION; -import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT; import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT; import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources; @@ -112,8 +109,6 @@ import timber.log.Timber; public class MediaDetailFragment extends CommonsDaggerSupportFragment implements CategoryEditHelper.Callback { - private static final int REQUEST_CODE = 1001; - private static final int REQUEST_CODE_EDIT_DESCRIPTION = 1002; private static final String IMAGE_BACKGROUND_COLOR = "image_background_color"; static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0; @@ -277,6 +272,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements if (!sessionManager.isUserLoggedIn()) { binding.categoryEditButton.setVisibility(GONE); + binding.descriptionEdit.setVisibility(GONE); + binding.depictionsEditButton.setVisibility(GONE); + } else { + binding.categoryEditButton.setVisibility(VISIBLE); + binding.descriptionEdit.setVisibility(VISIBLE); + binding.depictionsEditButton.setVisibility(VISIBLE); } if(applicationKvStore.getBoolean("login_skipped")){ @@ -405,7 +406,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements } ); binding.progressBarEdit.setVisibility(GONE); - binding.descriptionEdit.setVisibility(VISIBLE); } @Override @@ -605,8 +605,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements // Check if the presented category is about need of category if (categoriesPresent) { for (String category : media.getCategories()) { - if (category.toLowerCase().contains(CATEGORY_NEEDING_CATEGORIES) || - category.toLowerCase().contains(CATEGORY_UNCATEGORISED)) { + if (category.toLowerCase(Locale.ROOT).contains(CATEGORY_NEEDING_CATEGORIES) || + category.toLowerCase(Locale.ROOT).contains(CATEGORY_UNCATEGORISED)) { categoriesPresent = false; } break; @@ -683,7 +683,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements // Stick in a filler element. allCategories.add(getString(R.string.detail_panel_cats_none)); } - binding.categoryEditButton.setVisibility(VISIBLE); + if(sessionManager.isUserLoggedIn()) { + binding.categoryEditButton.setVisibility(VISIBLE); + } rebuildCatList(allCategories); } @@ -1065,81 +1067,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements return captionList; } - /** - * Get the result from another activity and act accordingly. - * @param requestCode - * @param resultCode - * @param data - */ - @Override - public void onActivityResult(final int requestCode, final int resultCode, - @Nullable final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) { - final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT); - - try { - compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media, - updatedWikiText) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(s -> { - Timber.d("Descriptions are added."); - })); - } catch (Exception e) { - if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) { - final String username = sessionManager.getUserName(); - final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener( - getActivity(), - requireActivity().getString(R.string.invalid_login_message), - username - ); - - CommonsApplication.getInstance().clearApplicationData( - requireActivity(), logoutListener); - } - } - - final ArrayList uploadMediaDetails - = data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION); - - LinkedHashMap updatedCaptions = new LinkedHashMap<>(); - for (UploadMediaDetail mediaDetail: - uploadMediaDetails) { - try { - compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media, - mediaDetail.getLanguageCode(), mediaDetail.getCaptionText()) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(s -> { - updateCaptions(mediaDetail, updatedCaptions); - Timber.d("Caption is added."); - })); - - } catch (Exception e) { - if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) { - final String username = sessionManager.getUserName(); - final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener( - getActivity(), - requireActivity().getString(R.string.invalid_login_message), - username - ); - - CommonsApplication.getInstance().clearApplicationData( - requireActivity(), logoutListener); - } - } - } - binding.progressBarEdit.setVisibility(GONE); - binding.descriptionEdit.setVisibility(VISIBLE); - - } else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_CANCELED) { - binding.progressBarEdit.setVisibility(GONE); - binding.descriptionEdit.setVisibility(VISIBLE); - } - } - /** * Adds caption to the map and updates captions * @param mediaDetail UploadMediaDetail diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt index d08e3048c..14b5788c2 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt @@ -219,7 +219,7 @@ class ZoomableActivity : BaseActivity() { onSwipe() } } - binding.zoomProgressBar?.let { + binding.zoomProgressBar.let { it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE } } @@ -234,7 +234,7 @@ class ZoomableActivity : BaseActivity() { sharedPreferences.getBoolean(ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) if (!images.isNullOrEmpty()) { - binding.zoomable!!.setOnTouchListener( + binding.zoomable.setOnTouchListener( object : OnSwipeTouchListener(this) { // Swipe left to view next image in the folder. (if available) override fun onSwipeLeft() { @@ -271,7 +271,7 @@ class ZoomableActivity : BaseActivity() { * Handles down swipe action */ private fun onDownSwiped() { - if (binding.zoomable?.zoomableController?.isIdentity == false) { + if (binding.zoomable.zoomableController?.isIdentity == false) { return } @@ -341,7 +341,7 @@ class ZoomableActivity : BaseActivity() { * Handles up swipe action */ private fun onUpSwiped() { - if (binding.zoomable?.zoomableController?.isIdentity == false) { + if (binding.zoomable.zoomableController?.isIdentity == false) { return } @@ -414,7 +414,7 @@ class ZoomableActivity : BaseActivity() { * Handles right swipe action */ private fun onRightSwiped(showAlreadyActionedImages: Boolean) { - if (binding.zoomable?.zoomableController?.isIdentity == false) { + if (binding.zoomable.zoomableController?.isIdentity == false) { return } @@ -451,7 +451,7 @@ class ZoomableActivity : BaseActivity() { * Handles left swipe action */ private fun onLeftSwiped(showAlreadyActionedImages: Boolean) { - if (binding.zoomable?.zoomableController?.isIdentity == false) { + if (binding.zoomable.zoomableController?.isIdentity == false) { return } @@ -646,7 +646,7 @@ class ZoomableActivity : BaseActivity() { .setProgressBarImage(ProgressBarDrawable()) .setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) .build() - with(binding.zoomable!!) { + with(binding.zoomable) { setHierarchy(hierarchy) setAllowTouchInterceptionWhileZoomed(true) setIsLongpressEnabled(false) @@ -658,10 +658,10 @@ class ZoomableActivity : BaseActivity() { .setUri(imageUri) .setControllerListener(loadingListener) .build() - binding.zoomable!!.controller = controller + binding.zoomable.controller = controller if (photoBackgroundColor != null) { - binding.zoomable!!.setBackgroundColor(photoBackgroundColor!!) + binding.zoomable.setBackgroundColor(photoBackgroundColor!!) } if (!images.isNullOrEmpty()) { diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java index 8d6b74231..8ed37a293 100644 --- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java +++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java @@ -285,7 +285,7 @@ public class OkHttpJsonApiClient { throws Exception { Timber.d("Fetching nearby items at radius %s", radius); - Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null)); + Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); final String wikidataQuery; if (customQuery != null) { wikidataQuery = customQuery; @@ -344,7 +344,7 @@ public class OkHttpJsonApiClient { final boolean shouldQueryForMonuments, final String customQuery) throws Exception { - Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null)); + Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); final String wikidataQuery; if (customQuery != null) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java index 5d480f4f7..b5f760c9f 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java @@ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import fr.free.nrw.commons.R; +import java.util.Locale; public class NearbyFilterSearchRecyclerViewAdapter extends RecyclerView.Adapter @@ -121,11 +122,11 @@ public class NearbyFilterSearchRecyclerViewAdapter results.count = labels.size(); results.values = labels; } else { - constraint = constraint.toString().toLowerCase(); + constraint = constraint.toString().toLowerCase(Locale.ROOT); for (Label label : labels) { String data = label.toString(); - if (data.toLowerCase().startsWith(constraint.toString())) { + if (data.toLowerCase(Locale.ROOT).startsWith(constraint.toString())) { filteredArrayList.add(Label.fromText(label.getText())); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt index 5152ac0f7..a4ea3cd5b 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt @@ -1,5 +1,6 @@ package fr.free.nrw.commons.nearby +import android.content.Intent import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE @@ -17,9 +18,9 @@ import fr.free.nrw.commons.databinding.ItemPlaceBinding fun placeAdapterDelegate( bookmarkLocationDao: BookmarkLocationsDao, onItemClick: ((Place) -> Unit)? = null, - onCameraClicked: (Place, ActivityResultLauncher>) -> Unit, + onCameraClicked: (Place, ActivityResultLauncher>, ActivityResultLauncher) -> Unit, onCameraLongPressed: () -> Boolean, - onGalleryClicked: (Place) -> Unit, + onGalleryClicked: (Place, ActivityResultLauncher) -> Unit, onGalleryLongPressed: () -> Boolean, onBookmarkClicked: (Place, Boolean) -> Unit, onBookmarkLongPressed: () -> Boolean, @@ -28,6 +29,8 @@ fun placeAdapterDelegate( onDirectionsClicked: (Place) -> Unit, onDirectionsLongPressed: () -> Boolean, inAppCameraLocationPermissionLauncher: ActivityResultLauncher>, + cameraPickLauncherForResult: ActivityResultLauncher, + galleryPickLauncherForResult: ActivityResultLauncher ) = adapterDelegateViewBinding({ layoutInflater, parent -> ItemPlaceBinding.inflate(layoutInflater, parent, false) }) { @@ -44,10 +47,10 @@ fun placeAdapterDelegate( onItemClick?.invoke(item) } } - nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher) } + nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult) } nearbyButtonLayout.cameraButton.setOnLongClickListener { onCameraLongPressed() } - nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item) } + nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) } nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() } bookmarkButtonImage.setOnClickListener { val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item) diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java index f18b80004..9e4292114 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlaceDao.java @@ -4,7 +4,6 @@ import androidx.room.Dao; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; -import fr.free.nrw.commons.location.LatLng; import io.reactivex.Completable; /** @@ -38,8 +37,21 @@ public abstract class PlaceDao { */ public Completable save(final Place place) { return Completable - .fromAction(() -> { - saveSynchronous(place); - }); + .fromAction(() -> saveSynchronous(place)); + } + + /** + * Deletes all Place objects from the database. + */ + @Query("DELETE FROM place") + public abstract void deleteAllSynchronous(); + + /** + * Deletes all Place objects from the database. + * + * @return A Completable that completes once the deletion operation is done. + */ + public Completable deleteAll() { + return Completable.fromAction(this::deleteAllSynchronous); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java index 8de437c82..86a57eadc 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesLocalDataSource.java @@ -1,6 +1,5 @@ package fr.free.nrw.commons.nearby; -import fr.free.nrw.commons.location.LatLng; import io.reactivex.Completable; import javax.inject.Inject; @@ -36,4 +35,8 @@ public class PlacesLocalDataSource { public Completable savePlace(Place place) { return placeDao.save(place); } + + public Completable clearCache() { + return placeDao.deleteAll(); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java index 85e964ddb..846e54fac 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PlacesRepository.java @@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby; import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.location.LatLng; import io.reactivex.Completable; +import io.reactivex.schedulers.Schedulers; import javax.inject.Inject; /** @@ -38,4 +39,13 @@ public class PlacesRepository { return localDataSource.fetchPlace(entityID); } + /** + * Clears the Nearby cache on an IO thread. + * + * @return A Completable that completes once the cache has been successfully cleared. + */ + public Completable clearCache() { + return localDataSource.clearCache() + .subscribeOn(Schedulers.io()); // Ensure it runs on IO thread + } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt index d238296d1..299ac4b6e 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt @@ -87,7 +87,7 @@ class WikidataFeedback : BaseActivity() { lat, lng, ) - } as Callable>, + }, ).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ aBoolean: Boolean? -> diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt index f3eecf116..a4d6b14b7 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/CommonPlaceClickActions.kt @@ -28,14 +28,14 @@ class CommonPlaceClickActions private val activity: Activity, private val contributionController: ContributionController, ) { - fun onCameraClicked(): (Place, ActivityResultLauncher>) -> Unit = - { place, launcher -> + fun onCameraClicked(): (Place, ActivityResultLauncher>, ActivityResultLauncher) -> Unit = + { place, launcher, resultLauncher -> if (applicationKvStore.getBoolean("login_skipped", false)) { showLoginDialog() } else { Timber.d("Camera button tapped. Image title: ${place.getName()}Image desc: ${place.longDescription}") storeSharedPrefs(place) - contributionController.initiateCameraPick(activity, launcher) + contributionController.initiateCameraPick(activity, launcher, resultLauncher) } } @@ -72,14 +72,14 @@ class CommonPlaceClickActions true } - fun onGalleryClicked(): (Place) -> Unit = - { + fun onGalleryClicked(): (Place, ActivityResultLauncher) -> Unit = + {place, galleryPickLauncherForResult -> if (applicationKvStore.getBoolean("login_skipped", false)) { showLoginDialog() } else { - Timber.d("Gallery button tapped. Image title: ${it.getName()}Image desc: ${it.getLongDescription()}") - storeSharedPrefs(it) - contributionController.initiateGalleryPick(activity, false) + Timber.d("Gallery button tapped. Image title: ${place.getName()}Image desc: ${place.getLongDescription()}") + storeSharedPrefs(place) + contributionController.initiateGalleryPick(activity, galleryPickLauncherForResult, false) } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 8a3c0c330..6a2e5c3a9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -43,15 +43,18 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.Button; import android.widget.Toast; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog.Builder; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.recyclerview.widget.DividerItemDecoration; @@ -105,6 +108,7 @@ import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.SystemThemeUtils; import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.wikidata.WikidataEditListener; +import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; @@ -218,9 +222,36 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment private LatLng updatedLatLng; private boolean searchable; + private ConstraintLayout nearbyLegend; + private GridLayoutManager gridLayoutManager; private List dataList; private BottomSheetAdapter bottomSheetAdapter; + + private final ActivityResultLauncher galleryPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher customSelectorLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher cameraPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); + }); + private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult( new RequestMultiplePermissions(), new ActivityResultCallback>() { @@ -236,7 +267,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } else { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { controller.handleShowRationaleFlowCameraLocation(getActivity(), - inAppCameraLocationPermissionLauncher); + inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); } else { controller.locationPermissionCallback.onLocationPermissionDenied( getActivity().getString( @@ -302,6 +333,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment progressDialog.setCancelable(false); progressDialog.setMessage("Saving in progress..."); setHasOptionsMenu(true); + // Inflate the layout for this fragment return view; @@ -311,9 +343,21 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.nearby_fragment_menu, menu); + MenuItem refreshButton = menu.findItem(R.id.item_refresh); MenuItem listMenu = menu.findItem(R.id.list_sheet); MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); + refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + try { + emptyCache(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + }); listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { @@ -362,6 +406,16 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, this); + + // Set up the floating activity button to toggle the visibility of the legend + binding.fabLegend.setOnClickListener(v -> { + if (binding.nearbyLegendLayout.getRoot().getVisibility() == View.VISIBLE) { + binding.nearbyLegendLayout.getRoot().setVisibility(View.GONE); + } else { + binding.nearbyLegendLayout.getRoot().setVisibility(View.VISIBLE); + } + }); + presenter.attachView(this); isPermissionDenied = false; recenterToUserLocation = false; @@ -555,7 +609,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return Unit.INSTANCE; }, commonPlaceClickActions, - inAppCameraLocationPermissionLauncher + inAppCameraLocationPermissionLauncher, + galleryPickLauncherForResult, + cameraPickLauncherForResult ); binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter); } @@ -1115,6 +1171,48 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } } + /** + * Reloads the Nearby map + * Clears all location markers, refreshes them, reinserts them into the map. + * + */ + private void reloadMap() { + clearAllMarkers(); // Clear the list of markers + binding.map.getController().setZoom(ZOOM_LEVEL); // Reset the zoom level + binding.map.getController().setCenter(lastMapFocus); // Recenter the focus + if (locationPermissionsHelper.checkLocationPermission(getActivity())) { + locationPermissionGranted(); // Reload map with user's location + } else { + startMapWithoutPermission(); // Reload map without user's location + } + binding.map.invalidate(); // Invalidate the map + presenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); // Restart the map + Timber.d("Reloaded Map Successfully"); + } + + + /** + * Clears the Nearby local cache and then calls for the map to be reloaded + * + */ + private void emptyCache() { + // reload the map once the cache is cleared + compositeDisposable.add( + placesRepository.clearCache() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .andThen(Completable.fromAction(this::reloadMap)) + .subscribe( + () -> { + Timber.d("Nearby Cache cleared successfully."); + }, + throwable -> { + Timber.e(throwable, "Failed to clear the Nearby Cache"); + } + ) + ); + } + private void savePlacesAsKML() { final Observable savePlacesObservable = Observable .fromCallable(() -> nearbyController @@ -2186,7 +2284,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (binding.fabCamera.isShown()) { Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); - controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher); + controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); } }); @@ -2195,6 +2293,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); controller.initiateGalleryPick(getActivity(), + galleryPickLauncherForResult, false); } }); @@ -2203,7 +2302,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (binding.fabCustomGallery.isShown()) { Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); - controller.initiateCustomGalleryPickWithPermission(getActivity()); + controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); } }); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/PlaceAdapter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/PlaceAdapter.kt index 689aa7efc..e5cc92667 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/PlaceAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/PlaceAdapter.kt @@ -1,5 +1,6 @@ package fr.free.nrw.commons.nearby.fragments +import android.content.Intent import androidx.activity.result.ActivityResultLauncher import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao import fr.free.nrw.commons.nearby.Place @@ -12,6 +13,8 @@ class PlaceAdapter( onBookmarkClicked: (Place, Boolean) -> Unit, commonPlaceClickActions: CommonPlaceClickActions, inAppCameraLocationPermissionLauncher: ActivityResultLauncher>, + galleryPickLauncherForResult: ActivityResultLauncher, + cameraPickLauncherForResult: ActivityResultLauncher ) : BaseDelegateAdapter( placeAdapterDelegate( bookmarkLocationsDao, @@ -27,6 +30,8 @@ class PlaceAdapter( commonPlaceClickActions.onDirectionsClicked(), commonPlaceClickActions.onDirectionsLongPressed(), inAppCameraLocationPermissionLauncher, + cameraPickLauncherForResult, + galleryPickLauncherForResult ), areItemsTheSame = { oldItem, newItem -> oldItem.wikiDataEntityId == newItem.wikiDataEntityId }, ) diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java index 75a010ea7..572dd0317 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationActivity.java @@ -19,6 +19,7 @@ import fr.free.nrw.commons.databinding.ActivityNotificationBinding; import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException; import fr.free.nrw.commons.notification.models.Notification; +import fr.free.nrw.commons.notification.models.NotificationType; import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.utils.NetworkUtils; import fr.free.nrw.commons.utils.ViewUtil; @@ -148,7 +149,11 @@ public class NotificationActivity extends BaseActivity { } adapter = new NotificatinAdapter(item -> { Timber.d("Notification clicked %s", item.getLink()); - 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); return Unit.INSTANCE; }); diff --git a/app/src/main/java/fr/free/nrw/commons/notification/NotificationClient.kt b/app/src/main/java/fr/free/nrw/commons/notification/NotificationClient.kt index aa998ffb5..a0bf1176a 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/NotificationClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/notification/NotificationClient.kt @@ -51,13 +51,23 @@ class NotificationClient } } - private fun WikimediaNotification.toCommonsNotification() = - Notification( - notificationType = NotificationType.UNKNOWN, - notificationText = contents?.compactHeader ?: "", - date = DateUtil.getMonthOnlyDateString(timestamp), - link = contents?.links?.primary?.url ?: "", - iconUrl = "", - notificationId = id().toString(), - ) + private fun WikimediaNotification.toCommonsNotification() : + Notification { + val notificationText = contents?.compactHeader ?: "" + val notificationType = + if (notificationText.contains("Sent you an email", ignoreCase = true)) { + NotificationType.EMAIL + } else { + NotificationType.UNKNOWN + } + + return Notification( + notificationType = notificationType, + notificationText = notificationText, + date = DateUtil.getMonthOnlyDateString(timestamp), + link = contents?.links?.primary?.url ?: "", + iconUrl = "", + notificationId = id().toString(), + ) + } } diff --git a/app/src/main/java/fr/free/nrw/commons/notification/models/NotificationType.java b/app/src/main/java/fr/free/nrw/commons/notification/models/NotificationType.java index 1b825f071..fb9ae7e99 100644 --- a/app/src/main/java/fr/free/nrw/commons/notification/models/NotificationType.java +++ b/app/src/main/java/fr/free/nrw/commons/notification/models/NotificationType.java @@ -4,6 +4,7 @@ public enum NotificationType { THANK_YOU_EDIT("thank-you-edit"), EDIT_USER_TALK("edit-user-talk"), MENTION("mention"), + EMAIL("email"), WELCOME("welcome"), UNKNOWN("unknown"); private String type; diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java index 9acf5b595..c6d09fdc6 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java @@ -32,6 +32,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import javax.inject.Inject; /** @@ -139,14 +140,14 @@ public class ProfileActivity extends BaseActivity { leaderboardFragment.setArguments(leaderBoardBundle); fragmentList.add(leaderboardFragment); - titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase()); + titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase(Locale.ROOT)); contributionsFragment = new ContributionsFragment(); Bundle contributionsListBundle = new Bundle(); contributionsListBundle.putString(KEY_USERNAME, userName); contributionsFragment.setArguments(contributionsListBundle); fragmentList.add(contributionsFragment); - titleList.add(getString(R.string.contributions_fragment).toUpperCase()); + titleList.add(getString(R.string.contributions_fragment).toUpperCase(Locale.ROOT)); viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.java b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.java index 46ea631fb..f44b7eb6d 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.java @@ -27,6 +27,7 @@ import fr.free.nrw.commons.profile.ProfileActivity; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import java.util.Locale; import java.util.Objects; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; @@ -361,7 +362,7 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment { + levelInfo.getMaxUniqueImages()); binding.imageFeatured.setText(String.valueOf(achievements.getFeaturedImages())); binding.qualityImages.setText(String.valueOf(achievements.getQualityImages())); - String levelUpInfoString = getString(R.string.level).toUpperCase(); + String levelUpInfoString = getString(R.string.level).toUpperCase(Locale.ROOT); levelUpInfoString += " " + levelInfo.getLevelNumber(); binding.achievementLevel.setText(levelUpInfoString); binding.achievementBadgeImage.setImageDrawable(VectorDrawableCompat.create(getResources(), R.drawable.badge, diff --git a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java index c4a4bf518..cbb8c8a1c 100644 --- a/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java +++ b/app/src/main/java/fr/free/nrw/commons/recentlanguages/RecentLanguagesDao.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.recentlanguages; +import android.annotation.SuppressLint; import android.content.ContentProviderClient; import android.content.ContentValues; import android.database.Cursor; @@ -117,6 +118,7 @@ public class RecentLanguagesDao { * @return Language object */ @NonNull + @SuppressLint("Range") Language fromCursor(final Cursor cursor) { // Hardcoding column positions! final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); diff --git a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java index 5eb758ada..40d743a19 100644 --- a/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/review/ReviewActivity.java @@ -25,6 +25,7 @@ import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import java.util.Locale; import javax.inject.Inject; public class ReviewActivity extends BaseActivity { @@ -241,7 +242,7 @@ public class ReviewActivity extends BaseActivity { public void showSkipImageInfo(){ DialogUtil.showAlertDialog(ReviewActivity.this, - getString(R.string.skip_image).toUpperCase(), + getString(R.string.skip_image).toUpperCase(Locale.ROOT), getString(R.string.skip_image_explanation), getString(android.R.string.ok), "", diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java index b6b5b6d18..20fc831a8 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.java @@ -22,6 +22,7 @@ import android.widget.TextView; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.preference.ListPreference; import androidx.preference.MultiSelectListPreference; import androidx.preference.Preference; @@ -88,6 +89,15 @@ public class SettingsFragment extends PreferenceFragmentCompat { private View separator; private ListView languageHistoryListView; private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"; + + private final ActivityResultLauncher cameraPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { + contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); + }); + private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { @Override public void onActivityResult(Map result) { @@ -96,7 +106,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { areAllGranted = areAllGranted && b; } if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); + contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); } } }); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt index 876fb3cd3..c0e5097c0 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/FailedUploadsFragment.kt @@ -63,7 +63,7 @@ class FailedUploadsFragment : } if (StringUtils.isEmpty(userName)) { - userName = sessionManager!!.getUserName() + userName = sessionManager.getUserName() } } @@ -96,8 +96,8 @@ class FailedUploadsFragment : fun initRecyclerView() { binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) binding.failedUploadsRecyclerView.adapter = adapter - pendingUploadsPresenter!!.getFailedContributions() - pendingUploadsPresenter!!.failedContributionList.observe( + pendingUploadsPresenter.getFailedContributions() + pendingUploadsPresenter.failedContributionList.observe( viewLifecycleOwner, ) { list: PagedList -> adapter.submitList(list) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java index b45e4b57d..8a8fa35b3 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/FileUtils.java @@ -19,6 +19,7 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; import timber.log.Timber; public class FileUtils { @@ -139,7 +140,7 @@ public class FileUtils { String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri .toString()); mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - fileExtension.toLowerCase()); + fileExtension.toLowerCase(Locale.getDefault())); } return mimeType; } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt index 4d79bc88e..4442a64ea 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/PendingUploadsFragment.kt @@ -74,8 +74,8 @@ class PendingUploadsFragment : fun initRecyclerView() { binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) binding.pendingUploadsRecyclerView.adapter = adapter - pendingUploadsPresenter!!.setup() - pendingUploadsPresenter!!.totalContributionList.observe( + pendingUploadsPresenter.setup() + pendingUploadsPresenter.totalContributionList.observe( viewLifecycleOwner, ) { list: PagedList -> contributionsSize = list.size diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java index eb180ec44..35906c3fb 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java @@ -320,6 +320,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, finish(); } + /** + * go to the uploadProgress activity to check the status of uploading + */ + @Override + public void goToUploadProgressActivity() { + startActivity(new Intent(this, UploadProgressActivity.class)); + } + /** * Show/Hide the progress dialog */ @@ -433,14 +441,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, super.onRequestPermissionsResult(requestCode, permissions, grantResults); } - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) { - //TODO: Confirm if handling manual permission enabled is required - } - } - /** * Sets the flag indicating whether the upload is of a specific place. * diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadContract.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadContract.java index 4df778746..5f41a17c9 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadContract.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadContract.java @@ -18,6 +18,13 @@ public interface UploadContract { void returnToMainActivity(); + /** + * When submission successful, go to the loadProgressActivity to hint the user this + * submission is valid. And the user will see the upload progress in this activity; + * Fixes: Issue + */ + void goToUploadProgressActivity(); + void askUserToLogIn(); /** diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java index a4e0d1029..6fc8b3266 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java @@ -20,6 +20,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; @@ -57,27 +58,29 @@ public class UploadMediaDetailAdapter extends private int currentPosition; private Fragment fragment; private Activity activity; + private final ActivityResultLauncher voiceInputResultLauncher; private SelectedVoiceIcon selectedVoiceIcon; - private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213; private RowItemDescriptionBinding binding; public UploadMediaDetailAdapter(Fragment fragment, String savedLanguageValue, - RecentLanguagesDao recentLanguagesDao) { + RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher voiceInputResultLauncher) { uploadMediaDetails = new ArrayList<>(); selectedLanguages = new HashMap<>(); this.savedLanguageValue = savedLanguageValue; this.recentLanguagesDao = recentLanguagesDao; this.fragment = fragment; + this.voiceInputResultLauncher = voiceInputResultLauncher; } public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue, - List uploadMediaDetails, RecentLanguagesDao recentLanguagesDao) { + List uploadMediaDetails, RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher voiceInputResultLauncher) { this.uploadMediaDetails = uploadMediaDetails; selectedLanguages = new HashMap<>(); this.savedLanguageValue = savedLanguageValue; this.recentLanguagesDao = recentLanguagesDao; this.activity = activity; + this.voiceInputResultLauncher = voiceInputResultLauncher; } public void setCallback(Callback callback) { @@ -150,11 +153,7 @@ public class UploadMediaDetailAdapter extends ); try { - if (activity == null) { - fragment.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT); - } else { - activity.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT); - } + voiceInputResultLauncher.launch(intent); } catch (Exception e) { Timber.e(e.getMessage()); } @@ -407,7 +406,7 @@ public class UploadMediaDetailAdapter extends recentLanguagesDao .addRecentLanguage(new Language(languageName, languageCode)); - selectedLanguages.remove(position); + selectedLanguages.clear(); selectedLanguages.put(position, languageCode); ((LanguagesAdapter) adapterView .getAdapter()).setSelectedLangCode(languageCode); @@ -497,7 +496,7 @@ public class UploadMediaDetailAdapter extends } recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode)); - selectedLanguages.remove(position); + selectedLanguages.clear(); selectedLanguages.put(position, languageCode); ((RecentLanguagesAdapter) adapterView .getAdapter()).setSelectedLangCode(languageCode); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java index 144859432..093412c25 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadPresenter.java @@ -123,6 +123,9 @@ public class UploadPresenter implements UploadContract.UserActionListener { view.returnToMainActivity(); compositeDisposable.clear(); Timber.e("failed to upload: " + e.getMessage()); + + //is submission error, not need to go to the uploadActivity + //not start the uploading progress } @Override @@ -131,6 +134,10 @@ public class UploadPresenter implements UploadContract.UserActionListener { repository.cleanup(); view.returnToMainActivity(); compositeDisposable.clear(); + + //after finish the uploadActivity, if successful, + //directly go to the upload progress activity + view.goToUploadProgressActivity(); } }); } else { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/BaseDelegateAdapter.kt b/app/src/main/java/fr/free/nrw/commons/upload/categories/BaseDelegateAdapter.kt index f1e4917a0..ce12d3915 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/categories/BaseDelegateAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/BaseDelegateAdapter.kt @@ -10,15 +10,13 @@ abstract class BaseDelegateAdapter( areContentsTheSame: (T, T) -> Boolean = { old, new -> old == new }, ) : AsyncListDifferDelegationAdapter( object : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: T, - newItem: T, - ) = areItemsTheSame(oldItem, newItem) + override fun areItemsTheSame(oldItem: T & Any, newItem: T & Any): Boolean { + return areItemsTheSame(oldItem, newItem) + } - override fun areContentsTheSame( - oldItem: T, - newItem: T, - ) = areContentsTheSame(oldItem, newItem) + override fun areContentsTheSame(oldItem: T & Any, newItem: T & Any): Boolean { + return areContentsTheSame(oldItem, newItem) + } }, *delegates, ) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java index 8503b1d05..dd264655f 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java @@ -372,7 +372,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate return false; }); - Objects.requireNonNull(getView()).setFocusableInTouchMode(true); + requireView().setFocusableInTouchMode(true); getView().requestFocus(); getView().setOnKeyListener((v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { @@ -387,7 +387,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate }); Objects.requireNonNull( - ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) + ((AppCompatActivity) requireActivity()).getSupportActionBar()) .hide(); if (getParentFragment().getParentFragment().getParentFragment() @@ -407,7 +407,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate super.onStop(); if (media != null) { Objects.requireNonNull( - ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) + ((AppCompatActivity) requireActivity()).getSupportActionBar()) .show(); } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsDao.kt b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsDao.kt index 684400301..139b67d59 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsDao.kt @@ -36,7 +36,7 @@ abstract class DepictsDao { /** * Gets all Depicts objects from the database, ordered by lastUsed in descending order. * - * @return A list of Depicts objects. + * @return Deferred list of Depicts objects. */ fun depictsList(): Deferred> = CoroutineScope(Dispatchers.IO).async { @@ -48,7 +48,7 @@ abstract class DepictsDao { * * @param depictedItem The Depicts object to insert. */ - private fun insertDepict(depictedItem: Depicts) = + fun insertDepict(depictedItem: Depicts) = CoroutineScope(Dispatchers.IO).launch { insert(depictedItem) } @@ -59,7 +59,7 @@ abstract class DepictsDao { * @param n The number of depicts to delete. * @return A list of Depicts objects to delete. */ - private suspend fun depictsForDeletion(n: Int): Deferred> = + fun depictsForDeletion(n: Int): Deferred> = CoroutineScope(Dispatchers.IO).async { getDepictsForDeletion(n) } @@ -69,7 +69,7 @@ abstract class DepictsDao { * * @param depicts The Depicts object to delete. */ - private suspend fun deleteDepicts(depicts: Depicts) = + fun deleteDepicts(depicts: Depicts) = CoroutineScope(Dispatchers.IO).launch { delete(depicts) } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java index bd52a8d35..9000e513d 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java @@ -398,7 +398,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra return false; }); - Objects.requireNonNull(getView()).setFocusableInTouchMode(true); + requireView().setFocusableInTouchMode(true); getView().requestFocus(); getView().setOnKeyListener((v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { @@ -411,7 +411,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra }); Objects.requireNonNull( - ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) + ((AppCompatActivity) requireActivity()).getSupportActionBar()) .hide(); if (getParentFragment().getParentFragment().getParentFragment() @@ -431,7 +431,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra super.onStop(); if (media != null) { Objects.requireNonNull( - ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) + ((AppCompatActivity) requireActivity()).getSupportActionBar()) .show(); } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index 105df1837..5581cfeb1 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -18,6 +18,9 @@ import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; @@ -58,9 +61,24 @@ import timber.log.Timber; public class UploadMediaDetailFragment extends UploadBaseFragment implements UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { - private static final int REQUEST_CODE = 1211; - private static final int REQUEST_CODE_FOR_EDIT_ACTIVITY = 1212; - private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213; + private UploadMediaDetailAdapter uploadMediaDetailAdapter; + + private final ActivityResultLauncher startForResult = registerForActivityResult( + new StartActivityForResult(), result -> { + onCameraPosition(result); + }); + + private final ActivityResultLauncher startForEditActivityResult = registerForActivityResult( + new StartActivityForResult(), result -> { + onEditActivityResult(result); + } + ); + + private final ActivityResultLauncher voiceInputResultLauncher = registerForActivityResult( + new StartActivityForResult(), result -> { + onVoiceInput(result); + } + ); public static Activity activity ; @@ -84,8 +102,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements private boolean hasUserRemovedLocation; - private UploadMediaDetailAdapter uploadMediaDetailAdapter; - @Inject UploadMediaDetailsContract.UserActionListener presenter; @@ -279,7 +295,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements */ private void initRecyclerView() { uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this, - defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao); + defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao, voiceInputResultLauncher); uploadMediaDetailAdapter.setCallback(this::showInfoAlert); uploadMediaDetailAdapter.setEventListener(this); binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext())); @@ -593,14 +609,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements * This method is called to start the image editing activity for a specific UploadItem. * It sets the UploadItem as the currently editable item, creates an intent to launch the * EditActivity, and passes the image file path as an extra in the intent. The activity - * is started with a request code, allowing the result to be handled in onActivityResult. + * is started using resultLauncher that handles the result in respective callback. */ @Override public void showEditActivity(UploadItem uploadItem) { editableUploadItem = uploadItem; Intent intent = new Intent(getContext(), EditActivity.class); intent.putExtra("image", uploadableFile.getFilePath().toString()); - startActivityForResult(intent, REQUEST_CODE_FOR_EDIT_ACTIVITY); + startForEditActivityResult.launch(intent); } /** @@ -615,6 +631,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements double defaultLongitude = -122.431297; double defaultZoom = 16.0; + final Intent locationPickerIntent; + /* Retrieve image location from EXIF if present or check if user has provided location while using the in-app camera. Use location of last UploadItem if none of them is available */ @@ -624,10 +642,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements .getDecLatitude(); defaultLongitude = uploadItem.getGpsCoords().getDecLongitude(); defaultZoom = uploadItem.getGpsCoords().getZoomLevel(); - startActivityForResult(new LocationPicker.IntentBuilder() + + locationPickerIntent = new LocationPicker.IntentBuilder() .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) .activityKey("UploadActivity") - .build(getActivity()), REQUEST_CODE); + .build(getActivity()); } else { if (defaultKvStore.getString(LAST_LOCATION) != null) { final String[] locationLatLng @@ -638,27 +657,20 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements if (defaultKvStore.getString(LAST_ZOOM) != null) { defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM)); } - startActivityForResult(new LocationPicker.IntentBuilder() + + locationPickerIntent = new LocationPicker.IntentBuilder() .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) .activityKey("NoLocationUploadActivity") - .build(getActivity()), REQUEST_CODE); + .build(getActivity()); } + startForResult.launch(locationPickerIntent); } - /** - * Get the coordinates and update the existing coordinates. - * @param requestCode code of request - * @param resultCode code of result - * @param data intent - */ - @Override - public void onActivityResult(final int requestCode, final int resultCode, - @Nullable final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { + private void onCameraPosition(ActivityResult result){ + if (result.getResultCode() == RESULT_OK) { - assert data != null; - final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data); + assert result.getData() != null; + final CameraPosition cameraPosition = LocationPicker.getCameraPosition(result.getData()); if (cameraPosition != null) { @@ -678,8 +690,21 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements removeLocation(); } } - if (requestCode == REQUEST_CODE_FOR_EDIT_ACTIVITY && resultCode == RESULT_OK) { - String result = data.getStringExtra("editedImageFilePath"); + } + + private void onVoiceInput(ActivityResult result) { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + ArrayList resultData = result.getData().getStringArrayListExtra( + RecognizerIntent.EXTRA_RESULTS); + uploadMediaDetailAdapter.handleSpeechResult(resultData.get(0)); + }else { + Timber.e("Error %s", result.getResultCode()); + } + } + + private void onEditActivityResult(ActivityResult result){ + if (result.getResultCode() == RESULT_OK) { + String path = result.getData().getStringExtra("editedImageFilePath"); if (Objects.equals(result, "Error")) { Timber.e("Error in rotating image"); @@ -687,24 +712,15 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements } try { if (binding != null){ - binding.backgroundImage.setImageURI(Uri.fromFile(new File(result))); + binding.backgroundImage.setImageURI(Uri.fromFile(new File(path))); } - editableUploadItem.setContentUri(Uri.fromFile(new File(result))); + editableUploadItem.setContentUri(Uri.fromFile(new File(path))); callback.changeThumbnail(indexOfFragment, - result); + path); } catch (Exception e) { Timber.e(e); } } - else if (requestCode == REQUEST_CODE_FOR_VOICE_INPUT) { - if (resultCode == RESULT_OK && data != null) { - ArrayList result = data.getStringArrayListExtra( - RecognizerIntent.EXTRA_RESULTS); - uploadMediaDetailAdapter.handleSpeechResult(result.get(0)); - }else { - Timber.e("Error %s", resultCode); - } - } } /** @@ -809,7 +825,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements @Override public void displayAddLocationDialog(final Runnable onSkipClicked) { isMissingLocationDialog = true; - DialogUtil.showAlertDialog(Objects.requireNonNull(getActivity()), + DialogUtil.showAlertDialog(requireActivity(), getString(R.string.no_location_found_title), getString(R.string.no_location_found_message), getString(R.string.add_location), diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java index 7152d4d8f..cd533401b 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java @@ -129,9 +129,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt if (place.location != null) { final String countryCode = reverseGeoCode(place.location); if (countryCode != null && WLM_SUPPORTED_COUNTRIES - .contains(countryCode.toLowerCase())) { + .contains(countryCode.toLowerCase(Locale.ROOT))) { uploadItem.setWLMUpload(true); - uploadItem.setCountryCode(countryCode.toLowerCase()); + uploadItem.setCountryCode(countryCode.toLowerCase(Locale.ROOT)); } } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt index 2c9022d73..144c503bb 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/worker/UploadWorker.kt @@ -17,6 +17,7 @@ import androidx.work.Data import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import dagger.android.ContributesAndroidInjector +import fr.free.nrw.commons.BuildConfig.HOME_URL import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.Media import fr.free.nrw.commons.R @@ -30,6 +31,7 @@ import fr.free.nrw.commons.customselector.database.UploadedStatus import fr.free.nrw.commons.customselector.database.UploadedStatusDao import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.media.MediaClient +import fr.free.nrw.commons.nearby.PlacesRepository import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.upload.FileUtilsWrapper import fr.free.nrw.commons.upload.StashUploadResult @@ -38,8 +40,9 @@ import fr.free.nrw.commons.upload.UploadClient import fr.free.nrw.commons.upload.UploadProgressActivity import fr.free.nrw.commons.upload.UploadResult import fr.free.nrw.commons.wikidata.WikidataEditService +import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import timber.log.Timber @@ -74,6 +77,9 @@ class UploadWorker( @Inject lateinit var fileUtilsWrapper: FileUtilsWrapper + @Inject + lateinit var placesRepository: PlacesRepository + private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag" private val processingUploadsNotificationId = 101 @@ -379,7 +385,7 @@ class UploadWorker( saveCompletedContribution(contribution, uploadResult) } else { Timber.d( - "WikiDataEdit not required, making wikidata edit", + "WikiDataEdit required, making wikidata edit", ) makeWikiDataEdit(uploadResult, contribution) } @@ -432,7 +438,7 @@ class UploadWorker( username, ) CommonsApplication - .getInstance() + .instance!! .clearApplicationData(appContext, logoutListener) } } @@ -471,6 +477,16 @@ class UploadWorker( contribution.media.captions, ) if (null != revisionID) { + withContext(Dispatchers.IO) { + val place = placesRepository.fetchPlace(wikiDataPlace.id); + place.name = wikiDataPlace.name; + place.pic = HOME_URL + uploadResult.createCanonicalFileName() + placesRepository + .save(place) + .subscribeOn(Schedulers.io()) + .blockingAwait() + Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}") + } showSuccessNotification(contribution) } } catch (exception: Exception) { @@ -518,7 +534,7 @@ class UploadWorker( contribution.contentUri?.let { val imageSha1 = contribution.imageSHA1.toString() val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path)) - MainScope().launch { + CoroutineScope(Dispatchers.IO).launch { uploadedStatusDao.insertUploaded( UploadedStatus( imageSha1, diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java index c51d9dd40..692194234 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java +++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.java @@ -59,8 +59,7 @@ public class PermissionUtils { final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); final Uri uri = Uri.fromParts("package", activity.getPackageName(), null); intent.setData(uri); - activity.startActivityForResult(intent, - CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS); + activity.startActivity(intent); } /** diff --git a/app/src/main/res/drawable/ic_refresh_24dp_nearby.xml b/app/src/main/res/drawable/ic_refresh_24dp_nearby.xml new file mode 100644 index 000000000..89f49ad9e --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh_24dp_nearby.xml @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/app/src/main/res/layout/activity_description_edit.xml b/app/src/main/res/layout/activity_description_edit.xml index ed50193a2..1a8d3b8ce 100644 --- a/app/src/main/res/layout/activity_description_edit.xml +++ b/app/src/main/res/layout/activity_description_edit.xml @@ -36,11 +36,11 @@ android:layout_height="wrap_content" android:layout_marginStart="16dp" android:contentDescription="@string/exit_location_picker" - android:tint="@color/white" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/ic_arrow_back_white" /> + app:srcCompat="@drawable/ic_arrow_back_white" + app:tint="@color/white" /> @@ -69,7 +69,7 @@ android:id="@+id/btn_edit_submit" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" android:text="@string/submit" android:textColor="@android:color/white" /> diff --git a/app/src/main/res/layout/bottom_sheet_details_explore.xml b/app/src/main/res/layout/bottom_sheet_details_explore.xml index 1da5c7f3e..6558c9afe 100644 --- a/app/src/main/res/layout/bottom_sheet_details_explore.xml +++ b/app/src/main/res/layout/bottom_sheet_details_explore.xml @@ -31,7 +31,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" - android:layout_marginRight="50dp" + android:layout_marginEnd="50dp" android:maxLines="2" android:ellipsize="end" /> @@ -58,6 +58,7 @@ android:layout_width="@dimen/dimen_0" android:layout_height="wrap_content" android:layout_weight="1" + android:focusable="true" android:padding="@dimen/standard_gap" android:clickable="true" android:background="@drawable/button_background_selector" @@ -69,8 +70,7 @@ android:layout_gravity="center_horizontal" android:duplicateParentState="true" app:srcCompat="@drawable/ic_directions_black_24dp" - android:tint="?attr/rowButtonColor" - /> + app:tint="?attr/rowButtonColor" /> diff --git a/app/src/main/res/layout/bottom_sheet_item_layout.xml b/app/src/main/res/layout/bottom_sheet_item_layout.xml index 4f4c2c854..c569e523a 100644 --- a/app/src/main/res/layout/bottom_sheet_item_layout.xml +++ b/app/src/main/res/layout/bottom_sheet_item_layout.xml @@ -1,11 +1,13 @@ @@ -14,7 +16,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:tint="?attr/rowButtonColor" /> + app:tint="?attr/rowButtonColor" /> @@ -36,7 +35,6 @@ style="?android:textAppearanceLarge" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" android:layout_marginTop="@dimen/activity_margin_horizontal" android:text="@string/level" @@ -48,13 +46,11 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/activity_margin_vertical" - android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" - android:layout_alignParentRight="true" android:layout_alignParentEnd="true" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/black" - android:layout_marginVertical="@dimen/activity_margin_vertical" /> + android:layout_marginVertical="@dimen/activity_margin_vertical" + app:tint="@color/black" /> + android:layout_marginStart="@dimen/activity_margin_horizontal" + app:tint="@color/primaryLightColor" /> @@ -189,7 +182,6 @@ style="?android:textAppearanceMedium" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:id="@+id/images_reverted_text" android:layout_marginStart="@dimen/activity_margin_horizontal" android:text="@string/image_reverts" /> @@ -200,24 +192,19 @@ android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" - android:layout_toRightOf="@+id/images_reverted_text" - android:layout_toEndOf="@+id/images_reverted_text" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" android:layout_marginLeft="@dimen/activity_margin_horizontal" - android:layout_marginStart="@dimen/activity_margin_horizontal"/> + android:layout_marginStart="@dimen/activity_margin_horizontal" app:tint="@color/primaryLightColor" /> - @@ -278,7 +265,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/images_used_by_wiki_text" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" android:layout_marginTop="@dimen/achievements_activity_margin_vertical" android:text="@string/images_used_by_wiki" /> @@ -289,12 +275,10 @@ android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" - android:layout_toRightOf="@+id/images_used_by_wiki_text" - android:layout_toEndOf="@+id/images_used_by_wiki_text" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" android:layout_marginLeft="@dimen/activity_margin_horizontal" - android:layout_marginStart="@dimen/activity_margin_horizontal"/> + android:layout_marginStart="@dimen/activity_margin_horizontal" + app:tint="@color/primaryLightColor" /> @@ -353,7 +337,6 @@ android:layout_height="wrap_content" android:text="@string/statistics" style="?android:textAppearanceLarge" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" android:layout_marginTop="@dimen/activity_margin_vertical" android:textAllCaps="true"/> @@ -373,9 +356,7 @@ android:id="@+id/images_nearby_info" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/wikidata_edits" - android:layout_toLeftOf="@+id/wikidata_edits" android:orientation="horizontal" android:gravity="center_vertical"> @@ -407,14 +388,13 @@ android:layout_height="@dimen/medium_height" android:id="@+id/images_nearby_info_icon" android:layout_marginTop="@dimen/activity_margin_horizontal" - android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" android:layout_gravity="top" app:layout_constraintLeft_toRightOf="@id/images_nearby_data" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" /> + app:tint="@color/primaryLightColor" /> @@ -423,16 +403,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:textAppearanceMedium" - android:layout_alignParentRight="true" android:layout_alignParentEnd="true" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/half_standard_height" android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" android:layout_centerVertical="true" tools:text="2" android:id="@+id/wikidata_edits" - android:layout_marginRight="@dimen/half_standard_height" /> + /> @@ -451,9 +429,7 @@ android:id="@+id/images_featured_info" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/image_featured" - android:layout_toLeftOf="@+id/image_featured" android:orientation="horizontal" android:gravity="center_vertical"> @@ -486,14 +462,13 @@ android:layout_height="@dimen/medium_height" android:id="@+id/images_featured_info_icon" android:layout_marginTop="@dimen/activity_margin_horizontal" - android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" app:layout_constraintLeft_toRightOf="@id/images_featured_data" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_gravity="top" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" /> + app:tint="@color/primaryLightColor" /> @@ -501,16 +476,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:textAppearanceMedium" - android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" android:layout_centerVertical="true" tools:text="2" android:id="@+id/image_featured" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/half_standard_height" - android:layout_marginRight="@dimen/half_standard_height" /> + /> @@ -529,9 +502,7 @@ android:id="@+id/quality_images_info" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/quality_images" - android:layout_toLeftOf="@+id/quality_images" android:orientation="horizontal" android:gravity="center_vertical"> @@ -564,14 +535,13 @@ android:layout_height="@dimen/medium_height" android:id="@+id/quality_images_info_icon" android:layout_marginTop="@dimen/activity_margin_horizontal" - android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" app:layout_constraintLeft_toRightOf="@id/quality_images_data" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_gravity="top" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" /> + app:tint="@color/primaryLightColor" /> @@ -579,7 +549,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:textAppearanceMedium" - android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" @@ -587,9 +556,8 @@ tools:text="2" android:text="0" android:id="@+id/quality_images" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/half_standard_height" - android:layout_marginRight="@dimen/half_standard_height" /> + /> @@ -608,9 +576,7 @@ android:id="@+id/thanks_received_info" android:layout_centerVertical="true" android:layout_alignParentStart="true" - android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/thanks_received" - android:layout_toLeftOf="@+id/thanks_received" android:orientation="horizontal" android:gravity="center_vertical"> @@ -643,14 +609,13 @@ android:layout_height="@dimen/medium_height" android:id="@+id/thanks_received_info_icon" android:layout_marginTop="@dimen/activity_margin_horizontal" - android:layout_marginRight="@dimen/activity_margin_horizontal" android:layout_marginEnd="@dimen/activity_margin_horizontal" app:layout_constraintLeft_toRightOf="@id/thanks_received_data" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent" android:layout_gravity="top" app:srcCompat="@drawable/ic_info_outline_24dp" - android:tint="@color/primaryLightColor" /> + app:tint="@color/primaryLightColor" /> @@ -658,16 +623,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:textAppearanceMedium" - android:layout_alignParentRight="true" android:layout_alignParentEnd="true" android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginStart="@dimen/activity_margin_horizontal" - android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_centerVertical="true" tools:text="2" android:id="@+id/thanks_received" android:layout_marginEnd="@dimen/half_standard_height" - android:layout_marginRight="@dimen/half_standard_height" /> + /> diff --git a/app/src/main/res/layout/fragment_nearby_parent.xml b/app/src/main/res/layout/fragment_nearby_parent.xml index e5002fe11..e1d82e6e7 100644 --- a/app/src/main/res/layout/fragment_nearby_parent.xml +++ b/app/src/main/res/layout/fragment_nearby_parent.xml @@ -124,6 +124,33 @@ app:srcCompat="@drawable/ic_my_location_black_24dp" app:useCompatPadding="true" /> + + + + + app:srcCompat="@drawable/ic_round_star_border_24px" + app:tint="?attr/rowButtonColor" /> @@ -54,11 +52,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignTop="@id/distance" - android:layout_marginLeft="@dimen/standard_gap" android:layout_marginStart="@dimen/standard_gap" android:layout_toEndOf="@id/icon" - android:layout_toLeftOf="@id/distance" - android:layout_toRightOf="@id/icon" android:layout_toStartOf="@id/distance" android:ellipsize="end" android:maxLines="2" @@ -71,8 +66,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignEnd="@id/distance" - android:layout_alignLeft="@id/tvName" - android:layout_alignRight="@id/distance" android:layout_alignStart="@id/tvName" android:layout_below="@id/tvName" android:layout_marginBottom="@dimen/standard_gap" diff --git a/app/src/main/res/layout/layout_campagin.xml b/app/src/main/res/layout/layout_campagin.xml index 775a6a4ec..2a2891e84 100644 --- a/app/src/main/res/layout/layout_campagin.xml +++ b/app/src/main/res/layout/layout_campagin.xml @@ -19,17 +19,14 @@ android:id="@+id/iv_campaign" android:layout_width="@dimen/dimen_40" android:layout_height="@dimen/dimen_40" - android:layout_marginLeft="@dimen/standard_gap" android:layout_marginStart="@dimen/standard_gap" android:scaleType="centerCrop" app:srcCompat="@drawable/ic_campaign" - android:tint="?attr/card_item_color" - /> + app:tint="?attr/card_item_color" /> @@ -37,15 +34,13 @@ + android:layout_marginStart="@dimen/standard_gap" + android:layout_marginEnd="@dimen/tiny_margin"> + android:visibility="visible" + app:tint="?attr/contributionsListTextSecondary" /> diff --git a/app/src/main/res/layout/nearby_card_view.xml b/app/src/main/res/layout/nearby_card_view.xml index 7161a0936..bbd43249e 100644 --- a/app/src/main/res/layout/nearby_card_view.xml +++ b/app/src/main/res/layout/nearby_card_view.xml @@ -14,7 +14,6 @@ style="@style/Widget.AppCompat.Button.Borderless" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerInParent="true" android:layout_marginLeft="@dimen/activity_margin_horizontal" android:layout_marginTop="@dimen/activity_margin_horizontal" android:layout_marginRight="@dimen/activity_margin_horizontal" @@ -30,34 +29,28 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/content_layout" - android:layout_centerInParent="true" android:orientation="horizontal" > + android:id="@+id/progressBar" /> + app:tint="?attr/card_item_color" /> + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/nearby_row_button.xml b/app/src/main/res/layout/nearby_row_button.xml index 97ab2b2da..959fc60c8 100644 --- a/app/src/main/res/layout/nearby_row_button.xml +++ b/app/src/main/res/layout/nearby_row_button.xml @@ -17,6 +17,7 @@ android:layout_weight="1" android:background="@drawable/button_background_selector" android:clickable="true" + android:focusable="true" android:orientation="vertical" android:padding="@dimen/standard_gap"> @@ -24,8 +25,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:tint="?attr/bookmarkButtonColor" - app:srcCompat="@drawable/ic_photo_camera_white_24dp" /> + app:srcCompat="@drawable/ic_photo_camera_white_24dp" + app:tint="?attr/bookmarkButtonColor" /> @@ -53,8 +55,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:duplicateParentState="true" - android:tint="?attr/bookmarkButtonColor" - app:srcCompat="@drawable/ic_photo_white_24dp" /> + app:srcCompat="@drawable/ic_photo_white_24dp" + app:tint="?attr/bookmarkButtonColor" /> + android:duplicateParentState="true" + app:tint="?attr/bookmarkButtonColor" /> @@ -110,8 +114,8 @@ android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:duplicateParentState="true" - android:tint="?attr/bookmarkButtonColor" - app:srcCompat="@drawable/ic_overflow" /> + app:srcCompat="@drawable/ic_overflow" + app:tint="?attr/bookmarkButtonColor" /> + app:srcCompat="@drawable/ic_arrow_back_white" + app:tint="@color/white" /> \ No newline at end of file diff --git a/app/src/main/res/menu/nearby_fragment_menu.xml b/app/src/main/res/menu/nearby_fragment_menu.xml index 30b5c9dd5..fe049cde4 100644 --- a/app/src/main/res/menu/nearby_fragment_menu.xml +++ b/app/src/main/res/menu/nearby_fragment_menu.xml @@ -1,17 +1,25 @@ + + + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b46cbf547..e1f29eb2d 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -527,6 +527,7 @@ ليس لديك أي إشعارات غير مقروءة ليس لديك أي إشعاراتٍ غير مقروءة مشاركة السجلات باستخدام + تحقق من صندوق بريدك الإلكتروني عرض المقروءة عرض غير المقروءة حدث خطأ أثناء التقاط الصور @@ -690,6 +691,7 @@ مجاور مستخدَم ترتيبي + &#169; <a href=\"https://www.openstreetmap.org/copyright\">خريطة الشارع المفتوحة</a> وضع الاتصال المحدود مُمَكَّن! وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن. وضع الاتصال المحدود @@ -739,6 +741,7 @@ رفض الحد الأقصى: %1$d خطأ: تجاوز حد التحميل + دبليو إل إم سيتم إدخال هذه الصورة في مسابقة Wiki Loves Monuments عرض الآثار إنه شهر Wiki Loves Monuments! @@ -788,16 +791,66 @@ تم تحديد الصورة تم وضع علامة على الصورة على أنها ليست للتحميل تقرير + تعيين الخلفية البيضاء + تعيين خلفية سوداء تبليغ عن عنف أخطر عن هذا المستخدم الإبلاغ عن هذا المحتوى طلب منع هذا المستخدم مرحبًا بك في وضع التحديد بملء الشاشة استخدم إصبعين للتكبير والتصغير. - مرر سريعًا وطويلًا لتنفيذ هذه الإجراءات:! N! - يسار / يمين: انتقل إلى السابق / التالي! N! - لأعلى: حدد! N! - أسفل: وضع علامة على أنه ليس للتحميل. + مرر سريعًا وطويلًا لأداء هذه الإجراءات: \n- يسار/يمين: الانتقال إلى السابق/التالي \n- أعلى: تحديد\n- أسفل: وضع علامة على عدم التحميل. + لإعداد صورتك الرمزية في قائمة المتصدرين، اضغط على \"تعيين كصورة رمزية\" في قائمة النقاط الثلاث لأي صورة. + الإحداثيات ليست إحداثيات دقيقة، لكن الشخص الذي قام بتحميل هذه الصورة يعتقد أنها قريبة بما فيه الكفاية. رُفض إذن التخزين تعذر مشاركة هذا العنصر الإذن مطلوب لهذه الوظيفة + تعلم كيفية كتابة وصف مفيد + تعلم كيفية كتابة تعليق مفيد + شاهد إنجازاتك + تعديل الصورة + تعديل الموقع + تم تحديث الموقع! + إزالة الموقع + إزالة تحذير الموقع + يجعل تحديد الموقع الصور أكثر فائدة وسهولة في العثور عليها. هل ترغب حقًا في إزالة تحديد الموقع من هذه الصورة؟ + تمت إزالة الموقع! اشكر المؤلف حدث خطأ أثناء إرسال الشكر للمؤلف. + لقد انتهت صلاحية تسجيل الدخول الخاص بك. يرجى تسجيل الدخول مرة أخرى. + لا يوجد تطبيق متاح لفتح ملفات GPX + تم حفظ الملف بنجاح + هل تريد فتح ملف GPX؟ + هل تريد فتح ملف KML؟ + فشل في حفظ ملف KML. + فشل في حفظ ملف GPX. + حفظ ملف KML + حفظ ملف GPX + + لا صور تم اختيارها + %d صورة تم اختيارها + صورتان تم اختيارهما + صور قليلة تم اختيارها + صور كثيرة تم اختيارها + %d صور تم اختيارها + + يرجى تذكر أن جميع الصور في التحميل المتعدد تحصل على نفس الفئات والأوصاف. إذا لم تتشارك الصور في الأوصاف والفئات، فيرجى إجراء عدة عمليات تحميل منفصلة. + ملاحظة حول التحميلات المتعددة + الإبلاغ عن مشكلة حول هذا العنصر إلى Wikidata + الرجاء إدخال بعض التعليقات + نقاش + اكتب شيئًا عن العنصر \'%1$s\'. سيكون مرئيًا للعامة. + \'%1$s\' لم يعد موجودًا، ولا يمكن التقاط صورة له أبدًا. + \'%1$s\' موجود في مكان مختلف. يرجى تحديد المكان الصحيح أدناه، وإذا أمكن، اكتب خط العرض وخط الطول الصحيحين. + مشكلة أو معلومات أخرى (يرجى التوضيح أدناه). + سيتم نشر تعليقاتك على صفحة الويكي التالية: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a> + هل أنت متأكد أنك تريد إلغاء كافة التحميلات؟ + إلغاء كافة التحميلات... + المرفوعات + قيد الانتظار + فشل + تعذر تحميل بيانات المكان + هذا المكان ليس له صورة بعد، اذهب والتقط واحدة! + هذا المكان لديه صورة بالفعل. + الآن التحقق ما إذا كان هذا المكان لديه صورة. diff --git a/app/src/main/res/values-az/error.xml b/app/src/main/res/values-az/error.xml index e698eab9d..6e9503c43 100644 --- a/app/src/main/res/values-az/error.xml +++ b/app/src/main/res/values-az/error.xml @@ -2,10 +2,11 @@ Nasazlıq Uups. Nəsə düzgün çalışmır! - 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. - Təşəkkür! + 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! + Təşəkkürlər! diff --git a/app/src/main/res/values-az/strings.xml b/app/src/main/res/values-az/strings.xml index d5e83a2c7..1edbe43fc 100644 --- a/app/src/main/res/values-az/strings.xml +++ b/app/src/main/res/values-az/strings.xml @@ -12,34 +12,60 @@ * Şeyx Şamil --> + Commons Facebook səhifəsi + Commons Github Mənbə Kodu + Commons Loqotipi + Commons Veb-saytı + Məkan seçicidən çıxın + Göndər + Başqa təsvir əlavə et + Yeni töhfə + Kamera ilə töhfə ver + Fotolar ilə töhfə ver + Əvvəlki töhfələr qalereyasından töhfə əlavə et + Başlıqlar + Dil təsviri + Başlıq + Təsvir + Şəkil + Hamısı + Aç/Bağla + Axtarış Görünüşü + Məkanın Vəziyyəti + Günün Şəkli %1$d fayl yüklənir %1$d fayllar yüklənir + + (%1$d) + (%1$d) + + Yükləmələrə Başlanılır Ümumi Məxfilik - Vikimedia Commons + Vikianbar Tənzimləmələr - Ləqəb + İstifadəçi adı Parol Daxil ol Qeydiyyatdan keç Giriş edilir - Lütfən gözləyin… + Zəhmət olmasa, gözləyin… Daxil oldunuz! Giriş baş tutmadı! Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin. Doğrulama alınmadı, xahiş edirəm yenidən daxil olun Yükləmə başladı! %1$s yükləndi! - Yüklədiyini izlə + Yükləmənizə baxmaq üçün toxunun %1$s yüklənməsi başlanır %1$s yüklənir %1$s yüklənməsi başa çatdı %1$s faylının yüklənməsi alınmadı - Baxmaq üçün toxunun - Yükləmələrim - Sırada + Baxmaq üçün toxun + Son Yükləmələrim + Növbəyə alındı Uğursuz %1$d%% tamamlandı Yüklənir @@ -52,15 +78,15 @@ Açıqlama Daxil olmaq olmur — şəbəkə xətası Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin. - Bağışlayın, bu istifadəçi Commons-da bloklanmışdır. - İki faktorlu giriş doğrulama kodunu verməlisiniz. + Bağışlayın, bu istifadəçi Vikianbardan bloklanıb + Siz iki faktorlu autentifikasiya kodunuzu təqdim etməlisiniz. Daxil olma uğursuz oldu Yüklə Bu dəsti adlandırın - Bildirişlər + Dəyişikliklər Yüklə - Kateqoriyaları axtar - Qeyd et + Kateqoriyalarda axtar + Yadda saxla Yenilə Siyahı (Hələ yükləmə yoxdur) diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 0f765a1fb..6ee931542 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -307,4 +307,5 @@ Моля, изчакайте... напълно размазано Наблизо + Прочетете повече diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index be454b51b..1c7d09617 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -242,8 +242,8 @@ Meneget eo bet ar skeudenn evit lemel. Lezel a-gostez Kevreañ - Ha c\'hoant ho peus, evit gwir, da gevreañ ? - Da gevreañ ho po en amzer-da-zont evit pellgargañ skeudennoù. + Ha n\'ho peus ket c\'hoant, evit gwir, da gevreañ ? + Ret e vo deoc\'h kevreañ en amzer-da-zont evit pellgargañ skeudennoù. Kevreit, mar plij, evit implijout an arc\'hwel-mañ Eilañ an destenn wiki er golver Testenn wiki eilet er golver diff --git a/app/src/main/res/values-ce/strings.xml b/app/src/main/res/values-ce/strings.xml index 1676c72a3..e13e8c040 100644 --- a/app/src/main/res/values-ce/strings.xml +++ b/app/src/main/res/values-ce/strings.xml @@ -106,7 +106,7 @@ Хьажа файлан агӀоне Куьг йазор (ТIедилина ду) Дехар ду, хӀокху файлан цIе гайта - Цунах лаьцна + Цуьнах лаьцна Куьг Чувала(йала) тара цало — сетан гӀалат ТӀех дукха кхиаме боцу гӀертарш. Дехар ду масех минот йаьлча йуха а хьажа. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 4116080ea..3b6822c47 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -481,6 +481,7 @@ Du har ingen ulæste notifikationer Du har ingen læste notifikationer Del logs ved hjælp af + Tjek din e-mail-indbakke Vis læste Vis ulæste Der opstod en fejl under udvælgelse af billeder @@ -788,4 +789,7 @@ Afventer Mislykkedes Kunne ikke indlæse steddata + Dette sted har endnu ikke noget billede, så gå hen og tag et! + Dette sted har allerede et billede. + Tjekker nu, om dette sted har et billede. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 25498e395..a6471c1fe 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -29,6 +29,7 @@ * Sujan * Sushi * Tacsipacsi +* TheRabbit22 * ThisCarthing * Tobi 406 * TomatoCake @@ -802,4 +803,17 @@ 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. Hinweis zu Mehrfach-Uploads Melde ein Problem mit diesem Datenobjekt an Wikidata + Bitte gib einige Kommentare ein + Diskussion + Schreibe etwas über das Objekt ‚%1$s‘. Deine Beschreibung wird öffentlich sichtbar sein. + ‚%1$s‘ existiert nicht mehr, es kann kein Foto mehr davon gemacht werden. + ‚%1$s‘ ist jetzt an einem anderen Ort. Bitte gib den richtigen Ort und, wenn möglich, den Breiten- und Längengrad an. + Sonstiges Problem oder Information (bitte unten erläutern). + Dein Feedback wird auf der folgenden Wiki-Seite veröffentlicht werden: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a> + Möchtest du wirklich alle Uploads abbrechen? + Alle Uploads werden abgebrochen… + Hochgeladene Dateien + Ausstehend + Fehlgeschlagen + Ortsdaten konnten nicht geladen werden diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e5797e656..ae4dfb966 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -22,6 +22,7 @@ * JenyxGym * KATRINE1992 * Koreller +* Mahabarata * McDutchie * Melissadeba95 * Metroitendo @@ -516,6 +517,7 @@ Vous n’avez aucune notification non lue Vous n’avez aucune notification lue Partager les journaux en utilisant + Vérifiez votre boîte de réception Afficher les lus Afficher les non lus Une erreur est survenue lors de la sélection des images @@ -814,7 +816,7 @@ Signaler un problème concernant cet élément à Wikidata Merci de saisir vos commentaires Discussion - Ecrivez quelque chose sur l\'article \"%1$s\". Il sera visible par le public\nAjouter une définition terminologique pour ce terme + Écrivez quelque chose à propos de l’élément « %1$s ». Il sera visible publiquement. \'%1$s\' n\'existe plus, aucune photo ne pourra jamais en être prise. \'%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. Autre problème ou information (merci d\'expliquer ci-dessous). @@ -824,5 +826,8 @@ Téléversements En attente Échec - Ne peut pas supporter les données + Les données du lieu n\'ont pas pu être chargées + Cet endroit n\'a pas encore de photo, allez en prendre une ! + Cet endroit a déjà une photo. + Je vérifie maintenant si cet endroit a une photo. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index aefc17d9d..d4e6c0f98 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -374,7 +374,7 @@ A helymeghatározás nélkül nem használható ez a funkció. Ne kérdezd meg többször Helymeghatározási engedély - Valami hiba történt, nem sikerült az eredményeid betöltése + Valami hiba történt, nem sikerült az eredményeid betöltése Kampányok megjelenítése Folyamatban lévő kampányok megjelenítése 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 @@ -385,7 +385,7 @@ Az alkalmazás helymeghatározási engedély hiányában nem rögzíti a helyszínt a felvételekkel együtt Az alkalmazás nem rögzít helyszínt a felvételekkel együtt, mivel a GPS ki van kapcsolva Többé nem lesznek láthatók a kampányok. Ha akarod, visszakapcsolható a Beállításoknál. - Ehhez a funkcióhoz hálózati kapcsolat szükséges, kérlek ellenőrizd az internetbeállításaidat. + Ehhez a funkcióhoz hálózati kapcsolat szükséges. Kérlek ellenőrizd az internetbeállításaidat! Hiba történt a kép feltöltése során. Próbáld meg újra! Szerkesztő token beszerzése Kategóriaellenőrző sablon felhelyezése @@ -454,9 +454,9 @@ Törlésre jelölve: %1$s. Sikertelen Nem sikerült a törlés kérése. - Egy szelfi - Homályos - Nonszensz + egy szelfi, amely egyetlen cikkben sem szerepel + teljesen homályos + nonszensz, abszolút használhatatlan bármely cikkben is Sajtófotó Random fénykép az Internetről Logó @@ -472,7 +472,7 @@ Fénykép szükséges Hely típusa: Híd, múzeum, szálloda, stb. - A belépés nem sikerült, kérj új jelszót. + A belépés nem sikerült. Kérj új jelszót! Beállítás háttérképnek Beállítás háttérképnek. Kérem várjon... Rendszerbeállítás követése diff --git a/app/src/main/res/values-ia/strings.xml b/app/src/main/res/values-ia/strings.xml index 0e595af04..7b9b3f607 100644 --- a/app/src/main/res/values-ia/strings.xml +++ b/app/src/main/res/values-ia/strings.xml @@ -765,8 +765,15 @@ Signalar a Wikidata un problema sur iste elemento Per favor insere alcun commentos Discussion + Scribe qualcosa sur le elemento ‘%1$s’. Isto essera visibile publicamente. ‘%1$s’ non existe plus, necun imagine pote jammais esser prendite de illo. ‘%1$s’ es in un altere loco. Per favor specifica le loco correcte hic infra, e si possibile, indica le latitude e longitude correcte. Altere problema o information (per favor explica hic infra). Tu retroaction apparera sur le sequente pagina wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a> + Es tu secur de voler cancellar tote le incargamentos? + Cancella tote le incargamentos… + Incargamentos + Pendente + Fallite + Non poteva cargar le datos del loco diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index be69fa045..f40863870 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -760,4 +760,7 @@ %d immagine selezionata %d immagini selezionate + Questo posto non ha ancora una foto, scattane una! + Questo posto ha già una foto. + Ora controlliamo se questo posto ha una foto. diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 0b512102b..3389a227e 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -505,6 +505,7 @@ אין לך התראות שלא נקראו אין לך התראות שנקראו שיתוף יומנים בעזרת + לבדוק את תיבת הדוא״ל הנכנס שלך הצגת התראות שנקראו הצגת התראות שלא נקראו אירעה שגיאה בעת בחירת תמונות @@ -820,4 +821,7 @@ ממתינות נכשלו לא היה אפשר לטעון את נתוני המקום + אין עדיין תמונה למקום הזה, אפשר פשוט לצלם אחת! + למקום הזה כבר יש תמונה. + עכשיו מתבצעת בדיקה האם למקום הזה יש תמונה. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 6fb40d2ff..f20b986f8 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -462,6 +462,7 @@ 未読の通知はありません 既読のお知らせはありません ログの共有に使うアプリ + メールをご確認ください 既読を表示 未読を見る 画像の選択中にエラーが発生しました diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index b729838b9..aa7ae98e7 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -284,6 +284,7 @@ 위키텍스트를 클립보드에 복사했습니다 주변이 제대로 작동되지 않을 수 있습니다. 위치를 사용할 수 없습니다. 주변 장소의 목록을 표시하기 위한 권한이 필요합니다. + 주변 장소의 이미지 목록을 표시하기 위한 권한이 필요합니다 방향 위키데이터 위키백과 @@ -439,6 +440,7 @@ 완료 감사 표현 보내기: 성공 감사 표현 보내기: 실패 + 이것이 저작권 규정을 준수하고 있습니까? 알맞게 분류됐습니까? 기여자에게 감사를 표하시겠습니까? 앗, 분류가 달리지 않은 것 같습니다! @@ -453,6 +455,7 @@ 이미지가 올려지지 않음 읽지 않은 알림이 없습니다 읽은 알림이 없습니다 + 이메일의 받은 편지함을 확인하세요 읽은 항목 보기 읽지 않은 항목 보기 이미지 선택 도중 오류가 발생했습니다 @@ -499,6 +502,7 @@ 성공 설명이 추가되었습니다. 캡션이 추가되었습니다. + 좌표를 추가하지 못했습니다. 설명을 추가하지 못했습니다. 캡션을 추가하지 못했습니다. 이미지 좌표가 업데이트되지 않았습니다 @@ -549,6 +553,7 @@ 일시 정지 계속하기 일시 중단됨 + 더 보기 책갈피 리더보드 순위: @@ -644,6 +649,9 @@ 전체 화면 선택 모드에 오신 것을 환영합니다 두 손가락으로 확대 / 축소하세요. 다음 방향으로 길고 재빠르게 넘겨보세요. \n- 왼쪽/오른쪽: 이전/다음으로 이동 \n- 위쪽: 선택\n- 아래쪽: 비업로드용으로 표시 + 스토리지 접근이 거부됨 + 이 항목을 공유할 수 없습니다 + 기능에 대한 권한이 필요합니다 유용한 설명을 추가하는 법 알아보기 유용한 캡션을 추가하는 법 알아보기 업적 보기 @@ -657,6 +665,7 @@ 작성자에게 감사 표시하기 작성자에게 감사를 표하던 도중에 오류가 발생하였습니다. 로그인 세션 만료. 다시 로그인해 주십시오. + GPX 파일을 열 수 있는 응용 프로그램이 없습니다 파일이 성공적으로 저장되었습니다 GPX 파일을 여시겠습니까? KML 파일을 여시겠습니까? @@ -664,8 +673,20 @@ GPX 파일을 저장하지 못했습니다. KML 파일을 저장 중 GPX 파일을 저장 중 + + %d개 이미지 선택됨 + 다중 업로드에 대한 참고사항 이 항목에 관한 문제를 위키데이터에 보고하기 + 의견을 입력해 주십시오 토론 기타 문제 또는 정보 (아래에 설명해 주십시오) + 모든 업로드를 취소하는 중... + 업로드 + 보류 중 + 실패 + 장소 데이터를 불러오지 못했습니다 + 이 장소에 아직 사진이 없습니다. 사진을 찍어보세요! + 이 장소에 이미 사진이 있습니다. + 지금 이 장소에 사진이 있는지 확인 중입니다. diff --git a/app/src/main/res/values-krc/strings.xml b/app/src/main/res/values-krc/strings.xml index b97684821..eb9193e7f 100644 --- a/app/src/main/res/values-krc/strings.xml +++ b/app/src/main/res/values-krc/strings.xml @@ -227,7 +227,7 @@ Ызына ал Ач Джаб - Баш бет + Тамал бет Джюкле Джуўукъда Юсюнден @@ -473,6 +473,7 @@ Окъулмагъана хапарландырыуугъуз джокъду Окъулмагъан хапарландырыуугъуз барды Логларыгъызны хайырланыб юлюшлегиз + Электрон почтагъызны тинтигиз Окъулгъанны кёргюз Окъулмагъанланы кёргюз Суратла сайланнган заманда халат болду. @@ -686,7 +687,7 @@ Джууукъдагъы картала тюз ишлер ючюн ТЕЛЕФОННУ БОЛУМУн окъургъа амал болургъа кереклиди Хайырланыучуну къошумлары: %s Хайырланыучуну джетишимлери: %s - Хайырланыучу бетни кёргюз + Хайырланыучу профильни кёр Танытыуланы тюзет Категорияланы тюзет Кенгленнген Сайлаула @@ -774,4 +775,13 @@ \'%1$s\' - башха джерди. Тилейбиз, тюз джерни энишгерекде белгилегиз, эмда мадар бар эсе, тюз кенглик бла узунлукъну джазыгъыз. Башха проблема неда информация (тилейбиз, энишгерекде ангылатыгъыз). Сизни кери оюмугъуз тюбюндеги вики бетге джиберилликди: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a> + Бютеу джюклеулени тохтатыргъа излегенигизге ишексизмисиз? + Бютеу джюклеулени тохтатыу... + Джюклеуле + Сакълауда + Джетишимсиз + Джерни юсюнден билгилени джюклеялмады + Бу джерни сураты джокъду, хайда бирин эт! + Бу джерни алайсыз да сураты барды. + Бу джерни сураты болуб-болмагъанын тинте турама. diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 9d69efabb..aa6f8e58d 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -72,8 +72,9 @@ Dréckt fir de Fichier ze gesinn deen Dir eropgelueden hutt Fichier eroplueden: %s %1$s gëtt eropgelueden - Eroplueden vu(n) %1$s ofschléissen + Eropluede vu(n) %1$s ofschléissen %1$s konnt net eropgeluede ginn + Eropluede vu(n) %1$s pauséiert Dréckt fir nach eng Kéier ze probéieren Tippe fir ze kucken Meng rezent eropgeluede Fichieren @@ -86,6 +87,7 @@ Nobäi Meng eropgeluede Fichieren Deelen + Fichierssäit weisen Beschrëftung (obligatoresch) Gitt wgl. eng Beschrëftung fir dëse Fichier un Beschreiwung @@ -93,6 +95,7 @@ Aloggen huet net funktionéiert – Problemer mam Reseau Ze dacks ouni Succès probéiert. Probéiert wgl. an e puer Minutten nach eng Kéier. Pardon, dëse Benotzer ass op Commons gespaart + Dir musst de Code vun Ärer Zwee-Facteur-Authentifizéierung uginn. Aloggen huet net funktionéiert Eroplueden Gitt dëser Biller een Numm @@ -498,11 +501,20 @@ Dësen Inhalt mellen Ufroe fir dëse Benotzer ze spären Léiert, wéi een nëtzlech Beschrëftunge schreift + Bild änneren + Plaz änneren Plaz aktualiséiert! Plaz ewechhuelen Plaz-Ewechhuele-Warnung Duerch Plaze sinn d\'Biller méi nëtzlech a besser sichbar. Wëllt Dir wierklech d\'Plaz vun dësem Bild ewechhuelen? Plat ewechgeholl! + Dem Auteur Merci soen + Feeler beim Schécke vun engem Merci un den Auteur. + Fichier erfollegräich gespäichert + Wëllt Dir de GPW-Fichier opmaachen? + Wëllt Dir de KML-Fichier opmaachen? + De KML-Fichier konnt net gespäichert ginn. + De GPX-Fichier konnt net gespäichert ginn. KML-Fichier späicheren GPX-Fichier späicheren diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 22749651c..26a9bc7f7 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -28,6 +28,7 @@ Aprašymas Paveikslėlis Visi + Perjungti aukštyn Paieškos rodinys Dienos nuotrauka @@ -57,6 +58,7 @@ Vikiteka Nustatymai Įkelti į Vikiteką + Vyksta įkėlimas Naudotojo vardas Slaptažodis Prisijunkite prie savo Commons Beta paskyros @@ -67,10 +69,13 @@ Prašome palaukti… Antraštės ir aprašymai atnaujinami Prašome palaukti... - Sėkmingai prisijungėte! - Prisijungti nepavyko! + Sėkmingai prisijungėte! + Prisijungti nepavyko! Failas nerastas. Prašome pabandyti kitą failą. - Autentifikavimas nepavyko, prašome prisijungti dar kartą + Pasiektas maksimalus pakartotinių bandymų limitas! Atšaukite įkėlimą ir bandykite dar kartą + Išjungti akumuliatoriaus optimizavimą? + Daugiau nei 3 paveikslėlių įkėlimas veikia patikimiau, kai akumuliatoriaus optimizavimas išjungtas. Išjunkite Vikitekos programėlės akumuliatoriaus optimizavimą nustatymuose, kad įkėlimas būtų sklandus. \n\nGalimi akumuliatoriaus optimizavimo išjungimo veiksmai:\n\n1 veiksmas: bakstelėkite toliau esantį mygtuką „Nustatymai“.\n\n2 veiksmas: perjunkite iš „Neoptimizuota“ į „Visos programėlės“.\n\n3 veiksmas: ieškokite „Vikiteka“ arba „fr.free.nrw.commons“.\n\n4 veiksmas: spustelėkite jį ir pasirinkite „Neoptimizuoti“.\n\n5 veiksmas: paspauskite „Atlikta“. + Autentifikavimas nepavyko. Prašome prisijungti dar kartą. Įkėlimas prasidėjo! Įkėlimas eilėje (įgalintas riboto ryšio režimas) %1$s įkelta! @@ -97,11 +102,11 @@ Pateikite šio failo antraštę Aprašymas Antraštė - Negalima prisijungti - tinklo klaida + Negalima prisijungti - tinklo klaida Per daug nesėkmingų bandymų. Pabandykite dar kartą po keleto minučių. Atsiprašome, šis vartotojas buvo užblokuotas Commons Turite pateikti savo dviejų žingsnių patvirtinimo kodą. - Prisijungti nepavyko + Prisijungti nepavyko Įkelti Pavadinkite šį rinkinį Pakeitimai @@ -123,7 +128,7 @@ Kategorija Apie Vikitekos programėlė yra atviro kodo programėlė, kurią sukūrė ir prižiūri Vikitekos bendruomenės dotacijų gavėjai ir savanoriai. „Wikimedia Foundation“ nedalyvauja kuriant, plėtojant ar prižiūrint programėlę. - Sukurkite naują <a href=\"%1$s\">GitHub pranešimą</a>, siekiant pranešti apie klaidas ir pateikti siūlymus. + Sukurkite naują <a href=\"%1$s\">GitHub pranešimą</a>, siekiant pranešti apie klaidas ir pateikti siūlymus. Privatumo politika Kūrėjai Apie @@ -178,6 +183,8 @@ Reikalinga teisė: Skaityti išorinę talpyklą. Programėle be to negali prieiti prie jūsų galerijos. Reikalingas leidimas: rašyti į išorinę saugyklą. Programėlė be to negali pasiekti jūsų fotoaparato/galerijos. Prašoma vietovės leidimo + Įrašyti vietovę nuotraukoms, kurios fotografuotos programėlėje + Įjunkite šią funkciją, kad įrašytumėte vietovę nuotraukoms, jei jūsų įrenginio kamera to nepadaro Gerai Įspėjimas Rastas pasikartojantis failo pavadinimas @@ -198,6 +205,7 @@ Prisijunkite prie beta kanalo Google Play ir gaukite išankstinę prieigą prie naujų funkcijų bei klaidų pataisymų 2FA kodas Ar tikrai norite atsijungti? + Medijos paveikslėlis nepavyko Subkategorijų nerasta Zao kalnas Lamos @@ -214,6 +222,7 @@ Apie Nustatymai Atsiliepimai + Atsiliepimai per GitHub Atsijungti Pamoka Pranešimai @@ -247,13 +256,15 @@ Žiūrėkite tinklapį dėl daugiau informacijos Praleisti Prisijungti - Ar tikrai norite praleisti prisijungimą? - Norėdami ateityje įkelti nuotraukas, turėsite prisijungti. + Ar tikrai norite praleisti prisijungimą? + Norėdami ateityje įkelti nuotraukas, turėsite prisijungti. Jei norite naudotis šia funkcija, prisijunkite Nukopijuokite vikitekstą į mainų sritį Vikitekstas buvo nukopijuotas į mainų sritį Netoliese gali tinkamai neveikti, vieta nepasiekiama. + Prieiga prie vietos uždrausta. Norėdami naudotis šia funkcija, nustatykite savo vietą rankiniu būdu. Norint rodyti netoliese esančių vietų sąrašą, reikalingas leidimas + Norint rodyti netoliese esančių paveikslėlių sąrašą, reikalingas leidimas Nurodymai Vikiduomenys Vikipedija @@ -313,14 +324,16 @@ Nuotraukos, kuriose pavaizduotos technologijos ar kultūra, yra labai laukiamos Vikitekoje. Surinkote %1$s teisingų atsakymų. Sveikiname! Norėdami atsakyti į klausimą, pasirinkite vieną iš dviejų variantų - Prisijungimo sesija baigėsi, prisijunkite dar kartą. + Prisijungimo sesija baigėsi, prašome prisijungti dar kartą. Pasidalinkite savo apkalusa su draugais! Tęsti Teisingas atsakymas Atsakymas neteisingas Ar šią ekrano kopiją galima įkelti? Dalintis programėle - Klaida gaunant netoliese esančias vietas. + Pasukti + Nepavyko įkelti netoliese esančių vietų + Šioje vietovėje nuotraukų nėra Nėra šalia esančių vietų Gaunant netoliese esančius paminklus įvyko klaida. Nėra naujausių paieškų @@ -338,6 +351,7 @@ Vaizdai per „Netoliese esančios vietos“ Lygis Vaizdai įkelti + Paveikslėliai negrąžinti Naudoti vaizdai Pasidalinkite savo pasiekimais su draugais! Jūsų lygis kyla, kai atitinkate šiuos reikalavimus. Skiltyje „statistika“ esantys elementai neįskaičiuojami į jūsų lygį. @@ -394,12 +408,22 @@ Niekada daugiau to neklausti Paprašyti vietos leidimo Jei reikia, kad būtų galima naudoti netoliese esančio pranešimų kortelės peržiūros funkciją, paprašykite leidimo nustatyti vietą. - Kažkas ne taip. Nepavyko gauti jūsų pasiekimų + Kažkas ne taip, mums nepavyko gauti pasiekimų Prisidėjote tiek daug, kad mūsų pasiekimų skaičiavimo sistema negali susidoroti. Tai yra didžiausias pasiekimas. Baigiasi: Peržiūrėkite vykstančias kampanijas + Leiskite programėlei nuskaityti vietą, jei fotoaparatas jos neįrašo. Kai kurių įrenginių kameros neįrašo vietos. Tokiais atvejais leidus programai gauti ir pridėti vietą, jūsų indėlis bus naudingesnis. Tai galite bet kada pakeisti nustatymuose + Leisti + Paslėpti + Nustatymuose įjunkite prieigą prie vietos ir bandykite dar kartą. \n\nPastaba: įkėlimas gali neturėti vietos, jei programėlė negali per trumpą laiką nuskaityti vietos iš įrenginio. + Programėlėje esančiam fotoaparatui reikalingas vietos leidimas, kad jis būtų pridėtas prie vaizdų, jei EXIF nėra vietos. Leiskite programėlei pasiekti jūsų buvimo vietą ir bandykite dar kartą.\n\nPastaba: įkėlimas gali neturėti vietos, jei programėlė negali per trumpą laiką nuskaityti vietos iš įrenginio. + Programėlė neįrašys vietos kartu su kadrais, nes neturi vietos leidimo + Programa neįrašys vietos kartu su kadrais, nes GPS išjungtas + Naudokite dokumentais pagrįstą nuotraukų rinkiklį + Naujasis „Android“ nuotraukų rinkiklis gali prarasti vietos informaciją. Įjunkite, jei atrodo, kad jį naudojate. + Išjungus tai gali suaktyvinti naująjį „Android“ nuotraukų rinkiklį. Dėl to kyla pavojus prarasti vietos informaciją.\n\nNorėdami gauti daugiau informacijos, bakstelėkite „Skaityti daugiau“. Kampanijų nebematysite. Tačiau, jei norite, galite iš naujo įjungti šį pranešimą nustatymuose. - Šiai funkcijai reikalingas tinklo ryšys, patikrinkite ryšio nustatymus. + Šiai funkcijai reikalingas tinklo ryšys. Prašome patikrinti savo ryšio nustatymus. Apdorojant vaizdą įvyko klaida. Pabandykite dar kartą! Gaunamas redagavimo prieigos raktas Kategorijos tikrinimo šablonas pridedamas @@ -418,22 +442,26 @@ Siunčiama padėka už %1$s Ar tai atitinka autorines teises? Ar tai teisingai priskirta kategorijoms? + Ar tai taikytina? Ar norėtumėte padėkoti prisidėjusiam? Spustelėkite NE, kad pasiūlytumėte šį vaizdą ištrinti, jei jis visai nenaudingas. Logotipai, ekrano kopijos, filmų plakatai dažnai pažeidžia autorines teises. Spustelėkite NE, jei norite pasiūlyti šį vaizdą ištrinti %1$s bus padrąsintas jūsų dėkingumu Oi, tai net nėra priskirta kategorijai! Šis vaizdas priklauso %1$s kategorijoms. + Tai nėra taikytina, nes Tai yra autorių teisių pažeidimas, nes Kitas vaizdas Taip, kodėl gi ne Spustelėję šį mygtuką pamatysite kitą neseniai įkeltą vaizdą iš Vikitekos + Galite peržiūrėti vaizdus, kad pagerintumėte Vikitekos kokybę.\nTrys peržiūros parametrai yra:\n\n- Ar šis vaizdas tinkamas?\nKai paliesite Ne (nepatenka į sritį), jūs prie šio paveikslėlio pridedate ištrynimo nominacijos šabloną.\n\n- Ar šis vaizdas atitinka autorių teisių taisykles?\nKai paliesite Ne (neatitinka autorių teisių taisyklių), pridedate ištrynimo nominacijos šabloną prie šio paveikslėlio.\n\n- Ar šis vaizdas teisingai suskirstytas į kategorijas?\nKai paliesite Ne (neteisingai suskirstytas į kategorijas), prie šio paveikslėlio pridedate kategorizavimo užklausos šabloną.\n\nJei viskas yra gerai, prie paveikslėlio nepridedamas joks šablonas, ir jūs turite galimybę padėkoti bendraautoriui. Nenaudojami jokie vaizdai Jokie vaizdai negrąžinti Neįkelta jokių vaizdų Neturite neskaitytų pranešimų Neturite perskaitytų pranešimų Dalinkitės žurnalus naudodami + Patikrinkite savo el. pašto dėžutę Žiūrėti perskaitytus Žiūrėti neperskaitytus Renkant vaizdus įvyko klaida @@ -478,6 +506,7 @@ Žiniasklaidos nuotrauka Atsitiktinė nuotrauka iš interneto Logotipas + Panoramos laisvės pažeidimas Nes Bandoma atnaujinti kategorijas. Kategorijos atnaujinimas @@ -498,7 +527,7 @@ Nepavyko pridėti koordinačių. Nepavyko pridėti aprašymų. Nepavyko pridėti antraštę. - Nepavyko gauti koordinačių. + Paveikslėlio koordinatės neatnaujintos Nepavyko gauti aprašymų. Redaguokite aprašymus ir antraštes Dalintis vaizdu per @@ -513,10 +542,11 @@ Reikia Nuotraukos Vietos tipas: Tiltas, muziejus, viešbutis ir t.t. - Kažkas nepavyko prisijungiant, turite iš naujo nustatyti slaptažodį !! + Kažkas nepavyko prisijungiant. Turite iš naujo nustatyti slaptažodį! MEDIJA Netoliese rasta vieta - Ar tai vietos %1$s nuotrauka? + Ar tai %1$s nuotraukos? + Ar tai %1$s nuotrauka? Žymės Nustatymai Pašalinta iš žymių @@ -524,12 +554,16 @@ Kažkas nepavyko. Nepavyko nustatyti fono paveikslėlio Nustatyti kaip fono paveikslėlį Fono paveikslėlis nustatomas. Prašome palaukti… + Sekti sistemos Tamsus Šviesus Nepavyko atidaryti vietos nustatymų. Įjunkite vietą rankiniu būdu Norėdami gauti geriausius rezultatus, pasirinkite didelio tikslumo režimą. Įjungti vietą? + Įjunkite vietos nustatymo paslaugas, kad programa parodytų jūsų dabartinę vietą Kad tinkamai veiktų, Netoliese turi būti įjungta vieta + Žemėlapio naršymui reikia vietos leidimo, kad būtų rodomi netoliese esantys paveikslėliai + Norėdami automatiškai nustatyti vietą, turite suteikti vietos leidimą. Ar nufotografavote šias dvi nuotraukas toje pačioje vietoje? Ar norite naudoti dešinėje esančio paveikslėlio platumą/ilgumą? Įkelti daugiau Vietų nerasta, pabandykite pakeisti paieškos kriterijus. @@ -617,6 +651,10 @@ Skirtingai nuo paveikslėlio kairėje, paveikslėlyje dešinėje yra Vikitekos logotipas, nurodantis, kad jis jau įkeltas. \n Palieskite ir palaikykite, kad peržiūrėtumėte vaizdą. Puiku Šis vaizdas jau buvo įkeltas į Vikiteką. + Dėl techninių priežasčių programėlė negali patikimai įkelti daugiau nei %1$d nuotraukos vienu metu. %1$d įkėlimo limitas buvo viršytas %2$d. + Paslėpti + Maksimalus: %1$d + Klaida: viršytas įkėlimo limitas Šis vaizdas bus įtrauktas į konkursą \"Wiki Loves Monuments\" (\"Wiki\" mėgsta paminklus) Rodyti paminklus Vyksta Viki myli paminklus mėnuo! @@ -626,7 +664,7 @@ Netoliese žemėlapiai turi perskaityti TELENFONO BŪSENĄ, kad tinkamai funkcionuotų Naudotojo indėlis: %s Naudotojo pasiekimai: %s - Žiūrėti naudotojo puslapį + Žiūrėti naudotojo puslapį Redaguoti vaizdus Redaguoti kategorijas Išplėstiniai nustatymai @@ -652,6 +690,8 @@ Jūsų atsiliepimas Pažymėti kaip neskirtą įkėlimui Panaikinkite žymėjimą kaip neskirto įkėlimui + Žymima kaip neįkėlimui + Naikinamas žymėjimas kaip neįkėlimui Rodyti jau padarytas nuotraukas Slepiamos jau padarytos nuotraukos Daugiau paveikslėlių nerasta @@ -660,6 +700,8 @@ Paveiklėlis pasirinktas Paveikslėlis pažymėtas kaip neskirtas įkėlimui Pranešti + Nustatyti baltą foną + Nustatyti juodą foną Pranešti apie pažeidimą Pranešti apie šį nauodotoją Pranešti apie šį turinį @@ -669,4 +711,44 @@ Norėdami atlikti šiuos veiksmus, braukite greitai ir ilgai: \n- Kairėn/dešinėn: Pereikite prie ankstesnio/kito\n- Į viršų: Pasirinkite\n- Žemyn: Pažymėti kaip neskirtą įkėlimui. Norėdami nustatyti pirmaujančiųjų sąrašo avatarą, bet kurio vaizdo trijų taškų meniu palieskite „Nustatyti kaip avatarą“. Koordinatės nėra tikslios koordinatės, bet asmuo, kuris įkėlė šią nuotrauką, mano, kad jos yra pakankamai arti. + Saugyklos leidimai atmesti + Nepavyko bendrinti šio elemento + Funkcionalumui reikalingi leidimai + Sužinokite, kaip parašyti naudingą aprašymą + Sužinokite, kaip parašyti naudingą antraštę + Pamatykite savo pasiekimus + Redaguoti paveikslėlį + Redaguoti vietą + Vieta atnaujinta! + Pašalinti vietą + Pašalinti vietos įspėjimą + Vieta daro nuotraukas naudingesnes ir lengviau randamas. Ar tikrai norite pašalinti vietą iš šios nuotraukos? + Vieta pašalinta! + Padėkoti autoriui + Klaida siunčiant padėką autoriui. + Jūsų prisijungimo sesija baigėsi. Prašome prisijungti dar kartą. + Nėra jokios programos GPX failams atidaryti + Failas sėkmingai išsaugotas + Ar norite atidaryti GPX failą? + Ar norite atidaryti KML failą? + Nepavyko išsaugoti KML failo. + Nepavyko išsaugoti GPX failo. + Išsaugomas KML failas + Išsaugomas GPX failas + Atminkite, kad visi paveikslėliai, įkeliant kelis, turi tas pačias kategorijas ir vaizdus. Jei paveikslėliuose vaizdai ir kategorijos skiriasi, prašome atlikti kelis atskirus įkėlimus. + Pastaba apie kelis įkėlimus + Praneškite apie problemą dėl šio elemento Vikiduomenims. + Įveskite keletą komentarų + Aptarimas + Parašykite ką nors apie \'%1$s\' elementą. Tai bus matoma viešai. + \'%1$s\' nebeegzistuoja, niekada nebegalima jo nufotografuoti. + \'%1$s\' yra kitoje vietoje. Toliau nurodykite teisingą vietą ir, jei įmanoma, parašykite teisingą platumą ir ilgumą. + Kita problema arba informacija (paaiškinkite toliau). + Jūsų atsiliepimai bus paskelbti šiame viki puslapyje: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile App/Feedback</a> + Ar tikrai norite atšaukti visus įkėlimus? + Atšaukiami visi įkėlimai... + Įkėlimai + Laukiama + Nepavyko + Nepavyko įkelti vietos duomenų diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 00a8ba098..916f4f420 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -475,6 +475,7 @@ Немате непрочитани известувања Немате прочитани известувања Споделувај дневници користејќи + Проверете си ја дојдовната е-пошта Погл. прочитани Погл. непрочитани Се јави грешка при избирањето на сликите @@ -784,4 +785,7 @@ Во исчекување Неуспешно Не можев да ги вчитам податоците за место + Местово сè уште нема слика. Направете ја! + Местово веќе има слика. + Проверувам дали местово има слика. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index aeb8eab94..8cc3553a1 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -131,7 +131,7 @@ Aanmelden niet mogelijk. Er is een probleem met het netwerk Te veel mislukte pogingen. Probeer het over een paar minuten opnieuw. Deze gebruiker is helaas geblokkeerd op Wikimedia Commons - U moet uw code voor tweefactor-authenticatie opgeven. + U moet uw code voor tweetrapsauthenticatie opgeven. Aanmelden mislukt Uploaden Geef deze verzameling een naam @@ -496,6 +496,7 @@ U heeft geen ongelezen meldingen U heeft geen gelezen meldingen Logboeken delen via + Bekijk uw e-mailinbox Bekijk gelezen Ongelezen bekijken Er is een fout opgetreden bij het kiezen van afbeeldingen @@ -805,4 +806,7 @@ In behandeling Mislukt Plaatsgegevens konden niet geladen worden + Er is nog geen foto van deze plek, maak er eentje! + Er is al een foto van deze plek. + We controleren nu of er een foto van deze plek is. diff --git a/app/src/main/res/values-nqo/strings.xml b/app/src/main/res/values-nqo/strings.xml index 7e11ea03a..434298a44 100644 --- a/app/src/main/res/values-nqo/strings.xml +++ b/app/src/main/res/values-nqo/strings.xml @@ -161,6 +161,7 @@ ߘߌ߲߬ߞߌߙߊ ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߡߊߢߌߣߌ߲ߠߌ߲ ߏ߬ߞߍ߫ ߖߊ߲߬ߓߌ߬ߟߊ߬ߟߌ + ߞߐߕߐ߯ ߕߐ߯ ߓߊߟߌߣߍ߲ ߓߘߊ߫ ߦߋ߫ ߊ߬ ߟߊߦߟߍ߬ ߐ߲߬ߐ߲߬ߐ߲߫ ߍ߲߬ߍ߲߫ @@ -175,6 +176,7 @@ ߘߏ߲߬ߖߟߎ߬ߡߊ߬ߟߌ ߡߊ߫ ߡߊߛߐ߫ ߌ ߞߊߘߊ߲߫ ߞߊ߬ ߞߍ߫ ߓߋߕߊ ߣߍߣߍߓߊ߮ ߘߏ߫ ߘߌ߫ + ߌ ߕߐ߮ ߛߓߍ߫ ߊ߲ ߠߊ߫ ߓߋߕߊ ߥߏ߬ߦߏ ߟߊ߫ ߜ߭ߎߜ߭ߏߟ ߔߑߟߋߦ ߞߊ߲߬ ߞߊ߬ ߗߋߘߊ߫ ߞߎߘߊ ߟߎ߬ ߟߊߛߐ߬ߘߐ߲߫ ߊ߬ ߣߌ߫ ߞߐߕߐ߯ ߕߌߢߍߣߍ߲ ߠߎ߬ 2FA Code ߌ ߦߴߊ߬ ߝߍ߬ ߞߵߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲ ߓߐ߫ ߝߛߊߦߌ߫؟ ߡߋߘߌߦߊ ߖߌ߬ߦߊ߬ߓߍ ߓߘߊ߫ ߗߌߙߏ߲߫ @@ -196,6 +198,7 @@ ߞߊ߬ ߓߍ߲߬ ߟߊ߬ߓߍ߲߬ߢߐ߲߰ߡߦߊ߬ߘߊ ߞߙߐ߬ߛߌ߬ߕߊ + ߌ ߕߊ߫ ߦߋߕߊ ߗߋ߫ ߜ߭ߌߕߑߤߐߕ ߛߌߟߊ ߝߍ߬ ߌ ߜߊ߲߬ߞߎ߲߬ߣߍ߲ ߓߐ߫ ߟߞߊ߬ߙߊ߲߬ߠߌ߲ ߖߊ߲߬ߓߌ߬ߟߊ߬ߟߌ ߟߎ߬ @@ -227,13 +230,15 @@ ߞߍߦߙߐ߫ ߞߐߜߍ ߘߐߜߍ߫ ߝߊߙߊ߲ߝߊ߯ߛߌ߫ ߞߏ ߘߐ߫ ߊ߬ ߟߊߜߊ߲߫ ߌ ߜߊ߲߬ߞߎ߲߬ - ߌ ߦߴߊ߬ ߝߍ߬ ߓߊ߬ ߞߵߌ ߜߊ߲߫ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߞߎ߲߬ߠߊ߫؟ - ߌ ߞߊߞߊ߲߫ ߞߵߌ ߜߊ߲߬ߞߎ߲߫ ߞߊ߬ ߖߌ߬ߦߊ߬ߓߍ ߟߊߦߍ߬ߟߍ߫ ߛߐ߲߬. + ߌ ߦߴߊ߬ ߝߍ߬ ߞߊ߬ ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߓߴߦߋ߲߬ ߝߛߊߦߌ߫؟ + ߌ ߞߊ߫ ߞߊ߲߫ ߞߵߌ ߜߊ߲߬ߞߎ߲߫ ߡߎߣߎ߲߬ ߛߴߌ ߘߴߛߋ߫ ߖߌ߬ߦߊ߬ߓߍ ߟߊߦߍ߬ߟߍ߫ ߢߍߝߍ߬. ߌ ߜߊ߲߬ߞߎ߲߫ ߖߊ߰ߣߌ߲߫ ߞߊ߬ ߟߊ߬ߓߊ߰ߙߊ߬ߟߌ ߣߌ߲߬ ߠߊߓߊ߯ߙߊ߫ ߥߞߌߛߓߍߟߌ ߓߊߓߌ߬ߟߊ߬ ߛߓߍߝߌߘߊ-ߜߍ߬ߙߍ߲߬ߘߍ߬ߣߍ߲ ߘߐ߫ ߥߞߌߛߓߍߟߌ ߓߘߊ߫ ߓߊ߲߫ ߓߊߓߌ߬ߟߊ߬ ߟߊ߫ ߛߓߍߝߌߘߊ-ߜߍ߬ߙߍ߲߬ߘߍ߬ߣߍ߲ ߘߐ߫ ߕߙߐ߬ߝߍ߬ߟߊ ߓߊ߯ߙߊߣߍ߲߫ ߕߍ߫ ߞߍ߫ ߟߴߊ߬ ߢߊߓߘߍ ߡߊ߬߸ ߘߌ߲߬ߞߌߙߊ ߕߴߦߋ߲߬. + ߓߊ߲߬ ߓߘߊ߫ ߞߍ߫ ߘߌ߲߬ߞߌߙߊ ߡߊߛߐ߬ߘߐ߲ ߡߊ߬. ߌ ߘߌ߲߬ߞߌߙߊ ߘߊ߲߬ߠߊߕߍ߰ ߖߊ߰ߣߌ߲߫ ߛߴߌ ߘߌ߫ ߗߋߘߊ ߣߌ߲߬ ߠߊߛߐ߬ߘߐ߲߫. ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲߫ ߦߋ߫ ߞߊ߬ ߛߌ߬ߢߐ߲߰ߦߊ߫ ߛߙߍߘߍ ߟߎ߬ ߟߊߓߊ߯ߙߊ߫ + ߟߊ߬ߘߌ߬ߢߍ߬ߟߌ ߢߌ߬ߣߌ߲߬ߞߊ߬ߣߍ߲߫ ߦߋ߫ ߞߊ߬ ߛߌ߬ߢߐ߲߰ ߖߌߦߊߓߍ ߟߎ߬ ߛߙߍߘߍ ߟߊߓߊ߯ߙߊ߫ ߞߎ߲߬ߕߋߟߋ߲ ߥߞߌߘߕߊ ߥߞߌߔߋߘߌߦߊ߫ @@ -277,6 +282,7 @@ ߟߊ߬ߦߟߍ߬ߣߍ߲ ߠߎ߬ ߜߋߟߋ߲ߜߋߟߋ߲ ߠߊ߫ ߔߊ߬ߔߘߊ ߖߌ߬ߦߊ߬ߓߍ ߓߌ߬ߟߊ߬ߣߍ߲߬ ߦߋ߫ %1$s ߟߊ߫ ߥߞߌߘߕߊ ߛߌߟߊ ߟߋ߬ ߝߍ߬߹ + ߥߞߌߘߕߊ ߞߣߐߘߐ ߟߎ߬ ߓߘߊ߫ ߞߢߊ߬ ߟߊߞߎߘߦߊ߫ ߟߊ߫߹ ߊ߬ ߓߌ߬ߟߊ߬ ߘߊ߬ߣߊ߲߬ߥߟߊ ߟߊ߫. ߘߊ߬ߣߊ߲߬ߥߟߊ ߓߌ߬ߟߊ ߓߘߊ߫ ߛߎߘߊ߲߫߹ ߡߊ߬ߝߍ߬ߣߍ߲߬ߠߌ߲ @@ -290,14 +296,16 @@ ߖߌ߬ߦߊ߬ߓߍ ߡߍ߲ ߠߎ߬ ߦߋ߫ ߛߋߒߞߏߟߦߊ ߦߌ߬ߘߊ߬ ߟߊ߫ ߥߟߊ߫ ߟߐ߲ߠߌ߲ߦߊ߸ ߏ߬ ߟߎ߬ ߟߊߛߣߍߣߍ߲ߓߊ ߟߋ߬ ߦߋ߫ ߞߐߡߐ߲ߛ ߞߊ߲߬. ߌ ߓߘߊ߫ %1$s ߖߋ߬ߓߟߌ߬ ߓߘߍ ߛߐ߬ߘߐ߲߬. ߌ ߞߎߟߎ߲ߖߋ߫߹ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߝߌ߬ߟߊ ߘߏ߫ ߘߐ߫ ߞߋߟߋ߲߫ ߢߣߊߕߊߟߌ ߓߊߕߐ߬ߡߐ߲߫ ߞߊ߬ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߖߋ߬ߓߌ߫ - ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߥߎ߬ߛߎ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬߸ ߌ ߜߊ߲߬ߞߎ߲߬ ߕߎ߲߯ߣߌ߲߫ ߖߊ߰ߣߌ߲߫. + ߜߊ߲߬ߞߎ߲߬ߠߌ߲ ߥߎ߬ߛߎ ߓߘߊ߫ ߕߊ߬ߡߌ߲߬߸ ߌ ߜߊ߲߬ߞߎ߲߬ ߌߞߐ߫ ߖߊ߰ߣߌ߲߫. ߌ ߟߊ߫ ߢߌ߬ߣߌ߲߬ߞߊ߬ߟߌ ߟߊߖߍ߲ߛߍ߲߫ ߌ ߕߋߙߌ ߟߎ߬ ߡߊ߬߹ ߘߊߓߊ߲ߠߌ߲ ߖߋ߬ߓߟߌ߬ ߢߌߡߊ ߖߋ߬ߓߌ߬ߟߌ ߝߘߏ߬ߣߍ߲ ߊ߬ ߝߐ߫ ߘߊ߬ߣߊ߲߬ߥߟߊ߬ߖߌߦߊ ߣߌ߲߬ ߓߍ߲߬ߣߍ߲߫ ߦߋ߫ ߟߊ߬ߦߟߍ߬ߟߌ ߘߐ߫ ߝߋߎ߫؟ ߟߥߊ߬ߟߌ߬ߟߊ߲ ߟߊ߬ߖߍ߲߬ߛߍ߲߬ߠߌ߲ - ߝߌ߬ߟߌ ߦߋ߫ ߛߌ߰ߢߐ߲߰ ߘߌ߲ߞߌߙߊ ߟߊߘߏ߲߬ߕߐ ߘߐ߫. + ߡߊߖߍ߲߬ߞߍ߫ + ߛߌ߰ߢߐ߲߰ ߘߌ߲ߞߌߙߊ ߕߴߛߋ߫ ߟߊߢߎ߲߫ ߠߊ߫. + ߖߌ߬ߦߊ߬ߓߍ߫ ߕߍ߫ ߘߌ߲߬ߞߌߙߊ ߣߌ߲߬ ߞߊ߲߬ ߛߌ߰ߢߐ߲߰ ߦߙߐ߫ ߡߊ߫ ߛߐ߬ߘߐ߲߫ ߟߊ߬ߡߌߣߌ߲ ߘߐ߫ ߝߟߌ߬ ߓߘߊ߫ ߞߍ߫ ߛߌ߰ߢߐ߲߰ ߝߙߎߕߎ ߟߊߛߐ߬ߘߐ߲ ߘߐ߫. ߢߌߣߌ߲ߠߌ߲߫ ߠߊߓߊ߲ ߕߴߦߋ߲߬ diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index ab3d3a28b..ef9ed130d 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -9,7 +9,7 @@ * ਗੁਰਪ੍ਰੀਤ ਹੁੰਦਲ --> - ਕਾਮਨਜ਼ ਲੋਗੋ + ਕਾਮਨਜ਼ ਮਾਰਕਾ ਇੱਕ ਹੋਰ ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ ਨਵਾਂ ਯੋਗਦਾਨ ਸ਼ਾਮਲ ਕਰੋ ਵੇਰਵਾ @@ -38,10 +38,11 @@ ਦਿੱਖ ਆਮ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ - ਸੈਟਿੰਗ + ਪਸੰਦਾਂ ਵਰਤੋਂਕਾਰ ਨਾਂ ਲੰਘ-ਸ਼ਬਦ ਦਾਖ਼ਲ ਹੋਵੋ + ਪਾਰਸ਼ਬਦ ਭੁੱਲ ਗਏ? ਦਾਖ਼ਲਾ ਹੋ ਰਿਹਾ ਹੈ ਉਡੀਕੋ ਜੀ… ਕਿਰਪਾ ਕਰਕੇ ਉਡੀਕੋ... @@ -67,7 +68,7 @@ ਨੇੜੇ-ਤੇੜੇ ਮੇਰੇ ਅੱਪਲੋਡ ਸਾਂਝਾ ਕਰੋ - ਸਿਰਲੇਖ + ਸੁਰਖੀ (ਲੋੜੀਂਦੀ) ਵੇਰਵਾ ਦਾਖ਼ਲ ਹੋਣ ਵਿੱਚ ਅਸਮਰੱਥ - ਨੈੱਟਵਰਕ ਫੇਲ੍ਹ ਹੋਇਆ ਹੈ ਬਹੁਤ ਸਾਰੀਆਂ ਅਸਫ਼ਲ ਕੋਸ਼ਿਸ਼ਾਂ। ਥੋੜ੍ਹੀ ਦੇਰ ਬਾਅਦ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ। @@ -85,8 +86,8 @@ %1$s ਨਾਲ਼ ਮੇਲ ਖਾਂਦੀ ਕੋਈ ਸ਼੍ਰੇਣੀ ਨਹੀਂ ਲੱਭੀ ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਵਿਚ ਜ਼ਿਆਦਾ ਲੱਭਣਯੋਗ ਬਣਾਉਣ ਲਈ ਸ਼੍ਰੇਣੀਆਂ ਜੋੜੋ।\n\nਸ਼੍ਰੇਣੀਆਂ ਜੋੜਨ ਲਈ ਟਾਈਪ ਕਰਨ ਅਰੰਭ ਕਰੋ।\nਇਸ ਕਾਰਜ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰਨ ਲਈ ਇਹ ਸੁਨੇਹਾ ਥਪੇੜੋ (ਜਾਂ ਵਾਪਸੀ ਬਟਨ ਦਬਾਓ)। ਸ਼੍ਰੇਣੀਆਂ - ਸੈਟਿੰਗ - ਸਾਈਨ ਅੱਪ + ਪਸੰਦਾਂ + ਖਾਤਾ ਬਣਾਓ ਸ਼੍ਰੇਣੀ ਇਸ ਬਾਰੇ ਅਜ਼ਾਦ ਸਰੋਤ ਸਾਫ਼ਟਵੇਅਰ ਨੂੰ <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a> ਅਧੀਨ ਜਾਰੀ ਕੀਤਾ ਗਿਆ ਹੈ @@ -97,7 +98,7 @@ ਵਿਚਾਰ ਭੇਜੋ (ਈਮੇਲ ਰਾਹੀਂ) ਕੋਈ ਈਮੇਲ ਸਾਧਨ ਇੰਸਟਾਲ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹਾਲ \'ਚ ਵਰਤੀਆਂ ਗਈਆਂ ਸ਼੍ਰੇਣੀਆਂ - ਪਹਿਲੀ ਸਿੰਕ ਲਈ ਉਡੀਕ… + ਪਹਿਲੇ ਸਮਕਾਲੀਕਰਨ ਦੀ ਉਡੀਕ ਕੀਤੀ ਜਾ ਰਹੀ ਹੈ... ਤੁਸੀਂ ਹਾਲੇ ਤੱਕ ਕੋਈ ਤਸਵੀਰਾਂ ਅੱਪਲੋਡ ਨਹੀਂ ਕੀਤੀਆਂ ਮੁੜ-ਕੋਸ਼ਿਸ਼ ਕਰੋ ਰੱਦ ਕਰੋ @@ -126,6 +127,7 @@ ਇੰਟਰਨੈੱਟ ਉੱਤੇ ਮਿਲੀ ਕਾਪੀਰਾਈਟ ਸਮੱਗਰੀ ਅਤੇ ਪੋਸਟਰਾਂ, ਕਿਤਾਬਾਂ ਦੀਆਂ ਜਿਲਦਾਂ ਦੀਆਂ ਤਸਵੀਆਂ ਆਦਿ ਤੋਂ ਪਰਹੇਜ਼ ਰੱਖੋ। ਤੁਹਾਨੂੰ ਲੱਗਦਾ ਹੈ ਕਿ ਤੁਹਾਡੇ ਕੋਲ ਹੈ? ਹਾਂ! + ਹੋਰ ਜਾਣਕਾਰੀ ਸ਼੍ਰੇਣੀਆਂ ਲੱਦ ਰਿਹਾ ਹੈ... ਕੋਈ ਵੀ ਨਹੀਂ ਚੁਣਿਆ @@ -148,8 +150,8 @@ ਕੋਈ ਉਪਲਬਧ ਨਹੀਂ 2FA ਕੋਡ ਕੀ ਤੁਸੀਂ ਸੱਚੀਂ ਬੰਦ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ? - ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਸੁਆਗਤ - ਕਾਪੀਰਾਈਟ ਸੁਆਗਤ + ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਜੀ ਆਇਆਂ ਨੂੰ + ਜੀ ਆਇਆਂ ਨੂੰ ਕਾਪੀਰਾਈਟ ਰੱਦ ਕਰੋ ਖੋਲ੍ਹੋ ਬੰਦ ਕਰੋ @@ -160,7 +162,7 @@ ਸੈਟਿੰਗਾਂ ਸੁਝਾਅ ਬਾਹਰ ਆਉ - ਟਿਊਟੋਰਿਅਲ + ਸਿਖਲਾਈ ਸੂਚਨਾਵਾਂ ਪਰਖੋ ਵਿਕੀਪੀਡੀਆ ਲੇਖ @@ -177,10 +179,14 @@ ਰੱਦ ਕਰੋ ਲੱਭੋ ਲੱਭੋ + ਹਾਲੀਆ ਖੋਜਾਂ: + ਹਾਲ ਦੀਆਂ ਪੁੱਛਗਿੱਛ ਖੋਜਾਂ + ਹਾਲ ਹੀ ਵਿੱਚ ਬੋਲੀਆਂ ਬਾਰੇ ਪੁੱਛਗਿੱਛ ਸ਼੍ਰੇਣੀਆਂ ਨਕਸ਼ਾ ਸਵਾਲ ਜਾਰੀ ਰੱਖੋ + ਕੋਈ ਤਾਜ਼ਾ ਖੋਜ ਨਹੀਂ ਮਿਟਾਓ ਪ੍ਰਾਪਤੀਆਂ ਅੰਕੜੇ @@ -198,6 +204,11 @@ ਕਿਰਪਾ ਕਰਕੇ ਉਡੀਕੋ... ਉਤਾਰਾ ਕੀਤਾ ਟਿਕਾਣਾ + ਲਿਖਤ ਚੂੰਢੀ-ਤਖਤੀ \'ਤੇ ਲਾਹੀ ਗਈ ਏ। ਲਿਖਤ ਛਾਪੋ + ਵਿਕੀਕੋਡ ਦਾ ਉਤਾਰਾ ਚੂੰਢੀ-ਤਖਤੀ \'ਤੇ ਲਿਖੋ + ਮੁਹਰੈਲ + ਵਰਤੋਂਕਾਰ + ਟਿਕਾਣਾ ਨਵਿਆਈਆ ਗਿਆ ਤੁਹਾਡੇ ਦਾਖਲੇ ਦੀ ਮਿਆਦ ਪੁੱਗ ਗਈ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਦਾਖਲ ਹੋਵੋ। diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index dcd8ea284..9ea9ecb06 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -769,6 +769,8 @@ Aby skonfigurować swojego awatara rankingu, dotknij \'Ustaw jako awatar\' w menu z trzema kropkami dowolnego obrazu. Współrzędne nie są dokładnymi współrzędnymi, ale osoba, która przesłała to zdjęcie, uważa, że są wystarczająco blisko. Nie można udostępnić tego elementu + Przeczytaj jak napisać użyteczny opis + Przeczytaj jak napisać użyteczny podpis Edytuj obraz Edytuj lokalizację Lokalizacja zaktualizowana! diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 788a34a4b..bfbd64413 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -473,6 +473,7 @@ A l\'ha gnun-e notìfiche nen lesùe A l\'ha gnun-e notìfiche lesùe Partagé j\'argistr dovrand + Ch\'a contròla soa casela ëd pòsta eletrònica Vëdde lòn ch\'a l\'é stàit lesù Vëdde lòn ch\'a l\'é ancor nen ëstàit lesù A-i é staje n\'eror an selessionand le plance @@ -780,4 +781,7 @@ An atèisa Falì Impossìbil carié ij dàit dël pòst + Ës pòst a l\'ha ancor gnun-e fòto, ch\'a na pija un-a! + Ës pòst a l\'ha già dle fòto. + An camin ch\'as verìfica si cost pòst -sì a l\'ha dle fòto. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ca771ea98..54acc5273 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -530,6 +530,7 @@ У вас нет непрочитанных уведомлений Нет прочитанных уведомлений Поделиться лог-файлами + Проверьте свой почтовый ящик Просмотр прочитанного См. непрочитанные Произошла ошибка при загрузке изображений @@ -845,4 +846,7 @@ В ожидании Не удалось Не удалось загрузить данные о месте + У этого места пока нет фотографий, так что сделайте несколько! + У этого места уже есть фотография. + Сейчас проверим, есть ли у этого места фотография. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index fe70bc6b6..40e99618f 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -737,4 +737,6 @@ Отпремања На чекању Није успело + Ово место већ има слику + Проверавање да ли ово место има слику. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 31a9f0b53..54593c681 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -502,6 +502,7 @@ Okunmamış bildiriminiz yok Okundu bildiriminiz yok Günlükleri kullanarak paylaş + E-posta kutunuzu denetleyin Okunanları görüntüle Okunmayanları görüntüle Resimler seçilirken hata oluştu @@ -715,7 +716,7 @@ Yakındaki haritalar düzgün çalışmak için TELEFON DURUMUNU okumaya ihtiyaç duyuyor Kullanıcının katkıları: %s Kullanıcının başarıları: %s - Kullanıcı sayfasını görüntüle + Kullanıcı profilini görüntüle Betimlemeleri düzenle Kategorileri düzenle Gelişmiş Seçenekler @@ -795,5 +796,16 @@ Lütfen çoklu yüklemedeki tüm görsellerin aynı kategorilere ve tasvirlere sahip olduğunu unutmayın. Görseller tasvirleri ve kategorileri paylaşmıyorsa, lütfen birkaç ayrı yükleme gerçekleştirin. Çoklu yüklemelerle ilgili not + Bu öge ile ilgili bir sorunu Vikiveri\'ye bildirin + Lütfen bir yorum girin + Tartışma + \' %1$s \' öğesi hakkında bir şeyler yazın. Herkes tarafından görülebilir olacaktır. + Diğer sorun veya bilgi (lütfen aşağıda açıklayınız). Geri bildiriminiz aşağıdaki wiki sayfasına gönderilir: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a> + Tüm yüklemeleri iptal etmek istediğinizden emin misiniz? + Tüm yüklemeler iptal ediliyor... + Yüklemeler + Beklemede + Başarısız + Bu yerin zaten bir resmi var. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 224492ab7..635d71a3f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1,6 +1,7 @@