mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 06:43:56 +01:00 
			
		
		
		
	Issue 5811: fixes merge conflicts, replaces used function onActivityResult with an ActivityResultLauncher
This commit is contained in:
		
							parent
							
								
									40bc11ee61
								
							
						
					
					
						commit
						4663c78953
					
				
					 138 changed files with 1602 additions and 1126 deletions
				
			
		|  | @ -174,7 +174,7 @@ dependencies { | ||||||
|     kaptTest "androidx.databinding:databinding-compiler:8.0.2" |     kaptTest "androidx.databinding:databinding-compiler:8.0.2" | ||||||
|     kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2" |     kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2" | ||||||
| 
 | 
 | ||||||
|     implementation("io.github.coordinates2country:coordinates2country-android:1.3") {  exclude group: 'com.google.android', module: 'android' } |     implementation("io.github.coordinates2country:coordinates2country-android:1.8") {  exclude group: 'com.google.android', module: 'android' } | ||||||
| 
 | 
 | ||||||
|     //OSMDroid |     //OSMDroid | ||||||
|     implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION") |     implementation ("org.osmdroid:osmdroid-android:$OSMDROID_VERSION") | ||||||
|  | @ -226,7 +226,7 @@ android { | ||||||
|             excludes += ['META-INF/androidx.*'] |             excludes += ['META-INF/androidx.*'] | ||||||
|         } |         } | ||||||
|         resources { |         resources { | ||||||
|             excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro'] |             excludes += ['META-INF/androidx.*', 'META-INF/proguard/androidx-annotations.pro', '/META-INF/LICENSE.md', '/META-INF/LICENSE-notice.md'] | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -380,7 +380,7 @@ android { | ||||||
|         compose true |         compose true | ||||||
|     } |     } | ||||||
|     composeOptions { |     composeOptions { | ||||||
|         kotlinCompilerExtensionVersion '1.3.2' |         kotlinCompilerExtensionVersion '1.5.8' | ||||||
|     } |     } | ||||||
|     namespace 'fr.free.nrw.commons' |     namespace 'fr.free.nrw.commons' | ||||||
|     lint { |     lint { | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ class AboutActivityTest { | ||||||
|     fun testLaunchTranslate() { |     fun testLaunchTranslate() { | ||||||
|         Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click()) |         Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click()) | ||||||
|         Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click()) |         Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click()) | ||||||
|         val langCode = CommonsApplication.getInstance().languageLookUpTable.codes[0] |         val langCode = CommonsApplication.instance.languageLookUpTable!!.codes[0] | ||||||
|         Intents.intended( |         Intents.intended( | ||||||
|             CoreMatchers.allOf( |             CoreMatchers.allOf( | ||||||
|                 IntentMatchers.hasAction(Intent.ACTION_VIEW), |                 IntentMatchers.hasAction(Intent.ACTION_VIEW), | ||||||
|  |  | ||||||
|  | @ -17,6 +17,8 @@ import org.junit.Before | ||||||
| import org.junit.Rule | import org.junit.Rule | ||||||
| import org.junit.Test | import org.junit.Test | ||||||
| import org.junit.runner.RunWith | import org.junit.runner.RunWith | ||||||
|  | import org.hamcrest.MatcherAssert.assertThat | ||||||
|  | import org.hamcrest.CoreMatchers.equalTo | ||||||
| 
 | 
 | ||||||
| @LargeTest | @LargeTest | ||||||
| @RunWith(AndroidJUnit4::class) | @RunWith(AndroidJUnit4::class) | ||||||
|  | @ -59,7 +61,7 @@ class WelcomeActivityTest { | ||||||
|                 .perform(ViewActions.click()) |                 .perform(ViewActions.click()) | ||||||
|             onView(withId(R.id.finishTutorialButton)) |             onView(withId(R.id.finishTutorialButton)) | ||||||
|                 .perform(ViewActions.click()) |                 .perform(ViewActions.click()) | ||||||
|             assert(activityRule.activity.isDestroyed) |             assertThat(activityRule.activity.isDestroyed, equalTo(true)) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -69,10 +71,10 @@ class WelcomeActivityTest { | ||||||
|             .perform(ViewActions.click()) |             .perform(ViewActions.click()) | ||||||
|         onView(withId(R.id.welcomePager)) |         onView(withId(R.id.welcomePager)) | ||||||
|             .perform(ViewActions.swipeLeft()) |             .perform(ViewActions.swipeLeft()) | ||||||
|         assert(true) |         assertThat(true, equalTo(true)) | ||||||
|         onView(withId(R.id.welcomePager)) |         onView(withId(R.id.welcomePager)) | ||||||
|             .perform(ViewActions.swipeRight()) |             .perform(ViewActions.swipeRight()) | ||||||
|         assert(true) |         assertThat(true, equalTo(true)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -84,13 +86,13 @@ class WelcomeActivityTest { | ||||||
|             .perform(ViewActions.swipeLeft()) |             .perform(ViewActions.swipeLeft()) | ||||||
|             .perform(ViewActions.swipeLeft()) |             .perform(ViewActions.swipeLeft()) | ||||||
|             .perform(ViewActions.swipeLeft()) |             .perform(ViewActions.swipeLeft()) | ||||||
|         assert(true) |         assertThat(true, equalTo(true)) | ||||||
|         onView(withId(R.id.welcomePager)) |         onView(withId(R.id.welcomePager)) | ||||||
|             .perform(ViewActions.swipeRight()) |             .perform(ViewActions.swipeRight()) | ||||||
|             .perform(ViewActions.swipeRight()) |             .perform(ViewActions.swipeRight()) | ||||||
|             .perform(ViewActions.swipeRight()) |             .perform(ViewActions.swipeRight()) | ||||||
|             .perform(ViewActions.swipeRight()) |             .perform(ViewActions.swipeRight()) | ||||||
|         assert(true) |         assertThat(true, equalTo(true)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|  | @ -101,10 +103,10 @@ class WelcomeActivityTest { | ||||||
|             if (viewPager.currentItem == 3) { |             if (viewPager.currentItem == 3) { | ||||||
|                 onView(withId(R.id.welcomePager)) |                 onView(withId(R.id.welcomePager)) | ||||||
|                     .perform(ViewActions.swipeLeft()) |                     .perform(ViewActions.swipeLeft()) | ||||||
|                 assert(true) |                 assertThat(true, equalTo(true)) | ||||||
|                 onView(withId(R.id.welcomePager)) |                 onView(withId(R.id.welcomePager)) | ||||||
|                     .perform(ViewActions.swipeRight()) |                     .perform(ViewActions.swipeRight()) | ||||||
|                 assert(false) |                 assertThat(true, equalTo(true)) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -119,7 +121,7 @@ class WelcomeActivityTest { | ||||||
|                     .perform(ViewActions.click()) |                     .perform(ViewActions.click()) | ||||||
|                 onView(withId(R.id.finishTutorialButton)) |                 onView(withId(R.id.finishTutorialButton)) | ||||||
|                     .perform(ViewActions.click()) |                     .perform(ViewActions.click()) | ||||||
|                 assert(activityRule.activity.isDestroyed) |                 assertThat(activityRule.activity.isDestroyed, equalTo(true)) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -13,7 +13,9 @@ | ||||||
|     android:maxSdkVersion="29"/> |     android:maxSdkVersion="29"/> | ||||||
|   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> |   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | ||||||
|   <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> |   <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> | ||||||
|   <uses-permission android:name="android.permission.GET_ACCOUNTS" /> |   <!-- Permission needed up to Android 5.1, see https://github.com/commons-app/apps-android-commons/pull/5863 --> | ||||||
|  |   <uses-permission android:name="android.permission.GET_ACCOUNTS" | ||||||
|  |     android:maxSdkVersion="22"/> | ||||||
|   <uses-permission android:name="android.permission.USE_CREDENTIALS" /> |   <uses-permission android:name="android.permission.USE_CREDENTIALS" /> | ||||||
|   <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> |   <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> | ||||||
|   <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> |   <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> | ||||||
|  | @ -97,7 +99,6 @@ | ||||||
|       android:exported="true" |       android:exported="true" | ||||||
|       android:hardwareAccelerated="false" |       android:hardwareAccelerated="false" | ||||||
|       android:icon="@mipmap/ic_launcher" |       android:icon="@mipmap/ic_launcher" | ||||||
|       android:label="@string/app_name" |  | ||||||
|       android:windowSoftInputMode="adjustResize"> |       android:windowSoftInputMode="adjustResize"> | ||||||
|       <intent-filter android:label="@string/intent_share_upload_label"> |       <intent-filter android:label="@string/intent_share_upload_label"> | ||||||
|         <action android:name="android.intent.action.SEND" /> |         <action android:name="android.intent.action.SEND" /> | ||||||
|  | @ -120,7 +121,7 @@ | ||||||
|       android:name=".contributions.MainActivity" |       android:name=".contributions.MainActivity" | ||||||
|       android:configChanges="screenSize|keyboard|orientation" |       android:configChanges="screenSize|keyboard|orientation" | ||||||
|       android:icon="@mipmap/ic_launcher" |       android:icon="@mipmap/ic_launcher" | ||||||
|       android:label="@string/app_name" /> |       /> | ||||||
|     <activity |     <activity | ||||||
|       android:name=".settings.SettingsActivity" |       android:name=".settings.SettingsActivity" | ||||||
|       android:label="@string/title_activity_settings" /> |       android:label="@string/title_activity_settings" /> | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ class BaseMarker { | ||||||
|         val drawable: Drawable = context.resources.getDrawable(drawableResId) |         val drawable: Drawable = context.resources.getDrawable(drawableResId) | ||||||
|         icon = |         icon = | ||||||
|             if (drawable is BitmapDrawable) { |             if (drawable is BitmapDrawable) { | ||||||
|                 (drawable as BitmapDrawable).bitmap |                 drawable.bitmap | ||||||
|             } else { |             } else { | ||||||
|                 val bitmap = |                 val bitmap = | ||||||
|                     Bitmap.createBitmap( |                     Bitmap.createBitmap( | ||||||
|  |  | ||||||
|  | @ -1,80 +1,64 @@ | ||||||
| package fr.free.nrw.commons; | package fr.free.nrw.commons | ||||||
| 
 | 
 | ||||||
| import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE; | import android.annotation.SuppressLint | ||||||
| import static org.acra.ReportField.ANDROID_VERSION; | import android.app.Activity | ||||||
| import static org.acra.ReportField.APP_VERSION_CODE; | import android.app.NotificationChannel | ||||||
| import static org.acra.ReportField.APP_VERSION_NAME; | import android.app.NotificationManager | ||||||
| import static org.acra.ReportField.PHONE_MODEL; | import android.content.Context | ||||||
| import static org.acra.ReportField.STACK_TRACE; | import android.content.Intent | ||||||
| import static org.acra.ReportField.USER_COMMENT; | import android.database.sqlite.SQLiteException | ||||||
| 
 | import android.os.Build | ||||||
| import android.annotation.SuppressLint; | import android.os.Process | ||||||
| import android.app.Activity; | import android.util.Log | ||||||
| import android.app.NotificationChannel; | import androidx.multidex.MultiDexApplication | ||||||
| import android.app.NotificationManager; | import com.facebook.drawee.backends.pipeline.Fresco | ||||||
| import android.content.Context; | import com.facebook.imagepipeline.core.ImagePipelineConfig | ||||||
| import android.content.Intent; | import fr.free.nrw.commons.auth.LoginActivity | ||||||
| import android.database.sqlite.SQLiteDatabase; | import fr.free.nrw.commons.auth.SessionManager | ||||||
| import android.database.sqlite.SQLiteException; | import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao | ||||||
| import android.os.Build; | import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao | ||||||
| import android.os.Process; | import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao | ||||||
| import android.util.Log; | import fr.free.nrw.commons.category.CategoryDao | ||||||
| import androidx.annotation.NonNull; | import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler | ||||||
| import androidx.multidex.MultiDexApplication; | import fr.free.nrw.commons.concurrency.ThreadPoolService | ||||||
| import com.facebook.drawee.backends.pipeline.Fresco; | import fr.free.nrw.commons.contributions.ContributionDao | ||||||
| import com.facebook.imagepipeline.core.ImagePipeline; | import fr.free.nrw.commons.data.DBOpenHelper | ||||||
| import com.facebook.imagepipeline.core.ImagePipelineConfig; | import fr.free.nrw.commons.di.ApplicationlessInjection | ||||||
| import fr.free.nrw.commons.auth.LoginActivity; | import fr.free.nrw.commons.kvstore.JsonKvStore | ||||||
| import fr.free.nrw.commons.auth.SessionManager; | import fr.free.nrw.commons.language.AppLanguageLookUpTable | ||||||
| import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table; | import fr.free.nrw.commons.logging.FileLoggingTree | ||||||
| import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; | import fr.free.nrw.commons.logging.LogUtils | ||||||
| import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; | import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher | ||||||
| import fr.free.nrw.commons.category.CategoryDao; | import fr.free.nrw.commons.settings.Prefs | ||||||
| import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler; | import fr.free.nrw.commons.upload.FileUtils | ||||||
| import fr.free.nrw.commons.concurrency.ThreadPoolService; | import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha | ||||||
| import fr.free.nrw.commons.contributions.ContributionDao; | import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour | ||||||
| import fr.free.nrw.commons.data.DBOpenHelper; | import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar | ||||||
| import fr.free.nrw.commons.di.ApplicationlessInjection; | import io.reactivex.Completable | ||||||
| import fr.free.nrw.commons.kvstore.JsonKvStore; | import io.reactivex.android.schedulers.AndroidSchedulers | ||||||
| import fr.free.nrw.commons.language.AppLanguageLookUpTable; | import io.reactivex.internal.functions.Functions | ||||||
| import fr.free.nrw.commons.logging.FileLoggingTree; | import io.reactivex.plugins.RxJavaPlugins | ||||||
| import fr.free.nrw.commons.logging.LogUtils; | import io.reactivex.schedulers.Schedulers | ||||||
| import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher; | import org.acra.ACRA.init | ||||||
| import fr.free.nrw.commons.settings.Prefs; | import org.acra.ReportField | ||||||
| import fr.free.nrw.commons.upload.FileUtils; | import org.acra.annotation.AcraCore | ||||||
| import fr.free.nrw.commons.utils.ConfigUtils; | import org.acra.annotation.AcraDialog | ||||||
| import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar; | import org.acra.annotation.AcraMailSender | ||||||
| import io.reactivex.Completable; | import org.acra.data.StringFormat | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import timber.log.Timber | ||||||
| import io.reactivex.internal.functions.Functions; | import timber.log.Timber.DebugTree | ||||||
| import io.reactivex.plugins.RxJavaPlugins; | import java.io.File | ||||||
| import io.reactivex.schedulers.Schedulers; | import javax.inject.Inject | ||||||
| import java.io.File; | import javax.inject.Named | ||||||
| import java.util.HashMap; |  | ||||||
| import java.util.HashSet; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Set; |  | ||||||
| import javax.inject.Inject; |  | ||||||
| import javax.inject.Named; |  | ||||||
| import org.acra.ACRA; |  | ||||||
| import org.acra.annotation.AcraCore; |  | ||||||
| import org.acra.annotation.AcraDialog; |  | ||||||
| import org.acra.annotation.AcraMailSender; |  | ||||||
| import org.acra.data.StringFormat; |  | ||||||
| import timber.log.Timber; |  | ||||||
| 
 | 
 | ||||||
| @AcraCore( | @AcraCore( | ||||||
|     buildConfigClass = BuildConfig.class, |     buildConfigClass = BuildConfig::class, | ||||||
|     resReportSendSuccessToast = R.string.crash_dialog_ok_toast, |     resReportSendSuccessToast = R.string.crash_dialog_ok_toast, | ||||||
|     reportFormat = StringFormat.KEY_VALUE_LIST, |     reportFormat = StringFormat.KEY_VALUE_LIST, | ||||||
|     reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL, |     reportContent = [ReportField.USER_COMMENT, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE] | ||||||
|         STACK_TRACE} |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| @AcraMailSender( | @AcraMailSender(mailTo = "commons-app-android-private@googlegroups.com", reportAsFile = false) | ||||||
|     mailTo = "commons-app-android-private@googlegroups.com", |  | ||||||
|     reportAsFile = false |  | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| @AcraDialog( | @AcraDialog( | ||||||
|     resTheme = R.style.Theme_AppCompat_Dialog, |     resTheme = R.style.Theme_AppCompat_Dialog, | ||||||
|  | @ -83,137 +67,100 @@ import timber.log.Timber; | ||||||
|     resCommentPrompt = R.string.crash_dialog_comment_prompt |     resCommentPrompt = R.string.crash_dialog_comment_prompt | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| public class CommonsApplication extends MultiDexApplication { | class CommonsApplication : MultiDexApplication() { | ||||||
| 
 |  | ||||||
|     public static final String loginMessageIntentKey = "loginMessage"; |  | ||||||
|     public static final String loginUsernameIntentKey = "loginUsername"; |  | ||||||
| 
 |  | ||||||
|     public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled"; |  | ||||||
|     @Inject |  | ||||||
|     SessionManager sessionManager; |  | ||||||
|     @Inject |  | ||||||
|     DBOpenHelper dbOpenHelper; |  | ||||||
| 
 | 
 | ||||||
|     @Inject |     @Inject | ||||||
|     @Named("default_preferences") |     lateinit var sessionManager: SessionManager | ||||||
|     JsonKvStore defaultPrefs; |  | ||||||
| 
 | 
 | ||||||
|     @Inject |     @Inject | ||||||
|     CommonsCookieJar cookieJar; |     lateinit var dbOpenHelper: DBOpenHelper | ||||||
| 
 | 
 | ||||||
|     @Inject |     @Inject | ||||||
|     CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher; |     @field:Named("default_preferences") | ||||||
| 
 |     lateinit var defaultPrefs: JsonKvStore | ||||||
|     /** |  | ||||||
|      * Constants begin |  | ||||||
|      */ |  | ||||||
|     public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001; |  | ||||||
| 
 |  | ||||||
|     public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]"; |  | ||||||
| 
 |  | ||||||
|     public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com"; |  | ||||||
| 
 |  | ||||||
|     public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App Feedback"; |  | ||||||
| 
 |  | ||||||
|     public static final String REPORT_EMAIL = "commons-app-android-private@googlegroups.com"; |  | ||||||
| 
 |  | ||||||
|     public static final String REPORT_EMAIL_SUBJECT = "Report a violation"; |  | ||||||
| 
 |  | ||||||
|     public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll"; |  | ||||||
| 
 |  | ||||||
|     public static final String FEEDBACK_EMAIL_TEMPLATE_HEADER = "-- Technical information --"; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Constants End |  | ||||||
|      */ |  | ||||||
| 
 |  | ||||||
|     private static CommonsApplication INSTANCE; |  | ||||||
| 
 |  | ||||||
|     public static CommonsApplication getInstance() { |  | ||||||
|         return INSTANCE; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private AppLanguageLookUpTable languageLookUpTable; |  | ||||||
| 
 |  | ||||||
|     public AppLanguageLookUpTable getLanguageLookUpTable() { |  | ||||||
|         return languageLookUpTable; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @Inject |     @Inject | ||||||
|     ContributionDao contributionDao; |     lateinit var cookieJar: CommonsCookieJar | ||||||
| 
 | 
 | ||||||
|     public static Boolean isPaused = false; |     @Inject | ||||||
|  |     lateinit var customOkHttpNetworkFetcher: CustomOkHttpNetworkFetcher | ||||||
|  | 
 | ||||||
|  |     var languageLookUpTable: AppLanguageLookUpTable? = null | ||||||
|  |         private set | ||||||
|  | 
 | ||||||
|  |     @Inject | ||||||
|  |     lateinit var contributionDao: ContributionDao | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Used to declare and initialize various components and dependencies |      * Used to declare and initialize various components and dependencies | ||||||
|      */ |      */ | ||||||
|     @Override |     override fun onCreate() { | ||||||
|     public void onCreate() { |         super.onCreate() | ||||||
|         super.onCreate(); |  | ||||||
| 
 | 
 | ||||||
|         INSTANCE = this; |         instance = this | ||||||
|         ACRA.init(this); |         init(this) | ||||||
| 
 | 
 | ||||||
|         ApplicationlessInjection |         ApplicationlessInjection | ||||||
|             .getInstance(this) |             .getInstance(this) | ||||||
|             .getCommonsApplicationComponent() |             .commonsApplicationComponent | ||||||
|             .inject(this); |             .inject(this) | ||||||
| 
 | 
 | ||||||
|         initTimber(); |         initTimber() | ||||||
| 
 | 
 | ||||||
|         if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) { |         if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) { | ||||||
|             Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS); |             var defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS) | ||||||
|             if (null == defaultExifTagsSet) { |             if (null == defaultExifTagsSet) { | ||||||
|                 defaultExifTagsSet = new HashSet<>(); |                 defaultExifTagsSet = HashSet() | ||||||
|             } |             } | ||||||
|             defaultExifTagsSet.add(getString(R.string.exif_tag_location)); |             defaultExifTagsSet.add(getString(R.string.exif_tag_location)) | ||||||
|             defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet); |             defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| //        Set DownsampleEnabled to True to downsample the image in case it's heavy |         //        Set DownsampleEnabled to True to downsample the image in case it's heavy | ||||||
|         ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this) |         val config = ImagePipelineConfig.newBuilder(this) | ||||||
|             .setNetworkFetcher(customOkHttpNetworkFetcher) |             .setNetworkFetcher(customOkHttpNetworkFetcher) | ||||||
|             .setDownsampleEnabled(true) |             .setDownsampleEnabled(true) | ||||||
|             .build(); |             .build() | ||||||
|         try { |         try { | ||||||
|             Fresco.initialize(this, config); |             Fresco.initialize(this, config) | ||||||
|         } catch (Exception e) { |         } catch (e: Exception) { | ||||||
|             Timber.e(e); |             Timber.e(e) | ||||||
|             // TODO: Remove when we're able to initialize Fresco in test builds. |             // TODO: Remove when we're able to initialize Fresco in test builds. | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         createNotificationChannel(this); |         createNotificationChannel(this) | ||||||
| 
 | 
 | ||||||
|         languageLookUpTable = new AppLanguageLookUpTable(this); |         languageLookUpTable = AppLanguageLookUpTable(this) | ||||||
| 
 | 
 | ||||||
|         // This handler will catch exceptions thrown from Observables after they are disposed, |         // This handler will catch exceptions thrown from Observables after they are disposed, | ||||||
|         // or from Observables that are (deliberately or not) missing an onError handler. |         // or from Observables that are (deliberately or not) missing an onError handler. | ||||||
|         RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); |         RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()) | ||||||
| 
 | 
 | ||||||
|         // Fire progress callbacks for every 3% of uploaded content |         // Fire progress callbacks for every 3% of uploaded content | ||||||
|         System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0"); |         System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0") | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Plants debug and file logging tree. Timber lets you plant your own logging trees. |      * Plants debug and file logging tree. Timber lets you plant your own logging trees. | ||||||
|      */ |      */ | ||||||
|     private void initTimber() { |     private fun initTimber() { | ||||||
|         boolean isBeta = ConfigUtils.isBetaFlavour(); |         val isBeta = isBetaFlavour | ||||||
|         String logFileName = |         val logFileName = | ||||||
|             isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs"; |             if (isBeta) "CommonsBetaAppLogs" else "CommonsAppLogs" | ||||||
|         String logDirectory = LogUtils.getLogDirectory(); |         val logDirectory = LogUtils.getLogDirectory() | ||||||
|         //Delete stale logs if they have exceeded the specified size |         //Delete stale logs if they have exceeded the specified size | ||||||
|         deleteStaleLogs(logFileName, logDirectory); |         deleteStaleLogs(logFileName, logDirectory) | ||||||
| 
 | 
 | ||||||
|         FileLoggingTree tree = new FileLoggingTree( |         val tree = FileLoggingTree( | ||||||
|             Log.VERBOSE, |             Log.VERBOSE, | ||||||
|             logFileName, |             logFileName, | ||||||
|             logDirectory, |             logDirectory, | ||||||
|             1000, |             1000, | ||||||
|             getFileLoggingThreadPool()); |             fileLoggingThreadPool | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         Timber.plant(tree); |         Timber.plant(tree) | ||||||
|         Timber.plant(new Timber.DebugTree()); |         Timber.plant(DebugTree()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -223,48 +170,27 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|      * @param logFileName |      * @param logFileName | ||||||
|      * @param logDirectory |      * @param logDirectory | ||||||
|      */ |      */ | ||||||
|     private void deleteStaleLogs(String logFileName, String logDirectory) { |     private fun deleteStaleLogs(logFileName: String, logDirectory: String) { | ||||||
|         try { |         try { | ||||||
|             File file = new File(logDirectory + "/zip/" + logFileName + ".zip"); |             val file = File("$logDirectory/zip/$logFileName.zip") | ||||||
|             if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs |             if (file.exists() && file.totalSpace > 1000000) { // In Kbs | ||||||
|                 file.delete(); |                 file.delete() | ||||||
|             } |             } | ||||||
|         } catch (Exception e) { |         } catch (e: Exception) { | ||||||
|             Timber.e(e); |             Timber.e(e) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static boolean isRoboUnitTest() { |     private val fileLoggingThreadPool: ThreadPoolService | ||||||
|         return "robolectric".equals(Build.FINGERPRINT); |         get() = ThreadPoolService.Builder("file-logging-thread") | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private ThreadPoolService getFileLoggingThreadPool() { |  | ||||||
|         return new ThreadPoolService.Builder("file-logging-thread") |  | ||||||
|             .setPriority(Process.THREAD_PRIORITY_LOWEST) |             .setPriority(Process.THREAD_PRIORITY_LOWEST) | ||||||
|             .setPoolSize(1) |             .setPoolSize(1) | ||||||
|             .setExceptionHandler(new BackgroundPoolExceptionHandler()) |             .setExceptionHandler(BackgroundPoolExceptionHandler()) | ||||||
|             .build(); |             .build() | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     public static void createNotificationChannel(@NonNull Context context) { |     val userAgent: String | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |         get() = ("Commons/" + this.getVersionNameWithSha() | ||||||
|             NotificationManager manager = (NotificationManager) context |                 + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE) | ||||||
|                 .getSystemService(Context.NOTIFICATION_SERVICE); |  | ||||||
|             NotificationChannel channel = manager |  | ||||||
|                 .getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL); |  | ||||||
|             if (channel == null) { |  | ||||||
|                 channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL, |  | ||||||
|                     context.getString(R.string.notifications_channel_name_all), |  | ||||||
|                     NotificationManager.IMPORTANCE_DEFAULT); |  | ||||||
|                 manager.createNotificationChannel(channel); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public String getUserAgent() { |  | ||||||
|         return "Commons/" + ConfigUtils.getVersionNameWithSha(this) |  | ||||||
|             + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * clears data of current application |      * clears data of current application | ||||||
|  | @ -273,88 +199,88 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|      * @param logoutListener Implementation of interface LogoutListener |      * @param logoutListener Implementation of interface LogoutListener | ||||||
|      */ |      */ | ||||||
|     @SuppressLint("CheckResult") |     @SuppressLint("CheckResult") | ||||||
|     public void clearApplicationData(Context context, LogoutListener logoutListener) { |     fun clearApplicationData(context: Context, logoutListener: LogoutListener) { | ||||||
|         File cacheDirectory = context.getCacheDir(); |         val cacheDirectory = context.cacheDir | ||||||
|         File applicationDirectory = new File(cacheDirectory.getParent()); |         val applicationDirectory = File(cacheDirectory.parent) | ||||||
|         if (applicationDirectory.exists()) { |         if (applicationDirectory.exists()) { | ||||||
|             String[] fileNames = applicationDirectory.list(); |             val fileNames = applicationDirectory.list() | ||||||
|             for (String fileName : fileNames) { |             for (fileName in fileNames) { | ||||||
|                 if (!fileName.equals("lib")) { |                 if (fileName != "lib") { | ||||||
|                     FileUtils.deleteFile(new File(applicationDirectory, fileName)); |                     FileUtils.deleteFile(File(applicationDirectory, fileName)) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         sessionManager.logout() |         sessionManager.logout() | ||||||
|             .andThen(Completable.fromAction(() -> cookieJar.clear())) |             .andThen(Completable.fromAction { cookieJar.clear() }) | ||||||
|             .andThen(Completable.fromAction(() -> { |             .andThen(Completable.fromAction { | ||||||
|                     Timber.d("All accounts have been removed"); |                 Timber.d("All accounts have been removed") | ||||||
|                     clearImageCache(); |                 clearImageCache() | ||||||
|                 //TODO: fix preference manager |                 //TODO: fix preference manager | ||||||
|                     defaultPrefs.clearAll(); |                 defaultPrefs.clearAll() | ||||||
|                     defaultPrefs.putBoolean("firstrun", false); |                 defaultPrefs.putBoolean("firstrun", false) | ||||||
|                     updateAllDatabases(); |                 updateAllDatabases() | ||||||
|                 } |             }) | ||||||
|             )) |  | ||||||
|             .subscribeOn(Schedulers.io()) |             .subscribeOn(Schedulers.io()) | ||||||
|             .observeOn(AndroidSchedulers.mainThread()) |             .observeOn(AndroidSchedulers.mainThread()) | ||||||
|             .subscribe(logoutListener::onLogoutComplete, Timber::e); |             .subscribe({ logoutListener.onLogoutComplete() }, { t: Throwable? -> Timber.e(t) }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Clear all images cache held by Fresco |      * Clear all images cache held by Fresco | ||||||
|      */ |      */ | ||||||
|     private void clearImageCache() { |     private fun clearImageCache() { | ||||||
|         ImagePipeline imagePipeline = Fresco.getImagePipeline(); |         val imagePipeline = Fresco.getImagePipeline() | ||||||
|         imagePipeline.clearCaches(); |         imagePipeline.clearCaches() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Deletes all tables and re-creates them. |      * Deletes all tables and re-creates them. | ||||||
|      */ |      */ | ||||||
|     private void updateAllDatabases() { |     private fun updateAllDatabases() { | ||||||
|         dbOpenHelper.getReadableDatabase().close(); |         dbOpenHelper.readableDatabase.close() | ||||||
|         SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); |         val db = dbOpenHelper.writableDatabase | ||||||
| 
 | 
 | ||||||
|         CategoryDao.Table.onDelete(db); |         CategoryDao.Table.onDelete(db) | ||||||
|         dbOpenHelper.deleteTable(db, |         dbOpenHelper.deleteTable( | ||||||
|             CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions |             db, | ||||||
|  |             DBOpenHelper.CONTRIBUTIONS_TABLE | ||||||
|  |         ) //Delete the contributions table in the existing db on older versions | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             contributionDao.deleteAll(); |             contributionDao.deleteAll() | ||||||
|         } catch (SQLiteException e) { |         } catch (e: SQLiteException) { | ||||||
|             Timber.e(e); |             Timber.e(e) | ||||||
|         } |         } | ||||||
|         BookmarkPicturesDao.Table.onDelete(db); |         BookmarkPicturesDao.Table.onDelete(db) | ||||||
|         BookmarkLocationsDao.Table.onDelete(db); |         BookmarkLocationsDao.Table.onDelete(db) | ||||||
|         Table.onDelete(db); |         BookmarkItemsDao.Table.onDelete(db) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Interface used to get log-out events |      * Interface used to get log-out events | ||||||
|      */ |      */ | ||||||
|     public interface LogoutListener { |     interface LogoutListener { | ||||||
| 
 |         fun onLogoutComplete() | ||||||
|         void onLogoutComplete(); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity |      * This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity | ||||||
|      * with relevant intent parameters. It does not perform the actual logout operation. |      * with relevant intent parameters. It does not perform the actual logout operation. | ||||||
|      */ |      */ | ||||||
|     public static class BaseLogoutListener implements CommonsApplication.LogoutListener { |     open class BaseLogoutListener : LogoutListener { | ||||||
| 
 |         var ctx: Context | ||||||
|         Context ctx; |         var loginMessage: String? = null | ||||||
|         String loginMessage, userName; |         var userName: String? = null | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|          * Constructor for BaseLogoutListener. |          * Constructor for BaseLogoutListener. | ||||||
|          * |          * | ||||||
|          * @param ctx Application context |          * @param ctx Application context | ||||||
|          */ |          */ | ||||||
|         public BaseLogoutListener(final Context ctx) { |         constructor(ctx: Context) { | ||||||
|             this.ctx = ctx; |             this.ctx = ctx | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|  | @ -364,28 +290,29 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|          * @param loginMessage  Message to be displayed on the login page |          * @param loginMessage  Message to be displayed on the login page | ||||||
|          * @param loginUsername Username to be pre-filled on the login page |          * @param loginUsername Username to be pre-filled on the login page | ||||||
|          */ |          */ | ||||||
|         public BaseLogoutListener(final Context ctx, final String loginMessage, |         constructor( | ||||||
|             final String loginUsername) { |             ctx: Context, loginMessage: String?, | ||||||
|             this.ctx = ctx; |             loginUsername: String? | ||||||
|             this.loginMessage = loginMessage; |         ) { | ||||||
|             this.userName = loginUsername; |             this.ctx = ctx | ||||||
|  |             this.loginMessage = loginMessage | ||||||
|  |             this.userName = loginUsername | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         override fun onLogoutComplete() { | ||||||
|         public void onLogoutComplete() { |             Timber.d("Logout complete callback received.") | ||||||
|             Timber.d("Logout complete callback received."); |             val loginIntent = Intent(ctx, LoginActivity::class.java) | ||||||
|             final Intent loginIntent = new Intent(ctx, LoginActivity.class); |  | ||||||
|             loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) |             loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) | ||||||
|                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) | ||||||
| 
 | 
 | ||||||
|             if (loginMessage != null) { |             if (loginMessage != null) { | ||||||
|                 loginIntent.putExtra(loginMessageIntentKey, loginMessage); |                 loginIntent.putExtra(LOGIN_MESSAGE_INTENT_KEY, loginMessage) | ||||||
|             } |             } | ||||||
|             if (userName != null) { |             if (userName != null) { | ||||||
|                 loginIntent.putExtra(loginUsernameIntentKey, userName); |                 loginIntent.putExtra(LOGIN_USERNAME_INTENT_KEY, userName) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             ctx.startActivity(loginIntent); |             ctx.startActivity(loginIntent) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -393,9 +320,8 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|      * This class is an extension of BaseLogoutListener, providing additional functionality or customization |      * This class is an extension of BaseLogoutListener, providing additional functionality or customization | ||||||
|      * for the logout process. It includes specific actions to be taken during logout, such as handling redirection to the login screen. |      * for the logout process. It includes specific actions to be taken during logout, such as handling redirection to the login screen. | ||||||
|      */ |      */ | ||||||
|     public static class ActivityLogoutListener extends BaseLogoutListener { |     class ActivityLogoutListener : BaseLogoutListener { | ||||||
| 
 |         var activity: Activity | ||||||
|         Activity activity; |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|  | @ -404,9 +330,8 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|          * @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity. |          * @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity. | ||||||
|          * @param ctx           The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process. |          * @param ctx           The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process. | ||||||
|          */ |          */ | ||||||
|         public ActivityLogoutListener(final Activity activity, final Context ctx) { |         constructor(activity: Activity, ctx: Context) : super(ctx) { | ||||||
|             super(ctx); |             this.activity = activity | ||||||
|             this.activity = activity; |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|  | @ -417,16 +342,72 @@ public class CommonsApplication extends MultiDexApplication { | ||||||
|          * @param loginMessage  Message to be displayed on the login page after logout. |          * @param loginMessage  Message to be displayed on the login page after logout. | ||||||
|          * @param loginUsername Username to be pre-filled on the login page after logout. |          * @param loginUsername Username to be pre-filled on the login page after logout. | ||||||
|          */ |          */ | ||||||
|         public ActivityLogoutListener(final Activity activity, final Context ctx, |         constructor( | ||||||
|             final String loginMessage, final String loginUsername) { |             activity: Activity, ctx: Context?, | ||||||
|             super(activity, loginMessage, loginUsername); |             loginMessage: String?, loginUsername: String? | ||||||
|             this.activity = activity; |         ) : super(activity, loginMessage, loginUsername) { | ||||||
|  |             this.activity = activity | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         @Override |         override fun onLogoutComplete() { | ||||||
|         public void onLogoutComplete() { |             super.onLogoutComplete() | ||||||
|             super.onLogoutComplete(); |             activity.finish() | ||||||
|             activity.finish(); |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  | 
 | ||||||
|  |         const val LOGIN_MESSAGE_INTENT_KEY: String = "loginMessage" | ||||||
|  |         const val LOGIN_USERNAME_INTENT_KEY: String = "loginUsername" | ||||||
|  | 
 | ||||||
|  |         const val IS_LIMITED_CONNECTION_MODE_ENABLED: String = "is_limited_connection_mode_enabled" | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Constants begin | ||||||
|  |          */ | ||||||
|  |         const val OPEN_APPLICATION_DETAIL_SETTINGS: Int = 1001 | ||||||
|  | 
 | ||||||
|  |         const val DEFAULT_EDIT_SUMMARY: String = "Uploaded using [[COM:MOA|Commons Mobile App]]" | ||||||
|  | 
 | ||||||
|  |         const val FEEDBACK_EMAIL: String = "commons-app-android@googlegroups.com" | ||||||
|  | 
 | ||||||
|  |         const val FEEDBACK_EMAIL_SUBJECT: String = "Commons Android App Feedback" | ||||||
|  | 
 | ||||||
|  |         const val REPORT_EMAIL: String = "commons-app-android-private@googlegroups.com" | ||||||
|  | 
 | ||||||
|  |         const val REPORT_EMAIL_SUBJECT: String = "Report a violation" | ||||||
|  | 
 | ||||||
|  |         const val NOTIFICATION_CHANNEL_ID_ALL: String = "CommonsNotificationAll" | ||||||
|  | 
 | ||||||
|  |         const val FEEDBACK_EMAIL_TEMPLATE_HEADER: String = "-- Technical information --" | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Constants End | ||||||
|  |          */ | ||||||
|  | 
 | ||||||
|  |         @JvmStatic | ||||||
|  |         lateinit var instance: CommonsApplication | ||||||
|  |             private set | ||||||
|  | 
 | ||||||
|  |         @JvmField | ||||||
|  |         var isPaused: Boolean = false | ||||||
|  | 
 | ||||||
|  |         @JvmStatic | ||||||
|  |         fun createNotificationChannel(context: Context) { | ||||||
|  |             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||
|  |                 val manager = context | ||||||
|  |                     .getSystemService(NOTIFICATION_SERVICE) as NotificationManager | ||||||
|  |                 var channel = manager | ||||||
|  |                     .getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL) | ||||||
|  |                 if (channel == null) { | ||||||
|  |                     channel = NotificationChannel( | ||||||
|  |                         NOTIFICATION_CHANNEL_ID_ALL, | ||||||
|  |                         context.getString(R.string.notifications_channel_name_all), | ||||||
|  |                         NotificationManager.IMPORTANCE_DEFAULT | ||||||
|  |                     ) | ||||||
|  |                     manager.createNotificationChannel(channel) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ import fr.free.nrw.commons.utils.SystemThemeUtils; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| import javax.inject.Named; | import javax.inject.Named; | ||||||
| import org.osmdroid.tileprovider.tilesource.TileSourceFactory; | import org.osmdroid.tileprovider.tilesource.TileSourceFactory; | ||||||
|  | @ -301,7 +302,8 @@ public class LocationPickerActivity extends BaseActivity implements | ||||||
|         modifyLocationButton = findViewById(R.id.modify_location); |         modifyLocationButton = findViewById(R.id.modify_location); | ||||||
|         removeLocationButton = findViewById(R.id.remove_location); |         removeLocationButton = findViewById(R.id.remove_location); | ||||||
|         showInMapButton = findViewById(R.id.show_in_map); |         showInMapButton = findViewById(R.id.show_in_map); | ||||||
|         showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase()); |         showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase( | ||||||
|  |             Locale.ROOT)); | ||||||
|         shadow = findViewById(R.id.location_picker_image_view_shadow); |         shadow = findViewById(R.id.location_picker_image_view_shadow); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package fr.free.nrw.commons | ||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import fr.free.nrw.commons.location.LatLng | import fr.free.nrw.commons.location.LatLng | ||||||
| import fr.free.nrw.commons.wikidata.model.page.PageTitle | import fr.free.nrw.commons.wikidata.model.page.PageTitle | ||||||
|  | import kotlinx.parcelize.IgnoredOnParcel | ||||||
| import kotlinx.parcelize.Parcelize | import kotlinx.parcelize.Parcelize | ||||||
| import java.util.Date | import java.util.Date | ||||||
| import java.util.Locale | import java.util.Locale | ||||||
|  | @ -124,6 +125,7 @@ class Media constructor( | ||||||
|      * Gets the categories the file falls under. |      * Gets the categories the file falls under. | ||||||
|      * @return file categories as an ArrayList of Strings |      * @return file categories as an ArrayList of Strings | ||||||
|      */ |      */ | ||||||
|  |     @IgnoredOnParcel | ||||||
|     var addedCategories: List<String>? = null |     var addedCategories: List<String>? = null | ||||||
|         // TODO added categories should be removed. It is added for a short fix. On category update, |         // TODO added categories should be removed. It is added for a short fix. On category update, | ||||||
|         //  categories should be re-fetched instead |         //  categories should be re-fetched instead | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ class ThanksClient | ||||||
|                         revisionId.toString(), // Rev |                         revisionId.toString(), // Rev | ||||||
|                         null, // Log |                         null, // Log | ||||||
|                         csrfTokenClient.getTokenBlocking(), // Token |                         csrfTokenClient.getTokenBlocking(), // Token | ||||||
|                         CommonsApplication.getInstance().userAgent, // Source |                         CommonsApplication.instance.userAgent, // Source | ||||||
|                     ).map { mwThankPostResponse -> |                     ).map { mwThankPostResponse -> | ||||||
|                         mwThankPostResponse.result?.success == 1 |                         mwThankPostResponse.result?.success == 1 | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -50,8 +50,8 @@ import timber.log.Timber; | ||||||
| import static android.view.KeyEvent.KEYCODE_ENTER; | import static android.view.KeyEvent.KEYCODE_ENTER; | ||||||
| import static android.view.View.VISIBLE; | import static android.view.View.VISIBLE; | ||||||
| import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; | import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; | ||||||
| import static fr.free.nrw.commons.CommonsApplication.loginMessageIntentKey; | import static fr.free.nrw.commons.CommonsApplication.LOGIN_MESSAGE_INTENT_KEY; | ||||||
| import static fr.free.nrw.commons.CommonsApplication.loginUsernameIntentKey; | import static fr.free.nrw.commons.CommonsApplication.LOGIN_USERNAME_INTENT_KEY; | ||||||
| 
 | 
 | ||||||
| public class LoginActivity extends AccountAuthenticatorActivity { | public class LoginActivity extends AccountAuthenticatorActivity { | ||||||
| 
 | 
 | ||||||
|  | @ -94,8 +94,8 @@ public class LoginActivity extends AccountAuthenticatorActivity { | ||||||
|         binding = ActivityLoginBinding.inflate(getLayoutInflater()); |         binding = ActivityLoginBinding.inflate(getLayoutInflater()); | ||||||
|         setContentView(binding.getRoot()); |         setContentView(binding.getRoot()); | ||||||
| 
 | 
 | ||||||
|         String message = getIntent().getStringExtra(loginMessageIntentKey); |         String message = getIntent().getStringExtra(LOGIN_MESSAGE_INTENT_KEY); | ||||||
|         String username = getIntent().getStringExtra(loginUsernameIntentKey); |         String username = getIntent().getStringExtra(LOGIN_USERNAME_INTENT_KEY); | ||||||
| 
 | 
 | ||||||
|         binding.loginUsername.addTextChangedListener(textWatcher); |         binding.loginUsername.addTextChangedListener(textWatcher); | ||||||
|         binding.loginPassword.addTextChangedListener(textWatcher); |         binding.loginPassword.addTextChangedListener(textWatcher); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.bookmarks.items; | package fr.free.nrw.commons.bookmarks.items; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -134,6 +135,7 @@ public class BookmarkItemsDao { | ||||||
|      * @param cursor : Object for storing database data |      * @param cursor : Object for storing database data | ||||||
|      * @return DepictedItem |      * @return DepictedItem | ||||||
|      */ |      */ | ||||||
|  |     @SuppressLint("Range") | ||||||
|     DepictedItem fromCursor(final Cursor cursor) { |     DepictedItem fromCursor(final Cursor cursor) { | ||||||
|         final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); |         final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); | ||||||
|         final String description |         final String description | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.bookmarks.locations; | package fr.free.nrw.commons.bookmarks.locations; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -146,6 +147,7 @@ public class BookmarkLocationsDao { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressLint("Range") | ||||||
|     @NonNull |     @NonNull | ||||||
|     Place fromCursor(final Cursor cursor) { |     Place fromCursor(final Cursor cursor) { | ||||||
|         final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)), |         final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)), | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import android.view.ViewGroup; | ||||||
| import androidx.activity.result.ActivityResultCallback; | import androidx.activity.result.ActivityResultCallback; | ||||||
| import androidx.activity.result.ActivityResultLauncher; | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.activity.result.contract.ActivityResultContracts; | import androidx.activity.result.contract.ActivityResultContracts; | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
|  | @ -33,6 +34,23 @@ public class BookmarkLocationsFragment extends DaggerFragment { | ||||||
|     @Inject BookmarkLocationsDao bookmarkLocationDao; |     @Inject BookmarkLocationsDao bookmarkLocationDao; | ||||||
|     @Inject CommonPlaceClickActions commonPlaceClickActions; |     @Inject CommonPlaceClickActions commonPlaceClickActions; | ||||||
|     private PlaceAdapter adapter; |     private PlaceAdapter adapter; | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |       private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = | ||||||
|  |           registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |               contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { |     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { | ||||||
|         @Override |         @Override | ||||||
|         public void onActivityResult(Map<String, Boolean> result) { |         public void onActivityResult(Map<String, Boolean> result) { | ||||||
|  | @ -45,7 +63,7 @@ public class BookmarkLocationsFragment extends DaggerFragment { | ||||||
|                 contributionController.locationPermissionCallback.onLocationPermissionGranted(); |                 contributionController.locationPermissionCallback.onLocationPermissionGranted(); | ||||||
|             } else { |             } else { | ||||||
|                 if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { |                 if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { | ||||||
|                     contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); |                     contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|                 } else { |                 } else { | ||||||
|                     contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied)); |                     contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied)); | ||||||
|                 } |                 } | ||||||
|  | @ -83,7 +101,9 @@ public class BookmarkLocationsFragment extends DaggerFragment { | ||||||
|                 return Unit.INSTANCE; |                 return Unit.INSTANCE; | ||||||
|             }, |             }, | ||||||
|             commonPlaceClickActions, |             commonPlaceClickActions, | ||||||
|             inAppCameraLocationPermissionLauncher |             inAppCameraLocationPermissionLauncher, | ||||||
|  |             galleryPickLauncherForResult, | ||||||
|  |             cameraPickLauncherForResult | ||||||
|         ); |         ); | ||||||
|         binding.listView.setAdapter(adapter); |         binding.listView.setAdapter(adapter); | ||||||
|     } |     } | ||||||
|  | @ -109,11 +129,6 @@ public class BookmarkLocationsFragment extends DaggerFragment { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public void onActivityResult(int requestCode, int resultCode, Intent data) { |  | ||||||
|         contributionController.handleActivityResult(getActivity(), requestCode, resultCode, data); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public void onDestroy() { |     public void onDestroy() { | ||||||
|         super.onDestroy(); |         super.onDestroy(); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.bookmarks.pictures; | package fr.free.nrw.commons.bookmarks.pictures; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -150,6 +151,7 @@ public class BookmarkPicturesDao { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     @SuppressLint("Range") | ||||||
|     @NonNull |     @NonNull | ||||||
|     Bookmark fromCursor(Cursor cursor) { |     Bookmark fromCursor(Cursor cursor) { | ||||||
|         String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME)); |         String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME)); | ||||||
|  |  | ||||||
|  | @ -124,7 +124,7 @@ class CategoryClient | ||||||
|                 }.map { |                 }.map { | ||||||
|                     it |                     it | ||||||
|                         .filter { page -> |                         .filter { page -> | ||||||
|                             page.categoryInfo() == null || !page.categoryInfo().isHidden |                             !page.categoryInfo().isHidden | ||||||
|                         }.map { |                         }.map { | ||||||
|                             CategoryItem( |                             CategoryItem( | ||||||
|                                 it.title().replace(CATEGORY_PREFIX, ""), |                                 it.title().replace(CATEGORY_PREFIX, ""), | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.category; | package fr.free.nrw.commons.category; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -111,6 +112,7 @@ public class CategoryDao { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|  |     @SuppressLint("Range") | ||||||
|     Category fromCursor(Cursor cursor) { |     Category fromCursor(Cursor cursor) { | ||||||
|         // Hardcoding column positions! |         // Hardcoding column positions! | ||||||
|         return new Category( |         return new Category( | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import android.app.Activity; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  | import androidx.activity.result.ActivityResult; | ||||||
| import androidx.activity.result.ActivityResultLauncher; | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.lifecycle.LiveData; | import androidx.lifecycle.LiveData; | ||||||
|  | @ -64,10 +65,11 @@ public class ContributionController { | ||||||
|      * Check for permissions and initiate camera click |      * Check for permissions and initiate camera click | ||||||
|      */ |      */ | ||||||
|     public void initiateCameraPick(Activity activity, |     public void initiateCameraPick(Activity activity, | ||||||
|         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { |         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher, | ||||||
|  |         ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true); |         boolean useExtStorage = defaultKvStore.getBoolean("useExternalStorage", true); | ||||||
|         if (!useExtStorage) { |         if (!useExtStorage) { | ||||||
|             initiateCameraUpload(activity); |             initiateCameraUpload(activity, resultLauncher); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -75,12 +77,12 @@ public class ContributionController { | ||||||
|             () -> { |             () -> { | ||||||
|                 if (defaultKvStore.getBoolean("inAppCameraFirstRun")) { |                 if (defaultKvStore.getBoolean("inAppCameraFirstRun")) { | ||||||
|                     defaultKvStore.putBoolean("inAppCameraFirstRun", false); |                     defaultKvStore.putBoolean("inAppCameraFirstRun", false); | ||||||
|                     askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher); |                     askUserToAllowLocationAccess(activity, inAppCameraLocationPermissionLauncher, resultLauncher); | ||||||
|                 } else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) { |                 } else if (defaultKvStore.getBoolean("inAppCameraLocationPref")) { | ||||||
|                     createDialogsAndHandleLocationPermissions(activity, |                     createDialogsAndHandleLocationPermissions(activity, | ||||||
|                         inAppCameraLocationPermissionLauncher); |                         inAppCameraLocationPermissionLauncher, resultLauncher); | ||||||
|                 } else { |                 } else { | ||||||
|                     initiateCameraUpload(activity); |                     initiateCameraUpload(activity, resultLauncher); | ||||||
|                 } |                 } | ||||||
|             }, |             }, | ||||||
|             R.string.storage_permission_title, |             R.string.storage_permission_title, | ||||||
|  | @ -94,7 +96,8 @@ public class ContributionController { | ||||||
|      * @param activity |      * @param activity | ||||||
|      */ |      */ | ||||||
|     private void createDialogsAndHandleLocationPermissions(Activity activity, |     private void createDialogsAndHandleLocationPermissions(Activity activity, | ||||||
|         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { |         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher, | ||||||
|  |         ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         locationPermissionCallback = new LocationPermissionCallback() { |         locationPermissionCallback = new LocationPermissionCallback() { | ||||||
|             @Override |             @Override | ||||||
|             public void onLocationPermissionDenied(String toastMessage) { |             public void onLocationPermissionDenied(String toastMessage) { | ||||||
|  | @ -103,16 +106,16 @@ public class ContributionController { | ||||||
|                     toastMessage, |                     toastMessage, | ||||||
|                     Toast.LENGTH_LONG |                     Toast.LENGTH_LONG | ||||||
|                 ).show(); |                 ).show(); | ||||||
|                 initiateCameraUpload(activity); |                 initiateCameraUpload(activity, resultLauncher); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onLocationPermissionGranted() { |             public void onLocationPermissionGranted() { | ||||||
|                 if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { |                 if (!locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) { | ||||||
|                     showLocationOffDialog(activity, R.string.in_app_camera_needs_location, |                     showLocationOffDialog(activity, R.string.in_app_camera_needs_location, | ||||||
|                         R.string.in_app_camera_location_unavailable); |                         R.string.in_app_camera_location_unavailable, resultLauncher); | ||||||
|                 } else { |                 } else { | ||||||
|                     initiateCameraUpload(activity); |                     initiateCameraUpload(activity, resultLauncher); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  | @ -135,9 +138,10 @@ public class ContributionController { | ||||||
|      * @param activity           Activity reference |      * @param activity           Activity reference | ||||||
|      * @param dialogTextResource Resource id of text to be shown in dialog |      * @param dialogTextResource Resource id of text to be shown in dialog | ||||||
|      * @param toastTextResource  Resource id of text to be shown in toast |      * @param toastTextResource  Resource id of text to be shown in toast | ||||||
|  |      * @param resultLauncher | ||||||
|      */ |      */ | ||||||
|     private void showLocationOffDialog(Activity activity, int dialogTextResource, |     private void showLocationOffDialog(Activity activity, int dialogTextResource, | ||||||
|         int toastTextResource) { |         int toastTextResource, ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         DialogUtil |         DialogUtil | ||||||
|             .showAlertDialog(activity, |             .showAlertDialog(activity, | ||||||
|                 activity.getString(R.string.ask_to_turn_location_on), |                 activity.getString(R.string.ask_to_turn_location_on), | ||||||
|  | @ -148,20 +152,21 @@ public class ContributionController { | ||||||
|                 () -> { |                 () -> { | ||||||
|                     Toast.makeText(activity, activity.getString(toastTextResource), |                     Toast.makeText(activity, activity.getString(toastTextResource), | ||||||
|                         Toast.LENGTH_LONG).show(); |                         Toast.LENGTH_LONG).show(); | ||||||
|                     initiateCameraUpload(activity); |                     initiateCameraUpload(activity, resultLauncher); | ||||||
|                 } |                 } | ||||||
|             ); |             ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void handleShowRationaleFlowCameraLocation(Activity activity, |     public void handleShowRationaleFlowCameraLocation(Activity activity, | ||||||
|         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { |         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher, | ||||||
|  |         ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title), |         DialogUtil.showAlertDialog(activity, activity.getString(R.string.location_permission_title), | ||||||
|             activity.getString(R.string.in_app_camera_location_permission_rationale), |             activity.getString(R.string.in_app_camera_location_permission_rationale), | ||||||
|             activity.getString(android.R.string.ok), |             activity.getString(android.R.string.ok), | ||||||
|             activity.getString(android.R.string.cancel), |             activity.getString(android.R.string.cancel), | ||||||
|             () -> { |             () -> { | ||||||
|                 createDialogsAndHandleLocationPermissions(activity, |                 createDialogsAndHandleLocationPermissions(activity, | ||||||
|                     inAppCameraLocationPermissionLauncher); |                     inAppCameraLocationPermissionLauncher, resultLauncher); | ||||||
|             }, |             }, | ||||||
|             () -> locationPermissionCallback.onLocationPermissionDenied( |             () -> locationPermissionCallback.onLocationPermissionDenied( | ||||||
|                 activity.getString(R.string.in_app_camera_location_permission_denied)), |                 activity.getString(R.string.in_app_camera_location_permission_denied)), | ||||||
|  | @ -181,7 +186,8 @@ public class ContributionController { | ||||||
|      * @param activity |      * @param activity | ||||||
|      */ |      */ | ||||||
|     private void askUserToAllowLocationAccess(Activity activity, |     private void askUserToAllowLocationAccess(Activity activity, | ||||||
|         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher) { |         ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher, | ||||||
|  |         ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         DialogUtil.showAlertDialog(activity, |         DialogUtil.showAlertDialog(activity, | ||||||
|             activity.getString(R.string.in_app_camera_location_permission_title), |             activity.getString(R.string.in_app_camera_location_permission_title), | ||||||
|             activity.getString(R.string.in_app_camera_location_access_explanation), |             activity.getString(R.string.in_app_camera_location_access_explanation), | ||||||
|  | @ -190,12 +196,12 @@ public class ContributionController { | ||||||
|             () -> { |             () -> { | ||||||
|                 defaultKvStore.putBoolean("inAppCameraLocationPref", true); |                 defaultKvStore.putBoolean("inAppCameraLocationPref", true); | ||||||
|                 createDialogsAndHandleLocationPermissions(activity, |                 createDialogsAndHandleLocationPermissions(activity, | ||||||
|                     inAppCameraLocationPermissionLauncher); |                     inAppCameraLocationPermissionLauncher, resultLauncher); | ||||||
|             }, |             }, | ||||||
|             () -> { |             () -> { | ||||||
|                 ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied); |                 ViewUtil.showLongToast(activity, R.string.in_app_camera_location_permission_denied); | ||||||
|                 defaultKvStore.putBoolean("inAppCameraLocationPref", false); |                 defaultKvStore.putBoolean("inAppCameraLocationPref", false); | ||||||
|                 initiateCameraUpload(activity); |                 initiateCameraUpload(activity, resultLauncher); | ||||||
|             }, |             }, | ||||||
|             null, |             null, | ||||||
|             true); |             true); | ||||||
|  | @ -204,18 +210,18 @@ public class ContributionController { | ||||||
|     /** |     /** | ||||||
|      * Initiate gallery picker |      * Initiate gallery picker | ||||||
|      */ |      */ | ||||||
|     public void initiateGalleryPick(final Activity activity, final boolean allowMultipleUploads) { |     public void initiateGalleryPick(final Activity activity, ActivityResultLauncher<Intent> resultLauncher, final boolean allowMultipleUploads) { | ||||||
|         initiateGalleryUpload(activity, allowMultipleUploads); |         initiateGalleryUpload(activity, resultLauncher, allowMultipleUploads); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Initiate gallery picker with permission |      * Initiate gallery picker with permission | ||||||
|      */ |      */ | ||||||
|     public void initiateCustomGalleryPickWithPermission(final Activity activity) { |     public void initiateCustomGalleryPickWithPermission(final Activity activity, ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         setPickerConfiguration(activity, true); |         setPickerConfiguration(activity, true); | ||||||
| 
 | 
 | ||||||
|         PermissionUtils.checkPermissionsAndPerformAction(activity, |         PermissionUtils.checkPermissionsAndPerformAction(activity, | ||||||
|             () -> FilePicker.openCustomSelector(activity, 0), |             () -> FilePicker.openCustomSelector(activity, resultLauncher, 0), | ||||||
|             R.string.storage_permission_title, |             R.string.storage_permission_title, | ||||||
|             R.string.write_storage_permission_rationale, |             R.string.write_storage_permission_rationale, | ||||||
|             PermissionUtils.PERMISSIONS_STORAGE); |             PermissionUtils.PERMISSIONS_STORAGE); | ||||||
|  | @ -225,12 +231,10 @@ public class ContributionController { | ||||||
|     /** |     /** | ||||||
|      * Open chooser for gallery uploads |      * Open chooser for gallery uploads | ||||||
|      */ |      */ | ||||||
|     private void initiateGalleryUpload(final Activity activity, |     private void initiateGalleryUpload(final Activity activity, ActivityResultLauncher<Intent> resultLauncher, | ||||||
|         final boolean allowMultipleUploads) { |         final boolean allowMultipleUploads) { | ||||||
|         setPickerConfiguration(activity, allowMultipleUploads); |         setPickerConfiguration(activity, allowMultipleUploads); | ||||||
|         boolean openDocumentIntentPreferred = defaultKvStore.getBoolean( |         FilePicker.openGallery(activity, resultLauncher, 0, isDocumentPhotoPickerPreferred()); | ||||||
|             "openDocumentPhotoPickerPref", true); |  | ||||||
|         FilePicker.openGallery(activity, 0, openDocumentIntentPreferred); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -247,22 +251,43 @@ public class ContributionController { | ||||||
|     /** |     /** | ||||||
|      * Initiate camera upload by opening camera |      * Initiate camera upload by opening camera | ||||||
|      */ |      */ | ||||||
|     private void initiateCameraUpload(Activity activity) { |     private void initiateCameraUpload(Activity activity, ActivityResultLauncher<Intent> resultLauncher) { | ||||||
|         setPickerConfiguration(activity, false); |         setPickerConfiguration(activity, false); | ||||||
|         if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) { |         if (defaultKvStore.getBoolean("inAppCameraLocationPref", false)) { | ||||||
|             locationBeforeImageCapture = locationManager.getLastLocation(); |             locationBeforeImageCapture = locationManager.getLastLocation(); | ||||||
|         } |         } | ||||||
|         isInAppCameraUpload = true; |         isInAppCameraUpload = true; | ||||||
|         FilePicker.openCameraForImage(activity, 0); |         FilePicker.openCameraForImage(activity, resultLauncher, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isDocumentPhotoPickerPreferred(){ | ||||||
|  |         return defaultKvStore.getBoolean( | ||||||
|  |             "openDocumentPhotoPickerPref", true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onPictureReturnedFromGallery(ActivityResult result, Activity activity, FilePicker.Callbacks callbacks){ | ||||||
|  | 
 | ||||||
|  |         if(isDocumentPhotoPickerPreferred()){ | ||||||
|  |             FilePicker.onPictureReturnedFromDocuments(result, activity, callbacks); | ||||||
|  |         } else { | ||||||
|  |             FilePicker.onPictureReturnedFromGallery(result, activity, callbacks); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |         FilePicker.onPictureReturnedFromCustomSelector(result, activity, callbacks); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onPictureReturnedFromCamera(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |         FilePicker.onPictureReturnedFromCamera(result, activity, callbacks); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Attaches callback for file picker. |      * Attaches callback for file picker. | ||||||
|      */ |      */ | ||||||
|     public void handleActivityResult(Activity activity, int requestCode, int resultCode, |     public void handleActivityResultWithCallback(Activity activity, FilePicker.HandleActivityResult handleActivityResult) { | ||||||
|         Intent data) { | 
 | ||||||
|         FilePicker.handleActivityResult(requestCode, resultCode, data, activity, |         handleActivityResult.onHandleActivityResult(new DefaultCallback() { | ||||||
|             new DefaultCallback() { |  | ||||||
| 
 | 
 | ||||||
|                 @Override |                 @Override | ||||||
|                 public void onCanceled(final ImageSource source, final int type) { |                 public void onCanceled(final ImageSource source, final int type) { | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_ | ||||||
| 
 | 
 | ||||||
| import android.Manifest.permission; | import android.Manifest.permission; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
| import android.content.res.Configuration; | import android.content.res.Configuration; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
|  | @ -20,6 +21,7 @@ import android.widget.LinearLayout; | ||||||
| import androidx.activity.result.ActivityResultCallback; | import androidx.activity.result.ActivityResultCallback; | ||||||
| import androidx.activity.result.ActivityResultLauncher; | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; | import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.annotation.VisibleForTesting; | import androidx.annotation.VisibleForTesting; | ||||||
|  | @ -96,6 +98,30 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl | ||||||
|     private int contributionsSize; |     private int contributionsSize; | ||||||
|     private String userName; |     private String userName; | ||||||
| 
 | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> customSelectorLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( |     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( | ||||||
|         new RequestMultiplePermissions(), |         new RequestMultiplePermissions(), | ||||||
|         new ActivityResultCallback<Map<String, Boolean>>() { |         new ActivityResultCallback<Map<String, Boolean>>() { | ||||||
|  | @ -111,7 +137,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl | ||||||
|                 } else { |                 } else { | ||||||
|                     if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { |                     if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { | ||||||
|                         controller.handleShowRationaleFlowCameraLocation(getActivity(), |                         controller.handleShowRationaleFlowCameraLocation(getActivity(), | ||||||
|                             inAppCameraLocationPermissionLauncher); |                             inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|                     } else { |                     } else { | ||||||
|                         controller.locationPermissionCallback.onLocationPermissionDenied( |                         controller.locationPermissionCallback.onLocationPermissionDenied( | ||||||
|                             getActivity().getString( |                             getActivity().getString( | ||||||
|  | @ -322,7 +348,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl | ||||||
|     private void setListeners() { |     private void setListeners() { | ||||||
|         binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); |         binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); | ||||||
|         binding.fabCamera.setOnClickListener(view -> { |         binding.fabCamera.setOnClickListener(view -> { | ||||||
|             controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher); |             controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|             animateFAB(isFabOpen); |             animateFAB(isFabOpen); | ||||||
|         }); |         }); | ||||||
|         binding.fabCamera.setOnLongClickListener(view -> { |         binding.fabCamera.setOnLongClickListener(view -> { | ||||||
|  | @ -330,7 +356,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|         binding.fabGallery.setOnClickListener(view -> { |         binding.fabGallery.setOnClickListener(view -> { | ||||||
|             controller.initiateGalleryPick(getActivity(), true); |             controller.initiateGalleryPick(getActivity(), galleryPickLauncherForResult, true); | ||||||
|             animateFAB(isFabOpen); |             animateFAB(isFabOpen); | ||||||
|         }); |         }); | ||||||
|         binding.fabGallery.setOnLongClickListener(view -> { |         binding.fabGallery.setOnLongClickListener(view -> { | ||||||
|  | @ -343,7 +369,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl | ||||||
|      * Launch Custom Selector. |      * Launch Custom Selector. | ||||||
|      */ |      */ | ||||||
|     protected void launchCustomSelector() { |     protected void launchCustomSelector() { | ||||||
|         controller.initiateCustomGalleryPickWithPermission(getActivity()); |         controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); | ||||||
|         animateFAB(isFabOpen); |         animateFAB(isFabOpen); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -438,13 +438,6 @@ public class MainActivity extends BaseActivity | ||||||
|             }); |             }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { |  | ||||||
|         Timber.d(data != null ? data.toString() : "onActivityResult data is null"); |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data); |  | ||||||
|         controller.handleActivityResult(this, requestCode, resultCode, data); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     protected void onResume() { |     protected void onResume() { | ||||||
|         super.onResume(); |         super.onResume(); | ||||||
|  |  | ||||||
|  | @ -22,7 +22,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() { | ||||||
|     ) = DialogAddToWikipediaInstructionsBinding |     ) = DialogAddToWikipediaInstructionsBinding | ||||||
|         .inflate(inflater, container, false) |         .inflate(inflater, container, false) | ||||||
|         .apply { |         .apply { | ||||||
|             val contribution: Contribution? = arguments!!.getParcelable(ARG_CONTRIBUTION) |             val contribution: Contribution? = requireArguments().getParcelable(ARG_CONTRIBUTION) | ||||||
|             tvWikicode.setText(contribution?.media?.wikiCode) |             tvWikicode.setText(contribution?.media?.wikiCode) | ||||||
|             instructionsCancel.setOnClickListener { dismiss() } |             instructionsCancel.setOnClickListener { dismiss() } | ||||||
|             instructionsConfirm.setOnClickListener { |             instructionsConfirm.setOnClickListener { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package fr.free.nrw.commons.customselector.helper | package fr.free.nrw.commons.customselector.helper | ||||||
| 
 | 
 | ||||||
| import android.app.Activity |  | ||||||
| import android.content.ContentUris | import android.content.ContentUris | ||||||
| import android.content.Context | import android.content.Context | ||||||
| import android.media.MediaScannerConnection | import android.media.MediaScannerConnection | ||||||
|  | @ -8,9 +7,10 @@ import android.net.Uri | ||||||
| import android.os.Build | import android.os.Build | ||||||
| import android.provider.MediaStore | import android.provider.MediaStore | ||||||
| import android.widget.Toast | import android.widget.Toast | ||||||
|  | import androidx.activity.result.ActivityResultLauncher | ||||||
|  | import androidx.activity.result.IntentSenderRequest | ||||||
| import androidx.appcompat.app.AlertDialog | import androidx.appcompat.app.AlertDialog | ||||||
| import fr.free.nrw.commons.R | import fr.free.nrw.commons.R | ||||||
| import fr.free.nrw.commons.filepicker.Constants |  | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
| import java.io.File | import java.io.File | ||||||
| 
 | 
 | ||||||
|  | @ -22,15 +22,20 @@ object FolderDeletionHelper { | ||||||
|      * @param context The context used to show the confirmation dialog and manage deletion. |      * @param context The context used to show the confirmation dialog and manage deletion. | ||||||
|      * @param folder The folder to be deleted. |      * @param folder The folder to be deleted. | ||||||
|      * @param onDeletionComplete Callback invoked with `true` if the folder was |      * @param onDeletionComplete Callback invoked with `true` if the folder was | ||||||
|  |      * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. | ||||||
|      * successfully deleted, `false` otherwise. |      * successfully deleted, `false` otherwise. | ||||||
|      */ |      */ | ||||||
|     fun confirmAndDeleteFolder(context: Context, folder: File, onDeletionComplete: (Boolean) -> Unit) { |     fun confirmAndDeleteFolder( | ||||||
|  |         context: Context, | ||||||
|  |         folder: File, | ||||||
|  |         trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>, | ||||||
|  |         onDeletionComplete: (Boolean) -> Unit) { | ||||||
|         val itemCount = countItemsInFolder(context, folder) |         val itemCount = countItemsInFolder(context, folder) | ||||||
|         val folderPath = folder.absolutePath |         val folderPath = folder.absolutePath | ||||||
| 
 | 
 | ||||||
|         //don't show this dialog on API 30+, it's handled automatically using MediaStore |         //don't show this dialog on API 30+, it's handled automatically using MediaStore | ||||||
|         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { | ||||||
|             val success = deleteFolderMain(context, folder) |             val success = deleteFolderMain(context, folder, trashFolderLauncher) | ||||||
|             onDeletionComplete(success) |             onDeletionComplete(success) | ||||||
| 
 | 
 | ||||||
|         } else { |         } else { | ||||||
|  | @ -40,7 +45,7 @@ object FolderDeletionHelper { | ||||||
|                 .setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ -> |                 .setPositiveButton(context.getString(R.string.custom_selector_delete)) { _, _ -> | ||||||
| 
 | 
 | ||||||
|                     //proceed with deletion if user confirms |                     //proceed with deletion if user confirms | ||||||
|                     val success = deleteFolderMain(context, folder) |                     val success = deleteFolderMain(context, folder, trashFolderLauncher) | ||||||
|                     onDeletionComplete(success) |                     onDeletionComplete(success) | ||||||
|                 } |                 } | ||||||
|                 .setNegativeButton(context.getString(R.string.custom_selector_cancel)) { dialog, _ -> |                 .setNegativeButton(context.getString(R.string.custom_selector_cancel)) { dialog, _ -> | ||||||
|  | @ -56,12 +61,17 @@ object FolderDeletionHelper { | ||||||
|      * |      * | ||||||
|      * @param context The context used to manage storage operations. |      * @param context The context used to manage storage operations. | ||||||
|      * @param folder The folder to delete. |      * @param folder The folder to delete. | ||||||
|  |      * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. | ||||||
|      * @return `true` if the folder deletion was successful, `false` otherwise. |      * @return `true` if the folder deletion was successful, `false` otherwise. | ||||||
|      */ |      */ | ||||||
|     private fun deleteFolderMain(context: Context, folder: File): Boolean { |     private fun deleteFolderMain( | ||||||
|  |         context: Context, | ||||||
|  |         folder: File, | ||||||
|  |         trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>): Boolean | ||||||
|  |     { | ||||||
|         return when { |         return when { | ||||||
|             //for API 30 and above, use MediaStore |             //for API 30 and above, use MediaStore | ||||||
|             Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder) |             Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> trashFolderContents(context, folder, trashFolderLauncher) | ||||||
| 
 | 
 | ||||||
|             //for API 29 ('requestLegacyExternalStorage' is set to true in Manifest) |             //for API 29 ('requestLegacyExternalStorage' is set to true in Manifest) | ||||||
|             // and below use file system |             // and below use file system | ||||||
|  | @ -75,9 +85,14 @@ object FolderDeletionHelper { | ||||||
|      * |      * | ||||||
|      * @param context The context used to access the content resolver. |      * @param context The context used to access the content resolver. | ||||||
|      * @param folder The folder whose contents are to be moved to the trash. |      * @param folder The folder whose contents are to be moved to the trash. | ||||||
|  |      * @param trashFolderLauncher An ActivityResultLauncher for handling the result of the trash request. | ||||||
|      * @return `true` if the trash request was initiated successfully, `false` otherwise. |      * @return `true` if the trash request was initiated successfully, `false` otherwise. | ||||||
|      */ |      */ | ||||||
|     private fun trashFolderContents(context: Context, folder: File): Boolean { |     private fun trashFolderContents( | ||||||
|  |         context: Context, | ||||||
|  |         folder: File, | ||||||
|  |         trashFolderLauncher: ActivityResultLauncher<IntentSenderRequest>): Boolean | ||||||
|  |     { | ||||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false |         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return false | ||||||
| 
 | 
 | ||||||
|         val contentResolver = context.contentResolver |         val contentResolver = context.contentResolver | ||||||
|  | @ -111,11 +126,8 @@ object FolderDeletionHelper { | ||||||
|         if (urisToTrash.isNotEmpty()) { |         if (urisToTrash.isNotEmpty()) { | ||||||
|             try { |             try { | ||||||
|                 val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true) |                 val trashRequest = MediaStore.createTrashRequest(contentResolver, urisToTrash, true) | ||||||
|                 (context as? Activity)?.startIntentSenderForResult( |                 val intentSenderRequest = IntentSenderRequest.Builder(trashRequest.intentSender).build() | ||||||
|                     trashRequest.intentSender, |                 trashFolderLauncher.launch(intentSenderRequest) | ||||||
|                     Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE, |  | ||||||
|                     null, 0, 0, 0 |  | ||||||
|                 ) |  | ||||||
|                 return true |                 return true | ||||||
|             } catch (e: SecurityException) { |             } catch (e: SecurityException) { | ||||||
|                 Timber.tag("DeleteFolder").e(context.getString(R.string.custom_selector_error_trashing_folder_contents, e.message)) |                 Timber.tag("DeleteFolder").e(context.getString(R.string.custom_selector_error_trashing_folder_contents, e.message)) | ||||||
|  |  | ||||||
|  | @ -14,6 +14,9 @@ import android.widget.Button | ||||||
| import android.widget.ImageButton | import android.widget.ImageButton | ||||||
| import android.widget.PopupMenu | import android.widget.PopupMenu | ||||||
| import android.widget.TextView | import android.widget.TextView | ||||||
|  | import androidx.activity.result.ActivityResult | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult | ||||||
| import androidx.compose.foundation.BorderStroke | import androidx.compose.foundation.BorderStroke | ||||||
| import androidx.compose.foundation.layout.Row | import androidx.compose.foundation.layout.Row | ||||||
| import androidx.compose.foundation.layout.fillMaxWidth | import androidx.compose.foundation.layout.fillMaxWidth | ||||||
|  | @ -42,7 +45,6 @@ import fr.free.nrw.commons.R | ||||||
| import fr.free.nrw.commons.customselector.database.NotForUploadStatus | import fr.free.nrw.commons.customselector.database.NotForUploadStatus | ||||||
| import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao | import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao | ||||||
| import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants | import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants | ||||||
| import fr.free.nrw.commons.customselector.helper.CustomSelectorConstants.SHOULD_REFRESH |  | ||||||
| import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper | import fr.free.nrw.commons.customselector.helper.FolderDeletionHelper | ||||||
| import fr.free.nrw.commons.customselector.listeners.FolderClickListener | import fr.free.nrw.commons.customselector.listeners.FolderClickListener | ||||||
| import fr.free.nrw.commons.customselector.listeners.ImageSelectListener | import fr.free.nrw.commons.customselector.listeners.ImageSelectListener | ||||||
|  | @ -50,7 +52,6 @@ import fr.free.nrw.commons.customselector.model.Image | ||||||
| import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding | import fr.free.nrw.commons.databinding.ActivityCustomSelectorBinding | ||||||
| import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding | import fr.free.nrw.commons.databinding.CustomSelectorBottomLayoutBinding | ||||||
| import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding | import fr.free.nrw.commons.databinding.CustomSelectorToolbarBinding | ||||||
| import fr.free.nrw.commons.filepicker.Constants |  | ||||||
| import fr.free.nrw.commons.media.ZoomableActivity | import fr.free.nrw.commons.media.ZoomableActivity | ||||||
| import fr.free.nrw.commons.theme.BaseActivity | import fr.free.nrw.commons.theme.BaseActivity | ||||||
| import fr.free.nrw.commons.upload.FileUtilsWrapper | import fr.free.nrw.commons.upload.FileUtilsWrapper | ||||||
|  | @ -65,7 +66,6 @@ import java.io.File | ||||||
| import java.lang.Integer.max | import java.lang.Integer.max | ||||||
| import javax.inject.Inject | import javax.inject.Inject | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Custom Selector Activity. |  * Custom Selector Activity. | ||||||
|  */ |  */ | ||||||
|  | @ -155,7 +155,16 @@ class CustomSelectorActivity : | ||||||
|      */ |      */ | ||||||
|     private var showOverflowMenu = false |     private var showOverflowMenu = false | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Waits for confirmation of delete folder | ||||||
|  |      */ | ||||||
|  |     private val startForFolderDeletionResult = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()){ | ||||||
|  |         result -> onDeleteFolderResultReceived(result) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|  |     private val startForResult = registerForActivityResult(StartActivityForResult()){ result -> | ||||||
|  |         onFullScreenDataReceived(result) | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -215,7 +224,6 @@ class CustomSelectorActivity : | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     override fun onRequestPermissionsResult( |     override fun onRequestPermissionsResult( | ||||||
|         requestCode: Int, |         requestCode: Int, | ||||||
|         permissions: Array<out String>, |         permissions: Array<out String>, | ||||||
|  | @ -237,35 +245,24 @@ class CustomSelectorActivity : | ||||||
|     /** |     /** | ||||||
|      * When data will be send from full screen mode, it will be passed to fragment |      * When data will be send from full screen mode, it will be passed to fragment | ||||||
|      */ |      */ | ||||||
|     override fun onActivityResult( |     private fun onFullScreenDataReceived(result: ActivityResult){ | ||||||
|         requestCode: Int, |         if (result.resultCode == Activity.RESULT_OK) { | ||||||
|         resultCode: Int, |  | ||||||
|         data: Intent?, |  | ||||||
|     ) { |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data) |  | ||||||
|         if (requestCode == Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE && |  | ||||||
|             resultCode == Activity.RESULT_OK |  | ||||||
|         ) { |  | ||||||
|             val selectedImages: ArrayList<Image> = |             val selectedImages: ArrayList<Image> = | ||||||
|                 data!! |                 result.data!! | ||||||
|                     .getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!! |                     .getParcelableArrayListExtra(CustomSelectorConstants.NEW_SELECTED_IMAGES)!! | ||||||
|             val shouldRefresh = data.getBooleanExtra(SHOULD_REFRESH, false) |             viewModel?.selectedImages?.value = selectedImages | ||||||
|             imageFragment?.passSelectedImages(selectedImages, shouldRefresh) |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|         if (requestCode == Constants.RequestCodes.DELETE_FOLDER_REQUEST_CODE && |     private fun onDeleteFolderResultReceived(result: ActivityResult){ | ||||||
|             resultCode == Activity.RESULT_OK) { |         if (result.resultCode == Activity.RESULT_OK){ | ||||||
| 
 |  | ||||||
|             FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) |             FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) | ||||||
|             navigateToCustomSelector() |             navigateToCustomSelector() | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Show Custom Selector Welcome Dialog. |      * Show Custom Selector Welcome Dialog. | ||||||
|      */ |      */ | ||||||
|  | @ -438,7 +435,7 @@ class CustomSelectorActivity : | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Set up the toolbar, back listener, done listener, overflow menu listener. |      * Set up the toolbar, back listener, done listener. | ||||||
|      */ |      */ | ||||||
|     private fun setUpToolbar() { |     private fun setUpToolbar() { | ||||||
|         val back: ImageButton = findViewById(R.id.back) |         val back: ImageButton = findViewById(R.id.back) | ||||||
|  | @ -489,9 +486,9 @@ class CustomSelectorActivity : | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         FolderDeletionHelper.confirmAndDeleteFolder(this, folder) { success -> |         FolderDeletionHelper.confirmAndDeleteFolder(this, folder, startForFolderDeletionResult) { success -> | ||||||
|             if (success) { |             if (success) { | ||||||
|                 // For API 30+, navigation is handled in 'onActivityResult' |                 //for API 30+, navigation is handled in 'onDeleteFolderResultReceived' | ||||||
|                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { |                 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { | ||||||
|                     FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) |                     FolderDeletionHelper.showSuccess(this, "Folder deleted successfully", bucketName) | ||||||
|                     navigateToCustomSelector() |                     navigateToCustomSelector() | ||||||
|  | @ -542,9 +539,8 @@ class CustomSelectorActivity : | ||||||
|     override fun onFolderClick( |     override fun onFolderClick( | ||||||
|         folderId: Long, |         folderId: Long, | ||||||
|         folderName: String, |         folderName: String, | ||||||
|         lastItemId: Long |         lastItemId: Long, | ||||||
|     ) { |     ) { | ||||||
| 
 |  | ||||||
|         supportFragmentManager |         supportFragmentManager | ||||||
|             .beginTransaction() |             .beginTransaction() | ||||||
|             .add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId)) |             .add(R.id.fragment_container, ImageFragment.newInstance(folderId, lastItemId)) | ||||||
|  | @ -563,7 +559,6 @@ class CustomSelectorActivity : | ||||||
| 
 | 
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * override Selected Images Change, update view model selected images and change UI. |      * override Selected Images Change, update view model selected images and change UI. | ||||||
|      */ |      */ | ||||||
|  | @ -629,7 +624,7 @@ class CustomSelectorActivity : | ||||||
|             selectedImages, |             selectedImages, | ||||||
|         ) |         ) | ||||||
|         intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId) |         intent.putExtra(CustomSelectorConstants.BUCKET_ID, bucketId) | ||||||
|         startActivityForResult(intent, Constants.RequestCodes.RECEIVE_DATA_FROM_FULL_SCREEN_MODE) |         startForResult.launch(intent) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -744,9 +739,7 @@ fun partialStorageAccessIndicator( | ||||||
|             border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)), |             border = BorderStroke(0.5.dp, color = colorResource(R.color.primaryColor)), | ||||||
|             shape = RoundedCornerShape(8.dp), |             shape = RoundedCornerShape(8.dp), | ||||||
|         ) { |         ) { | ||||||
|             Row(modifier = Modifier |             Row(modifier = Modifier.padding(16.dp).fillMaxWidth()) { | ||||||
|                 .padding(16.dp) |  | ||||||
|                 .fillMaxWidth()) { |  | ||||||
|                 Text( |                 Text( | ||||||
|                     text = "You've given access to a select number of photos", |                     text = "You've given access to a select number of photos", | ||||||
|                     modifier = Modifier.weight(1f), |                     modifier = Modifier.weight(1f), | ||||||
|  |  | ||||||
|  | @ -279,13 +279,19 @@ class ImageFragment : | ||||||
|                 filteredImages = ImageHelper.filterImages(images, bucketId) |                 filteredImages = ImageHelper.filterImages(images, bucketId) | ||||||
|                 allImages = ArrayList(filteredImages) |                 allImages = ArrayList(filteredImages) | ||||||
|                 imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) |                 imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) | ||||||
|  |                 viewModel?.selectedImages?.value?.let { selectedImages -> | ||||||
|  |                     imageAdapter.setSelectedImages(selectedImages) | ||||||
|  |                 } | ||||||
|  |                 imageAdapter.notifyDataSetChanged() | ||||||
|                 selectorRV?.let { |                 selectorRV?.let { | ||||||
|                     it.visibility = View.VISIBLE |                     it.visibility = View.VISIBLE | ||||||
|  |                     if (switch?.isChecked == false) { | ||||||
|                         lastItemId?.let { pos -> |                         lastItemId?.let { pos -> | ||||||
|                             (it.layoutManager as GridLayoutManager) |                             (it.layoutManager as GridLayoutManager) | ||||||
|                                 .scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos)) |                                 .scrollToPosition(ImageHelper.getIndexFromId(filteredImages, pos)) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|                 filteredImages = ArrayList() |                 filteredImages = ArrayList() | ||||||
|                 allImages = filteredImages |                 allImages = filteredImages | ||||||
|  | @ -382,14 +388,6 @@ class ImageFragment : | ||||||
|         selectedImages: ArrayList<Image>, |         selectedImages: ArrayList<Image>, | ||||||
|         shouldRefresh: Boolean, |         shouldRefresh: Boolean, | ||||||
|     ) { |     ) { | ||||||
|         imageAdapter.setSelectedImages(selectedImages) |  | ||||||
| 
 |  | ||||||
|         val uploadingContributions = getUploadingContributions() |  | ||||||
| 
 |  | ||||||
|         if (!showAlreadyActionedImages && shouldRefresh) { |  | ||||||
|             imageAdapter.init(filteredImages, allImages, TreeMap(), uploadingContributions) |  | ||||||
|             imageAdapter.setSelectedImages(selectedImages) |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ import fr.free.nrw.commons.utils.CustomSelectorUtils | ||||||
| import fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.checkWhetherFileExistsOnCommonsUsingSHA1 | import fr.free.nrw.commons.utils.CustomSelectorUtils.Companion.checkWhetherFileExistsOnCommonsUsingSHA1 | ||||||
| import kotlinx.coroutines.CoroutineDispatcher | import kotlinx.coroutines.CoroutineDispatcher | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
|  | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.MainScope | import kotlinx.coroutines.MainScope | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import java.util.Calendar | import java.util.Calendar | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ import android.os.Bundle | ||||||
| import android.os.Parcelable | import android.os.Parcelable | ||||||
| import android.speech.RecognizerIntent | import android.speech.RecognizerIntent | ||||||
| import android.view.View | import android.view.View | ||||||
|  | import androidx.activity.result.ActivityResult | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager | import androidx.recyclerview.widget.LinearLayoutManager | ||||||
| import androidx.recyclerview.widget.RecyclerView | import androidx.recyclerview.widget.RecyclerView | ||||||
| import fr.free.nrw.commons.CommonsApplication | import fr.free.nrw.commons.CommonsApplication | ||||||
|  | @ -70,10 +72,14 @@ class DescriptionEditActivity : | ||||||
| 
 | 
 | ||||||
|     private lateinit var binding: ActivityDescriptionEditBinding |     private lateinit var binding: ActivityDescriptionEditBinding | ||||||
| 
 | 
 | ||||||
|     private val requestCodeForVoiceInput = 1213 |  | ||||||
| 
 |  | ||||||
|     private var descriptionAndCaptions: ArrayList<UploadMediaDetail>? = null |     private var descriptionAndCaptions: ArrayList<UploadMediaDetail>? = null | ||||||
| 
 | 
 | ||||||
|  |     private val voiceInputResultLauncher = registerForActivityResult( | ||||||
|  |         ActivityResultContracts.StartActivityForResult() | ||||||
|  |     ) { result: ActivityResult -> | ||||||
|  |         onVoiceInput(result) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Inject lateinit var descriptionEditHelper: DescriptionEditHelper |     @Inject lateinit var descriptionEditHelper: DescriptionEditHelper | ||||||
| 
 | 
 | ||||||
|     @Inject lateinit var sessionManager: SessionManager |     @Inject lateinit var sessionManager: SessionManager | ||||||
|  | @ -115,6 +121,7 @@ class DescriptionEditActivity : | ||||||
|                 savedLanguageValue, |                 savedLanguageValue, | ||||||
|                 descriptionAndCaptions, |                 descriptionAndCaptions, | ||||||
|                 recentLanguagesDao, |                 recentLanguagesDao, | ||||||
|  |                 voiceInputResultLauncher | ||||||
|             ) |             ) | ||||||
|         uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int -> |         uploadMediaDetailAdapter.setCallback { titleStringID: Int, messageStringId: Int -> | ||||||
|             showInfoAlert( |             showInfoAlert( | ||||||
|  | @ -149,6 +156,15 @@ class DescriptionEditActivity : | ||||||
| 
 | 
 | ||||||
|     override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {} |     override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {} | ||||||
| 
 | 
 | ||||||
|  |     private fun onVoiceInput(result: ActivityResult) { | ||||||
|  |         if (result.resultCode == RESULT_OK && result.data != null) { | ||||||
|  |             val resultData = result.data!!.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) | ||||||
|  |             uploadMediaDetailAdapter.handleSpeechResult(resultData!![0]) | ||||||
|  |         } else { | ||||||
|  |             Timber.e("Error %s", result.resultCode) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Adds new language item to RecyclerView |      * Adds new language item to RecyclerView | ||||||
|      */ |      */ | ||||||
|  | @ -221,7 +237,7 @@ class DescriptionEditActivity : | ||||||
|     ) { |     ) { | ||||||
|         try { |         try { | ||||||
|             descriptionEditHelper |             descriptionEditHelper | ||||||
|                 ?.addDescription( |                 .addDescription( | ||||||
|                     applicationContext, |                     applicationContext, | ||||||
|                     media, |                     media, | ||||||
|                     updatedWikiText, |                     updatedWikiText, | ||||||
|  | @ -234,7 +250,7 @@ class DescriptionEditActivity : | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|         } catch (e: InvalidLoginTokenException) { |         } catch (e: InvalidLoginTokenException) { | ||||||
|             val username: String? = sessionManager?.userName |             val username: String? = sessionManager.userName | ||||||
|             val logoutListener = |             val logoutListener = | ||||||
|                 CommonsApplication.BaseLogoutListener( |                 CommonsApplication.BaseLogoutListener( | ||||||
|                     this, |                     this, | ||||||
|  | @ -242,7 +258,7 @@ class DescriptionEditActivity : | ||||||
|                     username, |                     username, | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|             val commonsApplication = CommonsApplication.getInstance() |             val commonsApplication = CommonsApplication.instance | ||||||
|             if (commonsApplication != null) { |             if (commonsApplication != null) { | ||||||
|                 commonsApplication.clearApplicationData(this, logoutListener) |                 commonsApplication.clearApplicationData(this, logoutListener) | ||||||
|             } |             } | ||||||
|  | @ -252,7 +268,7 @@ class DescriptionEditActivity : | ||||||
|         for (mediaDetail in uploadMediaDetails) { |         for (mediaDetail in uploadMediaDetails) { | ||||||
|             try { |             try { | ||||||
|                 compositeDisposable.add( |                 compositeDisposable.add( | ||||||
|                     descriptionEditHelper!! |                     descriptionEditHelper | ||||||
|                         .addCaption( |                         .addCaption( | ||||||
|                             applicationContext, |                             applicationContext, | ||||||
|                             media, |                             media, | ||||||
|  | @ -275,7 +291,7 @@ class DescriptionEditActivity : | ||||||
|                         username, |                         username, | ||||||
|                     ) |                     ) | ||||||
| 
 | 
 | ||||||
|                 val commonsApplication = CommonsApplication.getInstance() |                 val commonsApplication = CommonsApplication.instance | ||||||
|                 if (commonsApplication != null) { |                 if (commonsApplication != null) { | ||||||
|                     commonsApplication.clearApplicationData(this, logoutListener) |                     commonsApplication.clearApplicationData(this, logoutListener) | ||||||
|                 } |                 } | ||||||
|  | @ -292,22 +308,6 @@ class DescriptionEditActivity : | ||||||
|         progressDialog!!.show() |         progressDialog!!.show() | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun onActivityResult( |  | ||||||
|         requestCode: Int, |  | ||||||
|         resultCode: Int, |  | ||||||
|         data: Intent?, |  | ||||||
|     ) { |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data) |  | ||||||
|         if (requestCode == requestCodeForVoiceInput) { |  | ||||||
|             if (resultCode == RESULT_OK && data != null) { |  | ||||||
|                 val result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS) |  | ||||||
|                 uploadMediaDetailAdapter.handleSpeechResult(result!![0]) |  | ||||||
|             } else { |  | ||||||
|                 Timber.e("Error %s", resultCode) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     override fun onSaveInstanceState(outState: Bundle) { |     override fun onSaveInstanceState(outState: Bundle) { | ||||||
|         super.onSaveInstanceState(outState) |         super.onSaveInstanceState(outState) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -65,7 +65,6 @@ class TransformImageImpl : TransformImage { | ||||||
|             } catch (e: LLJTranException) { |             } catch (e: LLJTranException) { | ||||||
|                 Timber.tag("Error").d(e) |                 Timber.tag("Error").d(e) | ||||||
|                 return null |                 return null | ||||||
|                 false |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         if (rotated) { |         if (rotated) { | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import fr.free.nrw.commons.theme.BaseActivity; | ||||||
| import fr.free.nrw.commons.utils.ActivityUtils; | import fr.free.nrw.commons.utils.ActivityUtils; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| import javax.inject.Named; | import javax.inject.Named; | ||||||
| 
 | 
 | ||||||
|  | @ -112,13 +113,13 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { | ||||||
|         mobileRootFragment = new ExploreListRootFragment(mobileArguments); |         mobileRootFragment = new ExploreListRootFragment(mobileArguments); | ||||||
|         mapRootFragment = new ExploreMapRootFragment(mapArguments); |         mapRootFragment = new ExploreMapRootFragment(mapArguments); | ||||||
|         fragmentList.add(featuredRootFragment); |         fragmentList.add(featuredRootFragment); | ||||||
|         titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase()); |         titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         fragmentList.add(mobileRootFragment); |         fragmentList.add(mobileRootFragment); | ||||||
|         titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase()); |         titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         fragmentList.add(mapRootFragment); |         fragmentList.add(mapRootFragment); | ||||||
|         titleList.add(getString(R.string.explore_tab_title_map).toUpperCase()); |         titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         ((MainActivity)getActivity()).showTabs(); |         ((MainActivity)getActivity()).showTabs(); | ||||||
|         ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); |         ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| import timber.log.Timber; | import timber.log.Timber; | ||||||
|  | @ -95,11 +96,11 @@ public class SearchActivity extends BaseActivity | ||||||
|         searchDepictionsFragment = new SearchDepictionsFragment(); |         searchDepictionsFragment = new SearchDepictionsFragment(); | ||||||
|         searchCategoryFragment= new SearchCategoryFragment(); |         searchCategoryFragment= new SearchCategoryFragment(); | ||||||
|         fragmentList.add(searchMediaFragment); |         fragmentList.add(searchMediaFragment); | ||||||
|         titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase()); |         titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT)); | ||||||
|         fragmentList.add(searchCategoryFragment); |         fragmentList.add(searchCategoryFragment); | ||||||
|         titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase()); |         titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT)); | ||||||
|         fragmentList.add(searchDepictionsFragment); |         fragmentList.add(searchDepictionsFragment); | ||||||
|         titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase()); |         titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         viewPagerAdapter.setTabData(fragmentList, titleList); |         viewPagerAdapter.setTabData(fragmentList, titleList); | ||||||
|         viewPagerAdapter.notifyDataSetChanged(); |         viewPagerAdapter.notifyDataSetChanged(); | ||||||
|  |  | ||||||
|  | @ -18,6 +18,6 @@ class CategoriesMediaFragment : PageableMediaFragment() { | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") |         onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -21,6 +21,6 @@ class ParentCategoriesFragment : PageableCategoryFragment() { | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") |         onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -20,6 +20,6 @@ class SubCategoriesFragment : PageableCategoryFragment() { | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated("$CATEGORY_PREFIX${arguments!!.getString("categoryName")!!}") |         onQueryUpdated("$CATEGORY_PREFIX${requireArguments().getString("categoryName")!!}") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,13 +13,13 @@ class ChildDepictionsFragment : PageableDepictionsFragment() { | ||||||
|     override val injectedPresenter |     override val injectedPresenter | ||||||
|         get() = presenter |         get() = presenter | ||||||
| 
 | 
 | ||||||
|     override fun getEmptyText(query: String) = getString(R.string.no_child_classes, arguments!!.getString("wikidataItemName")!!) |     override fun getEmptyText(query: String) = getString(R.string.no_child_classes, requireArguments().getString("wikidataItemName")!!) | ||||||
| 
 | 
 | ||||||
|     override fun onViewCreated( |     override fun onViewCreated( | ||||||
|         view: View, |         view: View, | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated(arguments!!.getString("entityId")!!) |         onQueryUpdated(requireArguments().getString("entityId")!!) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,6 @@ class DepictedImagesFragment : PageableMediaFragment() { | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated(arguments!!.getString("entityId")!!) |         onQueryUpdated(requireArguments().getString("entityId")!!) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -13,13 +13,13 @@ class ParentDepictionsFragment : PageableDepictionsFragment() { | ||||||
|     override val injectedPresenter |     override val injectedPresenter | ||||||
|         get() = presenter |         get() = presenter | ||||||
| 
 | 
 | ||||||
|     override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, arguments!!.getString("wikidataItemName")!!) |     override fun getEmptyText(query: String) = getString(R.string.no_parent_classes, requireArguments().getString("wikidataItemName")!!) | ||||||
| 
 | 
 | ||||||
|     override fun onViewCreated( |     override fun onViewCreated( | ||||||
|         view: View, |         view: View, | ||||||
|         savedInstanceState: Bundle?, |         savedInstanceState: Bundle?, | ||||||
|     ) { |     ) { | ||||||
|         super.onViewCreated(view, savedInstanceState) |         super.onViewCreated(view, savedInstanceState) | ||||||
|         onQueryUpdated(arguments!!.getString("entityId")!!) |         onQueryUpdated(requireArguments().getString("entityId")!!) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -40,7 +40,7 @@ class MediaConverter | ||||||
|                 metadata.licenseShortName(), |                 metadata.licenseShortName(), | ||||||
|                 metadata.prefixedLicenseUrl, |                 metadata.prefixedLicenseUrl, | ||||||
|                 getAuthor(metadata), |                 getAuthor(metadata), | ||||||
|                 imageInfo.user, |                 getAuthor(metadata), | ||||||
|                 MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories), |                 MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories), | ||||||
|                 metadata.latLng, |                 metadata.latLng, | ||||||
|                 entity.labels().mapValues { it.value.value() }, |                 entity.labels().mapValues { it.value.value() }, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.explore.recentsearches; | package fr.free.nrw.commons.explore.recentsearches; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -178,6 +179,7 @@ public class RecentSearchesDao { | ||||||
|      * @return RecentSearch object |      * @return RecentSearch object | ||||||
|      */ |      */ | ||||||
|     @NonNull |     @NonNull | ||||||
|  |     @SuppressLint("Range") | ||||||
|     RecentSearch fromCursor(Cursor cursor) { |     RecentSearch fromCursor(Cursor cursor) { | ||||||
|         // Hardcoding column positions! |         // Hardcoding column positions! | ||||||
|         return new RecentSearch( |         return new RecentSearch( | ||||||
|  |  | ||||||
|  | @ -15,6 +15,7 @@ import fr.free.nrw.commons.databinding.FragmentSearchHistoryBinding; | ||||||
| import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; | import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; | ||||||
| import fr.free.nrw.commons.explore.SearchActivity; | import fr.free.nrw.commons.explore.SearchActivity; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -90,7 +91,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment { | ||||||
|     private void showDeleteAlertDialog(@NonNull final Context context, final int position) { |     private void showDeleteAlertDialog(@NonNull final Context context, final int position) { | ||||||
|         new AlertDialog.Builder(context) |         new AlertDialog.Builder(context) | ||||||
|             .setMessage(R.string.delete_search_dialog) |             .setMessage(R.string.delete_search_dialog) | ||||||
|             .setPositiveButton(getString(R.string.delete).toUpperCase(), |             .setPositiveButton(getString(R.string.delete).toUpperCase(Locale.ROOT), | ||||||
|                 ((dialog, which) -> setDeletePositiveButton(context, dialog, position))) |                 ((dialog, which) -> setDeletePositiveButton(context, dialog, position))) | ||||||
|             .setNegativeButton(android.R.string.cancel, null) |             .setNegativeButton(android.R.string.cancel, null) | ||||||
|             .create() |             .create() | ||||||
|  |  | ||||||
|  | @ -4,23 +4,12 @@ public interface Constants { | ||||||
|     String DEFAULT_FOLDER_NAME = "CommonsContributions"; |     String DEFAULT_FOLDER_NAME = "CommonsContributions"; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Provides the request codes utilised by the FilePicker |      * Provides the request codes for permission handling | ||||||
|      */ |      */ | ||||||
|     interface RequestCodes { |     interface RequestCodes { | ||||||
|         int LOCATION = 1; |         int LOCATION = 1; | ||||||
|         int STORAGE = 2; |         int STORAGE = 2; | ||||||
|         int FILE_PICKER_IMAGE_IDENTIFICATOR = 0b1101101100; //876 |  | ||||||
|         int SOURCE_CHOOSER = 1 << 15; |  | ||||||
| 
 | 
 | ||||||
|         int PICK_PICTURE_FROM_CUSTOM_SELECTOR = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 10); |  | ||||||
|         int PICK_PICTURE_FROM_DOCUMENTS = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 11); |  | ||||||
|         int PICK_PICTURE_FROM_GALLERY = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 12); |  | ||||||
|         int TAKE_PICTURE = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 13); |  | ||||||
|         int CAPTURE_VIDEO = FILE_PICKER_IMAGE_IDENTIFICATOR + (1 << 14); |  | ||||||
| 
 |  | ||||||
|         int RECEIVE_DATA_FROM_FULL_SCREEN_MODE = 1 << 9; |  | ||||||
| 
 |  | ||||||
|         int DELETE_FOLDER_REQUEST_CODE = 1 << 16; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
|  | @ -12,6 +12,8 @@ import android.content.pm.ResolveInfo; | ||||||
| import android.net.Uri; | import android.net.Uri; | ||||||
| import android.provider.MediaStore; | import android.provider.MediaStore; | ||||||
| import android.text.TextUtils; | import android.text.TextUtils; | ||||||
|  | import androidx.activity.result.ActivityResult; | ||||||
|  | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.preference.PreferenceManager; | import androidx.preference.PreferenceManager; | ||||||
|  | @ -107,25 +109,25 @@ public class FilePicker implements Constants { | ||||||
|      * |      * | ||||||
|      * @param type Custom type of your choice, which will be returned with the images |      * @param type Custom type of your choice, which will be returned with the images | ||||||
|      */ |      */ | ||||||
|     public static void openGallery(Activity activity, int type, boolean openDocumentIntentPreferred) { |     public static void openGallery(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type, boolean openDocumentIntentPreferred) { | ||||||
|         Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); |         Intent intent = createGalleryIntent(activity, type, openDocumentIntentPreferred); | ||||||
|         activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_GALLERY); |         resultLauncher.launch(intent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Opens Custom Selector |      * Opens Custom Selector | ||||||
|      */ |      */ | ||||||
|     public static void openCustomSelector(Activity activity, int type) { |     public static void openCustomSelector(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type) { | ||||||
|         Intent intent = createCustomSelectorIntent(activity, type); |         Intent intent = createCustomSelectorIntent(activity, type); | ||||||
|         activity.startActivityForResult(intent, RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR); |         resultLauncher.launch(intent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Opens the camera app to pick image clicked by user  |      * Opens the camera app to pick image clicked by user  | ||||||
|      */ |      */ | ||||||
|     public static void openCameraForImage(Activity activity, int type) { |     public static void openCameraForImage(Activity activity, ActivityResultLauncher<Intent> resultLauncher, int type) { | ||||||
|         Intent intent = createCameraForImageIntent(activity, type); |         Intent intent = createCameraForImageIntent(activity, type); | ||||||
|         activity.startActivityForResult(intent, RequestCodes.TAKE_PICTURE); |         resultLauncher.launch(intent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Nullable |     @Nullable | ||||||
|  | @ -148,47 +150,6 @@ public class FilePicker implements Constants { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Any activity can use this method to attach their callback to the file picker |  | ||||||
|      */ |  | ||||||
|     public static void handleActivityResult(int requestCode, int resultCode, Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |  | ||||||
|         boolean isHandledPickedFile = (requestCode & RequestCodes.FILE_PICKER_IMAGE_IDENTIFICATOR) > 0; |  | ||||||
|         if (isHandledPickedFile) { |  | ||||||
|             requestCode &= ~RequestCodes.SOURCE_CHOOSER; |  | ||||||
|             if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY || |  | ||||||
|                     requestCode == RequestCodes.TAKE_PICTURE || |  | ||||||
|                     requestCode == RequestCodes.CAPTURE_VIDEO || |  | ||||||
|                     requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS || |  | ||||||
|                     requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) { |  | ||||||
|                 if (resultCode == Activity.RESULT_OK) { |  | ||||||
|                     if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS && !isPhoto(data)) { |  | ||||||
|                         onPictureReturnedFromDocuments(data, activity, callbacks); |  | ||||||
|                     } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY && !isPhoto(data)) { |  | ||||||
|                         onPictureReturnedFromGallery(data, activity, callbacks); |  | ||||||
|                     } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_CUSTOM_SELECTOR) { |  | ||||||
|                         onPictureReturnedFromCustomSelector(data, activity, callbacks); |  | ||||||
|                     } else if (requestCode == RequestCodes.TAKE_PICTURE) { |  | ||||||
|                         onPictureReturnedFromCamera(activity, callbacks); |  | ||||||
|                     } else if (requestCode == RequestCodes.CAPTURE_VIDEO) { |  | ||||||
|                         onVideoReturnedFromCamera(activity, callbacks); |  | ||||||
|                     } else if (isPhoto(data)) { |  | ||||||
|                         onPictureReturnedFromCamera(activity, callbacks); |  | ||||||
|                     } else { |  | ||||||
|                         onPictureReturnedFromDocuments(data, activity, callbacks); |  | ||||||
|                     } |  | ||||||
|                 } else { |  | ||||||
|                     if (requestCode == RequestCodes.PICK_PICTURE_FROM_DOCUMENTS) { |  | ||||||
|                         callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |  | ||||||
|                     } else if (requestCode == RequestCodes.PICK_PICTURE_FROM_GALLERY) { |  | ||||||
|                         callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); |  | ||||||
|                     } else { |  | ||||||
|                         callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public static List<UploadableFile> handleExternalImagesPicked(Intent data, Activity activity) { |     public static List<UploadableFile> handleExternalImagesPicked(Intent data, Activity activity) { | ||||||
|         try { |         try { | ||||||
|             return getFilesFromGalleryPictures(data, activity); |             return getFilesFromGalleryPictures(data, activity); | ||||||
|  | @ -241,9 +202,10 @@ public class FilePicker implements Constants { | ||||||
|         return intent; |         return intent; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void onPictureReturnedFromDocuments(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     public static void onPictureReturnedFromDocuments(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |         if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ | ||||||
|             try { |             try { | ||||||
|             Uri photoPath = data.getData(); |                 Uri photoPath = result.getData().getData(); | ||||||
|                 UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); |                 UploadableFile photoFile = PickedFiles.pickedExistingPicture(activity, photoPath); | ||||||
|                 callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |                 callbacks.onImagesPicked(singleFileList(photoFile), FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); | ||||||
| 
 | 
 | ||||||
|  | @ -254,20 +216,27 @@ public class FilePicker implements Constants { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); |                 callbacks.onImagePickerError(e, FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); | ||||||
|             } |             } | ||||||
|  |         } else { | ||||||
|  |             callbacks.onCanceled(FilePicker.ImageSource.DOCUMENTS, restoreType(activity)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * onPictureReturnedFromCustomSelector. |      * onPictureReturnedFromCustomSelector. | ||||||
|      * Retrieve and forward the images to upload wizard through callback. |      * Retrieve and forward the images to upload wizard through callback. | ||||||
|      */ |      */ | ||||||
|     private static void onPictureReturnedFromCustomSelector(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     public static void onPictureReturnedFromCustomSelector(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |        if(result.getResultCode() == Activity.RESULT_OK){ | ||||||
|            try { |            try { | ||||||
|             List<UploadableFile> files = getFilesFromCustomSelector(data, activity); |                List<UploadableFile> files = getFilesFromCustomSelector(result.getData(), activity); | ||||||
|                callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); |                callbacks.onImagesPicked(files, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); | ||||||
|            } catch (Exception e) { |            } catch (Exception e) { | ||||||
|                e.printStackTrace(); |                e.printStackTrace(); | ||||||
|                callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); |                callbacks.onImagePickerError(e, ImageSource.CUSTOM_SELECTOR, restoreType(activity)); | ||||||
|            } |            } | ||||||
|  |        } else { | ||||||
|  |            callbacks.onCanceled(ImageSource.CUSTOM_SELECTOR, restoreType(activity)); | ||||||
|  |        } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -290,14 +259,18 @@ public class FilePicker implements Constants { | ||||||
|         return files; |         return files; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void onPictureReturnedFromGallery(Intent data, Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     public static void onPictureReturnedFromGallery(ActivityResult result, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |         if(result.getResultCode() == Activity.RESULT_OK && !isPhoto(result.getData())){ | ||||||
|             try { |             try { | ||||||
|             List<UploadableFile> files = getFilesFromGalleryPictures(data, activity); |                 List<UploadableFile> files = getFilesFromGalleryPictures(result.getData(), activity); | ||||||
|                 callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); |                 callbacks.onImagesPicked(files, FilePicker.ImageSource.GALLERY, restoreType(activity)); | ||||||
|             } catch (Exception e) { |             } catch (Exception e) { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); |                 callbacks.onImagePickerError(e, FilePicker.ImageSource.GALLERY, restoreType(activity)); | ||||||
|             } |             } | ||||||
|  |         } else{ | ||||||
|  |             callbacks.onCanceled(FilePicker.ImageSource.GALLERY, restoreType(activity)); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static List<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { |     private static List<UploadableFile> getFilesFromGalleryPictures(Intent data, Activity activity) throws IOException, SecurityException { | ||||||
|  | @ -322,7 +295,8 @@ public class FilePicker implements Constants { | ||||||
|         return files; |         return files; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static void onPictureReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) { |     public static void onPictureReturnedFromCamera(ActivityResult activityResult, Activity activity, @NonNull FilePicker.Callbacks callbacks) { | ||||||
|  |         if(activityResult.getResultCode() == Activity.RESULT_OK){ | ||||||
|             try { |             try { | ||||||
|                 String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); |                 String lastImageUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_PHOTO_URI, null); | ||||||
|                 if (!TextUtils.isEmpty(lastImageUri)) { |                 if (!TextUtils.isEmpty(lastImageUri)) { | ||||||
|  | @ -353,38 +327,8 @@ public class FilePicker implements Constants { | ||||||
|                 e.printStackTrace(); |                 e.printStackTrace(); | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); |                 callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); | ||||||
|             } |             } | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static void onVideoReturnedFromCamera(Activity activity, @NonNull FilePicker.Callbacks callbacks) { |  | ||||||
|         try { |  | ||||||
|             String lastVideoUri = PreferenceManager.getDefaultSharedPreferences(activity).getString(KEY_VIDEO_URI, null); |  | ||||||
|             if (!TextUtils.isEmpty(lastVideoUri)) { |  | ||||||
|                 revokeWritePermission(activity, Uri.parse(lastVideoUri)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             UploadableFile photoFile = FilePicker.takenCameraVideo(activity); |  | ||||||
|             List<UploadableFile> files = new ArrayList<>(); |  | ||||||
|             files.add(photoFile); |  | ||||||
| 
 |  | ||||||
|             if (photoFile == null) { |  | ||||||
|                 Exception e = new IllegalStateException("Unable to get the video returned from camera"); |  | ||||||
|                 callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity)); |  | ||||||
|         } else { |         } else { | ||||||
|                 if (configuration(activity).shouldCopyTakenPhotosToPublicGalleryAppFolder()) { |             callbacks.onCanceled(FilePicker.ImageSource.CAMERA_IMAGE, restoreType(activity)); | ||||||
|                     PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)); |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 callbacks.onImagesPicked(files, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity)); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             PreferenceManager.getDefaultSharedPreferences(activity) |  | ||||||
|                     .edit() |  | ||||||
|                     .remove(KEY_LAST_CAMERA_VIDEO) |  | ||||||
|                     .remove(KEY_VIDEO_URI) |  | ||||||
|                     .apply(); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             e.printStackTrace(); |  | ||||||
|             callbacks.onImagePickerError(e, FilePicker.ImageSource.CAMERA_VIDEO, restoreType(activity)); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -404,4 +348,8 @@ public class FilePicker implements Constants { | ||||||
| 
 | 
 | ||||||
|         void onCanceled(FilePicker.ImageSource source, int type); |         void onCanceled(FilePicker.ImageSource source, int type); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public interface HandleActivityResult{ | ||||||
|  |         void onHandleActivityResult(FilePicker.Callbacks callbacks); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,13 +1,10 @@ | ||||||
| package fr.free.nrw.commons.media; | package fr.free.nrw.commons.media; | ||||||
| 
 | 
 | ||||||
| import static android.app.Activity.RESULT_CANCELED; |  | ||||||
| import static android.app.Activity.RESULT_OK; |  | ||||||
| import static android.view.View.GONE; | import static android.view.View.GONE; | ||||||
| import static android.view.View.VISIBLE; | import static android.view.View.VISIBLE; | ||||||
| import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES; | import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_NEEDING_CATEGORIES; | ||||||
| import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED; | import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_UNCATEGORISED; | ||||||
| import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION; | import static fr.free.nrw.commons.description.EditDescriptionConstants.LIST_OF_DESCRIPTION_AND_CAPTION; | ||||||
| import static fr.free.nrw.commons.description.EditDescriptionConstants.UPDATED_WIKITEXT; |  | ||||||
| import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT; | import static fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT; | ||||||
| import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; | import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION; | ||||||
| import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources; | import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources; | ||||||
|  | @ -112,8 +109,6 @@ import timber.log.Timber; | ||||||
| public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
|     CategoryEditHelper.Callback { |     CategoryEditHelper.Callback { | ||||||
| 
 | 
 | ||||||
|     private static final int REQUEST_CODE = 1001; |  | ||||||
|     private static final int REQUEST_CODE_EDIT_DESCRIPTION = 1002; |  | ||||||
|     private static final String IMAGE_BACKGROUND_COLOR = "image_background_color"; |     private static final String IMAGE_BACKGROUND_COLOR = "image_background_color"; | ||||||
|     static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0; |     static final int DEFAULT_IMAGE_BACKGROUND_COLOR = 0; | ||||||
|      |      | ||||||
|  | @ -277,6 +272,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
| 
 | 
 | ||||||
|         if (!sessionManager.isUserLoggedIn()) { |         if (!sessionManager.isUserLoggedIn()) { | ||||||
|             binding.categoryEditButton.setVisibility(GONE); |             binding.categoryEditButton.setVisibility(GONE); | ||||||
|  |             binding.descriptionEdit.setVisibility(GONE); | ||||||
|  |             binding.depictionsEditButton.setVisibility(GONE); | ||||||
|  |         } else { | ||||||
|  |             binding.categoryEditButton.setVisibility(VISIBLE); | ||||||
|  |             binding.descriptionEdit.setVisibility(VISIBLE); | ||||||
|  |             binding.depictionsEditButton.setVisibility(VISIBLE); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(applicationKvStore.getBoolean("login_skipped")){ |         if(applicationKvStore.getBoolean("login_skipped")){ | ||||||
|  | @ -405,7 +406,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|         binding.progressBarEdit.setVisibility(GONE); |         binding.progressBarEdit.setVisibility(GONE); | ||||||
|         binding.descriptionEdit.setVisibility(VISIBLE); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | @ -605,8 +605,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
|         // Check if the presented category is about need of category |         // Check if the presented category is about need of category | ||||||
|         if (categoriesPresent) { |         if (categoriesPresent) { | ||||||
|             for (String category : media.getCategories()) { |             for (String category : media.getCategories()) { | ||||||
|                 if (category.toLowerCase().contains(CATEGORY_NEEDING_CATEGORIES) || |                 if (category.toLowerCase(Locale.ROOT).contains(CATEGORY_NEEDING_CATEGORIES) || | ||||||
|                     category.toLowerCase().contains(CATEGORY_UNCATEGORISED)) { |                     category.toLowerCase(Locale.ROOT).contains(CATEGORY_UNCATEGORISED)) { | ||||||
|                     categoriesPresent = false; |                     categoriesPresent = false; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|  | @ -683,7 +683,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
|             // Stick in a filler element. |             // Stick in a filler element. | ||||||
|             allCategories.add(getString(R.string.detail_panel_cats_none)); |             allCategories.add(getString(R.string.detail_panel_cats_none)); | ||||||
|         } |         } | ||||||
|  |         if(sessionManager.isUserLoggedIn()) { | ||||||
|             binding.categoryEditButton.setVisibility(VISIBLE); |             binding.categoryEditButton.setVisibility(VISIBLE); | ||||||
|  |         } | ||||||
|         rebuildCatList(allCategories); |         rebuildCatList(allCategories); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -1065,81 +1067,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements | ||||||
|         return captionList; |         return captionList; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |  | ||||||
|      * Get the result from another activity and act accordingly. |  | ||||||
|      * @param requestCode |  | ||||||
|      * @param resultCode |  | ||||||
|      * @param data |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void onActivityResult(final int requestCode, final int resultCode, |  | ||||||
|         @Nullable final Intent data) { |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data); |  | ||||||
| 
 |  | ||||||
|         if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_OK) { |  | ||||||
|             final String updatedWikiText = data.getStringExtra(UPDATED_WIKITEXT); |  | ||||||
| 
 |  | ||||||
|             try { |  | ||||||
|                 compositeDisposable.add(descriptionEditHelper.addDescription(getContext(), media, |  | ||||||
|                         updatedWikiText) |  | ||||||
|                     .subscribeOn(Schedulers.io()) |  | ||||||
|                     .observeOn(AndroidSchedulers.mainThread()) |  | ||||||
|                     .subscribe(s -> { |  | ||||||
|                         Timber.d("Descriptions are added."); |  | ||||||
|                     })); |  | ||||||
|             } catch (Exception e) { |  | ||||||
|                 if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) { |  | ||||||
|                     final String username = sessionManager.getUserName(); |  | ||||||
|                     final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener( |  | ||||||
|                         getActivity(), |  | ||||||
|                         requireActivity().getString(R.string.invalid_login_message), |  | ||||||
|                         username |  | ||||||
|                     ); |  | ||||||
| 
 |  | ||||||
|                     CommonsApplication.getInstance().clearApplicationData( |  | ||||||
|                         requireActivity(), logoutListener); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             final ArrayList<UploadMediaDetail> uploadMediaDetails |  | ||||||
|                 = data.getParcelableArrayListExtra(LIST_OF_DESCRIPTION_AND_CAPTION); |  | ||||||
| 
 |  | ||||||
|             LinkedHashMap<String, String> updatedCaptions = new LinkedHashMap<>(); |  | ||||||
|             for (UploadMediaDetail mediaDetail: |  | ||||||
|             uploadMediaDetails) { |  | ||||||
|                 try { |  | ||||||
|                     compositeDisposable.add(descriptionEditHelper.addCaption(getContext(), media, |  | ||||||
|                             mediaDetail.getLanguageCode(), mediaDetail.getCaptionText()) |  | ||||||
|                         .subscribeOn(Schedulers.io()) |  | ||||||
|                         .observeOn(AndroidSchedulers.mainThread()) |  | ||||||
|                         .subscribe(s -> { |  | ||||||
|                                 updateCaptions(mediaDetail, updatedCaptions); |  | ||||||
|                                 Timber.d("Caption is added."); |  | ||||||
|                             })); |  | ||||||
| 
 |  | ||||||
|                 } catch (Exception e) { |  | ||||||
|                     if (e.getLocalizedMessage().equals(CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE)) { |  | ||||||
|                         final String username = sessionManager.getUserName(); |  | ||||||
|                         final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener( |  | ||||||
|                             getActivity(), |  | ||||||
|                             requireActivity().getString(R.string.invalid_login_message), |  | ||||||
|                             username |  | ||||||
|                         ); |  | ||||||
| 
 |  | ||||||
|                         CommonsApplication.getInstance().clearApplicationData( |  | ||||||
|                             requireActivity(), logoutListener); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             binding.progressBarEdit.setVisibility(GONE); |  | ||||||
|             binding.descriptionEdit.setVisibility(VISIBLE); |  | ||||||
| 
 |  | ||||||
|         } else if (requestCode == REQUEST_CODE_EDIT_DESCRIPTION && resultCode == RESULT_CANCELED) { |  | ||||||
|             binding.progressBarEdit.setVisibility(GONE); |  | ||||||
|             binding.descriptionEdit.setVisibility(VISIBLE); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Adds caption to the map and updates captions |      * Adds caption to the map and updates captions | ||||||
|      * @param mediaDetail UploadMediaDetail |      * @param mediaDetail UploadMediaDetail | ||||||
|  |  | ||||||
|  | @ -219,7 +219,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|                 onSwipe() |                 onSwipe() | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         binding.zoomProgressBar?.let { |         binding.zoomProgressBar.let { | ||||||
|             it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE |             it.visibility = if (result.status is CallbackStatus.FETCHING) View.VISIBLE else View.GONE | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -234,7 +234,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|             sharedPreferences.getBoolean(ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) |             sharedPreferences.getBoolean(ImageHelper.SHOW_ALREADY_ACTIONED_IMAGES_PREFERENCE_KEY, true) | ||||||
| 
 | 
 | ||||||
|         if (!images.isNullOrEmpty()) { |         if (!images.isNullOrEmpty()) { | ||||||
|             binding.zoomable!!.setOnTouchListener( |             binding.zoomable.setOnTouchListener( | ||||||
|                 object : OnSwipeTouchListener(this) { |                 object : OnSwipeTouchListener(this) { | ||||||
|                     // Swipe left to view next image in the folder. (if available) |                     // Swipe left to view next image in the folder. (if available) | ||||||
|                     override fun onSwipeLeft() { |                     override fun onSwipeLeft() { | ||||||
|  | @ -271,7 +271,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|      * Handles down swipe action |      * Handles down swipe action | ||||||
|      */ |      */ | ||||||
|     private fun onDownSwiped() { |     private fun onDownSwiped() { | ||||||
|         if (binding.zoomable?.zoomableController?.isIdentity == false) { |         if (binding.zoomable.zoomableController?.isIdentity == false) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -341,7 +341,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|      * Handles up swipe action |      * Handles up swipe action | ||||||
|      */ |      */ | ||||||
|     private fun onUpSwiped() { |     private fun onUpSwiped() { | ||||||
|         if (binding.zoomable?.zoomableController?.isIdentity == false) { |         if (binding.zoomable.zoomableController?.isIdentity == false) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -414,7 +414,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|      * Handles right swipe action |      * Handles right swipe action | ||||||
|      */ |      */ | ||||||
|     private fun onRightSwiped(showAlreadyActionedImages: Boolean) { |     private fun onRightSwiped(showAlreadyActionedImages: Boolean) { | ||||||
|         if (binding.zoomable?.zoomableController?.isIdentity == false) { |         if (binding.zoomable.zoomableController?.isIdentity == false) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -451,7 +451,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|      * Handles left swipe action |      * Handles left swipe action | ||||||
|      */ |      */ | ||||||
|     private fun onLeftSwiped(showAlreadyActionedImages: Boolean) { |     private fun onLeftSwiped(showAlreadyActionedImages: Boolean) { | ||||||
|         if (binding.zoomable?.zoomableController?.isIdentity == false) { |         if (binding.zoomable.zoomableController?.isIdentity == false) { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -646,7 +646,7 @@ class ZoomableActivity : BaseActivity() { | ||||||
|                     .setProgressBarImage(ProgressBarDrawable()) |                     .setProgressBarImage(ProgressBarDrawable()) | ||||||
|                     .setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) |                     .setProgressBarImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) | ||||||
|                     .build() |                     .build() | ||||||
|             with(binding.zoomable!!) { |             with(binding.zoomable) { | ||||||
|                 setHierarchy(hierarchy) |                 setHierarchy(hierarchy) | ||||||
|                 setAllowTouchInterceptionWhileZoomed(true) |                 setAllowTouchInterceptionWhileZoomed(true) | ||||||
|                 setIsLongpressEnabled(false) |                 setIsLongpressEnabled(false) | ||||||
|  | @ -658,10 +658,10 @@ class ZoomableActivity : BaseActivity() { | ||||||
|                     .setUri(imageUri) |                     .setUri(imageUri) | ||||||
|                     .setControllerListener(loadingListener) |                     .setControllerListener(loadingListener) | ||||||
|                     .build() |                     .build() | ||||||
|             binding.zoomable!!.controller = controller |             binding.zoomable.controller = controller | ||||||
| 
 | 
 | ||||||
|             if (photoBackgroundColor != null) { |             if (photoBackgroundColor != null) { | ||||||
|                 binding.zoomable!!.setBackgroundColor(photoBackgroundColor!!) |                 binding.zoomable.setBackgroundColor(photoBackgroundColor!!) | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (!images.isNullOrEmpty()) { |             if (!images.isNullOrEmpty()) { | ||||||
|  |  | ||||||
|  | @ -285,7 +285,7 @@ public class OkHttpJsonApiClient { | ||||||
|         throws Exception { |         throws Exception { | ||||||
| 
 | 
 | ||||||
|         Timber.d("Fetching nearby items at radius %s", radius); |         Timber.d("Fetching nearby items at radius %s", radius); | ||||||
|         Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null)); |         Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); | ||||||
|         final String wikidataQuery; |         final String wikidataQuery; | ||||||
|         if (customQuery != null) { |         if (customQuery != null) { | ||||||
|             wikidataQuery = customQuery; |             wikidataQuery = customQuery; | ||||||
|  | @ -344,7 +344,7 @@ public class OkHttpJsonApiClient { | ||||||
|         final boolean shouldQueryForMonuments, final String customQuery) |         final boolean shouldQueryForMonuments, final String customQuery) | ||||||
|         throws Exception { |         throws Exception { | ||||||
| 
 | 
 | ||||||
|         Timber.d("CUSTOM_SPARQL%s", String.valueOf(customQuery != null)); |         Timber.d("CUSTOM_SPARQL: %s", String.valueOf(customQuery != null)); | ||||||
| 
 | 
 | ||||||
|         final String wikidataQuery; |         final String wikidataQuery; | ||||||
|         if (customQuery != null) { |         if (customQuery != null) { | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ import androidx.recyclerview.widget.RecyclerView; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| 
 | 
 | ||||||
| import fr.free.nrw.commons.R; | import fr.free.nrw.commons.R; | ||||||
|  | import java.util.Locale; | ||||||
| 
 | 
 | ||||||
| public class NearbyFilterSearchRecyclerViewAdapter | public class NearbyFilterSearchRecyclerViewAdapter | ||||||
|         extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder> |         extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder> | ||||||
|  | @ -121,11 +122,11 @@ public class NearbyFilterSearchRecyclerViewAdapter | ||||||
|                     results.count = labels.size(); |                     results.count = labels.size(); | ||||||
|                     results.values = labels; |                     results.values = labels; | ||||||
|                 } else { |                 } else { | ||||||
|                     constraint = constraint.toString().toLowerCase(); |                     constraint = constraint.toString().toLowerCase(Locale.ROOT); | ||||||
| 
 | 
 | ||||||
|                     for (Label label : labels) { |                     for (Label label : labels) { | ||||||
|                         String data = label.toString(); |                         String data = label.toString(); | ||||||
|                         if (data.toLowerCase().startsWith(constraint.toString())) { |                         if (data.toLowerCase(Locale.ROOT).startsWith(constraint.toString())) { | ||||||
|                             filteredArrayList.add(Label.fromText(label.getText())); |                             filteredArrayList.add(Label.fromText(label.getText())); | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.nearby | package fr.free.nrw.commons.nearby | ||||||
| 
 | 
 | ||||||
|  | import android.content.Intent | ||||||
| import android.view.View | import android.view.View | ||||||
| import android.view.View.GONE | import android.view.View.GONE | ||||||
| import android.view.View.INVISIBLE | import android.view.View.INVISIBLE | ||||||
|  | @ -17,9 +18,9 @@ import fr.free.nrw.commons.databinding.ItemPlaceBinding | ||||||
| fun placeAdapterDelegate( | fun placeAdapterDelegate( | ||||||
|     bookmarkLocationDao: BookmarkLocationsDao, |     bookmarkLocationDao: BookmarkLocationsDao, | ||||||
|     onItemClick: ((Place) -> Unit)? = null, |     onItemClick: ((Place) -> Unit)? = null, | ||||||
|     onCameraClicked: (Place, ActivityResultLauncher<Array<String>>) -> Unit, |     onCameraClicked: (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit, | ||||||
|     onCameraLongPressed: () -> Boolean, |     onCameraLongPressed: () -> Boolean, | ||||||
|     onGalleryClicked: (Place) -> Unit, |     onGalleryClicked: (Place, ActivityResultLauncher<Intent>) -> Unit, | ||||||
|     onGalleryLongPressed: () -> Boolean, |     onGalleryLongPressed: () -> Boolean, | ||||||
|     onBookmarkClicked: (Place, Boolean) -> Unit, |     onBookmarkClicked: (Place, Boolean) -> Unit, | ||||||
|     onBookmarkLongPressed: () -> Boolean, |     onBookmarkLongPressed: () -> Boolean, | ||||||
|  | @ -28,6 +29,8 @@ fun placeAdapterDelegate( | ||||||
|     onDirectionsClicked: (Place) -> Unit, |     onDirectionsClicked: (Place) -> Unit, | ||||||
|     onDirectionsLongPressed: () -> Boolean, |     onDirectionsLongPressed: () -> Boolean, | ||||||
|     inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>, |     inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>, | ||||||
|  |     cameraPickLauncherForResult: ActivityResultLauncher<Intent>, | ||||||
|  |     galleryPickLauncherForResult: ActivityResultLauncher<Intent> | ||||||
| ) = adapterDelegateViewBinding<Place, Place, ItemPlaceBinding>({ layoutInflater, parent -> | ) = adapterDelegateViewBinding<Place, Place, ItemPlaceBinding>({ layoutInflater, parent -> | ||||||
|     ItemPlaceBinding.inflate(layoutInflater, parent, false) |     ItemPlaceBinding.inflate(layoutInflater, parent, false) | ||||||
| }) { | }) { | ||||||
|  | @ -44,10 +47,10 @@ fun placeAdapterDelegate( | ||||||
|                 onItemClick?.invoke(item) |                 onItemClick?.invoke(item) | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher) } |         nearbyButtonLayout.cameraButton.setOnClickListener { onCameraClicked(item, inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult) } | ||||||
|         nearbyButtonLayout.cameraButton.setOnLongClickListener { onCameraLongPressed() } |         nearbyButtonLayout.cameraButton.setOnLongClickListener { onCameraLongPressed() } | ||||||
| 
 | 
 | ||||||
|         nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item) } |         nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) } | ||||||
|         nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() } |         nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() } | ||||||
|         bookmarkButtonImage.setOnClickListener { |         bookmarkButtonImage.setOnClickListener { | ||||||
|             val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item) |             val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item) | ||||||
|  |  | ||||||
|  | @ -4,7 +4,6 @@ import androidx.room.Dao; | ||||||
| import androidx.room.Insert; | import androidx.room.Insert; | ||||||
| import androidx.room.OnConflictStrategy; | import androidx.room.OnConflictStrategy; | ||||||
| import androidx.room.Query; | import androidx.room.Query; | ||||||
| import fr.free.nrw.commons.location.LatLng; |  | ||||||
| import io.reactivex.Completable; | import io.reactivex.Completable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -38,8 +37,21 @@ public abstract class PlaceDao { | ||||||
|      */ |      */ | ||||||
|     public Completable save(final Place place) { |     public Completable save(final Place place) { | ||||||
|         return Completable |         return Completable | ||||||
|             .fromAction(() -> { |             .fromAction(() -> saveSynchronous(place)); | ||||||
|                 saveSynchronous(place); |     } | ||||||
|             }); | 
 | ||||||
|  |     /** | ||||||
|  |      * Deletes all Place objects from the database. | ||||||
|  |      */ | ||||||
|  |     @Query("DELETE FROM place") | ||||||
|  |     public abstract void deleteAllSynchronous(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Deletes all Place objects from the database. | ||||||
|  |      * | ||||||
|  |      * @return A Completable that completes once the deletion operation is done. | ||||||
|  |      */ | ||||||
|  |     public Completable deleteAll() { | ||||||
|  |         return Completable.fromAction(this::deleteAllSynchronous); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,6 +1,5 @@ | ||||||
| package fr.free.nrw.commons.nearby; | package fr.free.nrw.commons.nearby; | ||||||
| 
 | 
 | ||||||
| import fr.free.nrw.commons.location.LatLng; |  | ||||||
| import io.reactivex.Completable; | import io.reactivex.Completable; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
|  | @ -36,4 +35,8 @@ public class PlacesLocalDataSource { | ||||||
|     public Completable savePlace(Place place) { |     public Completable savePlace(Place place) { | ||||||
|         return placeDao.save(place); |         return placeDao.save(place); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public Completable clearCache() { | ||||||
|  |         return placeDao.deleteAll(); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby; | ||||||
| import fr.free.nrw.commons.contributions.Contribution; | import fr.free.nrw.commons.contributions.Contribution; | ||||||
| import fr.free.nrw.commons.location.LatLng; | import fr.free.nrw.commons.location.LatLng; | ||||||
| import io.reactivex.Completable; | import io.reactivex.Completable; | ||||||
|  | import io.reactivex.schedulers.Schedulers; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -38,4 +39,13 @@ public class PlacesRepository { | ||||||
|         return localDataSource.fetchPlace(entityID); |         return localDataSource.fetchPlace(entityID); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Clears the Nearby cache on an IO thread. | ||||||
|  |      * | ||||||
|  |      * @return A Completable that completes once the cache has been successfully cleared. | ||||||
|  |      */ | ||||||
|  |     public Completable clearCache() { | ||||||
|  |         return localDataSource.clearCache() | ||||||
|  |             .subscribeOn(Schedulers.io()); // Ensure it runs on IO thread | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -87,7 +87,7 @@ class WikidataFeedback : BaseActivity() { | ||||||
|                                 lat, |                                 lat, | ||||||
|                                 lng, |                                 lng, | ||||||
|                             ) |                             ) | ||||||
|                         } as Callable<SingleSource<Boolean?>>, |                         }, | ||||||
|                     ).subscribeOn(Schedulers.io()) |                     ).subscribeOn(Schedulers.io()) | ||||||
|                     .observeOn(AndroidSchedulers.mainThread()) |                     .observeOn(AndroidSchedulers.mainThread()) | ||||||
|                     .subscribe({ aBoolean: Boolean? -> |                     .subscribe({ aBoolean: Boolean? -> | ||||||
|  |  | ||||||
|  | @ -28,14 +28,14 @@ class CommonPlaceClickActions | ||||||
|         private val activity: Activity, |         private val activity: Activity, | ||||||
|         private val contributionController: ContributionController, |         private val contributionController: ContributionController, | ||||||
|     ) { |     ) { | ||||||
|         fun onCameraClicked(): (Place, ActivityResultLauncher<Array<String>>) -> Unit = |         fun onCameraClicked(): (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit = | ||||||
|             { place, launcher -> |             { place, launcher, resultLauncher -> | ||||||
|                 if (applicationKvStore.getBoolean("login_skipped", false)) { |                 if (applicationKvStore.getBoolean("login_skipped", false)) { | ||||||
|                     showLoginDialog() |                     showLoginDialog() | ||||||
|                 } else { |                 } else { | ||||||
|                     Timber.d("Camera button tapped. Image title: ${place.getName()}Image desc: ${place.longDescription}") |                     Timber.d("Camera button tapped. Image title: ${place.getName()}Image desc: ${place.longDescription}") | ||||||
|                     storeSharedPrefs(place) |                     storeSharedPrefs(place) | ||||||
|                     contributionController.initiateCameraPick(activity, launcher) |                     contributionController.initiateCameraPick(activity, launcher, resultLauncher) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | @ -72,14 +72,14 @@ class CommonPlaceClickActions | ||||||
|                 true |                 true | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         fun onGalleryClicked(): (Place) -> Unit = |         fun onGalleryClicked(): (Place, ActivityResultLauncher<Intent>) -> Unit = | ||||||
|             { |             {place, galleryPickLauncherForResult -> | ||||||
|                 if (applicationKvStore.getBoolean("login_skipped", false)) { |                 if (applicationKvStore.getBoolean("login_skipped", false)) { | ||||||
|                     showLoginDialog() |                     showLoginDialog() | ||||||
|                 } else { |                 } else { | ||||||
|                     Timber.d("Gallery button tapped. Image title: ${it.getName()}Image desc: ${it.getLongDescription()}") |                     Timber.d("Gallery button tapped. Image title: ${place.getName()}Image desc: ${place.getLongDescription()}") | ||||||
|                     storeSharedPrefs(it) |                     storeSharedPrefs(place) | ||||||
|                     contributionController.initiateGalleryPick(activity, false) |                     contributionController.initiateGalleryPick(activity, galleryPickLauncherForResult, false) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -43,15 +43,18 @@ import android.view.ViewGroup; | ||||||
| import android.view.ViewGroup.LayoutParams; | import android.view.ViewGroup.LayoutParams; | ||||||
| import android.view.animation.Animation; | import android.view.animation.Animation; | ||||||
| import android.view.animation.AnimationUtils; | import android.view.animation.AnimationUtils; | ||||||
|  | import android.widget.Button; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| import androidx.activity.result.ActivityResultCallback; | import androidx.activity.result.ActivityResultCallback; | ||||||
| import androidx.activity.result.ActivityResultLauncher; | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; | import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; | ||||||
| import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; | import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||||
| import androidx.annotation.DrawableRes; | import androidx.annotation.DrawableRes; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.appcompat.app.AlertDialog.Builder; | import androidx.appcompat.app.AlertDialog.Builder; | ||||||
|  | import androidx.constraintlayout.widget.ConstraintLayout; | ||||||
| import androidx.core.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import androidx.core.content.FileProvider; | import androidx.core.content.FileProvider; | ||||||
| import androidx.recyclerview.widget.DividerItemDecoration; | import androidx.recyclerview.widget.DividerItemDecoration; | ||||||
|  | @ -105,6 +108,7 @@ import fr.free.nrw.commons.utils.NetworkUtils; | ||||||
| import fr.free.nrw.commons.utils.SystemThemeUtils; | import fr.free.nrw.commons.utils.SystemThemeUtils; | ||||||
| import fr.free.nrw.commons.utils.ViewUtil; | import fr.free.nrw.commons.utils.ViewUtil; | ||||||
| import fr.free.nrw.commons.wikidata.WikidataEditListener; | import fr.free.nrw.commons.wikidata.WikidataEditListener; | ||||||
|  | import io.reactivex.Completable; | ||||||
| import io.reactivex.Observable; | import io.reactivex.Observable; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.disposables.Disposable; | import io.reactivex.disposables.Disposable; | ||||||
|  | @ -218,9 +222,36 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|     private LatLng updatedLatLng; |     private LatLng updatedLatLng; | ||||||
|     private boolean searchable; |     private boolean searchable; | ||||||
| 
 | 
 | ||||||
|  |     private ConstraintLayout nearbyLegend; | ||||||
|  | 
 | ||||||
|     private GridLayoutManager gridLayoutManager; |     private GridLayoutManager gridLayoutManager; | ||||||
|     private List<BottomSheetItem> dataList; |     private List<BottomSheetItem> dataList; | ||||||
|     private BottomSheetAdapter bottomSheetAdapter; |     private BottomSheetAdapter bottomSheetAdapter; | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> customSelectorLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( |     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( | ||||||
|         new RequestMultiplePermissions(), |         new RequestMultiplePermissions(), | ||||||
|         new ActivityResultCallback<Map<String, Boolean>>() { |         new ActivityResultCallback<Map<String, Boolean>>() { | ||||||
|  | @ -236,7 +267,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|                 } else { |                 } else { | ||||||
|                     if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { |                     if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { | ||||||
|                         controller.handleShowRationaleFlowCameraLocation(getActivity(), |                         controller.handleShowRationaleFlowCameraLocation(getActivity(), | ||||||
|                             inAppCameraLocationPermissionLauncher); |                             inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|                     } else { |                     } else { | ||||||
|                         controller.locationPermissionCallback.onLocationPermissionDenied( |                         controller.locationPermissionCallback.onLocationPermissionDenied( | ||||||
|                             getActivity().getString( |                             getActivity().getString( | ||||||
|  | @ -302,6 +333,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|         progressDialog.setCancelable(false); |         progressDialog.setCancelable(false); | ||||||
|         progressDialog.setMessage("Saving in progress..."); |         progressDialog.setMessage("Saving in progress..."); | ||||||
|         setHasOptionsMenu(true); |         setHasOptionsMenu(true); | ||||||
|  | 
 | ||||||
|         // Inflate the layout for this fragment |         // Inflate the layout for this fragment | ||||||
|         return view; |         return view; | ||||||
| 
 | 
 | ||||||
|  | @ -311,9 +343,21 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|     public void onCreateOptionsMenu(@NonNull final Menu menu, |     public void onCreateOptionsMenu(@NonNull final Menu menu, | ||||||
|         @NonNull final MenuInflater inflater) { |         @NonNull final MenuInflater inflater) { | ||||||
|         inflater.inflate(R.menu.nearby_fragment_menu, menu); |         inflater.inflate(R.menu.nearby_fragment_menu, menu); | ||||||
|  |         MenuItem refreshButton = menu.findItem(R.id.item_refresh); | ||||||
|         MenuItem listMenu = menu.findItem(R.id.list_sheet); |         MenuItem listMenu = menu.findItem(R.id.list_sheet); | ||||||
|         MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); |         MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); | ||||||
|         MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); |         MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); | ||||||
|  |         refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { | ||||||
|  |             @Override | ||||||
|  |             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  |                 try { | ||||||
|  |                     emptyCache(); | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     throw new RuntimeException(e); | ||||||
|  |                 } | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|         listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { |         listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { | ||||||
|             @Override |             @Override | ||||||
|             public boolean onMenuItemClick(MenuItem item) { |             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  | @ -362,6 +406,16 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|         } |         } | ||||||
|         locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, |         locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, | ||||||
|             this); |             this); | ||||||
|  | 
 | ||||||
|  |         // Set up the floating activity button to toggle the visibility of the legend | ||||||
|  |         binding.fabLegend.setOnClickListener(v -> { | ||||||
|  |             if (binding.nearbyLegendLayout.getRoot().getVisibility() == View.VISIBLE) { | ||||||
|  |                 binding.nearbyLegendLayout.getRoot().setVisibility(View.GONE); | ||||||
|  |             } else { | ||||||
|  |                 binding.nearbyLegendLayout.getRoot().setVisibility(View.VISIBLE); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         presenter.attachView(this); |         presenter.attachView(this); | ||||||
|         isPermissionDenied = false; |         isPermissionDenied = false; | ||||||
|         recenterToUserLocation = false; |         recenterToUserLocation = false; | ||||||
|  | @ -555,7 +609,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|                 return Unit.INSTANCE; |                 return Unit.INSTANCE; | ||||||
|             }, |             }, | ||||||
|             commonPlaceClickActions, |             commonPlaceClickActions, | ||||||
|             inAppCameraLocationPermissionLauncher |             inAppCameraLocationPermissionLauncher, | ||||||
|  |             galleryPickLauncherForResult, | ||||||
|  |             cameraPickLauncherForResult | ||||||
|         ); |         ); | ||||||
|         binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter); |         binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter); | ||||||
|     } |     } | ||||||
|  | @ -1115,6 +1171,48 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      *  Reloads the Nearby map | ||||||
|  |      *  Clears all location markers, refreshes them, reinserts them into the map. | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     private void reloadMap() { | ||||||
|  |         clearAllMarkers(); // Clear the list of markers | ||||||
|  |         binding.map.getController().setZoom(ZOOM_LEVEL); // Reset the zoom level | ||||||
|  |         binding.map.getController().setCenter(lastMapFocus); // Recenter the focus | ||||||
|  |         if (locationPermissionsHelper.checkLocationPermission(getActivity())) { | ||||||
|  |             locationPermissionGranted(); // Reload map with user's location | ||||||
|  |         } else { | ||||||
|  |             startMapWithoutPermission(); // Reload map without user's location | ||||||
|  |         } | ||||||
|  |         binding.map.invalidate(); // Invalidate the map | ||||||
|  |         presenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); // Restart the map | ||||||
|  |         Timber.d("Reloaded Map Successfully"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Clears the Nearby local cache and then calls for the map to be reloaded | ||||||
|  |      * | ||||||
|  |      */ | ||||||
|  |     private void emptyCache() { | ||||||
|  |         // reload the map once the cache is cleared | ||||||
|  |         compositeDisposable.add( | ||||||
|  |             placesRepository.clearCache() | ||||||
|  |                 .subscribeOn(Schedulers.io()) | ||||||
|  |                 .observeOn(AndroidSchedulers.mainThread()) | ||||||
|  |                 .andThen(Completable.fromAction(this::reloadMap)) | ||||||
|  |                 .subscribe( | ||||||
|  |                     () -> { | ||||||
|  |                         Timber.d("Nearby Cache cleared successfully."); | ||||||
|  |                     }, | ||||||
|  |                     throwable -> { | ||||||
|  |                         Timber.e(throwable, "Failed to clear the Nearby Cache"); | ||||||
|  |                     } | ||||||
|  |                 ) | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private void savePlacesAsKML() { |     private void savePlacesAsKML() { | ||||||
|         final Observable<String> savePlacesObservable = Observable |         final Observable<String> savePlacesObservable = Observable | ||||||
|             .fromCallable(() -> nearbyController |             .fromCallable(() -> nearbyController | ||||||
|  | @ -2186,7 +2284,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|             if (binding.fabCamera.isShown()) { |             if (binding.fabCamera.isShown()) { | ||||||
|                 Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); |                 Timber.d("Camera button tapped. Place: %s", selectedPlace.toString()); | ||||||
|                 storeSharedPrefs(selectedPlace); |                 storeSharedPrefs(selectedPlace); | ||||||
|                 controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher); |                 controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|  | @ -2195,6 +2293,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|                 Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); |                 Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); | ||||||
|                 storeSharedPrefs(selectedPlace); |                 storeSharedPrefs(selectedPlace); | ||||||
|                 controller.initiateGalleryPick(getActivity(), |                 controller.initiateGalleryPick(getActivity(), | ||||||
|  |                     galleryPickLauncherForResult, | ||||||
|                     false); |                     false); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | @ -2203,7 +2302,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment | ||||||
|             if (binding.fabCustomGallery.isShown()) { |             if (binding.fabCustomGallery.isShown()) { | ||||||
|                 Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); |                 Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); | ||||||
|                 storeSharedPrefs(selectedPlace); |                 storeSharedPrefs(selectedPlace); | ||||||
|                 controller.initiateCustomGalleryPickWithPermission(getActivity()); |                 controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.nearby.fragments | package fr.free.nrw.commons.nearby.fragments | ||||||
| 
 | 
 | ||||||
|  | import android.content.Intent | ||||||
| import androidx.activity.result.ActivityResultLauncher | import androidx.activity.result.ActivityResultLauncher | ||||||
| import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao | import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao | ||||||
| import fr.free.nrw.commons.nearby.Place | import fr.free.nrw.commons.nearby.Place | ||||||
|  | @ -12,6 +13,8 @@ class PlaceAdapter( | ||||||
|     onBookmarkClicked: (Place, Boolean) -> Unit, |     onBookmarkClicked: (Place, Boolean) -> Unit, | ||||||
|     commonPlaceClickActions: CommonPlaceClickActions, |     commonPlaceClickActions: CommonPlaceClickActions, | ||||||
|     inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>, |     inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>, | ||||||
|  |     galleryPickLauncherForResult: ActivityResultLauncher<Intent>, | ||||||
|  |     cameraPickLauncherForResult: ActivityResultLauncher<Intent> | ||||||
| ) : BaseDelegateAdapter<Place>( | ) : BaseDelegateAdapter<Place>( | ||||||
|         placeAdapterDelegate( |         placeAdapterDelegate( | ||||||
|             bookmarkLocationsDao, |             bookmarkLocationsDao, | ||||||
|  | @ -27,6 +30,8 @@ class PlaceAdapter( | ||||||
|             commonPlaceClickActions.onDirectionsClicked(), |             commonPlaceClickActions.onDirectionsClicked(), | ||||||
|             commonPlaceClickActions.onDirectionsLongPressed(), |             commonPlaceClickActions.onDirectionsLongPressed(), | ||||||
|             inAppCameraLocationPermissionLauncher, |             inAppCameraLocationPermissionLauncher, | ||||||
|  |             cameraPickLauncherForResult, | ||||||
|  |             galleryPickLauncherForResult | ||||||
|         ), |         ), | ||||||
|         areItemsTheSame = { oldItem, newItem -> oldItem.wikiDataEntityId == newItem.wikiDataEntityId }, |         areItemsTheSame = { oldItem, newItem -> oldItem.wikiDataEntityId == newItem.wikiDataEntityId }, | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import fr.free.nrw.commons.databinding.ActivityNotificationBinding; | ||||||
| import fr.free.nrw.commons.auth.SessionManager; | import fr.free.nrw.commons.auth.SessionManager; | ||||||
| import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException; | import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException; | ||||||
| import fr.free.nrw.commons.notification.models.Notification; | import fr.free.nrw.commons.notification.models.Notification; | ||||||
|  | import fr.free.nrw.commons.notification.models.NotificationType; | ||||||
| import fr.free.nrw.commons.theme.BaseActivity; | import fr.free.nrw.commons.theme.BaseActivity; | ||||||
| import fr.free.nrw.commons.utils.NetworkUtils; | import fr.free.nrw.commons.utils.NetworkUtils; | ||||||
| import fr.free.nrw.commons.utils.ViewUtil; | import fr.free.nrw.commons.utils.ViewUtil; | ||||||
|  | @ -148,7 +149,11 @@ public class NotificationActivity extends BaseActivity { | ||||||
|         } |         } | ||||||
|         adapter = new NotificatinAdapter(item -> { |         adapter = new NotificatinAdapter(item -> { | ||||||
|             Timber.d("Notification clicked %s", item.getLink()); |             Timber.d("Notification clicked %s", item.getLink()); | ||||||
|  |             if (item.getNotificationType() == NotificationType.EMAIL){ | ||||||
|  |                 ViewUtil.showLongSnackbar(binding.container,getString(R.string.check_your_email_inbox)); | ||||||
|  |             } else { | ||||||
|                 handleUrl(item.getLink()); |                 handleUrl(item.getLink()); | ||||||
|  |             } | ||||||
|             removeNotification(item); |             removeNotification(item); | ||||||
|             return Unit.INSTANCE; |             return Unit.INSTANCE; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  | @ -51,13 +51,23 @@ class NotificationClient | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|         private fun WikimediaNotification.toCommonsNotification() = |         private fun WikimediaNotification.toCommonsNotification() : | ||||||
|             Notification( |             Notification { | ||||||
|                 notificationType = NotificationType.UNKNOWN, |             val notificationText = contents?.compactHeader ?: "" | ||||||
|                 notificationText = contents?.compactHeader ?: "", |             val notificationType = | ||||||
|  |                 if (notificationText.contains("Sent you an email", ignoreCase = true)) { | ||||||
|  |                     NotificationType.EMAIL | ||||||
|  |                 } else { | ||||||
|  |                     NotificationType.UNKNOWN | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 return Notification( | ||||||
|  |                     notificationType = notificationType, | ||||||
|  |                     notificationText = notificationText, | ||||||
|                     date = DateUtil.getMonthOnlyDateString(timestamp), |                     date = DateUtil.getMonthOnlyDateString(timestamp), | ||||||
|                     link = contents?.links?.primary?.url ?: "", |                     link = contents?.links?.primary?.url ?: "", | ||||||
|                     iconUrl = "", |                     iconUrl = "", | ||||||
|                     notificationId = id().toString(), |                     notificationId = id().toString(), | ||||||
|                 ) |                 ) | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ public enum NotificationType { | ||||||
|     THANK_YOU_EDIT("thank-you-edit"), |     THANK_YOU_EDIT("thank-you-edit"), | ||||||
|     EDIT_USER_TALK("edit-user-talk"), |     EDIT_USER_TALK("edit-user-talk"), | ||||||
|     MENTION("mention"), |     MENTION("mention"), | ||||||
|  |     EMAIL("email"), | ||||||
|     WELCOME("welcome"), |     WELCOME("welcome"), | ||||||
|     UNKNOWN("unknown"); |     UNKNOWN("unknown"); | ||||||
|     private String type; |     private String type; | ||||||
|  |  | ||||||
|  | @ -32,6 +32,7 @@ import java.io.FileOutputStream; | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  | import java.util.Locale; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | @ -139,14 +140,14 @@ public class ProfileActivity extends BaseActivity { | ||||||
|         leaderboardFragment.setArguments(leaderBoardBundle); |         leaderboardFragment.setArguments(leaderBoardBundle); | ||||||
| 
 | 
 | ||||||
|         fragmentList.add(leaderboardFragment); |         fragmentList.add(leaderboardFragment); | ||||||
|         titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase()); |         titleList.add(getResources().getString(R.string.leaderboard_tab_title).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         contributionsFragment = new ContributionsFragment(); |         contributionsFragment = new ContributionsFragment(); | ||||||
|         Bundle contributionsListBundle = new Bundle(); |         Bundle contributionsListBundle = new Bundle(); | ||||||
|         contributionsListBundle.putString(KEY_USERNAME, userName); |         contributionsListBundle.putString(KEY_USERNAME, userName); | ||||||
|         contributionsFragment.setArguments(contributionsListBundle); |         contributionsFragment.setArguments(contributionsListBundle); | ||||||
|         fragmentList.add(contributionsFragment); |         fragmentList.add(contributionsFragment); | ||||||
|         titleList.add(getString(R.string.contributions_fragment).toUpperCase()); |         titleList.add(getString(R.string.contributions_fragment).toUpperCase(Locale.ROOT)); | ||||||
| 
 | 
 | ||||||
|         viewPagerAdapter.setTabData(fragmentList, titleList); |         viewPagerAdapter.setTabData(fragmentList, titleList); | ||||||
|         viewPagerAdapter.notifyDataSetChanged(); |         viewPagerAdapter.notifyDataSetChanged(); | ||||||
|  |  | ||||||
|  | @ -27,6 +27,7 @@ import fr.free.nrw.commons.profile.ProfileActivity; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.disposables.CompositeDisposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
|  | import java.util.Locale; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
|  | @ -361,7 +362,7 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment { | ||||||
|             + levelInfo.getMaxUniqueImages()); |             + levelInfo.getMaxUniqueImages()); | ||||||
|         binding.imageFeatured.setText(String.valueOf(achievements.getFeaturedImages())); |         binding.imageFeatured.setText(String.valueOf(achievements.getFeaturedImages())); | ||||||
|         binding.qualityImages.setText(String.valueOf(achievements.getQualityImages())); |         binding.qualityImages.setText(String.valueOf(achievements.getQualityImages())); | ||||||
|         String levelUpInfoString = getString(R.string.level).toUpperCase(); |         String levelUpInfoString = getString(R.string.level).toUpperCase(Locale.ROOT); | ||||||
|         levelUpInfoString += " " + levelInfo.getLevelNumber(); |         levelUpInfoString += " " + levelInfo.getLevelNumber(); | ||||||
|         binding.achievementLevel.setText(levelUpInfoString); |         binding.achievementLevel.setText(levelUpInfoString); | ||||||
|         binding.achievementBadgeImage.setImageDrawable(VectorDrawableCompat.create(getResources(), R.drawable.badge, |         binding.achievementBadgeImage.setImageDrawable(VectorDrawableCompat.create(getResources(), R.drawable.badge, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,6 @@ | ||||||
| package fr.free.nrw.commons.recentlanguages; | package fr.free.nrw.commons.recentlanguages; | ||||||
| 
 | 
 | ||||||
|  | import android.annotation.SuppressLint; | ||||||
| import android.content.ContentProviderClient; | import android.content.ContentProviderClient; | ||||||
| import android.content.ContentValues; | import android.content.ContentValues; | ||||||
| import android.database.Cursor; | import android.database.Cursor; | ||||||
|  | @ -117,6 +118,7 @@ public class RecentLanguagesDao { | ||||||
|      * @return Language object |      * @return Language object | ||||||
|      */ |      */ | ||||||
|     @NonNull |     @NonNull | ||||||
|  |     @SuppressLint("Range") | ||||||
|     Language fromCursor(final Cursor cursor) { |     Language fromCursor(final Cursor cursor) { | ||||||
|         // Hardcoding column positions! |         // Hardcoding column positions! | ||||||
|         final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); |         final String languageName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)); | ||||||
|  |  | ||||||
|  | @ -25,6 +25,7 @@ import fr.free.nrw.commons.utils.ViewUtil; | ||||||
| import io.reactivex.android.schedulers.AndroidSchedulers; | import io.reactivex.android.schedulers.AndroidSchedulers; | ||||||
| import io.reactivex.disposables.CompositeDisposable; | import io.reactivex.disposables.CompositeDisposable; | ||||||
| import io.reactivex.schedulers.Schedulers; | import io.reactivex.schedulers.Schedulers; | ||||||
|  | import java.util.Locale; | ||||||
| import javax.inject.Inject; | import javax.inject.Inject; | ||||||
| 
 | 
 | ||||||
| public class ReviewActivity extends BaseActivity { | public class ReviewActivity extends BaseActivity { | ||||||
|  | @ -241,7 +242,7 @@ public class ReviewActivity extends BaseActivity { | ||||||
| 
 | 
 | ||||||
|     public void showSkipImageInfo(){ |     public void showSkipImageInfo(){ | ||||||
|         DialogUtil.showAlertDialog(ReviewActivity.this, |         DialogUtil.showAlertDialog(ReviewActivity.this, | ||||||
|                 getString(R.string.skip_image).toUpperCase(), |                 getString(R.string.skip_image).toUpperCase(Locale.ROOT), | ||||||
|                 getString(R.string.skip_image_explanation), |                 getString(R.string.skip_image_explanation), | ||||||
|                 getString(android.R.string.ok), |                 getString(android.R.string.ok), | ||||||
|                 "", |                 "", | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ import android.widget.TextView; | ||||||
| import androidx.activity.result.ActivityResultCallback; | import androidx.activity.result.ActivityResultCallback; | ||||||
| import androidx.activity.result.ActivityResultLauncher; | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.activity.result.contract.ActivityResultContracts; | import androidx.activity.result.contract.ActivityResultContracts; | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||||
| import androidx.preference.ListPreference; | import androidx.preference.ListPreference; | ||||||
| import androidx.preference.MultiSelectListPreference; | import androidx.preference.MultiSelectListPreference; | ||||||
| import androidx.preference.Preference; | import androidx.preference.Preference; | ||||||
|  | @ -88,6 +89,15 @@ public class SettingsFragment extends PreferenceFragmentCompat { | ||||||
|     private View separator; |     private View separator; | ||||||
|     private ListView languageHistoryListView; |     private ListView languageHistoryListView; | ||||||
|     private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"; |     private static final String GET_CONTENT_PICKER_HELP_URL = "https://commons-app.github.io/docs.html#get-content"; | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = | ||||||
|  |         registerForActivityResult(new StartActivityForResult(), | ||||||
|  |         result -> { | ||||||
|  |             contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> { | ||||||
|  |                 contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks); | ||||||
|  |             }); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { |     private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() { | ||||||
|         @Override |         @Override | ||||||
|         public void onActivityResult(Map<String, Boolean> result) { |         public void onActivityResult(Map<String, Boolean> result) { | ||||||
|  | @ -96,7 +106,7 @@ public class SettingsFragment extends PreferenceFragmentCompat { | ||||||
|                 areAllGranted = areAllGranted && b; |                 areAllGranted = areAllGranted && b; | ||||||
|             } |             } | ||||||
|             if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { |             if (!areAllGranted && shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { | ||||||
|                 contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); |                 contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ class FailedUploadsFragment : | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (StringUtils.isEmpty(userName)) { |         if (StringUtils.isEmpty(userName)) { | ||||||
|             userName = sessionManager!!.getUserName() |             userName = sessionManager.getUserName() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -96,8 +96,8 @@ class FailedUploadsFragment : | ||||||
|     fun initRecyclerView() { |     fun initRecyclerView() { | ||||||
|         binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) |         binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) | ||||||
|         binding.failedUploadsRecyclerView.adapter = adapter |         binding.failedUploadsRecyclerView.adapter = adapter | ||||||
|         pendingUploadsPresenter!!.getFailedContributions() |         pendingUploadsPresenter.getFailedContributions() | ||||||
|         pendingUploadsPresenter!!.failedContributionList.observe( |         pendingUploadsPresenter.failedContributionList.observe( | ||||||
|             viewLifecycleOwner, |             viewLifecycleOwner, | ||||||
|         ) { list: PagedList<Contribution?> -> |         ) { list: PagedList<Contribution?> -> | ||||||
|             adapter.submitList(list) |             adapter.submitList(list) | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ import java.math.BigInteger; | ||||||
| import java.security.MessageDigest; | import java.security.MessageDigest; | ||||||
| import java.security.NoSuchAlgorithmException; | import java.security.NoSuchAlgorithmException; | ||||||
| 
 | 
 | ||||||
|  | import java.util.Locale; | ||||||
| import timber.log.Timber; | import timber.log.Timber; | ||||||
| 
 | 
 | ||||||
| public class FileUtils { | public class FileUtils { | ||||||
|  | @ -139,7 +140,7 @@ public class FileUtils { | ||||||
|             String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri |             String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri | ||||||
|                     .toString()); |                     .toString()); | ||||||
|             mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( |             mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( | ||||||
|                     fileExtension.toLowerCase()); |                     fileExtension.toLowerCase(Locale.getDefault())); | ||||||
|         } |         } | ||||||
|         return mimeType; |         return mimeType; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -74,8 +74,8 @@ class PendingUploadsFragment : | ||||||
|     fun initRecyclerView() { |     fun initRecyclerView() { | ||||||
|         binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) |         binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) | ||||||
|         binding.pendingUploadsRecyclerView.adapter = adapter |         binding.pendingUploadsRecyclerView.adapter = adapter | ||||||
|         pendingUploadsPresenter!!.setup() |         pendingUploadsPresenter.setup() | ||||||
|         pendingUploadsPresenter!!.totalContributionList.observe( |         pendingUploadsPresenter.totalContributionList.observe( | ||||||
|             viewLifecycleOwner, |             viewLifecycleOwner, | ||||||
|         ) { list: PagedList<Contribution?> -> |         ) { list: PagedList<Contribution?> -> | ||||||
|             contributionsSize = list.size |             contributionsSize = list.size | ||||||
|  |  | ||||||
|  | @ -320,6 +320,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, | ||||||
|         finish(); |         finish(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * go to the uploadProgress activity to check the status of uploading | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     public void goToUploadProgressActivity() { | ||||||
|  |         startActivity(new Intent(this, UploadProgressActivity.class)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Show/Hide the progress dialog |      * Show/Hide the progress dialog | ||||||
|      */ |      */ | ||||||
|  | @ -433,14 +441,6 @@ public class UploadActivity extends BaseActivity implements UploadContract.View, | ||||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); |         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     protected void onActivityResult(int requestCode, int resultCode, Intent data) { |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data); |  | ||||||
|         if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) { |  | ||||||
|             //TODO: Confirm if handling manual permission enabled is required |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |     /** | ||||||
|      * Sets the flag indicating whether the upload is of a specific place. |      * Sets the flag indicating whether the upload is of a specific place. | ||||||
|      * |      * | ||||||
|  |  | ||||||
|  | @ -18,6 +18,13 @@ public interface UploadContract { | ||||||
| 
 | 
 | ||||||
|         void returnToMainActivity(); |         void returnToMainActivity(); | ||||||
| 
 | 
 | ||||||
|  |         /** | ||||||
|  |          * When submission successful, go to the loadProgressActivity to hint the user this | ||||||
|  |          * submission is valid. And the user will see the upload progress in this activity; | ||||||
|  |          * Fixes: <a href="https://github.com/commons-app/apps-android-commons/issues/5846">Issue</a> | ||||||
|  |          */ | ||||||
|  |         void goToUploadProgressActivity(); | ||||||
|  | 
 | ||||||
|         void askUserToLogIn(); |         void askUserToLogIn(); | ||||||
| 
 | 
 | ||||||
|         /** |         /** | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ import android.widget.ImageView; | ||||||
| import android.widget.LinearLayout; | import android.widget.LinearLayout; | ||||||
| import android.widget.ListView; | import android.widget.ListView; | ||||||
| import android.widget.TextView; | import android.widget.TextView; | ||||||
|  | import androidx.activity.result.ActivityResultLauncher; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.constraintlayout.widget.ConstraintLayout; | import androidx.constraintlayout.widget.ConstraintLayout; | ||||||
|  | @ -57,27 +58,29 @@ public class UploadMediaDetailAdapter extends | ||||||
|     private int currentPosition; |     private int currentPosition; | ||||||
|     private Fragment fragment; |     private Fragment fragment; | ||||||
|     private Activity activity; |     private Activity activity; | ||||||
|  |     private final ActivityResultLauncher<Intent> voiceInputResultLauncher; | ||||||
|     private SelectedVoiceIcon selectedVoiceIcon; |     private SelectedVoiceIcon selectedVoiceIcon; | ||||||
|     private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213; |  | ||||||
| 
 | 
 | ||||||
|     private RowItemDescriptionBinding binding; |     private RowItemDescriptionBinding binding; | ||||||
| 
 | 
 | ||||||
|     public UploadMediaDetailAdapter(Fragment fragment, String savedLanguageValue, |     public UploadMediaDetailAdapter(Fragment fragment, String savedLanguageValue, | ||||||
|         RecentLanguagesDao recentLanguagesDao) { |         RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) { | ||||||
|         uploadMediaDetails = new ArrayList<>(); |         uploadMediaDetails = new ArrayList<>(); | ||||||
|         selectedLanguages = new HashMap<>(); |         selectedLanguages = new HashMap<>(); | ||||||
|         this.savedLanguageValue = savedLanguageValue; |         this.savedLanguageValue = savedLanguageValue; | ||||||
|         this.recentLanguagesDao = recentLanguagesDao; |         this.recentLanguagesDao = recentLanguagesDao; | ||||||
|         this.fragment = fragment; |         this.fragment = fragment; | ||||||
|  |         this.voiceInputResultLauncher = voiceInputResultLauncher; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue, |     public UploadMediaDetailAdapter(Activity activity, final String savedLanguageValue, | ||||||
|         List<UploadMediaDetail> uploadMediaDetails, RecentLanguagesDao recentLanguagesDao) { |         List<UploadMediaDetail> uploadMediaDetails, RecentLanguagesDao recentLanguagesDao, ActivityResultLauncher<Intent> voiceInputResultLauncher) { | ||||||
|         this.uploadMediaDetails = uploadMediaDetails; |         this.uploadMediaDetails = uploadMediaDetails; | ||||||
|         selectedLanguages = new HashMap<>(); |         selectedLanguages = new HashMap<>(); | ||||||
|         this.savedLanguageValue = savedLanguageValue; |         this.savedLanguageValue = savedLanguageValue; | ||||||
|         this.recentLanguagesDao = recentLanguagesDao; |         this.recentLanguagesDao = recentLanguagesDao; | ||||||
|         this.activity = activity; |         this.activity = activity; | ||||||
|  |         this.voiceInputResultLauncher = voiceInputResultLauncher; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setCallback(Callback callback) { |     public void setCallback(Callback callback) { | ||||||
|  | @ -150,11 +153,7 @@ public class UploadMediaDetailAdapter extends | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if (activity == null) { |             voiceInputResultLauncher.launch(intent); | ||||||
|                 fragment.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT); |  | ||||||
|             } else { |  | ||||||
|                 activity.startActivityForResult(intent, REQUEST_CODE_FOR_VOICE_INPUT); |  | ||||||
|             } |  | ||||||
|         } catch (Exception e) { |         } catch (Exception e) { | ||||||
|             Timber.e(e.getMessage()); |             Timber.e(e.getMessage()); | ||||||
|         } |         } | ||||||
|  | @ -407,7 +406,7 @@ public class UploadMediaDetailAdapter extends | ||||||
|                             recentLanguagesDao |                             recentLanguagesDao | ||||||
|                                 .addRecentLanguage(new Language(languageName, languageCode)); |                                 .addRecentLanguage(new Language(languageName, languageCode)); | ||||||
| 
 | 
 | ||||||
|                             selectedLanguages.remove(position); |                             selectedLanguages.clear(); | ||||||
|                             selectedLanguages.put(position, languageCode); |                             selectedLanguages.put(position, languageCode); | ||||||
|                             ((LanguagesAdapter) adapterView |                             ((LanguagesAdapter) adapterView | ||||||
|                                 .getAdapter()).setSelectedLangCode(languageCode); |                                 .getAdapter()).setSelectedLangCode(languageCode); | ||||||
|  | @ -497,7 +496,7 @@ public class UploadMediaDetailAdapter extends | ||||||
|             } |             } | ||||||
|             recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode)); |             recentLanguagesDao.addRecentLanguage(new Language(languageName, languageCode)); | ||||||
| 
 | 
 | ||||||
|             selectedLanguages.remove(position); |             selectedLanguages.clear(); | ||||||
|             selectedLanguages.put(position, languageCode); |             selectedLanguages.put(position, languageCode); | ||||||
|             ((RecentLanguagesAdapter) adapterView |             ((RecentLanguagesAdapter) adapterView | ||||||
|                 .getAdapter()).setSelectedLangCode(languageCode); |                 .getAdapter()).setSelectedLangCode(languageCode); | ||||||
|  |  | ||||||
|  | @ -123,6 +123,9 @@ public class UploadPresenter implements UploadContract.UserActionListener { | ||||||
|                             view.returnToMainActivity(); |                             view.returnToMainActivity(); | ||||||
|                             compositeDisposable.clear(); |                             compositeDisposable.clear(); | ||||||
|                             Timber.e("failed to upload: " + e.getMessage()); |                             Timber.e("failed to upload: " + e.getMessage()); | ||||||
|  | 
 | ||||||
|  |                             //is submission error, not need to go to the uploadActivity | ||||||
|  |                             //not start the uploading progress | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         @Override |                         @Override | ||||||
|  | @ -131,6 +134,10 @@ public class UploadPresenter implements UploadContract.UserActionListener { | ||||||
|                             repository.cleanup(); |                             repository.cleanup(); | ||||||
|                             view.returnToMainActivity(); |                             view.returnToMainActivity(); | ||||||
|                             compositeDisposable.clear(); |                             compositeDisposable.clear(); | ||||||
|  | 
 | ||||||
|  |                             //after finish the uploadActivity, if successful, | ||||||
|  |                             //directly go to the upload progress activity | ||||||
|  |                             view.goToUploadProgressActivity(); | ||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|         } else { |         } else { | ||||||
|  |  | ||||||
|  | @ -10,15 +10,13 @@ abstract class BaseDelegateAdapter<T>( | ||||||
|     areContentsTheSame: (T, T) -> Boolean = { old, new -> old == new }, |     areContentsTheSame: (T, T) -> Boolean = { old, new -> old == new }, | ||||||
| ) : AsyncListDifferDelegationAdapter<T>( | ) : AsyncListDifferDelegationAdapter<T>( | ||||||
|         object : DiffUtil.ItemCallback<T>() { |         object : DiffUtil.ItemCallback<T>() { | ||||||
|             override fun areItemsTheSame( |             override fun areItemsTheSame(oldItem: T & Any, newItem: T & Any): Boolean { | ||||||
|                 oldItem: T, |                 return areItemsTheSame(oldItem, newItem) | ||||||
|                 newItem: T, |             } | ||||||
|             ) = areItemsTheSame(oldItem, newItem) |  | ||||||
| 
 | 
 | ||||||
|             override fun areContentsTheSame( |             override fun areContentsTheSame(oldItem: T & Any, newItem: T & Any): Boolean { | ||||||
|                 oldItem: T, |                 return areContentsTheSame(oldItem, newItem) | ||||||
|                 newItem: T, |             } | ||||||
|             ) = areContentsTheSame(oldItem, newItem) |  | ||||||
|         }, |         }, | ||||||
|         *delegates, |         *delegates, | ||||||
|     ) { |     ) { | ||||||
|  |  | ||||||
|  | @ -372,7 +372,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate | ||||||
|                 return false; |                 return false; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             Objects.requireNonNull(getView()).setFocusableInTouchMode(true); |             requireView().setFocusableInTouchMode(true); | ||||||
|             getView().requestFocus(); |             getView().requestFocus(); | ||||||
|             getView().setOnKeyListener((v, keyCode, event) -> { |             getView().setOnKeyListener((v, keyCode, event) -> { | ||||||
|                 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { |                 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { | ||||||
|  | @ -387,7 +387,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             Objects.requireNonNull( |             Objects.requireNonNull( | ||||||
|                 ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) |                 ((AppCompatActivity) requireActivity()).getSupportActionBar()) | ||||||
|                 .hide(); |                 .hide(); | ||||||
| 
 | 
 | ||||||
|             if (getParentFragment().getParentFragment().getParentFragment() |             if (getParentFragment().getParentFragment().getParentFragment() | ||||||
|  | @ -407,7 +407,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate | ||||||
|         super.onStop(); |         super.onStop(); | ||||||
|         if (media != null) { |         if (media != null) { | ||||||
|             Objects.requireNonNull( |             Objects.requireNonNull( | ||||||
|                 ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) |                 ((AppCompatActivity) requireActivity()).getSupportActionBar()) | ||||||
|                 .show(); |                 .show(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -36,7 +36,7 @@ abstract class DepictsDao { | ||||||
|     /** |     /** | ||||||
|      * Gets all Depicts objects from the database, ordered by lastUsed in descending order. |      * Gets all Depicts objects from the database, ordered by lastUsed in descending order. | ||||||
|      * |      * | ||||||
|      * @return A list of Depicts objects. |      * @return Deferred list of Depicts objects. | ||||||
|      */ |      */ | ||||||
|     fun depictsList(): Deferred<List<Depicts>> = |     fun depictsList(): Deferred<List<Depicts>> = | ||||||
|         CoroutineScope(Dispatchers.IO).async { |         CoroutineScope(Dispatchers.IO).async { | ||||||
|  | @ -48,7 +48,7 @@ abstract class DepictsDao { | ||||||
|      * |      * | ||||||
|      * @param depictedItem The Depicts object to insert. |      * @param depictedItem The Depicts object to insert. | ||||||
|      */ |      */ | ||||||
|     private fun insertDepict(depictedItem: Depicts) = |     fun insertDepict(depictedItem: Depicts) = | ||||||
|         CoroutineScope(Dispatchers.IO).launch { |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|             insert(depictedItem) |             insert(depictedItem) | ||||||
|         } |         } | ||||||
|  | @ -59,7 +59,7 @@ abstract class DepictsDao { | ||||||
|      * @param n The number of depicts to delete. |      * @param n The number of depicts to delete. | ||||||
|      * @return A list of Depicts objects to delete. |      * @return A list of Depicts objects to delete. | ||||||
|      */ |      */ | ||||||
|     private suspend fun depictsForDeletion(n: Int): Deferred<List<Depicts>> = |     fun depictsForDeletion(n: Int): Deferred<List<Depicts>> = | ||||||
|         CoroutineScope(Dispatchers.IO).async { |         CoroutineScope(Dispatchers.IO).async { | ||||||
|             getDepictsForDeletion(n) |             getDepictsForDeletion(n) | ||||||
|         } |         } | ||||||
|  | @ -69,7 +69,7 @@ abstract class DepictsDao { | ||||||
|      * |      * | ||||||
|      * @param depicts The Depicts object to delete. |      * @param depicts The Depicts object to delete. | ||||||
|      */ |      */ | ||||||
|     private suspend fun deleteDepicts(depicts: Depicts) = |     fun deleteDepicts(depicts: Depicts) = | ||||||
|         CoroutineScope(Dispatchers.IO).launch { |         CoroutineScope(Dispatchers.IO).launch { | ||||||
|             delete(depicts) |             delete(depicts) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -398,7 +398,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra | ||||||
|                 return false; |                 return false; | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             Objects.requireNonNull(getView()).setFocusableInTouchMode(true); |             requireView().setFocusableInTouchMode(true); | ||||||
|             getView().requestFocus(); |             getView().requestFocus(); | ||||||
|             getView().setOnKeyListener((v, keyCode, event) -> { |             getView().setOnKeyListener((v, keyCode, event) -> { | ||||||
|                 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { |                 if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { | ||||||
|  | @ -411,7 +411,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             Objects.requireNonNull( |             Objects.requireNonNull( | ||||||
|                 ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) |                 ((AppCompatActivity) requireActivity()).getSupportActionBar()) | ||||||
|                 .hide(); |                 .hide(); | ||||||
| 
 | 
 | ||||||
|             if (getParentFragment().getParentFragment().getParentFragment() |             if (getParentFragment().getParentFragment().getParentFragment() | ||||||
|  | @ -431,7 +431,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra | ||||||
|         super.onStop(); |         super.onStop(); | ||||||
|         if (media != null) { |         if (media != null) { | ||||||
|             Objects.requireNonNull( |             Objects.requireNonNull( | ||||||
|                 ((AppCompatActivity) Objects.requireNonNull(getActivity())).getSupportActionBar()) |                 ((AppCompatActivity) requireActivity()).getSupportActionBar()) | ||||||
|                 .show(); |                 .show(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,9 @@ import android.view.ViewGroup; | ||||||
| import android.widget.CheckBox; | import android.widget.CheckBox; | ||||||
| import android.widget.ImageView; | import android.widget.ImageView; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  | import androidx.activity.result.ActivityResult; | ||||||
|  | import androidx.activity.result.ActivityResultLauncher; | ||||||
|  | import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.exifinterface.media.ExifInterface; | import androidx.exifinterface.media.ExifInterface; | ||||||
|  | @ -58,9 +61,24 @@ import timber.log.Timber; | ||||||
| public class UploadMediaDetailFragment extends UploadBaseFragment implements | public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|     UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { |     UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener { | ||||||
| 
 | 
 | ||||||
|     private static final int REQUEST_CODE = 1211; |     private UploadMediaDetailAdapter uploadMediaDetailAdapter; | ||||||
|     private static final int REQUEST_CODE_FOR_EDIT_ACTIVITY = 1212; | 
 | ||||||
|     private static final int REQUEST_CODE_FOR_VOICE_INPUT = 1213; |     private final ActivityResultLauncher<Intent> startForResult = registerForActivityResult( | ||||||
|  |         new StartActivityForResult(), result -> { | ||||||
|  |                 onCameraPosition(result); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> startForEditActivityResult = registerForActivityResult( | ||||||
|  |         new StartActivityForResult(), result -> { | ||||||
|  |             onEditActivityResult(result); | ||||||
|  |         } | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     private final ActivityResultLauncher<Intent> voiceInputResultLauncher = registerForActivityResult( | ||||||
|  |         new StartActivityForResult(), result -> { | ||||||
|  |             onVoiceInput(result); | ||||||
|  |         } | ||||||
|  |     ); | ||||||
| 
 | 
 | ||||||
|     public static Activity activity ; |     public static Activity activity ; | ||||||
| 
 | 
 | ||||||
|  | @ -84,8 +102,6 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|     private boolean hasUserRemovedLocation; |     private boolean hasUserRemovedLocation; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     private UploadMediaDetailAdapter uploadMediaDetailAdapter; |  | ||||||
| 
 |  | ||||||
|     @Inject |     @Inject | ||||||
|     UploadMediaDetailsContract.UserActionListener presenter; |     UploadMediaDetailsContract.UserActionListener presenter; | ||||||
| 
 | 
 | ||||||
|  | @ -279,7 +295,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|      */ |      */ | ||||||
|     private void initRecyclerView() { |     private void initRecyclerView() { | ||||||
|         uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this, |         uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this, | ||||||
|             defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao); |             defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao, voiceInputResultLauncher); | ||||||
|         uploadMediaDetailAdapter.setCallback(this::showInfoAlert); |         uploadMediaDetailAdapter.setCallback(this::showInfoAlert); | ||||||
|         uploadMediaDetailAdapter.setEventListener(this); |         uploadMediaDetailAdapter.setEventListener(this); | ||||||
|         binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext())); |         binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext())); | ||||||
|  | @ -593,14 +609,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|      * This method is called to start the image editing activity for a specific UploadItem. |      * This method is called to start the image editing activity for a specific UploadItem. | ||||||
|      * It sets the UploadItem as the currently editable item, creates an intent to launch the |      * It sets the UploadItem as the currently editable item, creates an intent to launch the | ||||||
|      * EditActivity, and passes the image file path as an extra in the intent. The activity |      * EditActivity, and passes the image file path as an extra in the intent. The activity | ||||||
|      * is started with a request code, allowing the result to be handled in onActivityResult. |      * is started using resultLauncher that handles the result in respective callback. | ||||||
|      */ |      */ | ||||||
|     @Override |     @Override | ||||||
|     public void showEditActivity(UploadItem uploadItem) { |     public void showEditActivity(UploadItem uploadItem) { | ||||||
|         editableUploadItem = uploadItem; |         editableUploadItem = uploadItem; | ||||||
|         Intent intent = new Intent(getContext(), EditActivity.class); |         Intent intent = new Intent(getContext(), EditActivity.class); | ||||||
|         intent.putExtra("image", uploadableFile.getFilePath().toString()); |         intent.putExtra("image", uploadableFile.getFilePath().toString()); | ||||||
|         startActivityForResult(intent, REQUEST_CODE_FOR_EDIT_ACTIVITY); |         startForEditActivityResult.launch(intent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -615,6 +631,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|         double defaultLongitude = -122.431297; |         double defaultLongitude = -122.431297; | ||||||
|         double defaultZoom = 16.0; |         double defaultZoom = 16.0; | ||||||
| 
 | 
 | ||||||
|  |         final Intent locationPickerIntent; | ||||||
|  | 
 | ||||||
|         /* Retrieve image location from EXIF if present or |         /* Retrieve image location from EXIF if present or | ||||||
|            check if user has provided location while using the in-app camera. |            check if user has provided location while using the in-app camera. | ||||||
|            Use location of last UploadItem if none of them is available */ |            Use location of last UploadItem if none of them is available */ | ||||||
|  | @ -624,10 +642,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|                 .getDecLatitude(); |                 .getDecLatitude(); | ||||||
|             defaultLongitude = uploadItem.getGpsCoords().getDecLongitude(); |             defaultLongitude = uploadItem.getGpsCoords().getDecLongitude(); | ||||||
|             defaultZoom = uploadItem.getGpsCoords().getZoomLevel(); |             defaultZoom = uploadItem.getGpsCoords().getZoomLevel(); | ||||||
|             startActivityForResult(new LocationPicker.IntentBuilder() | 
 | ||||||
|  |             locationPickerIntent = new LocationPicker.IntentBuilder() | ||||||
|                 .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) |                 .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) | ||||||
|                 .activityKey("UploadActivity") |                 .activityKey("UploadActivity") | ||||||
|                 .build(getActivity()), REQUEST_CODE); |                 .build(getActivity()); | ||||||
|         } else { |         } else { | ||||||
|             if (defaultKvStore.getString(LAST_LOCATION) != null) { |             if (defaultKvStore.getString(LAST_LOCATION) != null) { | ||||||
|                 final String[] locationLatLng |                 final String[] locationLatLng | ||||||
|  | @ -638,27 +657,20 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|             if (defaultKvStore.getString(LAST_ZOOM) != null) { |             if (defaultKvStore.getString(LAST_ZOOM) != null) { | ||||||
|                 defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM)); |                 defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM)); | ||||||
|             } |             } | ||||||
|             startActivityForResult(new LocationPicker.IntentBuilder() | 
 | ||||||
|  |             locationPickerIntent = new LocationPicker.IntentBuilder() | ||||||
|                 .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) |                 .defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom)) | ||||||
|                 .activityKey("NoLocationUploadActivity") |                 .activityKey("NoLocationUploadActivity") | ||||||
|                 .build(getActivity()), REQUEST_CODE); |                 .build(getActivity()); | ||||||
|         } |         } | ||||||
|  |         startForResult.launch(locationPickerIntent); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     private void onCameraPosition(ActivityResult result){ | ||||||
|      * Get the coordinates and update the existing coordinates. |         if (result.getResultCode() == RESULT_OK) { | ||||||
|      * @param requestCode code of request |  | ||||||
|      * @param resultCode code of result |  | ||||||
|      * @param data intent |  | ||||||
|      */ |  | ||||||
|     @Override |  | ||||||
|     public void onActivityResult(final int requestCode, final int resultCode, |  | ||||||
|         @Nullable final Intent data) { |  | ||||||
|         super.onActivityResult(requestCode, resultCode, data); |  | ||||||
|         if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { |  | ||||||
| 
 | 
 | ||||||
|             assert data != null; |             assert result.getData() != null; | ||||||
|             final CameraPosition cameraPosition = LocationPicker.getCameraPosition(data); |             final CameraPosition cameraPosition = LocationPicker.getCameraPosition(result.getData()); | ||||||
| 
 | 
 | ||||||
|             if (cameraPosition != null) { |             if (cameraPosition != null) { | ||||||
| 
 | 
 | ||||||
|  | @ -678,8 +690,21 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|                 removeLocation(); |                 removeLocation(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (requestCode == REQUEST_CODE_FOR_EDIT_ACTIVITY && resultCode == RESULT_OK) { |     } | ||||||
|             String result = data.getStringExtra("editedImageFilePath"); | 
 | ||||||
|  |     private void onVoiceInput(ActivityResult result) { | ||||||
|  |         if (result.getResultCode() == RESULT_OK && result.getData() != null) { | ||||||
|  |             ArrayList<String> resultData = result.getData().getStringArrayListExtra( | ||||||
|  |                 RecognizerIntent.EXTRA_RESULTS); | ||||||
|  |             uploadMediaDetailAdapter.handleSpeechResult(resultData.get(0)); | ||||||
|  |         }else { | ||||||
|  |             Timber.e("Error %s", result.getResultCode()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void onEditActivityResult(ActivityResult result){ | ||||||
|  |         if (result.getResultCode() == RESULT_OK) { | ||||||
|  |             String path = result.getData().getStringExtra("editedImageFilePath"); | ||||||
| 
 | 
 | ||||||
|             if (Objects.equals(result, "Error")) { |             if (Objects.equals(result, "Error")) { | ||||||
|                 Timber.e("Error in rotating image"); |                 Timber.e("Error in rotating image"); | ||||||
|  | @ -687,24 +712,15 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|             } |             } | ||||||
|             try { |             try { | ||||||
|                 if (binding != null){ |                 if (binding != null){ | ||||||
|                     binding.backgroundImage.setImageURI(Uri.fromFile(new File(result))); |                     binding.backgroundImage.setImageURI(Uri.fromFile(new File(path))); | ||||||
|                 } |                 } | ||||||
|                 editableUploadItem.setContentUri(Uri.fromFile(new File(result))); |                 editableUploadItem.setContentUri(Uri.fromFile(new File(path))); | ||||||
|                 callback.changeThumbnail(indexOfFragment, |                 callback.changeThumbnail(indexOfFragment, | ||||||
|                     result); |                     path); | ||||||
|             } catch (Exception e) { |             } catch (Exception e) { | ||||||
|                 Timber.e(e); |                 Timber.e(e); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         else if (requestCode == REQUEST_CODE_FOR_VOICE_INPUT) { |  | ||||||
|             if (resultCode == RESULT_OK && data != null) { |  | ||||||
|                 ArrayList<String> result = data.getStringArrayListExtra( |  | ||||||
|                     RecognizerIntent.EXTRA_RESULTS); |  | ||||||
|                 uploadMediaDetailAdapter.handleSpeechResult(result.get(0)); |  | ||||||
|             }else { |  | ||||||
|                 Timber.e("Error %s", resultCode); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | @ -809,7 +825,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements | ||||||
|     @Override |     @Override | ||||||
|     public void displayAddLocationDialog(final Runnable onSkipClicked) { |     public void displayAddLocationDialog(final Runnable onSkipClicked) { | ||||||
|         isMissingLocationDialog = true; |         isMissingLocationDialog = true; | ||||||
|         DialogUtil.showAlertDialog(Objects.requireNonNull(getActivity()), |         DialogUtil.showAlertDialog(requireActivity(), | ||||||
|             getString(R.string.no_location_found_title), |             getString(R.string.no_location_found_title), | ||||||
|             getString(R.string.no_location_found_message), |             getString(R.string.no_location_found_message), | ||||||
|             getString(R.string.add_location), |             getString(R.string.add_location), | ||||||
|  |  | ||||||
|  | @ -129,9 +129,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt | ||||||
|                         if (place.location != null) { |                         if (place.location != null) { | ||||||
|                             final String countryCode = reverseGeoCode(place.location); |                             final String countryCode = reverseGeoCode(place.location); | ||||||
|                             if (countryCode != null && WLM_SUPPORTED_COUNTRIES |                             if (countryCode != null && WLM_SUPPORTED_COUNTRIES | ||||||
|                                 .contains(countryCode.toLowerCase())) { |                                 .contains(countryCode.toLowerCase(Locale.ROOT))) { | ||||||
|                                 uploadItem.setWLMUpload(true); |                                 uploadItem.setWLMUpload(true); | ||||||
|                                 uploadItem.setCountryCode(countryCode.toLowerCase()); |                                 uploadItem.setCountryCode(countryCode.toLowerCase(Locale.ROOT)); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ import androidx.work.Data | ||||||
| import androidx.work.ForegroundInfo | import androidx.work.ForegroundInfo | ||||||
| import androidx.work.WorkerParameters | import androidx.work.WorkerParameters | ||||||
| import dagger.android.ContributesAndroidInjector | import dagger.android.ContributesAndroidInjector | ||||||
|  | import fr.free.nrw.commons.BuildConfig.HOME_URL | ||||||
| import fr.free.nrw.commons.CommonsApplication | import fr.free.nrw.commons.CommonsApplication | ||||||
| import fr.free.nrw.commons.Media | import fr.free.nrw.commons.Media | ||||||
| import fr.free.nrw.commons.R | import fr.free.nrw.commons.R | ||||||
|  | @ -30,6 +31,7 @@ import fr.free.nrw.commons.customselector.database.UploadedStatus | ||||||
| import fr.free.nrw.commons.customselector.database.UploadedStatusDao | import fr.free.nrw.commons.customselector.database.UploadedStatusDao | ||||||
| import fr.free.nrw.commons.di.ApplicationlessInjection | import fr.free.nrw.commons.di.ApplicationlessInjection | ||||||
| import fr.free.nrw.commons.media.MediaClient | import fr.free.nrw.commons.media.MediaClient | ||||||
|  | import fr.free.nrw.commons.nearby.PlacesRepository | ||||||
| import fr.free.nrw.commons.theme.BaseActivity | import fr.free.nrw.commons.theme.BaseActivity | ||||||
| import fr.free.nrw.commons.upload.FileUtilsWrapper | import fr.free.nrw.commons.upload.FileUtilsWrapper | ||||||
| import fr.free.nrw.commons.upload.StashUploadResult | import fr.free.nrw.commons.upload.StashUploadResult | ||||||
|  | @ -38,8 +40,9 @@ import fr.free.nrw.commons.upload.UploadClient | ||||||
| import fr.free.nrw.commons.upload.UploadProgressActivity | import fr.free.nrw.commons.upload.UploadProgressActivity | ||||||
| import fr.free.nrw.commons.upload.UploadResult | import fr.free.nrw.commons.upload.UploadResult | ||||||
| import fr.free.nrw.commons.wikidata.WikidataEditService | import fr.free.nrw.commons.wikidata.WikidataEditService | ||||||
|  | import io.reactivex.schedulers.Schedulers | ||||||
|  | import kotlinx.coroutines.CoroutineScope | ||||||
| import kotlinx.coroutines.Dispatchers | import kotlinx.coroutines.Dispatchers | ||||||
| import kotlinx.coroutines.MainScope |  | ||||||
| import kotlinx.coroutines.launch | import kotlinx.coroutines.launch | ||||||
| import kotlinx.coroutines.withContext | import kotlinx.coroutines.withContext | ||||||
| import timber.log.Timber | import timber.log.Timber | ||||||
|  | @ -74,6 +77,9 @@ class UploadWorker( | ||||||
|     @Inject |     @Inject | ||||||
|     lateinit var fileUtilsWrapper: FileUtilsWrapper |     lateinit var fileUtilsWrapper: FileUtilsWrapper | ||||||
| 
 | 
 | ||||||
|  |     @Inject | ||||||
|  |     lateinit var placesRepository: PlacesRepository | ||||||
|  | 
 | ||||||
|     private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag" |     private val processingUploadsNotificationTag = BuildConfig.APPLICATION_ID + " : upload_tag" | ||||||
| 
 | 
 | ||||||
|     private val processingUploadsNotificationId = 101 |     private val processingUploadsNotificationId = 101 | ||||||
|  | @ -379,7 +385,7 @@ class UploadWorker( | ||||||
|                                 saveCompletedContribution(contribution, uploadResult) |                                 saveCompletedContribution(contribution, uploadResult) | ||||||
|                             } else { |                             } else { | ||||||
|                                 Timber.d( |                                 Timber.d( | ||||||
|                                     "WikiDataEdit not required, making wikidata edit", |                                     "WikiDataEdit required, making wikidata edit", | ||||||
|                                 ) |                                 ) | ||||||
|                                 makeWikiDataEdit(uploadResult, contribution) |                                 makeWikiDataEdit(uploadResult, contribution) | ||||||
|                             } |                             } | ||||||
|  | @ -432,7 +438,7 @@ class UploadWorker( | ||||||
|                                 username, |                                 username, | ||||||
|                             ) |                             ) | ||||||
|                         CommonsApplication |                         CommonsApplication | ||||||
|                             .getInstance() |                             .instance!! | ||||||
|                             .clearApplicationData(appContext, logoutListener) |                             .clearApplicationData(appContext, logoutListener) | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | @ -471,6 +477,16 @@ class UploadWorker( | ||||||
|                             contribution.media.captions, |                             contribution.media.captions, | ||||||
|                         ) |                         ) | ||||||
|                     if (null != revisionID) { |                     if (null != revisionID) { | ||||||
|  |                         withContext(Dispatchers.IO) { | ||||||
|  |                             val place = placesRepository.fetchPlace(wikiDataPlace.id); | ||||||
|  |                             place.name = wikiDataPlace.name; | ||||||
|  |                             place.pic = HOME_URL + uploadResult.createCanonicalFileName() | ||||||
|  |                             placesRepository | ||||||
|  |                                 .save(place) | ||||||
|  |                                 .subscribeOn(Schedulers.io()) | ||||||
|  |                                 .blockingAwait() | ||||||
|  |                             Timber.d("Updated WikiItem place ${place.name} with image ${place.pic}") | ||||||
|  |                         } | ||||||
|                         showSuccessNotification(contribution) |                         showSuccessNotification(contribution) | ||||||
|                     } |                     } | ||||||
|                 } catch (exception: Exception) { |                 } catch (exception: Exception) { | ||||||
|  | @ -518,7 +534,7 @@ class UploadWorker( | ||||||
|         contribution.contentUri?.let { |         contribution.contentUri?.let { | ||||||
|             val imageSha1 = contribution.imageSHA1.toString() |             val imageSha1 = contribution.imageSHA1.toString() | ||||||
|             val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path)) |             val modifiedSha1 = fileUtilsWrapper.getSHA1(fileUtilsWrapper.getFileInputStream(contribution.localUri?.path)) | ||||||
|             MainScope().launch { |             CoroutineScope(Dispatchers.IO).launch { | ||||||
|                 uploadedStatusDao.insertUploaded( |                 uploadedStatusDao.insertUploaded( | ||||||
|                     UploadedStatus( |                     UploadedStatus( | ||||||
|                         imageSha1, |                         imageSha1, | ||||||
|  |  | ||||||
|  | @ -59,8 +59,7 @@ public class PermissionUtils { | ||||||
|         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); |         final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); | ||||||
|         final Uri uri = Uri.fromParts("package", activity.getPackageName(), null); |         final Uri uri = Uri.fromParts("package", activity.getPackageName(), null); | ||||||
|         intent.setData(uri); |         intent.setData(uri); | ||||||
|         activity.startActivityForResult(intent, |         activity.startActivity(intent); | ||||||
|             CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  |  | ||||||
							
								
								
									
										18
									
								
								app/src/main/res/drawable/ic_refresh_24dp_nearby.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								app/src/main/res/drawable/ic_refresh_24dp_nearby.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,18 @@ | ||||||
|  | <vector xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |   android:width="@dimen/half_standard_height" | ||||||
|  |   android:height="@dimen/half_standard_height" | ||||||
|  |   android:viewportHeight="24.0" | ||||||
|  |   android:viewportWidth="24.0"> | ||||||
|  | 
 | ||||||
|  |     <group | ||||||
|  |       android:scaleX="1.0" | ||||||
|  |     android:scaleY="1.0" | ||||||
|  |     android:translateX="-0.0" | ||||||
|  |     android:translateY="-0.0"> | ||||||
|  | 
 | ||||||
|  |     <path | ||||||
|  |       android:fillColor="?attr/menu_item_tint" | ||||||
|  |       android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/> | ||||||
|  | 
 | ||||||
|  | </group> | ||||||
|  |   </vector> | ||||||
|  | @ -36,11 +36,11 @@ | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_marginStart="16dp" |       android:layout_marginStart="16dp" | ||||||
|       android:contentDescription="@string/exit_location_picker" |       android:contentDescription="@string/exit_location_picker" | ||||||
|       android:tint="@color/white" |  | ||||||
|       app:layout_constraintBottom_toBottomOf="parent" |       app:layout_constraintBottom_toBottomOf="parent" | ||||||
|       app:layout_constraintLeft_toLeftOf="parent" |       app:layout_constraintLeft_toLeftOf="parent" | ||||||
|       app:layout_constraintTop_toTopOf="parent" |       app:layout_constraintTop_toTopOf="parent" | ||||||
|       app:srcCompat="@drawable/ic_arrow_back_white" /> |       app:srcCompat="@drawable/ic_arrow_back_white" | ||||||
|  |       app:tint="@color/white" /> | ||||||
| 
 | 
 | ||||||
|   </androidx.constraintlayout.widget.ConstraintLayout> |   </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -69,7 +69,7 @@ | ||||||
|       android:id="@+id/btn_edit_submit" |       android:id="@+id/btn_edit_submit" | ||||||
|       android:layout_width="wrap_content" |       android:layout_width="wrap_content" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_alignParentRight="true" |       android:layout_alignParentEnd="true" | ||||||
|       android:text="@string/submit" |       android:text="@string/submit" | ||||||
|       android:textColor="@android:color/white" /> |       android:textColor="@android:color/white" /> | ||||||
|   </RelativeLayout> |   </RelativeLayout> | ||||||
|  |  | ||||||
|  | @ -31,7 +31,7 @@ | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:textSize="16sp" |                 android:textSize="16sp" | ||||||
|                 android:layout_marginRight="50dp" |                 android:layout_marginEnd="50dp" | ||||||
|                 android:maxLines="2" |                 android:maxLines="2" | ||||||
|                 android:ellipsize="end" |                 android:ellipsize="end" | ||||||
|                 /> |                 /> | ||||||
|  | @ -58,6 +58,7 @@ | ||||||
|         android:layout_width="@dimen/dimen_0" |         android:layout_width="@dimen/dimen_0" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_weight="1" |         android:layout_weight="1" | ||||||
|  |         android:focusable="true" | ||||||
|         android:padding="@dimen/standard_gap" |         android:padding="@dimen/standard_gap" | ||||||
|         android:clickable="true" |         android:clickable="true" | ||||||
|         android:background="@drawable/button_background_selector" |         android:background="@drawable/button_background_selector" | ||||||
|  | @ -69,8 +70,7 @@ | ||||||
|             android:layout_gravity="center_horizontal" |             android:layout_gravity="center_horizontal" | ||||||
|             android:duplicateParentState="true" |             android:duplicateParentState="true" | ||||||
|             app:srcCompat="@drawable/ic_directions_black_24dp" |             app:srcCompat="@drawable/ic_directions_black_24dp" | ||||||
|             android:tint="?attr/rowButtonColor" |             app:tint="?attr/rowButtonColor" /> | ||||||
|             /> |  | ||||||
|         <TextView |         <TextView | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|  | @ -89,6 +89,7 @@ | ||||||
|         android:layout_width="@dimen/dimen_0" |         android:layout_width="@dimen/dimen_0" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_weight="1" |         android:layout_weight="1" | ||||||
|  |         android:focusable="true" | ||||||
|         android:padding="@dimen/standard_gap" |         android:padding="@dimen/standard_gap" | ||||||
|         android:clickable="true" |         android:clickable="true" | ||||||
|         android:background="@drawable/button_background_selector" |         android:background="@drawable/button_background_selector" | ||||||
|  | @ -118,6 +119,7 @@ | ||||||
|       android:layout_width="@dimen/dimen_0" |       android:layout_width="@dimen/dimen_0" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_weight="1" |       android:layout_weight="1" | ||||||
|  |       android:focusable="true" | ||||||
|       android:padding="@dimen/standard_gap" |       android:padding="@dimen/standard_gap" | ||||||
|       android:clickable="true" |       android:clickable="true" | ||||||
|       android:background="@drawable/button_background_selector" |       android:background="@drawable/button_background_selector" | ||||||
|  | @ -153,8 +155,8 @@ | ||||||
|     android:id="@+id/description" |     android:id="@+id/description" | ||||||
|     android:layout_width="match_parent" |     android:layout_width="match_parent" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:layout_marginLeft="@dimen/large_height" |     android:layout_marginStart="@dimen/large_height" | ||||||
|     android:layout_marginRight="@dimen/standard_gap" |     android:layout_marginEnd="@dimen/standard_gap" | ||||||
|     android:layout_marginBottom="@dimen/standard_gap" |     android:layout_marginBottom="@dimen/standard_gap" | ||||||
|     android:textSize="16sp" /> |     android:textSize="16sp" /> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| <?xml version="1.0" encoding="utf-8"?> | <?xml version="1.0" encoding="utf-8"?> | ||||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |   xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|   android:id="@+id/bookmarkButton" |   android:id="@+id/bookmarkButton" | ||||||
|   android:layout_width="match_parent" |   android:layout_width="match_parent" | ||||||
|   android:layout_height="wrap_content" |   android:layout_height="wrap_content" | ||||||
|   android:layout_columnWeight="1" |   android:layout_columnWeight="1" | ||||||
|   android:background="@drawable/button_background_selector" |   android:background="@drawable/button_background_selector" | ||||||
|   android:clickable="true" |   android:clickable="true" | ||||||
|  |   android:focusable="true" | ||||||
|   android:orientation="vertical" |   android:orientation="vertical" | ||||||
|   android:padding="@dimen/standard_gap"> |   android:padding="@dimen/standard_gap"> | ||||||
| 
 | 
 | ||||||
|  | @ -14,7 +16,7 @@ | ||||||
|     android:layout_width="wrap_content" |     android:layout_width="wrap_content" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:layout_gravity="center_horizontal" |     android:layout_gravity="center_horizontal" | ||||||
|     android:tint="?attr/rowButtonColor" /> |     app:tint="?attr/rowButtonColor" /> | ||||||
| 
 | 
 | ||||||
|   <TextView |   <TextView | ||||||
|     android:id="@+id/buttonText" |     android:id="@+id/buttonText" | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ | ||||||
|         <RelativeLayout |         <RelativeLayout | ||||||
|           android:layout_width="match_parent" |           android:layout_width="match_parent" | ||||||
|           android:layout_height="wrap_content" |           android:layout_height="wrap_content" | ||||||
|           android:layout_below="@+id/toolbar" |  | ||||||
|           android:background="?attr/achievementBackground" |           android:background="?attr/achievementBackground" | ||||||
|           android:orientation="vertical"> |           android:orientation="vertical"> | ||||||
| 
 | 
 | ||||||
|  | @ -36,7 +35,6 @@ | ||||||
|             style="?android:textAppearanceLarge" |             style="?android:textAppearanceLarge" | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|             android:layout_marginStart="@dimen/activity_margin_horizontal" |             android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|             android:layout_marginTop="@dimen/activity_margin_horizontal" |             android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|             android:text="@string/level" |             android:text="@string/level" | ||||||
|  | @ -48,13 +46,11 @@ | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:layout_marginTop="@dimen/activity_margin_vertical" |             android:layout_marginTop="@dimen/activity_margin_vertical" | ||||||
|             android:layout_marginRight="@dimen/activity_margin_horizontal" |  | ||||||
|             android:layout_marginEnd="@dimen/activity_margin_horizontal" |             android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|             android:layout_alignParentRight="true" |  | ||||||
|             android:layout_alignParentEnd="true" |             android:layout_alignParentEnd="true" | ||||||
|             app:srcCompat="@drawable/ic_info_outline_24dp" |             app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|             android:tint="@color/black" |             android:layout_marginVertical="@dimen/activity_margin_vertical" | ||||||
|             android:layout_marginVertical="@dimen/activity_margin_vertical" /> |             app:tint="@color/black" /> | ||||||
| 
 | 
 | ||||||
|           <androidx.constraintlayout.widget.ConstraintLayout |           <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|             android:id="@+id/badge_layout" |             android:id="@+id/badge_layout" | ||||||
|  | @ -108,7 +104,6 @@ | ||||||
|                 style="?android:textAppearanceMedium" |                 style="?android:textAppearanceMedium" | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal" |                 android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|                 android:id="@+id/images_upload_text_param" |                 android:id="@+id/images_upload_text_param" | ||||||
|                 android:layout_marginTop="@dimen/achievements_activity_margin_vertical" |                 android:layout_marginTop="@dimen/achievements_activity_margin_vertical" | ||||||
|  | @ -120,12 +115,10 @@ | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |                 android:layout_marginRight="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_toRightOf="@+id/images_upload_text_param" |  | ||||||
|                 android:layout_toEndOf="@+id/images_upload_text_param" |  | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" |  | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |                 android:layout_marginLeft="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal"/> |                 android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|  |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -189,7 +182,6 @@ | ||||||
|                 style="?android:textAppearanceMedium" |                 style="?android:textAppearanceMedium" | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:id="@+id/images_reverted_text" |                 android:id="@+id/images_reverted_text" | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal" |                 android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|                 android:text="@string/image_reverts" /> |                 android:text="@string/image_reverts" /> | ||||||
|  | @ -200,24 +192,19 @@ | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |                 android:layout_marginRight="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_toRightOf="@+id/images_reverted_text" |  | ||||||
|                 android:layout_toEndOf="@+id/images_reverted_text" |  | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" |  | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |                 android:layout_marginLeft="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal"/> |                 android:layout_marginStart="@dimen/activity_margin_horizontal" app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|             <TextView |             <TextView | ||||||
|               android:layout_width="wrap_content" |               android:layout_width="wrap_content" | ||||||
|               android:layout_height="wrap_content" |               android:layout_height="wrap_content" | ||||||
|               android:text="@string/achievements_revert_limit_message" |               android:text="@string/achievements_revert_limit_message" | ||||||
|               android:textSize="@dimen/small_text" |               android:textSize="@dimen/small_text" | ||||||
|               android:id="@+id/images_revert_limit_text" |               android:id="@+id/images_revert_limit_text" | ||||||
|               android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|               android:layout_marginStart="@dimen/activity_margin_horizontal" |               android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_below="@id/images_reverted_info"/> |               android:layout_below="@id/images_reverted_info"/> | ||||||
| 
 | 
 | ||||||
|  | @ -278,7 +265,6 @@ | ||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:id="@+id/images_used_by_wiki_text" |                 android:id="@+id/images_used_by_wiki_text" | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal" |                 android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginTop="@dimen/achievements_activity_margin_vertical" |                 android:layout_marginTop="@dimen/achievements_activity_margin_vertical" | ||||||
|                 android:text="@string/images_used_by_wiki" /> |                 android:text="@string/images_used_by_wiki" /> | ||||||
|  | @ -289,12 +275,10 @@ | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |                 android:layout_marginRight="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_toRightOf="@+id/images_used_by_wiki_text" |  | ||||||
|                 android:layout_toEndOf="@+id/images_used_by_wiki_text" |  | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" |  | ||||||
|                 android:layout_marginLeft="@dimen/activity_margin_horizontal" |                 android:layout_marginLeft="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginStart="@dimen/activity_margin_horizontal"/> |                 android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|  |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </LinearLayout> |             </LinearLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -353,7 +337,6 @@ | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:text="@string/statistics" |             android:text="@string/statistics" | ||||||
|             style="?android:textAppearanceLarge" |             style="?android:textAppearanceLarge" | ||||||
|             android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|             android:layout_marginStart="@dimen/activity_margin_horizontal" |             android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|             android:layout_marginTop="@dimen/activity_margin_vertical" |             android:layout_marginTop="@dimen/activity_margin_vertical" | ||||||
|             android:textAllCaps="true"/> |             android:textAllCaps="true"/> | ||||||
|  | @ -373,9 +356,7 @@ | ||||||
|               android:id="@+id/images_nearby_info" |               android:id="@+id/images_nearby_info" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               android:layout_alignParentStart="true" |               android:layout_alignParentStart="true" | ||||||
|               android:layout_alignParentLeft="true" |  | ||||||
|               android:layout_toStartOf="@+id/wikidata_edits" |               android:layout_toStartOf="@+id/wikidata_edits" | ||||||
|               android:layout_toLeftOf="@+id/wikidata_edits" |  | ||||||
|               android:orientation="horizontal" |               android:orientation="horizontal" | ||||||
|               android:gravity="center_vertical"> |               android:gravity="center_vertical"> | ||||||
| 
 | 
 | ||||||
|  | @ -407,14 +388,13 @@ | ||||||
|                 android:layout_height="@dimen/medium_height" |                 android:layout_height="@dimen/medium_height" | ||||||
|                 android:id="@+id/images_nearby_info_icon" |                 android:id="@+id/images_nearby_info_icon" | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_gravity="top" |                 android:layout_gravity="top" | ||||||
|                 app:layout_constraintLeft_toRightOf="@id/images_nearby_data" |                 app:layout_constraintLeft_toRightOf="@id/images_nearby_data" | ||||||
|                 app:layout_constraintTop_toTopOf="parent" |                 app:layout_constraintTop_toTopOf="parent" | ||||||
|                 app:layout_constraintRight_toRightOf="parent" |                 app:layout_constraintRight_toRightOf="parent" | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" /> |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </androidx.constraintlayout.widget.ConstraintLayout> |             </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -423,16 +403,14 @@ | ||||||
|               android:layout_width="wrap_content" |               android:layout_width="wrap_content" | ||||||
|               android:layout_height="wrap_content" |               android:layout_height="wrap_content" | ||||||
|               style="?android:textAppearanceMedium" |               style="?android:textAppearanceMedium" | ||||||
|               android:layout_alignParentRight="true" |  | ||||||
|               android:layout_alignParentEnd="true" |               android:layout_alignParentEnd="true" | ||||||
|               android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|               android:layout_marginEnd="@dimen/half_standard_height" |               android:layout_marginEnd="@dimen/half_standard_height" | ||||||
|               android:layout_marginTop="@dimen/activity_margin_horizontal" |               android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_marginStart="@dimen/activity_margin_horizontal" |               android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               tools:text="2" |               tools:text="2" | ||||||
|               android:id="@+id/wikidata_edits" |               android:id="@+id/wikidata_edits" | ||||||
|               android:layout_marginRight="@dimen/half_standard_height" /> |               /> | ||||||
| 
 | 
 | ||||||
|           </RelativeLayout> |           </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -451,9 +429,7 @@ | ||||||
|               android:id="@+id/images_featured_info" |               android:id="@+id/images_featured_info" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               android:layout_alignParentStart="true" |               android:layout_alignParentStart="true" | ||||||
|               android:layout_alignParentLeft="true" |  | ||||||
|               android:layout_toStartOf="@+id/image_featured" |               android:layout_toStartOf="@+id/image_featured" | ||||||
|               android:layout_toLeftOf="@+id/image_featured" |  | ||||||
|               android:orientation="horizontal" |               android:orientation="horizontal" | ||||||
|               android:gravity="center_vertical"> |               android:gravity="center_vertical"> | ||||||
| 
 | 
 | ||||||
|  | @ -486,14 +462,13 @@ | ||||||
|                 android:layout_height="@dimen/medium_height" |                 android:layout_height="@dimen/medium_height" | ||||||
|                 android:id="@+id/images_featured_info_icon" |                 android:id="@+id/images_featured_info_icon" | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 app:layout_constraintLeft_toRightOf="@id/images_featured_data" |                 app:layout_constraintLeft_toRightOf="@id/images_featured_data" | ||||||
|                 app:layout_constraintTop_toTopOf="parent" |                 app:layout_constraintTop_toTopOf="parent" | ||||||
|                 app:layout_constraintRight_toRightOf="parent" |                 app:layout_constraintRight_toRightOf="parent" | ||||||
|                 android:layout_gravity="top" |                 android:layout_gravity="top" | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" /> |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </androidx.constraintlayout.widget.ConstraintLayout> |             </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -501,16 +476,14 @@ | ||||||
|               android:layout_width="wrap_content" |               android:layout_width="wrap_content" | ||||||
|               android:layout_height="wrap_content" |               android:layout_height="wrap_content" | ||||||
|               style="?android:textAppearanceMedium" |               style="?android:textAppearanceMedium" | ||||||
|               android:layout_alignParentRight="true" |  | ||||||
|               android:layout_alignParentEnd="true" |               android:layout_alignParentEnd="true" | ||||||
|               android:layout_marginTop="@dimen/activity_margin_horizontal" |               android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_marginStart="@dimen/activity_margin_horizontal" |               android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               tools:text="2" |               tools:text="2" | ||||||
|               android:id="@+id/image_featured" |               android:id="@+id/image_featured" | ||||||
|               android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|               android:layout_marginEnd="@dimen/half_standard_height" |               android:layout_marginEnd="@dimen/half_standard_height" | ||||||
|               android:layout_marginRight="@dimen/half_standard_height" /> |               /> | ||||||
| 
 | 
 | ||||||
|           </RelativeLayout> |           </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -529,9 +502,7 @@ | ||||||
|               android:id="@+id/quality_images_info" |               android:id="@+id/quality_images_info" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               android:layout_alignParentStart="true" |               android:layout_alignParentStart="true" | ||||||
|               android:layout_alignParentLeft="true" |  | ||||||
|               android:layout_toStartOf="@+id/quality_images" |               android:layout_toStartOf="@+id/quality_images" | ||||||
|               android:layout_toLeftOf="@+id/quality_images" |  | ||||||
|               android:orientation="horizontal" |               android:orientation="horizontal" | ||||||
|               android:gravity="center_vertical"> |               android:gravity="center_vertical"> | ||||||
| 
 | 
 | ||||||
|  | @ -564,14 +535,13 @@ | ||||||
|                 android:layout_height="@dimen/medium_height" |                 android:layout_height="@dimen/medium_height" | ||||||
|                 android:id="@+id/quality_images_info_icon" |                 android:id="@+id/quality_images_info_icon" | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 app:layout_constraintLeft_toRightOf="@id/quality_images_data" |                 app:layout_constraintLeft_toRightOf="@id/quality_images_data" | ||||||
|                 app:layout_constraintTop_toTopOf="parent" |                 app:layout_constraintTop_toTopOf="parent" | ||||||
|                 app:layout_constraintRight_toRightOf="parent" |                 app:layout_constraintRight_toRightOf="parent" | ||||||
|                 android:layout_gravity="top" |                 android:layout_gravity="top" | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" /> |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </androidx.constraintlayout.widget.ConstraintLayout> |             </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -579,7 +549,6 @@ | ||||||
|               android:layout_width="wrap_content" |               android:layout_width="wrap_content" | ||||||
|               android:layout_height="wrap_content" |               android:layout_height="wrap_content" | ||||||
|               style="?android:textAppearanceMedium" |               style="?android:textAppearanceMedium" | ||||||
|               android:layout_alignParentRight="true" |  | ||||||
|               android:layout_alignParentEnd="true" |               android:layout_alignParentEnd="true" | ||||||
|               android:layout_marginTop="@dimen/activity_margin_horizontal" |               android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_marginStart="@dimen/activity_margin_horizontal" |               android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|  | @ -587,9 +556,8 @@ | ||||||
|               tools:text="2" |               tools:text="2" | ||||||
|               android:text="0" |               android:text="0" | ||||||
|               android:id="@+id/quality_images" |               android:id="@+id/quality_images" | ||||||
|               android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|               android:layout_marginEnd="@dimen/half_standard_height" |               android:layout_marginEnd="@dimen/half_standard_height" | ||||||
|               android:layout_marginRight="@dimen/half_standard_height" /> |               /> | ||||||
| 
 | 
 | ||||||
|           </RelativeLayout> |           </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -608,9 +576,7 @@ | ||||||
|               android:id="@+id/thanks_received_info" |               android:id="@+id/thanks_received_info" | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               android:layout_alignParentStart="true" |               android:layout_alignParentStart="true" | ||||||
|               android:layout_alignParentLeft="true" |  | ||||||
|               android:layout_toStartOf="@+id/thanks_received" |               android:layout_toStartOf="@+id/thanks_received" | ||||||
|               android:layout_toLeftOf="@+id/thanks_received" |  | ||||||
|               android:orientation="horizontal" |               android:orientation="horizontal" | ||||||
|               android:gravity="center_vertical"> |               android:gravity="center_vertical"> | ||||||
| 
 | 
 | ||||||
|  | @ -643,14 +609,13 @@ | ||||||
|                 android:layout_height="@dimen/medium_height" |                 android:layout_height="@dimen/medium_height" | ||||||
|                 android:id="@+id/thanks_received_info_icon" |                 android:id="@+id/thanks_received_info_icon" | ||||||
|                 android:layout_marginTop="@dimen/activity_margin_horizontal" |                 android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|                 android:layout_marginRight="@dimen/activity_margin_horizontal" |  | ||||||
|                 android:layout_marginEnd="@dimen/activity_margin_horizontal" |                 android:layout_marginEnd="@dimen/activity_margin_horizontal" | ||||||
|                 app:layout_constraintLeft_toRightOf="@id/thanks_received_data" |                 app:layout_constraintLeft_toRightOf="@id/thanks_received_data" | ||||||
|                 app:layout_constraintTop_toTopOf="parent" |                 app:layout_constraintTop_toTopOf="parent" | ||||||
|                 app:layout_constraintRight_toRightOf="parent" |                 app:layout_constraintRight_toRightOf="parent" | ||||||
|                 android:layout_gravity="top" |                 android:layout_gravity="top" | ||||||
|                 app:srcCompat="@drawable/ic_info_outline_24dp" |                 app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|                 android:tint="@color/primaryLightColor" /> |                 app:tint="@color/primaryLightColor" /> | ||||||
| 
 | 
 | ||||||
|             </androidx.constraintlayout.widget.ConstraintLayout> |             </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
| 
 | 
 | ||||||
|  | @ -658,16 +623,14 @@ | ||||||
|               android:layout_width="wrap_content" |               android:layout_width="wrap_content" | ||||||
|               android:layout_height="wrap_content" |               android:layout_height="wrap_content" | ||||||
|               style="?android:textAppearanceMedium" |               style="?android:textAppearanceMedium" | ||||||
|               android:layout_alignParentRight="true" |  | ||||||
|               android:layout_alignParentEnd="true" |               android:layout_alignParentEnd="true" | ||||||
|               android:layout_marginTop="@dimen/activity_margin_horizontal" |               android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_marginStart="@dimen/activity_margin_horizontal" |               android:layout_marginStart="@dimen/activity_margin_horizontal" | ||||||
|               android:layout_marginLeft="@dimen/activity_margin_horizontal" |  | ||||||
|               android:layout_centerVertical="true" |               android:layout_centerVertical="true" | ||||||
|               tools:text="2" |               tools:text="2" | ||||||
|               android:id="@+id/thanks_received" |               android:id="@+id/thanks_received" | ||||||
|               android:layout_marginEnd="@dimen/half_standard_height" |               android:layout_marginEnd="@dimen/half_standard_height" | ||||||
|               android:layout_marginRight="@dimen/half_standard_height" /> |               /> | ||||||
| 
 | 
 | ||||||
|           </RelativeLayout> |           </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -124,6 +124,33 @@ | ||||||
|           app:srcCompat="@drawable/ic_my_location_black_24dp" |           app:srcCompat="@drawable/ic_my_location_black_24dp" | ||||||
|           app:useCompatPadding="true" /> |           app:useCompatPadding="true" /> | ||||||
| 
 | 
 | ||||||
|  |         <com.google.android.material.floatingactionbutton.FloatingActionButton | ||||||
|  |           android:id="@+id/fab_legend" | ||||||
|  |           android:layout_width="wrap_content" | ||||||
|  |           android:layout_height="wrap_content" | ||||||
|  |           android:layout_below="@id/fab_recenter" | ||||||
|  |           android:layout_alignParentEnd="true" | ||||||
|  |           android:layout_alignParentRight="true" | ||||||
|  |           android:clickable="true" | ||||||
|  |           android:visibility="visible" | ||||||
|  |           app:backgroundTint="@color/main_background_light" | ||||||
|  |           app:elevation="@dimen/dimen_6" | ||||||
|  |           app:fabSize="normal" | ||||||
|  |           app:layout_anchorGravity="top|right|end" | ||||||
|  |           app:srcCompat="@drawable/ic_info_outline_24dp" | ||||||
|  |           app:useCompatPadding="true" /> | ||||||
|  | 
 | ||||||
|  |         <include | ||||||
|  |           android:id="@+id/nearby_legend_layout" | ||||||
|  |           layout="@layout/nearby_legend" | ||||||
|  |           android:layout_width="wrap_content" | ||||||
|  |           android:layout_height="wrap_content" | ||||||
|  |           android:layout_below="@id/rl_container_wlm_month_message" | ||||||
|  |           android:visibility="gone" | ||||||
|  |           android:layout_marginTop="30dp" | ||||||
|  |           android:layout_marginStart="5dp" | ||||||
|  |           /> | ||||||
|  | 
 | ||||||
|       </RelativeLayout> |       </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|       <FrameLayout |       <FrameLayout | ||||||
|  |  | ||||||
|  | @ -11,8 +11,8 @@ | ||||||
|       android:layout_width="wrap_content" |       android:layout_width="wrap_content" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_margin="@dimen/standard_gap" |       android:layout_margin="@dimen/standard_gap" | ||||||
|       android:tint="?attr/rowButtonColor" |       app:srcCompat="@drawable/ic_round_star_border_24px" | ||||||
|       app:srcCompat="@drawable/ic_round_star_border_24px" /> |       app:tint="?attr/rowButtonColor" /> | ||||||
| 
 | 
 | ||||||
|     <com.facebook.drawee.view.SimpleDraweeView |     <com.facebook.drawee.view.SimpleDraweeView | ||||||
|         android:id="@+id/icon" |         android:id="@+id/icon" | ||||||
|  | @ -30,7 +30,6 @@ | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_alignParentEnd="true" |         android:layout_alignParentEnd="true" | ||||||
|         android:layout_alignParentRight="true" |  | ||||||
|         android:layout_marginLeft="@dimen/standard_gap" |         android:layout_marginLeft="@dimen/standard_gap" | ||||||
|         android:layout_marginRight="@dimen/standard_gap" |         android:layout_marginRight="@dimen/standard_gap" | ||||||
|         android:layout_marginTop="@dimen/standard_gap" |         android:layout_marginTop="@dimen/standard_gap" | ||||||
|  | @ -43,8 +42,7 @@ | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_alignParentEnd="true" |         android:layout_alignParentEnd="true" | ||||||
|         android:layout_alignParentRight="true" |         android:layout_marginEnd="@dimen/standard_gap" | ||||||
|         android:layout_marginRight="@dimen/standard_gap" |  | ||||||
|         android:layout_marginTop="@dimen/large_gap" |         android:layout_marginTop="@dimen/large_gap" | ||||||
| 
 | 
 | ||||||
|         /> |         /> | ||||||
|  | @ -54,11 +52,8 @@ | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_alignTop="@id/distance" |         android:layout_alignTop="@id/distance" | ||||||
|         android:layout_marginLeft="@dimen/standard_gap" |  | ||||||
|         android:layout_marginStart="@dimen/standard_gap" |         android:layout_marginStart="@dimen/standard_gap" | ||||||
|         android:layout_toEndOf="@id/icon" |         android:layout_toEndOf="@id/icon" | ||||||
|         android:layout_toLeftOf="@id/distance" |  | ||||||
|         android:layout_toRightOf="@id/icon" |  | ||||||
|         android:layout_toStartOf="@id/distance" |         android:layout_toStartOf="@id/distance" | ||||||
|         android:ellipsize="end" |         android:ellipsize="end" | ||||||
|         android:maxLines="2" |         android:maxLines="2" | ||||||
|  | @ -71,8 +66,6 @@ | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_alignEnd="@id/distance" |         android:layout_alignEnd="@id/distance" | ||||||
|         android:layout_alignLeft="@id/tvName" |  | ||||||
|         android:layout_alignRight="@id/distance" |  | ||||||
|         android:layout_alignStart="@id/tvName" |         android:layout_alignStart="@id/tvName" | ||||||
|         android:layout_below="@id/tvName" |         android:layout_below="@id/tvName" | ||||||
|         android:layout_marginBottom="@dimen/standard_gap" |         android:layout_marginBottom="@dimen/standard_gap" | ||||||
|  |  | ||||||
|  | @ -19,16 +19,13 @@ | ||||||
|       android:id="@+id/iv_campaign" |       android:id="@+id/iv_campaign" | ||||||
|       android:layout_width="@dimen/dimen_40" |       android:layout_width="@dimen/dimen_40" | ||||||
|       android:layout_height="@dimen/dimen_40" |       android:layout_height="@dimen/dimen_40" | ||||||
|       android:layout_marginLeft="@dimen/standard_gap" |  | ||||||
|       android:layout_marginStart="@dimen/standard_gap" |       android:layout_marginStart="@dimen/standard_gap" | ||||||
|       android:scaleType="centerCrop" |       android:scaleType="centerCrop" | ||||||
|       app:srcCompat="@drawable/ic_campaign" |       app:srcCompat="@drawable/ic_campaign" | ||||||
|       android:tint="?attr/card_item_color" |     app:tint="?attr/card_item_color" /> | ||||||
|       /> |  | ||||||
|   <LinearLayout |   <LinearLayout | ||||||
|       android:layout_width="match_parent" |       android:layout_width="match_parent" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_centerInParent="true" |  | ||||||
|     android:orientation="horizontal" |     android:orientation="horizontal" | ||||||
|       android:layout_gravity="center_vertical" |       android:layout_gravity="center_vertical" | ||||||
|       android:gravity="center_vertical" |       android:gravity="center_vertical" | ||||||
|  | @ -37,15 +34,13 @@ | ||||||
|   <androidx.constraintlayout.widget.ConstraintLayout |   <androidx.constraintlayout.widget.ConstraintLayout | ||||||
|       android:layout_width="match_parent" |       android:layout_width="match_parent" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_marginLeft="@dimen/standard_gap" |       android:layout_marginStart="@dimen/standard_gap" | ||||||
|       android:layout_marginRight="@dimen/tiny_margin" |       android:layout_marginEnd="@dimen/tiny_margin"> | ||||||
|       android:layout_centerInParent="true" |  | ||||||
|       > |  | ||||||
|     <TextView |     <TextView | ||||||
|         android:id="@+id/tv_title" |         android:id="@+id/tv_title" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_marginLeft="@dimen/standard_gap" |         android:layout_marginStart="@dimen/standard_gap" | ||||||
|         android:textColor="?attr/card_item_color" |         android:textColor="?attr/card_item_color" | ||||||
|         android:textStyle="bold" |         android:textStyle="bold" | ||||||
|         tools:text="Campaign Title" |         tools:text="Campaign Title" | ||||||
|  | @ -55,7 +50,7 @@ | ||||||
|         android:id="@+id/tv_description" |         android:id="@+id/tv_description" | ||||||
|         android:layout_width="wrap_content" |         android:layout_width="wrap_content" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_marginLeft="@dimen/standard_gap" |         android:layout_marginStart="@dimen/standard_gap" | ||||||
|         android:gravity="start" |         android:gravity="start" | ||||||
|         android:paddingTop="@dimen/miniscule_margin" |         android:paddingTop="@dimen/miniscule_margin" | ||||||
|         android:textAlignment="textStart" |         android:textAlignment="textStart" | ||||||
|  | @ -69,7 +64,7 @@ | ||||||
|         android:id="@+id/tv_dates" |         android:id="@+id/tv_dates" | ||||||
|         android:layout_width="@dimen/dimen_0" |         android:layout_width="@dimen/dimen_0" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_marginLeft="@dimen/standard_gap" |         android:layout_marginStart="@dimen/standard_gap" | ||||||
|         android:layout_weight="1" |         android:layout_weight="1" | ||||||
|         android:paddingTop="@dimen/miniscule_margin" |         android:paddingTop="@dimen/miniscule_margin" | ||||||
|         android:text="@string/ends_on" |         android:text="@string/ends_on" | ||||||
|  |  | ||||||
|  | @ -113,9 +113,9 @@ | ||||||
|         android:background="@android:color/transparent" |         android:background="@android:color/transparent" | ||||||
|         android:padding="@dimen/activity_margin_horizontal" |         android:padding="@dimen/activity_margin_horizontal" | ||||||
|         android:src="@drawable/ic_wikipedia" |         android:src="@drawable/ic_wikipedia" | ||||||
|         android:tint="?attr/contributionsListTextSecondary" |  | ||||||
|         android:text="@string/menu_cancel_upload" |         android:text="@string/menu_cancel_upload" | ||||||
|         android:visibility="visible" /> |         android:visibility="visible" | ||||||
|  |         app:tint="?attr/contributionsListTextSecondary" /> | ||||||
| 
 | 
 | ||||||
|     </RelativeLayout> |     </RelativeLayout> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,7 +14,6 @@ | ||||||
|       style="@style/Widget.AppCompat.Button.Borderless" |       style="@style/Widget.AppCompat.Button.Borderless" | ||||||
|       android:layout_width="wrap_content" |       android:layout_width="wrap_content" | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_centerInParent="true" |  | ||||||
|       android:layout_marginLeft="@dimen/activity_margin_horizontal" |       android:layout_marginLeft="@dimen/activity_margin_horizontal" | ||||||
|       android:layout_marginTop="@dimen/activity_margin_horizontal" |       android:layout_marginTop="@dimen/activity_margin_horizontal" | ||||||
|       android:layout_marginRight="@dimen/activity_margin_horizontal" |       android:layout_marginRight="@dimen/activity_margin_horizontal" | ||||||
|  | @ -30,33 +29,27 @@ | ||||||
|         android:layout_width="match_parent" |         android:layout_width="match_parent" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:id="@+id/content_layout" |         android:id="@+id/content_layout" | ||||||
|         android:layout_centerInParent="true" |  | ||||||
|       android:orientation="horizontal" |       android:orientation="horizontal" | ||||||
|         > |         > | ||||||
| 
 | 
 | ||||||
|         <ProgressBar |         <ProgressBar | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:id="@+id/progressBar" |             android:id="@+id/progressBar" /> | ||||||
|             android:layout_centerInParent="true" |  | ||||||
|             /> |  | ||||||
| 
 | 
 | ||||||
|         <ImageView |         <ImageView | ||||||
|             android:id="@+id/nearby_icon" |             android:id="@+id/nearby_icon" | ||||||
|             android:layout_width="@dimen/dimen_40" |             android:layout_width="@dimen/dimen_40" | ||||||
|             android:layout_height="@dimen/dimen_40" |             android:layout_height="@dimen/dimen_40" | ||||||
|             android:layout_marginLeft="@dimen/standard_gap" |  | ||||||
|             android:layout_marginStart="@dimen/standard_gap" |             android:layout_marginStart="@dimen/standard_gap" | ||||||
|             android:scaleType="centerCrop" |             android:scaleType="centerCrop" | ||||||
|             app:srcCompat="@drawable/ic_location_white_24dp" |             app:srcCompat="@drawable/ic_location_white_24dp" | ||||||
|             android:tint="?attr/card_item_color" |           app:tint="?attr/card_item_color" /> | ||||||
|           /> |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         <LinearLayout |         <LinearLayout | ||||||
|             android:layout_width="match_parent" |             android:layout_width="match_parent" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:layout_centerInParent="true" |  | ||||||
|           android:orientation="horizontal" |           android:orientation="horizontal" | ||||||
|             android:layout_gravity="center_vertical" |             android:layout_gravity="center_vertical" | ||||||
|             android:gravity="center_vertical" |             android:gravity="center_vertical" | ||||||
|  | @ -68,7 +61,6 @@ | ||||||
|                 android:layout_width="@dimen/dimen_0" |                 android:layout_width="@dimen/dimen_0" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_weight="3" |                 android:layout_weight="3" | ||||||
|                 android:layout_centerInParent="true" |  | ||||||
|               android:layout_marginLeft="@dimen/standard_gap" |               android:layout_marginLeft="@dimen/standard_gap" | ||||||
|                 android:layout_marginRight="@dimen/standard_gap" |                 android:layout_marginRight="@dimen/standard_gap" | ||||||
|                 tools:text="test distance" |                 tools:text="test distance" | ||||||
|  |  | ||||||
							
								
								
									
										74
									
								
								app/src/main/res/layout/nearby_legend.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								app/src/main/res/layout/nearby_legend.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,74 @@ | ||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |   xmlns:app="http://schemas.android.com/apk/res-auto" | ||||||
|  |   xmlns:tools="http://schemas.android.com/tools" | ||||||
|  |   android:layout_width="wrap_content" | ||||||
|  |   android:layout_height="wrap_content" | ||||||
|  |   android:background="#BFFFFFFF" | ||||||
|  |   android:orientation="vertical"> | ||||||
|  | 
 | ||||||
|  |   <ImageView | ||||||
|  |     android:id="@+id/imageRed" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_marginBottom="0dp" | ||||||
|  |     app:layout_constraintBottom_toTopOf="@+id/imageGreen" | ||||||
|  |     app:layout_constraintEnd_toEndOf="@+id/imageGreen" | ||||||
|  |     app:layout_constraintStart_toStartOf="@+id/imageGreen" | ||||||
|  |     app:srcCompat="@drawable/ic_custom_map_marker_red" /> | ||||||
|  | 
 | ||||||
|  |   <TextView | ||||||
|  |     android:id="@+id/textRed" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_marginStart="0dp" | ||||||
|  |     android:text="@string/red_pin" | ||||||
|  |     android:textColor="#F74D4D" | ||||||
|  |     android:textSize="12sp" | ||||||
|  |     app:layout_constraintBottom_toBottomOf="@+id/imageRed" | ||||||
|  |     app:layout_constraintStart_toEndOf="@+id/imageRed" | ||||||
|  |     app:layout_constraintTop_toTopOf="@+id/imageRed" /> | ||||||
|  | 
 | ||||||
|  |   <ImageView | ||||||
|  |     android:id="@+id/imageGreen" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_marginBottom="0dp" | ||||||
|  |     app:layout_constraintBottom_toTopOf="@+id/imageGrey" | ||||||
|  |     app:layout_constraintEnd_toEndOf="@+id/imageGrey" | ||||||
|  |     app:layout_constraintStart_toStartOf="@+id/imageGrey" | ||||||
|  |     app:srcCompat="@drawable/ic_custom_map_marker_green" /> | ||||||
|  | 
 | ||||||
|  |   <TextView | ||||||
|  |     android:id="@+id/textGreen" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_marginStart="0dp" | ||||||
|  |     android:text="@string/green_pin" | ||||||
|  |     android:textColor="#1F7123" | ||||||
|  |     android:textSize="12sp" | ||||||
|  |     app:layout_constraintBottom_toBottomOf="@+id/imageGreen" | ||||||
|  |     app:layout_constraintStart_toEndOf="@+id/imageGreen" | ||||||
|  |     app:layout_constraintTop_toTopOf="@+id/imageGreen" /> | ||||||
|  | 
 | ||||||
|  |   <ImageView | ||||||
|  |     android:id="@+id/imageGrey" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:layout_marginStart="0dp" | ||||||
|  |     app:layout_constraintBottom_toBottomOf="parent" | ||||||
|  |     app:layout_constraintStart_toStartOf="parent" | ||||||
|  |     app:srcCompat="@drawable/ic_custom_map_marker_grey" /> | ||||||
|  | 
 | ||||||
|  |   <TextView | ||||||
|  |     android:id="@+id/textGrey" | ||||||
|  |     android:layout_width="wrap_content" | ||||||
|  |     android:layout_height="wrap_content" | ||||||
|  |     android:text="@string/grey_pin" | ||||||
|  |     android:textColor="#454547" | ||||||
|  |     android:textSize="12sp" | ||||||
|  |     app:layout_constraintBottom_toBottomOf="@+id/imageGrey" | ||||||
|  |     app:layout_constraintStart_toEndOf="@+id/imageGrey" | ||||||
|  |     app:layout_constraintTop_toTopOf="@+id/imageGrey" /> | ||||||
|  | 
 | ||||||
|  | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
|       android:layout_weight="1" |       android:layout_weight="1" | ||||||
|       android:background="@drawable/button_background_selector" |       android:background="@drawable/button_background_selector" | ||||||
|       android:clickable="true" |       android:clickable="true" | ||||||
|  |       android:focusable="true" | ||||||
|       android:orientation="vertical" |       android:orientation="vertical" | ||||||
|       android:padding="@dimen/standard_gap"> |       android:padding="@dimen/standard_gap"> | ||||||
| 
 | 
 | ||||||
|  | @ -24,8 +25,8 @@ | ||||||
|           android:layout_width="wrap_content" |           android:layout_width="wrap_content" | ||||||
|           android:layout_height="wrap_content" |           android:layout_height="wrap_content" | ||||||
|           android:layout_gravity="center_horizontal" |           android:layout_gravity="center_horizontal" | ||||||
|           android:tint="?attr/bookmarkButtonColor" |           app:srcCompat="@drawable/ic_photo_camera_white_24dp" | ||||||
|           app:srcCompat="@drawable/ic_photo_camera_white_24dp" /> |           app:tint="?attr/bookmarkButtonColor" /> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
|           android:id="@+id/cameraButtonText" |           android:id="@+id/cameraButtonText" | ||||||
|  | @ -45,6 +46,7 @@ | ||||||
|       android:background="@drawable/button_background_selector" |       android:background="@drawable/button_background_selector" | ||||||
|       android:clickable="true" |       android:clickable="true" | ||||||
|       android:contentDescription="@string/nearby_row_image" |       android:contentDescription="@string/nearby_row_image" | ||||||
|  |       android:focusable="true" | ||||||
|       android:orientation="vertical" |       android:orientation="vertical" | ||||||
|       android:padding="@dimen/standard_gap"> |       android:padding="@dimen/standard_gap"> | ||||||
| 
 | 
 | ||||||
|  | @ -53,8 +55,8 @@ | ||||||
|           android:layout_height="wrap_content" |           android:layout_height="wrap_content" | ||||||
|           android:layout_gravity="center_horizontal" |           android:layout_gravity="center_horizontal" | ||||||
|           android:duplicateParentState="true" |           android:duplicateParentState="true" | ||||||
|           android:tint="?attr/bookmarkButtonColor" |           app:srcCompat="@drawable/ic_photo_white_24dp" | ||||||
|           app:srcCompat="@drawable/ic_photo_white_24dp" /> |           app:tint="?attr/bookmarkButtonColor" /> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
|           android:id="@+id/galleryButtonText" |           android:id="@+id/galleryButtonText" | ||||||
|  | @ -72,6 +74,7 @@ | ||||||
|         android:layout_width="@dimen/dimen_0" |         android:layout_width="@dimen/dimen_0" | ||||||
|         android:layout_height="wrap_content" |         android:layout_height="wrap_content" | ||||||
|         android:layout_weight="1" |         android:layout_weight="1" | ||||||
|  |         android:focusable="true" | ||||||
|         android:padding="@dimen/standard_gap" |         android:padding="@dimen/standard_gap" | ||||||
|         android:clickable="true" |         android:clickable="true" | ||||||
|         android:orientation="vertical" |         android:orientation="vertical" | ||||||
|  | @ -82,8 +85,8 @@ | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|             android:layout_gravity="center_horizontal" |             android:layout_gravity="center_horizontal" | ||||||
|             app:srcCompat="@drawable/ic_directions_black_24dp" |             app:srcCompat="@drawable/ic_directions_black_24dp" | ||||||
|             android:tint="?attr/bookmarkButtonColor" |             android:duplicateParentState="true" | ||||||
|             android:duplicateParentState="true"/> |             app:tint="?attr/bookmarkButtonColor" /> | ||||||
|         <TextView |         <TextView | ||||||
|             android:layout_width="wrap_content" |             android:layout_width="wrap_content" | ||||||
|             android:layout_height="wrap_content" |             android:layout_height="wrap_content" | ||||||
|  | @ -102,6 +105,7 @@ | ||||||
|       android:layout_height="wrap_content" |       android:layout_height="wrap_content" | ||||||
|       android:layout_weight="1" |       android:layout_weight="1" | ||||||
|       android:clickable="true" |       android:clickable="true" | ||||||
|  |       android:focusable="true" | ||||||
|       android:orientation="vertical" |       android:orientation="vertical" | ||||||
|       android:padding="@dimen/standard_gap"> |       android:padding="@dimen/standard_gap"> | ||||||
| 
 | 
 | ||||||
|  | @ -110,8 +114,8 @@ | ||||||
|           android:layout_height="wrap_content" |           android:layout_height="wrap_content" | ||||||
|           android:layout_gravity="center_horizontal" |           android:layout_gravity="center_horizontal" | ||||||
|           android:duplicateParentState="true" |           android:duplicateParentState="true" | ||||||
|           android:tint="?attr/bookmarkButtonColor" |           app:srcCompat="@drawable/ic_overflow" | ||||||
|           app:srcCompat="@drawable/ic_overflow" /> |           app:tint="?attr/bookmarkButtonColor" /> | ||||||
| 
 | 
 | ||||||
|         <TextView |         <TextView | ||||||
|           android:id="@+id/iconOverflowText" |           android:id="@+id/iconOverflowText" | ||||||
|  |  | ||||||
|  | @ -42,10 +42,10 @@ | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:layout_marginStart="16dp" |     android:layout_marginStart="16dp" | ||||||
|     android:contentDescription="@string/exit_location_picker" |     android:contentDescription="@string/exit_location_picker" | ||||||
|     android:tint="@color/white" |  | ||||||
|     app:layout_constraintBottom_toBottomOf="parent" |     app:layout_constraintBottom_toBottomOf="parent" | ||||||
|     app:layout_constraintLeft_toLeftOf="parent" |     app:layout_constraintLeft_toLeftOf="parent" | ||||||
|     app:layout_constraintTop_toTopOf="parent" |     app:layout_constraintTop_toTopOf="parent" | ||||||
|     app:srcCompat="@drawable/ic_arrow_back_white"/> |     app:srcCompat="@drawable/ic_arrow_back_white" | ||||||
|  |     app:tint="@color/white" /> | ||||||
| 
 | 
 | ||||||
| </androidx.constraintlayout.widget.ConstraintLayout> | </androidx.constraintlayout.widget.ConstraintLayout> | ||||||
|  | @ -1,17 +1,25 @@ | ||||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|   xmlns:app="http://schemas.android.com/apk/res-auto"> |   xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||||
| 
 | 
 | ||||||
|  |   <item android:id="@+id/item_refresh" | ||||||
|  |     android:title="Refresh" | ||||||
|  |     app:showAsAction="ifRoom" | ||||||
|  |     android:icon="@drawable/ic_refresh_24dp_nearby" /> | ||||||
|  | 
 | ||||||
|   <item android:id="@+id/list_sheet" |   <item android:id="@+id/list_sheet" | ||||||
|     android:title="@string/list_sheet" |     android:title="@string/list_sheet" | ||||||
|     app:showAsAction="ifRoom|withText" |     app:showAsAction="ifRoom|withText" | ||||||
|     android:icon="@drawable/ic_list_white_24dp" |     android:icon="@drawable/ic_list_white_24dp" | ||||||
|     /> |     /> | ||||||
|  | 
 | ||||||
|   <item android:id="@+id/list_item_gpx" |   <item android:id="@+id/list_item_gpx" | ||||||
|     android:layout_width="wrap_content" |     android:layout_width="wrap_content" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:title="Save as GPX file" /> |     android:title="Save as GPX file" /> | ||||||
|  | 
 | ||||||
|   <item android:id="@+id/list_item_kml" |   <item android:id="@+id/list_item_kml" | ||||||
|     android:layout_width="wrap_content" |     android:layout_width="wrap_content" | ||||||
|     android:layout_height="wrap_content" |     android:layout_height="wrap_content" | ||||||
|     android:title="Save as KML file" /> |     android:title="Save as KML file" /> | ||||||
|  | 
 | ||||||
| </menu> | </menu> | ||||||
|  |  | ||||||
|  | @ -527,6 +527,7 @@ | ||||||
|   <string name="no_notification">ليس لديك أي إشعارات غير مقروءة</string> |   <string name="no_notification">ليس لديك أي إشعارات غير مقروءة</string> | ||||||
|   <string name="no_read_notification">ليس لديك أي إشعاراتٍ غير مقروءة</string> |   <string name="no_read_notification">ليس لديك أي إشعاراتٍ غير مقروءة</string> | ||||||
|   <string name="share_logs_using">مشاركة السجلات باستخدام</string> |   <string name="share_logs_using">مشاركة السجلات باستخدام</string> | ||||||
|  |   <string name="check_your_email_inbox">تحقق من صندوق بريدك الإلكتروني</string> | ||||||
|   <string name="menu_option_read">عرض المقروءة</string> |   <string name="menu_option_read">عرض المقروءة</string> | ||||||
|   <string name="menu_option_unread">عرض غير المقروءة</string> |   <string name="menu_option_unread">عرض غير المقروءة</string> | ||||||
|   <string name="error_occurred_in_picking_images">حدث خطأ أثناء التقاط الصور</string> |   <string name="error_occurred_in_picking_images">حدث خطأ أثناء التقاط الصور</string> | ||||||
|  | @ -690,6 +691,7 @@ | ||||||
|   <string name="leaderboard_nearby">مجاور</string> |   <string name="leaderboard_nearby">مجاور</string> | ||||||
|   <string name="leaderboard_used">مستخدَم</string> |   <string name="leaderboard_used">مستخدَم</string> | ||||||
|   <string name="leaderboard_my_rank_button_text">ترتيبي</string> |   <string name="leaderboard_my_rank_button_text">ترتيبي</string> | ||||||
|  |   <string name="map_attribution">&#169; <a href=\"https://www.openstreetmap.org/copyright\">خريطة الشارع المفتوحة</a></string> | ||||||
|   <string name="limited_connection_enabled">وضع الاتصال المحدود مُمَكَّن!</string> |   <string name="limited_connection_enabled">وضع الاتصال المحدود مُمَكَّن!</string> | ||||||
|   <string name="limited_connection_disabled">وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن.</string> |   <string name="limited_connection_disabled">وضع الاتصال المحدود مُعطل. سيجري استئناف التحميلات المعلقة الآن.</string> | ||||||
|   <string name="limited_connection_mode">وضع الاتصال المحدود</string> |   <string name="limited_connection_mode">وضع الاتصال المحدود</string> | ||||||
|  | @ -739,6 +741,7 @@ | ||||||
|   <string name="custom_selector_dismiss_limit_warning_button_text">رفض</string> |   <string name="custom_selector_dismiss_limit_warning_button_text">رفض</string> | ||||||
|   <string name="custom_selector_button_limit_text">الحد الأقصى: %1$d</string> |   <string name="custom_selector_button_limit_text">الحد الأقصى: %1$d</string> | ||||||
|   <string name="custom_selector_limit_error_desc">خطأ: تجاوز حد التحميل</string> |   <string name="custom_selector_limit_error_desc">خطأ: تجاوز حد التحميل</string> | ||||||
|  |   <string name="place_state_wlm">دبليو إل إم</string> | ||||||
|   <string name="wlm_upload_info">سيتم إدخال هذه الصورة في مسابقة Wiki Loves Monuments</string> |   <string name="wlm_upload_info">سيتم إدخال هذه الصورة في مسابقة Wiki Loves Monuments</string> | ||||||
|   <string name="display_monuments">عرض الآثار</string> |   <string name="display_monuments">عرض الآثار</string> | ||||||
|   <string name="wlm_month_message">إنه شهر Wiki Loves Monuments!</string> |   <string name="wlm_month_message">إنه شهر Wiki Loves Monuments!</string> | ||||||
|  | @ -788,16 +791,66 @@ | ||||||
|   <string name="image_selected">تم تحديد الصورة</string> |   <string name="image_selected">تم تحديد الصورة</string> | ||||||
|   <string name="image_marked_as_not_for_upload">تم وضع علامة على الصورة على أنها ليست للتحميل</string> |   <string name="image_marked_as_not_for_upload">تم وضع علامة على الصورة على أنها ليست للتحميل</string> | ||||||
|   <string name="menu_view_report">تقرير</string> |   <string name="menu_view_report">تقرير</string> | ||||||
|  |   <string name="menu_view_set_white_background">تعيين الخلفية البيضاء</string> | ||||||
|  |   <string name="menu_view_set_black_background">تعيين خلفية سوداء</string> | ||||||
|   <string name="report_violation">تبليغ عن عنف</string> |   <string name="report_violation">تبليغ عن عنف</string> | ||||||
|   <string name="report_user">أخطر عن هذا المستخدم</string> |   <string name="report_user">أخطر عن هذا المستخدم</string> | ||||||
|   <string name="report_content">الإبلاغ عن هذا المحتوى</string> |   <string name="report_content">الإبلاغ عن هذا المحتوى</string> | ||||||
|   <string name="request_user_block">طلب منع هذا المستخدم</string> |   <string name="request_user_block">طلب منع هذا المستخدم</string> | ||||||
|   <string name="welcome_to_full_screen_mode_text">مرحبًا بك في وضع التحديد بملء الشاشة</string> |   <string name="welcome_to_full_screen_mode_text">مرحبًا بك في وضع التحديد بملء الشاشة</string> | ||||||
|   <string name="full_screen_mode_zoom_info">استخدم إصبعين للتكبير والتصغير.</string> |   <string name="full_screen_mode_zoom_info">استخدم إصبعين للتكبير والتصغير.</string> | ||||||
|   <string name="full_screen_mode_features_info" fuzzy="true">مرر سريعًا وطويلًا لتنفيذ هذه الإجراءات:! N! - يسار / يمين: انتقل إلى السابق / التالي! N! - لأعلى: حدد! N! - أسفل: وضع علامة على أنه ليس للتحميل.</string> |   <string name="full_screen_mode_features_info">مرر سريعًا وطويلًا لأداء هذه الإجراءات: \n- يسار/يمين: الانتقال إلى السابق/التالي \n- أعلى: تحديد\n- أسفل: وضع علامة على عدم التحميل.</string> | ||||||
|  |   <string name="set_up_avatar_toast_string">لإعداد صورتك الرمزية في قائمة المتصدرين، اضغط على \"تعيين كصورة رمزية\" في قائمة النقاط الثلاث لأي صورة.</string> | ||||||
|  |   <string name="similar_coordinate_description_auto_set">الإحداثيات ليست إحداثيات دقيقة، لكن الشخص الذي قام بتحميل هذه الصورة يعتقد أنها قريبة بما فيه الكفاية.</string> | ||||||
|   <string name="storage_permissions_denied">رُفض إذن التخزين</string> |   <string name="storage_permissions_denied">رُفض إذن التخزين</string> | ||||||
|   <string name="unable_to_share_upload_item">تعذر مشاركة هذا العنصر</string> |   <string name="unable_to_share_upload_item">تعذر مشاركة هذا العنصر</string> | ||||||
|   <string name="permissions_are_required_for_functionality">الإذن مطلوب لهذه الوظيفة</string> |   <string name="permissions_are_required_for_functionality">الإذن مطلوب لهذه الوظيفة</string> | ||||||
|  |   <string name="learn_how_to_write_a_useful_description">تعلم كيفية كتابة وصف مفيد</string> | ||||||
|  |   <string name="learn_how_to_write_a_useful_caption">تعلم كيفية كتابة تعليق مفيد</string> | ||||||
|  |   <string name="see_your_achievements">شاهد إنجازاتك</string> | ||||||
|  |   <string name="edit_image">تعديل الصورة</string> | ||||||
|  |   <string name="edit_location">تعديل الموقع</string> | ||||||
|  |   <string name="location_updated">تم تحديث الموقع!</string> | ||||||
|  |   <string name="remove_location">إزالة الموقع</string> | ||||||
|  |   <string name="remove_location_warning_title">إزالة تحذير الموقع</string> | ||||||
|  |   <string name="remove_location_warning_desc">يجعل تحديد الموقع الصور أكثر فائدة وسهولة في العثور عليها. هل ترغب حقًا في إزالة تحديد الموقع من هذه الصورة؟</string> | ||||||
|  |   <string name="location_removed">تمت إزالة الموقع!</string> | ||||||
|   <string name="send_thanks_to_author">اشكر المؤلف</string> |   <string name="send_thanks_to_author">اشكر المؤلف</string> | ||||||
|   <string name="error_sending_thanks">حدث خطأ أثناء إرسال الشكر للمؤلف.</string> |   <string name="error_sending_thanks">حدث خطأ أثناء إرسال الشكر للمؤلف.</string> | ||||||
|  |   <string name="invalid_login_message">لقد انتهت صلاحية تسجيل الدخول الخاص بك. يرجى تسجيل الدخول مرة أخرى.</string> | ||||||
|  |   <string name="no_application_available_to_open_gpx_files">لا يوجد تطبيق متاح لفتح ملفات GPX</string> | ||||||
|  |   <string name="file_saved_successfully">تم حفظ الملف بنجاح</string> | ||||||
|  |   <string name="do_you_want_to_open_gpx_file">هل تريد فتح ملف GPX؟</string> | ||||||
|  |   <string name="do_you_want_to_open_kml_file">هل تريد فتح ملف KML؟</string> | ||||||
|  |   <string name="failed_to_save_kml_file">فشل في حفظ ملف KML.</string> | ||||||
|  |   <string name="failed_to_save_gpx_file">فشل في حفظ ملف GPX.</string> | ||||||
|  |   <string name="saving_kml_file">حفظ ملف KML</string> | ||||||
|  |   <string name="saving_gpx_file">حفظ ملف GPX</string> | ||||||
|  |   <plurals name="custom_picker_images_selected_title_appendix"> | ||||||
|  |     <item quantity="zero">لا صور تم اختيارها</item> | ||||||
|  |     <item quantity="one">%d صورة تم اختيارها</item> | ||||||
|  |     <item quantity="two">صورتان تم اختيارهما</item> | ||||||
|  |     <item quantity="few">صور قليلة تم اختيارها</item> | ||||||
|  |     <item quantity="many">صور كثيرة تم اختيارها</item> | ||||||
|  |     <item quantity="other">%d صور تم اختيارها</item> | ||||||
|  |   </plurals> | ||||||
|  |   <string name="multiple_files_depiction">يرجى تذكر أن جميع الصور في التحميل المتعدد تحصل على نفس الفئات والأوصاف. إذا لم تتشارك الصور في الأوصاف والفئات، فيرجى إجراء عدة عمليات تحميل منفصلة.</string> | ||||||
|  |   <string name="multiple_files_depiction_header">ملاحظة حول التحميلات المتعددة</string> | ||||||
|  |   <string name="nearby_wikitalk">الإبلاغ عن مشكلة حول هذا العنصر إلى Wikidata</string> | ||||||
|  |   <string name="please_enter_some_comments">الرجاء إدخال بعض التعليقات</string> | ||||||
|  |   <string name="talk">نقاش</string> | ||||||
|  |   <string name="write_something_about_the_item">اكتب شيئًا عن العنصر \'%1$s\'. سيكون مرئيًا للعامة.</string> | ||||||
|  |   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' لم يعد موجودًا، ولا يمكن التقاط صورة له أبدًا.</string> | ||||||
|  |   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' موجود في مكان مختلف. يرجى تحديد المكان الصحيح أدناه، وإذا أمكن، اكتب خط العرض وخط الطول الصحيحين.</string> | ||||||
|  |   <string name="other_problem_or_information_please_explain_below">مشكلة أو معلومات أخرى (يرجى التوضيح أدناه).</string> | ||||||
|  |   <string name="feedback_destination_note">سيتم نشر تعليقاتك على صفحة الويكي التالية:  <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a></string> | ||||||
|  |   <string name="are_you_sure_that_you_want_cancel_all_the_uploads">هل أنت متأكد أنك تريد إلغاء كافة التحميلات؟</string> | ||||||
|  |   <string name="cancelling_all_the_uploads">إلغاء كافة التحميلات...</string> | ||||||
|  |   <string name="uploads">المرفوعات</string> | ||||||
|  |   <string name="pending">قيد الانتظار</string> | ||||||
|  |   <string name="failed">فشل</string> | ||||||
|  |   <string name="could_not_load_place_data">تعذر تحميل بيانات المكان</string> | ||||||
|  |   <string name="red_pin">هذا المكان ليس له صورة بعد، اذهب والتقط واحدة!</string> | ||||||
|  |   <string name="green_pin">هذا المكان لديه صورة بالفعل.</string> | ||||||
|  |   <string name="grey_pin">الآن التحقق ما إذا كان هذا المكان لديه صورة.</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -2,10 +2,11 @@ | ||||||
| <!-- Authors: | <!-- Authors: | ||||||
| * Dağlı95 | * Dağlı95 | ||||||
| * Khan27 | * Khan27 | ||||||
|  | * Nemoralis | ||||||
| --> | --> | ||||||
| <resources> | <resources> | ||||||
|   <string name="crash_dialog_title">Nasazlıq</string> |   <string name="crash_dialog_title">Nasazlıq</string> | ||||||
|   <string name="crash_dialog_text">Uups. Nəsə düzgün çalışmır!</string> |   <string name="crash_dialog_text">Uups. Nəsə düzgün çalışmır!</string> | ||||||
|   <string name="crash_dialog_comment_prompt">Nə etdiyinizi dəqiqləşdirib, bizə bildirin və sonra e-poçtla bizə göndərin. Bu problemi həll etməyə bizə kömək edin.</string> |   <string name="crash_dialog_comment_prompt">Nə etdiyinizi bizə deyin, sonra e-poçt vasitəsilə bizimlə paylaşın. Bu, bizə bunu düzəltməyə kömək edəcək!</string> | ||||||
|   <string name="crash_dialog_ok_toast">Təşəkkür!</string> |   <string name="crash_dialog_ok_toast">Təşəkkürlər!</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -12,34 +12,60 @@ | ||||||
| * Şeyx Şamil | * Şeyx Şamil | ||||||
| --> | --> | ||||||
| <resources> | <resources> | ||||||
|  |   <string name="commons_facebook">Commons Facebook səhifəsi</string> | ||||||
|  |   <string name="commons_github">Commons Github Mənbə Kodu</string> | ||||||
|  |   <string name="commons_logo">Commons Loqotipi</string> | ||||||
|  |   <string name="commons_website">Commons Veb-saytı</string> | ||||||
|  |   <string name="exit_location_picker">Məkan seçicidən çıxın</string> | ||||||
|  |   <string name="submit">Göndər</string> | ||||||
|  |   <string name="add_another_description">Başqa təsvir əlavə et</string> | ||||||
|  |   <string name="add_new_contribution">Yeni töhfə</string> | ||||||
|  |   <string name="add_contribution_from_camera">Kamera ilə töhfə ver</string> | ||||||
|  |   <string name="add_contribution_from_photos">Fotolar ilə töhfə ver</string> | ||||||
|  |   <string name="add_contribution_from_contributions_gallery">Əvvəlki töhfələr qalereyasından töhfə əlavə et</string> | ||||||
|  |   <string name="show_captions">Başlıqlar</string> | ||||||
|  |   <string name="row_item_language_description">Dil təsviri</string> | ||||||
|  |   <string name="row_item_caption">Başlıq</string> | ||||||
|  |   <string name="show_captions_description">Təsvir</string> | ||||||
|  |   <string name="nearby_row_image">Şəkil</string> | ||||||
|  |   <string name="nearby_all">Hamısı</string> | ||||||
|  |   <string name="nearby_filter_toggle">Aç/Bağla</string> | ||||||
|  |   <string name="nearby_filter_search">Axtarış Görünüşü</string> | ||||||
|  |   <string name="nearby_filter_state">Məkanın Vəziyyəti</string> | ||||||
|  |   <string name="appwidget_img">Günün Şəkli</string> | ||||||
|   <plurals name="uploads_pending_notification_indicator"> |   <plurals name="uploads_pending_notification_indicator"> | ||||||
|     <item quantity="one">%1$d fayl yüklənir</item> |     <item quantity="one">%1$d fayl yüklənir</item> | ||||||
|     <item quantity="other">%1$d fayllar yüklənir</item> |     <item quantity="other">%1$d fayllar yüklənir</item> | ||||||
|   </plurals> |   </plurals> | ||||||
|  |   <plurals name="contributions_subtitle"> | ||||||
|  |     <item quantity="one">(%1$d)</item> | ||||||
|  |     <item quantity="other">(%1$d)</item> | ||||||
|  |   </plurals> | ||||||
|  |   <string name="starting_uploads">Yükləmələrə Başlanılır</string> | ||||||
|   <string name="preference_category_general">Ümumi</string> |   <string name="preference_category_general">Ümumi</string> | ||||||
|   <string name="preference_category_privacy">Məxfilik</string> |   <string name="preference_category_privacy">Məxfilik</string> | ||||||
|   <string name="app_name">Vikimedia Commons</string> |   <string name="app_name">Vikianbar</string> | ||||||
|   <string name="menu_settings">Tənzimləmələr</string> |   <string name="menu_settings">Tənzimləmələr</string> | ||||||
|   <string name="username">Ləqəb</string> |   <string name="username">İstifadəçi adı</string> | ||||||
|   <string name="password">Parol</string> |   <string name="password">Parol</string> | ||||||
|   <string name="login">Daxil ol</string> |   <string name="login">Daxil ol</string> | ||||||
|   <string name="signup">Qeydiyyatdan keç</string> |   <string name="signup">Qeydiyyatdan keç</string> | ||||||
|   <string name="logging_in_title">Giriş edilir</string> |   <string name="logging_in_title">Giriş edilir</string> | ||||||
|   <string name="logging_in_message">Lütfən gözləyin…</string> |   <string name="logging_in_message">Zəhmət olmasa, gözləyin…</string> | ||||||
|   <string name="login_success" fuzzy="true">Daxil oldunuz!</string> |   <string name="login_success" fuzzy="true">Daxil oldunuz!</string> | ||||||
|   <string name="login_failed">Giriş baş tutmadı!</string> |   <string name="login_failed">Giriş baş tutmadı!</string> | ||||||
|   <string name="upload_failed">Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin.</string> |   <string name="upload_failed">Fayl tapılmadı. Xahiş edirik başqa bir fayl üzərində cəhd edin.</string> | ||||||
|   <string name="authentication_failed" fuzzy="true">Doğrulama alınmadı, xahiş edirəm yenidən daxil olun</string> |   <string name="authentication_failed" fuzzy="true">Doğrulama alınmadı, xahiş edirəm yenidən daxil olun</string> | ||||||
|   <string name="uploading_started">Yükləmə başladı!</string> |   <string name="uploading_started">Yükləmə başladı!</string> | ||||||
|   <string name="upload_completed_notification_title">%1$s yükləndi!</string> |   <string name="upload_completed_notification_title">%1$s yükləndi!</string> | ||||||
|   <string name="upload_completed_notification_text">Yüklədiyini izlə</string> |   <string name="upload_completed_notification_text">Yükləmənizə baxmaq üçün toxunun</string> | ||||||
|   <string name="upload_progress_notification_title_start" fuzzy="true">%1$s yüklənməsi başlanır</string> |   <string name="upload_progress_notification_title_start" fuzzy="true">%1$s yüklənməsi başlanır</string> | ||||||
|   <string name="upload_progress_notification_title_in_progress">%1$s yüklənir</string> |   <string name="upload_progress_notification_title_in_progress">%1$s yüklənir</string> | ||||||
|   <string name="upload_progress_notification_title_finishing">%1$s yüklənməsi başa çatdı</string> |   <string name="upload_progress_notification_title_finishing">%1$s yüklənməsi başa çatdı</string> | ||||||
|   <string name="upload_failed_notification_title" fuzzy="true">%1$s faylının yüklənməsi alınmadı</string> |   <string name="upload_failed_notification_title" fuzzy="true">%1$s faylının yüklənməsi alınmadı</string> | ||||||
|   <string name="upload_failed_notification_subtitle">Baxmaq üçün toxunun</string> |   <string name="upload_failed_notification_subtitle">Baxmaq üçün toxun</string> | ||||||
|   <string name="title_activity_contributions">Yükləmələrim</string> |   <string name="title_activity_contributions">Son Yükləmələrim</string> | ||||||
|   <string name="contribution_state_queued">Sırada</string> |   <string name="contribution_state_queued">Növbəyə alındı</string> | ||||||
|   <string name="contribution_state_failed">Uğursuz</string> |   <string name="contribution_state_failed">Uğursuz</string> | ||||||
|   <string name="contribution_state_in_progress">%1$d%% tamamlandı</string> |   <string name="contribution_state_in_progress">%1$d%% tamamlandı</string> | ||||||
|   <string name="contribution_state_starting">Yüklənir</string> |   <string name="contribution_state_starting">Yüklənir</string> | ||||||
|  | @ -52,15 +78,15 @@ | ||||||
|   <string name="share_description_hint">Açıqlama</string> |   <string name="share_description_hint">Açıqlama</string> | ||||||
|   <string name="login_failed_network" fuzzy="true">Daxil olmaq olmur — şəbəkə xətası</string> |   <string name="login_failed_network" fuzzy="true">Daxil olmaq olmur — şəbəkə xətası</string> | ||||||
|   <string name="login_failed_throttled">Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin.</string> |   <string name="login_failed_throttled">Çox sayda uğursuz daxil olma. Xahiş edirik bir neçə dəqiqə sonra yenidən cəhd edin.</string> | ||||||
|   <string name="login_failed_blocked">Bağışlayın, bu istifadəçi Commons-da bloklanmışdır.</string> |   <string name="login_failed_blocked">Bağışlayın, bu istifadəçi Vikianbardan bloklanıb</string> | ||||||
|   <string name="login_failed_2fa_needed">İki faktorlu giriş doğrulama kodunu verməlisiniz.</string> |   <string name="login_failed_2fa_needed">Siz iki faktorlu autentifikasiya kodunuzu təqdim etməlisiniz.</string> | ||||||
|   <string name="login_failed_generic" fuzzy="true">Daxil olma uğursuz oldu</string> |   <string name="login_failed_generic" fuzzy="true">Daxil olma uğursuz oldu</string> | ||||||
|   <string name="share_upload_button">Yüklə</string> |   <string name="share_upload_button">Yüklə</string> | ||||||
|   <string name="multiple_share_base_title">Bu dəsti adlandırın</string> |   <string name="multiple_share_base_title">Bu dəsti adlandırın</string> | ||||||
|   <string name="provider_modifications">Bildirişlər</string> |   <string name="provider_modifications">Dəyişikliklər</string> | ||||||
|   <string name="menu_upload_single">Yüklə</string> |   <string name="menu_upload_single">Yüklə</string> | ||||||
|   <string name="categories_search_text_hint">Kateqoriyaları axtar</string> |   <string name="categories_search_text_hint">Kateqoriyalarda axtar</string> | ||||||
|   <string name="menu_save_categories">Qeyd et</string> |   <string name="menu_save_categories">Yadda saxla</string> | ||||||
|   <string name="refresh_button">Yenilə</string> |   <string name="refresh_button">Yenilə</string> | ||||||
|   <string name="display_list_button">Siyahı</string> |   <string name="display_list_button">Siyahı</string> | ||||||
|   <string name="contributions_subtitle_zero">(Hələ yükləmə yoxdur)</string> |   <string name="contributions_subtitle_zero">(Hələ yükləmə yoxdur)</string> | ||||||
|  |  | ||||||
|  | @ -307,4 +307,5 @@ | ||||||
|   <string name="please_wait">Моля, изчакайте...</string> |   <string name="please_wait">Моля, изчакайте...</string> | ||||||
|   <string name="delete_helper_ask_spam_blurry">напълно размазано</string> |   <string name="delete_helper_ask_spam_blurry">напълно размазано</string> | ||||||
|   <string name="leaderboard_nearby">Наблизо</string> |   <string name="leaderboard_nearby">Наблизо</string> | ||||||
|  |   <string name="read_help_link">Прочетете повече</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -242,8 +242,8 @@ | ||||||
|   <string name="nominated_for_deletion">Meneget eo bet ar skeudenn evit lemel.</string> |   <string name="nominated_for_deletion">Meneget eo bet ar skeudenn evit lemel.</string> | ||||||
|   <string name="skip_login">Lezel a-gostez</string> |   <string name="skip_login">Lezel a-gostez</string> | ||||||
|   <string name="navigation_item_login">Kevreañ</string> |   <string name="navigation_item_login">Kevreañ</string> | ||||||
|   <string name="skip_login_title" fuzzy="true">Ha c\'hoant ho peus, evit gwir, da gevreañ ?</string> |   <string name="skip_login_title">Ha n\'ho peus ket c\'hoant, evit gwir, da gevreañ ?</string> | ||||||
|   <string name="skip_login_message" fuzzy="true">Da gevreañ ho po en amzer-da-zont evit pellgargañ skeudennoù.</string> |   <string name="skip_login_message">Ret e vo deoc\'h kevreañ en amzer-da-zont evit pellgargañ skeudennoù.</string> | ||||||
|   <string name="login_alert_message">Kevreit, mar plij, evit implijout an arc\'hwel-mañ</string> |   <string name="login_alert_message">Kevreit, mar plij, evit implijout an arc\'hwel-mañ</string> | ||||||
|   <string name="copy_wikicode">Eilañ an destenn wiki er golver</string> |   <string name="copy_wikicode">Eilañ an destenn wiki er golver</string> | ||||||
|   <string name="wikicode_copied">Testenn wiki eilet er golver</string> |   <string name="wikicode_copied">Testenn wiki eilet er golver</string> | ||||||
|  |  | ||||||
|  | @ -106,7 +106,7 @@ | ||||||
|   <string name="menu_view_file_page">Хьажа файлан агӀоне</string> |   <string name="menu_view_file_page">Хьажа файлан агӀоне</string> | ||||||
|   <string name="share_title_hint">Куьг йазор (ТIедилина ду)</string> |   <string name="share_title_hint">Куьг йазор (ТIедилина ду)</string> | ||||||
|   <string name="add_caption_toast">Дехар ду, хӀокху файлан цIе гайта</string> |   <string name="add_caption_toast">Дехар ду, хӀокху файлан цIе гайта</string> | ||||||
|   <string name="share_description_hint">Цунах лаьцна</string> |   <string name="share_description_hint">Цуьнах лаьцна</string> | ||||||
|   <string name="share_caption_hint">Куьг</string> |   <string name="share_caption_hint">Куьг</string> | ||||||
|   <string name="login_failed_network">Чувала(йала) тара цало — сетан гӀалат</string> |   <string name="login_failed_network">Чувала(йала) тара цало — сетан гӀалат</string> | ||||||
|   <string name="login_failed_throttled">ТӀех дукха кхиаме боцу гӀертарш. Дехар ду масех минот йаьлча йуха а хьажа.</string> |   <string name="login_failed_throttled">ТӀех дукха кхиаме боцу гӀертарш. Дехар ду масех минот йаьлча йуха а хьажа.</string> | ||||||
|  |  | ||||||
|  | @ -481,6 +481,7 @@ | ||||||
|   <string name="no_notification">Du har ingen ulæste notifikationer</string> |   <string name="no_notification">Du har ingen ulæste notifikationer</string> | ||||||
|   <string name="no_read_notification">Du har ingen læste notifikationer</string> |   <string name="no_read_notification">Du har ingen læste notifikationer</string> | ||||||
|   <string name="share_logs_using">Del logs ved hjælp af</string> |   <string name="share_logs_using">Del logs ved hjælp af</string> | ||||||
|  |   <string name="check_your_email_inbox">Tjek din e-mail-indbakke</string> | ||||||
|   <string name="menu_option_read">Vis læste</string> |   <string name="menu_option_read">Vis læste</string> | ||||||
|   <string name="menu_option_unread">Vis ulæste</string> |   <string name="menu_option_unread">Vis ulæste</string> | ||||||
|   <string name="error_occurred_in_picking_images">Der opstod en fejl under udvælgelse af billeder</string> |   <string name="error_occurred_in_picking_images">Der opstod en fejl under udvælgelse af billeder</string> | ||||||
|  | @ -788,4 +789,7 @@ | ||||||
|   <string name="pending">Afventer</string> |   <string name="pending">Afventer</string> | ||||||
|   <string name="failed">Mislykkedes</string> |   <string name="failed">Mislykkedes</string> | ||||||
|   <string name="could_not_load_place_data">Kunne ikke indlæse steddata</string> |   <string name="could_not_load_place_data">Kunne ikke indlæse steddata</string> | ||||||
|  |   <string name="red_pin">Dette sted har endnu ikke noget billede, så gå hen og tag et!</string> | ||||||
|  |   <string name="green_pin">Dette sted har allerede et billede.</string> | ||||||
|  |   <string name="grey_pin">Tjekker nu, om dette sted har et billede.</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ | ||||||
| * Sujan | * Sujan | ||||||
| * Sushi | * Sushi | ||||||
| * Tacsipacsi | * Tacsipacsi | ||||||
|  | * TheRabbit22 | ||||||
| * ThisCarthing | * ThisCarthing | ||||||
| * Tobi 406 | * Tobi 406 | ||||||
| * TomatoCake | * TomatoCake | ||||||
|  | @ -802,4 +803,17 @@ | ||||||
|   <string name="multiple_files_depiction">Bitte beachte, dass bei einem Multiupload alle Bilder die gleichen Kategorien und Bezeichnungen erhalten. Sollten die Bilder keine gemeinsamen Bezeichnungen und Kategorien haben, führe bitte mehrere separate Uploads durch.</string> |   <string name="multiple_files_depiction">Bitte beachte, dass bei einem Multiupload alle Bilder die gleichen Kategorien und Bezeichnungen erhalten. Sollten die Bilder keine gemeinsamen Bezeichnungen und Kategorien haben, führe bitte mehrere separate Uploads durch.</string> | ||||||
|   <string name="multiple_files_depiction_header">Hinweis zu Mehrfach-Uploads</string> |   <string name="multiple_files_depiction_header">Hinweis zu Mehrfach-Uploads</string> | ||||||
|   <string name="nearby_wikitalk">Melde ein Problem mit diesem Datenobjekt an Wikidata</string> |   <string name="nearby_wikitalk">Melde ein Problem mit diesem Datenobjekt an Wikidata</string> | ||||||
|  |   <string name="please_enter_some_comments">Bitte gib einige Kommentare ein</string> | ||||||
|  |   <string name="talk">Diskussion</string> | ||||||
|  |   <string name="write_something_about_the_item">Schreibe etwas über das Objekt ‚%1$s‘. Deine Beschreibung wird öffentlich sichtbar sein.</string> | ||||||
|  |   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">‚%1$s‘ existiert nicht mehr, es kann kein Foto mehr davon gemacht werden.</string> | ||||||
|  |   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">‚%1$s‘ ist jetzt an einem anderen Ort. Bitte gib den richtigen Ort und, wenn möglich, den Breiten- und Längengrad an.</string> | ||||||
|  |   <string name="other_problem_or_information_please_explain_below">Sonstiges Problem oder Information (bitte unten erläutern).</string> | ||||||
|  |   <string name="feedback_destination_note">Dein Feedback wird auf der folgenden Wiki-Seite veröffentlicht werden: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a></string> | ||||||
|  |   <string name="are_you_sure_that_you_want_cancel_all_the_uploads">Möchtest du wirklich alle Uploads abbrechen?</string> | ||||||
|  |   <string name="cancelling_all_the_uploads">Alle Uploads werden abgebrochen…</string> | ||||||
|  |   <string name="uploads">Hochgeladene Dateien</string> | ||||||
|  |   <string name="pending">Ausstehend</string> | ||||||
|  |   <string name="failed">Fehlgeschlagen</string> | ||||||
|  |   <string name="could_not_load_place_data">Ortsdaten konnten nicht geladen werden</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -22,6 +22,7 @@ | ||||||
| * JenyxGym | * JenyxGym | ||||||
| * KATRINE1992 | * KATRINE1992 | ||||||
| * Koreller | * Koreller | ||||||
|  | * Mahabarata | ||||||
| * McDutchie | * McDutchie | ||||||
| * Melissadeba95 | * Melissadeba95 | ||||||
| * Metroitendo | * Metroitendo | ||||||
|  | @ -516,6 +517,7 @@ | ||||||
|   <string name="no_notification">Vous n’avez aucune notification non lue</string> |   <string name="no_notification">Vous n’avez aucune notification non lue</string> | ||||||
|   <string name="no_read_notification">Vous n’avez aucune notification lue</string> |   <string name="no_read_notification">Vous n’avez aucune notification lue</string> | ||||||
|   <string name="share_logs_using">Partager les journaux en utilisant</string> |   <string name="share_logs_using">Partager les journaux en utilisant</string> | ||||||
|  |   <string name="check_your_email_inbox">Vérifiez votre boîte de réception</string> | ||||||
|   <string name="menu_option_read">Afficher les lus</string> |   <string name="menu_option_read">Afficher les lus</string> | ||||||
|   <string name="menu_option_unread">Afficher les non lus</string> |   <string name="menu_option_unread">Afficher les non lus</string> | ||||||
|   <string name="error_occurred_in_picking_images">Une erreur est survenue lors de la sélection des images</string> |   <string name="error_occurred_in_picking_images">Une erreur est survenue lors de la sélection des images</string> | ||||||
|  | @ -814,7 +816,7 @@ | ||||||
|   <string name="nearby_wikitalk">Signaler un problème concernant cet élément à Wikidata</string> |   <string name="nearby_wikitalk">Signaler un problème concernant cet élément à Wikidata</string> | ||||||
|   <string name="please_enter_some_comments">Merci de saisir vos commentaires</string> |   <string name="please_enter_some_comments">Merci de saisir vos commentaires</string> | ||||||
|   <string name="talk">Discussion</string> |   <string name="talk">Discussion</string> | ||||||
|   <string name="write_something_about_the_item">Ecrivez quelque chose sur l\'article \"%1$s\". Il sera visible par le public\nAjouter une définition terminologique pour ce terme</string> |   <string name="write_something_about_the_item">Écrivez quelque chose à propos de l’élément « %1$s ». Il sera visible publiquement.</string> | ||||||
|   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' n\'existe plus, aucune photo ne pourra jamais en être prise.</string> |   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' n\'existe plus, aucune photo ne pourra jamais en être prise.</string> | ||||||
|   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' se trouve à un endroit différent. Veuillez indiquer l\'endroit correct ci-dessous et, si possible, indiquez la latitude et la longitude correctes.</string> |   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' se trouve à un endroit différent. Veuillez indiquer l\'endroit correct ci-dessous et, si possible, indiquez la latitude et la longitude correctes.</string> | ||||||
|   <string name="other_problem_or_information_please_explain_below">Autre problème ou information (merci d\'expliquer ci-dessous).</string> |   <string name="other_problem_or_information_please_explain_below">Autre problème ou information (merci d\'expliquer ci-dessous).</string> | ||||||
|  | @ -824,5 +826,8 @@ | ||||||
|   <string name="uploads">Téléversements</string> |   <string name="uploads">Téléversements</string> | ||||||
|   <string name="pending">En attente</string> |   <string name="pending">En attente</string> | ||||||
|   <string name="failed">Échec</string> |   <string name="failed">Échec</string> | ||||||
|   <string name="could_not_load_place_data">Ne peut pas supporter les données</string> |   <string name="could_not_load_place_data">Les données du lieu n\'ont pas pu être chargées</string> | ||||||
|  |   <string name="red_pin">Cet endroit n\'a pas encore de photo, allez en prendre une !</string> | ||||||
|  |   <string name="green_pin">Cet endroit a déjà une photo.</string> | ||||||
|  |   <string name="grey_pin">Je vérifie maintenant si cet endroit a une photo.</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -374,7 +374,7 @@ | ||||||
|   <string name="unable_to_display_nearest_place">A helymeghatározás nélkül nem használható ez a funkció.</string> |   <string name="unable_to_display_nearest_place">A helymeghatározás nélkül nem használható ez a funkció.</string> | ||||||
|   <string name="never_ask_again">Ne kérdezd meg többször</string> |   <string name="never_ask_again">Ne kérdezd meg többször</string> | ||||||
|   <string name="display_location_permission_title">Helymeghatározási engedély</string> |   <string name="display_location_permission_title">Helymeghatározási engedély</string> | ||||||
|   <string name="achievements_fetch_failed" fuzzy="true">Valami hiba történt, nem sikerült az eredményeid betöltése</string> |   <string name="achievements_fetch_failed">Valami hiba történt, nem sikerült az eredményeid betöltése</string> | ||||||
|   <string name="display_campaigns">Kampányok megjelenítése</string> |   <string name="display_campaigns">Kampányok megjelenítése</string> | ||||||
|   <string name="display_campaigns_explanation">Folyamatban lévő kampányok megjelenítése</string> |   <string name="display_campaigns_explanation">Folyamatban lévő kampányok megjelenítése</string> | ||||||
|   <string name="in_app_camera_location_access_explanation">Engedélyezd az alkalmazás számára a helyszín lekérését, ha a kamera nem rögzíti azt! Egyes eszközök kamerái nem rögzítik a helyszínt. Közreműködésed hasznosabb, ha ilyen esetekben hagyod, hogy az alkalmazás lekérje és hozzárendelje a helyszínt. Ezt bármikor módosíthatod a Beállításokban</string> |   <string name="in_app_camera_location_access_explanation">Engedélyezd az alkalmazás számára a helyszín lekérését, ha a kamera nem rögzíti azt! Egyes eszközök kamerái nem rögzítik a helyszínt. Közreműködésed hasznosabb, ha ilyen esetekben hagyod, hogy az alkalmazás lekérje és hozzárendelje a helyszínt. Ezt bármikor módosíthatod a Beállításokban</string> | ||||||
|  | @ -385,7 +385,7 @@ | ||||||
|   <string name="in_app_camera_location_permission_denied">Az alkalmazás helymeghatározási engedély hiányában nem rögzíti a helyszínt a felvételekkel együtt</string> |   <string name="in_app_camera_location_permission_denied">Az alkalmazás helymeghatározási engedély hiányában nem rögzíti a helyszínt a felvételekkel együtt</string> | ||||||
|   <string name="in_app_camera_location_unavailable">Az alkalmazás nem rögzít helyszínt a felvételekkel együtt, mivel a GPS ki van kapcsolva</string> |   <string name="in_app_camera_location_unavailable">Az alkalmazás nem rögzít helyszínt a felvételekkel együtt, mivel a GPS ki van kapcsolva</string> | ||||||
|   <string name="nearby_campaign_dismiss_message">Többé nem lesznek láthatók a kampányok. Ha akarod, visszakapcsolható a Beállításoknál.</string> |   <string name="nearby_campaign_dismiss_message">Többé nem lesznek láthatók a kampányok. Ha akarod, visszakapcsolható a Beállításoknál.</string> | ||||||
|   <string name="this_function_needs_network_connection" fuzzy="true">Ehhez a funkcióhoz hálózati kapcsolat szükséges, kérlek ellenőrizd az internetbeállításaidat.</string> |   <string name="this_function_needs_network_connection">Ehhez a funkcióhoz hálózati kapcsolat szükséges. Kérlek ellenőrizd az internetbeállításaidat!</string> | ||||||
|   <string name="error_processing_image">Hiba történt a kép feltöltése során. Próbáld meg újra!</string> |   <string name="error_processing_image">Hiba történt a kép feltöltése során. Próbáld meg újra!</string> | ||||||
|   <string name="getting_edit_token">Szerkesztő token beszerzése</string> |   <string name="getting_edit_token">Szerkesztő token beszerzése</string> | ||||||
|   <string name="check_category_adding_template">Kategóriaellenőrző sablon felhelyezése</string> |   <string name="check_category_adding_template">Kategóriaellenőrző sablon felhelyezése</string> | ||||||
|  | @ -454,9 +454,9 @@ | ||||||
|   <string name="delete_helper_show_deletion_message_if">Törlésre jelölve: %1$s.</string> |   <string name="delete_helper_show_deletion_message_if">Törlésre jelölve: %1$s.</string> | ||||||
|   <string name="delete_helper_show_deletion_title_failed">Sikertelen</string> |   <string name="delete_helper_show_deletion_title_failed">Sikertelen</string> | ||||||
|   <string name="delete_helper_show_deletion_message_else">Nem sikerült a törlés kérése.</string> |   <string name="delete_helper_show_deletion_message_else">Nem sikerült a törlés kérése.</string> | ||||||
|   <string name="delete_helper_ask_spam_selfie" fuzzy="true">Egy szelfi</string> |   <string name="delete_helper_ask_spam_selfie">egy szelfi, amely egyetlen cikkben sem szerepel</string> | ||||||
|   <string name="delete_helper_ask_spam_blurry" fuzzy="true">Homályos</string> |   <string name="delete_helper_ask_spam_blurry">teljesen homályos</string> | ||||||
|   <string name="delete_helper_ask_spam_nonsense" fuzzy="true">Nonszensz</string> |   <string name="delete_helper_ask_spam_nonsense">nonszensz, abszolút használhatatlan bármely cikkben is</string> | ||||||
|   <string name="delete_helper_ask_reason_copyright_press_photo">Sajtófotó</string> |   <string name="delete_helper_ask_reason_copyright_press_photo">Sajtófotó</string> | ||||||
|   <string name="delete_helper_ask_reason_copyright_internet_photo">Random fénykép az Internetről</string> |   <string name="delete_helper_ask_reason_copyright_internet_photo">Random fénykép az Internetről</string> | ||||||
|   <string name="delete_helper_ask_reason_copyright_logo">Logó</string> |   <string name="delete_helper_ask_reason_copyright_logo">Logó</string> | ||||||
|  | @ -472,7 +472,7 @@ | ||||||
|   <string name="place_state_needs_photo">Fénykép szükséges</string> |   <string name="place_state_needs_photo">Fénykép szükséges</string> | ||||||
|   <string name="place_type">Hely típusa:</string> |   <string name="place_type">Hely típusa:</string> | ||||||
|   <string name="nearby_search_hint">Híd, múzeum, szálloda, stb.</string> |   <string name="nearby_search_hint">Híd, múzeum, szálloda, stb.</string> | ||||||
|   <string name="you_must_reset_your_passsword" fuzzy="true">A belépés nem sikerült, kérj új jelszót.</string> |   <string name="you_must_reset_your_passsword">A belépés nem sikerült. Kérj új jelszót!</string> | ||||||
|   <string name="setting_wallpaper_dialog_title">Beállítás háttérképnek</string> |   <string name="setting_wallpaper_dialog_title">Beállítás háttérképnek</string> | ||||||
|   <string name="setting_wallpaper_dialog_message">Beállítás háttérképnek. Kérem várjon...</string> |   <string name="setting_wallpaper_dialog_message">Beállítás háttérképnek. Kérem várjon...</string> | ||||||
|   <string name="theme_default_name">Rendszerbeállítás követése</string> |   <string name="theme_default_name">Rendszerbeállítás követése</string> | ||||||
|  |  | ||||||
|  | @ -765,8 +765,15 @@ | ||||||
|   <string name="nearby_wikitalk">Signalar a Wikidata un problema sur iste elemento</string> |   <string name="nearby_wikitalk">Signalar a Wikidata un problema sur iste elemento</string> | ||||||
|   <string name="please_enter_some_comments">Per favor insere alcun commentos</string> |   <string name="please_enter_some_comments">Per favor insere alcun commentos</string> | ||||||
|   <string name="talk">Discussion</string> |   <string name="talk">Discussion</string> | ||||||
|  |   <string name="write_something_about_the_item">Scribe qualcosa sur le elemento ‘%1$s’. Isto essera visibile publicamente.</string> | ||||||
|   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">‘%1$s’ non existe plus, necun imagine pote jammais esser prendite de illo.</string> |   <string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">‘%1$s’ non existe plus, necun imagine pote jammais esser prendite de illo.</string> | ||||||
|   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">‘%1$s’ es in un altere loco. Per favor specifica le loco correcte hic infra, e si possibile, indica le latitude e longitude correcte.</string> |   <string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">‘%1$s’ es in un altere loco. Per favor specifica le loco correcte hic infra, e si possibile, indica le latitude e longitude correcte.</string> | ||||||
|   <string name="other_problem_or_information_please_explain_below">Altere problema o information (per favor explica hic infra).</string> |   <string name="other_problem_or_information_please_explain_below">Altere problema o information (per favor explica hic infra).</string> | ||||||
|   <string name="feedback_destination_note">Tu retroaction apparera sur le sequente pagina wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a></string> |   <string name="feedback_destination_note">Tu retroaction apparera sur le sequente pagina wiki: <a href=\"https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback\">Commons:Mobile app/Feedback</a></string> | ||||||
|  |   <string name="are_you_sure_that_you_want_cancel_all_the_uploads">Es tu secur de voler cancellar tote le incargamentos?</string> | ||||||
|  |   <string name="cancelling_all_the_uploads">Cancella tote le incargamentos…</string> | ||||||
|  |   <string name="uploads">Incargamentos</string> | ||||||
|  |   <string name="pending">Pendente</string> | ||||||
|  |   <string name="failed">Fallite</string> | ||||||
|  |   <string name="could_not_load_place_data">Non poteva cargar le datos del loco</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
|  | @ -760,4 +760,7 @@ | ||||||
|     <item quantity="one">%d immagine selezionata</item> |     <item quantity="one">%d immagine selezionata</item> | ||||||
|     <item quantity="other">%d immagini selezionate</item> |     <item quantity="other">%d immagini selezionate</item> | ||||||
|   </plurals> |   </plurals> | ||||||
|  |   <string name="red_pin">Questo posto non ha ancora una foto, scattane una!</string> | ||||||
|  |   <string name="green_pin">Questo posto ha già una foto.</string> | ||||||
|  |   <string name="grey_pin">Ora controlliamo se questo posto ha una foto.</string> | ||||||
| </resources> | </resources> | ||||||
|  |  | ||||||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 why-lab
						why-lab