diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index dcbba0597..a4682fd3c 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -70,7 +70,7 @@ body: required: false - type: textarea attributes: - label: Screenshots + label: Screen-shots description: Add screenshots related to the issue (if available). Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher. validations: required: false diff --git a/CHANGELOG.md b/CHANGELOG.md index 575aa6a32..6fd325813 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,5 @@ # Wikimedia Commons for Android -## v6.0.2 - -### What's changed -* Addressed a bug that prevented the keyboard from appearing in various text fields, such as on the upload wizard -* Links in the "File usages" list are now clickable and will take you to the correct page. -* Titles for file usages are now clearer and easier to understand -* Bug fixes and stability improvements - ## v6.0.1 ### What's changed diff --git a/README.md b/README.md index 37f1a7872..0b31ff5be 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,11 @@ Thank you all for your work! | [
misaochan](https://github.com/misaochan) | [
translatewiki](https://github.com/translatewiki) | [
neslihanturan](https://github.com/neslihanturan) | [
yuvipanda](https://github.com/yuvipanda) | [
nicolas-raoul](https://github.com/nicolas-raoul) | | :---: | :---: | :---: | :---: | :---: | -| [
psh](https://github.com/psh) | [
domdomegg](https://github.com/domdomegg) | [
maskaravivek](https://github.com/maskaravivek) | [
madhurgupta10](https://github.com/madhurgupta10) | [
ashishkumar468](https://github.com/ashishkumar468) | -| [
bvibber](https://github.com/bvibber) | [
whym](https://github.com/whym) | [
akaita](https://github.com/akaita) | [
sivaraam](https://github.com/sivaraam) | [
veyndan](https://github.com/veyndan) | -| [
ujjwalagrawal17](https://github.com/ujjwalagrawal17) | [
macgills](https://github.com/macgills) | [
amire80](https://github.com/amire80) | [
dbrant](https://github.com/dbrant) | [
vanshikaarora](https://github.com/vanshikaarora) | -| [
RitikaPahwa4444](https://github.com/RitikaPahwa4444) | [
Ayan-10](https://github.com/Ayan-10) | [
rohit9625](https://github.com/rohit9625) | [
shashankiitbhu](https://github.com/shashankiitbhu) | [
Pratham2305](https://github.com/Pratham2305) | -| [
parneet-guraya](https://github.com/parneet-guraya) | [
sandarumk](https://github.com/sandarumk) | [
tanvidadu](https://github.com/tanvidadu) | [
cypherop](https://github.com/cypherop) | [
Prince-kushwaha](https://github.com/Prince-kushwaha) | - +| [
domdomegg](https://github.com/domdomegg) | [
maskaravivek](https://github.com/maskaravivek) | [
psh](https://github.com/psh) | [
madhurgupta10](https://github.com/madhurgupta10) | [
ashishkumar468](https://github.com/ashishkumar468) | +| [
bvibber](https://github.com/bvibber) | [
whym](https://github.com/whym) | [
akaita](https://github.com/akaita) | [
veyndan](https://github.com/veyndan) | [
ujjwalagrawal17](https://github.com/ujjwalagrawal17) | +| [
macgills](https://github.com/macgills) | [
dbrant](https://github.com/dbrant) | [
vanshikaarora](https://github.com/vanshikaarora) | [
sivaraam](https://github.com/sivaraam) | [
Ayan-10](https://github.com/Ayan-10) | +| [
shashankiitbhu](https://github.com/shashankiitbhu) | [
Pratham2305](https://github.com/Pratham2305) | [
sandarumk](https://github.com/sandarumk) | [
tanvidadu](https://github.com/tanvidadu) | [
cypherop](https://github.com/cypherop) | +| [
Prince-kushwaha](https://github.com/Prince-kushwaha) | [
tobias47n9e](https://github.com/tobias47n9e) | [
4D17Y4](https://github.com/4D17Y4) | [
hismaeel](https://github.com/hismaeel) | [
tshradheya](https://github.com/tshradheya) | .. and [many more](https://github.com/commons-app/apps-android-commons/graphs/contributors). diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 41788128c..7a6a2bcf4 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -24,8 +24,8 @@ android { applicationId = "fr.free.nrw.commons" minSdk = 21 targetSdk = 35 - versionCode = 1059 - versionName = "6.1.0" + versionCode = 1058 + versionName = "6.0.2" setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" @@ -226,7 +226,6 @@ dependencies { implementation(libs.rxbinding) implementation(libs.rxbinding.appcompat) implementation(libs.facebook.fresco) - implementation(libs.facebook.fresco.middleware) implementation(libs.apache.commons.lang3) // UI diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 17917666d..e8215bd90 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,7 +57,8 @@ tools:replace="android:appComponentFactory"> + android:exported="false" + android:label="@string/title_activity_single_web_view" /> @@ -84,7 +85,6 @@ android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" /> @@ -101,9 +101,8 @@ android:name=".upload.UploadActivity" android:configChanges="orientation|screenSize|keyboard" android:exported="true" - android:hardwareAccelerated="false" android:icon="@mipmap/ic_launcher" - android:windowSoftInputMode="adjustPan"> + android:windowSoftInputMode="adjustResize"> diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt index c54c3aefb..1c28d5fe4 100644 --- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt +++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt @@ -1,11 +1,7 @@ package fr.free.nrw.commons import androidx.annotation.VisibleForTesting -import fr.free.nrw.commons.wikidata.GsonUtil import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar -import fr.free.nrw.commons.wikidata.mwapi.MwErrorResponse -import fr.free.nrw.commons.wikidata.mwapi.MwIOException -import fr.free.nrw.commons.wikidata.mwapi.MwLegacyServiceError import okhttp3.Cache import okhttp3.Interceptor import okhttp3.OkHttpClient @@ -90,25 +86,16 @@ private class UnsuccessfulResponseInterceptor : Interceptor { rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody -> if (ERRORS_PREFIX == responseBody.string()) { rsp.body.use { body -> - val bodyString = body!!.string() - - throw MwIOException( - "MediaWiki API returned error: $bodyString", - GsonUtil.defaultGson.fromJson( - bodyString, - MwErrorResponse::class.java - ).error!!, - ) + throw IOException(body!!.string()) } } } - } catch (e: MwIOException) { + } catch (e: IOException) { // Log the error as debug (and therefore, "expected") or at error level if (suppressErrors) { Timber.d(e, "Suppressed (known / expected) error") } else { Timber.e(e) - throw e } } return rsp diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt index 0c9901b56..688f508ae 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt @@ -89,7 +89,7 @@ class LoginActivity : AccountAuthenticatorActivity() { binding = ActivityLoginBinding.inflate(layoutInflater) applyEdgeToEdgeAllInsets(binding!!.root) - binding!!.root.handleKeyboardInsets() + binding?.aboutPrivacyPolicy?.handleKeyboardInsets() with(binding!!) { setContentView(root) diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt index e21e1ac8f..d64ab16b3 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/items/BookmarkItemsDao.kt @@ -144,18 +144,8 @@ class BookmarkItemsDao @Inject constructor( */ @SuppressLint("Range") fun fromCursor(cursor: Cursor) = with(cursor) { - var name = getString(COLUMN_NAME) - if (name == null) { - name = "" - } - - var id = getString(COLUMN_ID) - if (id == null) { - id = "" - } - DepictedItem( - name, + getString(COLUMN_NAME), getString(COLUMN_DESCRIPTION), getString(COLUMN_IMAGE), getStringArray(COLUMN_INSTANCE_LIST), @@ -165,7 +155,7 @@ class BookmarkItemsDao @Inject constructor( getStringArray(COLUMN_CATEGORIES_THUMBNAIL_LIST) ), getString(COLUMN_IS_SELECTED).toBoolean(), - id + getString(COLUMN_ID) ) } @@ -173,13 +163,19 @@ class BookmarkItemsDao @Inject constructor( categoryNameList: List, categoryDescriptionList: List, categoryThumbnailList: List - ): List = categoryNameList.mapIndexed { index, name -> - CategoryItem( - name = name, - description = categoryDescriptionList.getOrNull(index), - thumbnail = categoryThumbnailList.getOrNull(index), - isSelected = false - ) + ): List { + return buildList { + for (i in categoryNameList.indices) { + add( + CategoryItem( + categoryNameList[i], + categoryDescriptionList[i], + categoryThumbnailList[i], + false + ) + ) + } + } } /** diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt index 00c8e3228..e30b3160d 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesDao.kt @@ -128,10 +128,7 @@ class BookmarkPicturesDao @Inject constructor( } fun fromCursor(cursor: Cursor): Bookmark { - var fileName = cursor.getString(COLUMN_MEDIA_NAME) - if (fileName == null) { - fileName = "" - } + val fileName = cursor.getString(COLUMN_MEDIA_NAME) return Bookmark( fileName, cursor.getString(COLUMN_CREATOR), uriForName(fileName) ) diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt index 9f94e8592..6bf0bc0ed 100644 --- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignConfig.kt @@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName */ class CampaignConfig { @SerializedName("showOnlyLiveCampaigns") - var showOnlyLiveCampaigns = false + private val showOnlyLiveCampaigns = false @SerializedName("sortBy") - var sortBy: String? = null -} \ No newline at end of file + private val sortBy: String? = null +} diff --git a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt index 1656109e7..767732eb7 100644 --- a/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt +++ b/app/src/main/java/fr/free/nrw/commons/campaigns/CampaignResponseDTO.kt @@ -8,8 +8,8 @@ import fr.free.nrw.commons.campaigns.models.Campaign */ class CampaignResponseDTO { @SerializedName("config") - var campaignConfig: CampaignConfig? = null + val campaignConfig: CampaignConfig? = null @SerializedName("campaigns") - var campaigns: List? = null -} \ No newline at end of file + val campaigns: List? = null +} diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt index b9532a12e..29267452b 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.kt @@ -180,8 +180,8 @@ class ContributionController @Inject constructor(@param:Named("default_preferenc showAlertDialog( activity, activity.getString(R.string.location_permission_title), activity.getString(R.string.in_app_camera_location_permission_rationale), - activity.getString(R.string.ok), - activity.getString(R.string.cancel), + activity.getString(android.R.string.ok), + activity.getString(android.R.string.cancel), { createDialogsAndHandleLocationPermissions( activity, diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt index 6d0822604..b86cd6dc9 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.kt @@ -5,6 +5,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.res.Configuration +import android.net.Uri import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater @@ -19,8 +20,6 @@ import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult import androidx.annotation.VisibleForTesting -import androidx.core.net.toUri -import androidx.core.os.BundleCompat import androidx.paging.PagedList import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -39,10 +38,12 @@ import fr.free.nrw.commons.filepicker.FilePicker import fr.free.nrw.commons.media.MediaClient import fr.free.nrw.commons.profile.ProfileActivity import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog +import fr.free.nrw.commons.utils.SystemThemeUtils import fr.free.nrw.commons.utils.ViewUtil.showShortToast import fr.free.nrw.commons.utils.copyToClipboard import fr.free.nrw.commons.utils.handleWebUrl import fr.free.nrw.commons.wikidata.model.WikiSite +import org.apache.commons.lang3.StringUtils import javax.inject.Inject import javax.inject.Named @@ -52,6 +53,10 @@ import javax.inject.Named */ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsListContract.View, ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback { + @JvmField + @Inject + var systemThemeUtils: SystemThemeUtils? = null + @JvmField @Inject var controller: ContributionController? = null @@ -78,14 +83,13 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL var sessionManager: SessionManager? = null private var binding: FragmentContributionsListBinding? = null - private var fabClose: Animation? = null - private var fabOpen: Animation? = null - private var rotateForward: Animation? = null - private var rotateBackward: Animation? = null + private var fab_close: Animation? = null + private var fab_open: Animation? = null + private var rotate_forward: Animation? = null + private var rotate_backward: Animation? = null private var isFabOpen = false - private lateinit var inAppCameraLocationPermissionLauncher: - ActivityResultLauncher> + private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher> @VisibleForTesting var rvContributionsList: RecyclerView? = null @@ -96,8 +100,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL @VisibleForTesting var callback: Callback? = null - private val spanCountLandscape = 3 - private val spanCountPortrait = 1 + private val SPAN_COUNT_LANDSCAPE = 3 + private val SPAN_COUNT_PORTRAIT = 1 private var contributionsSize = 0 private var userName: String? = null @@ -146,7 +150,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL userName = requireArguments().getString(ProfileActivity.KEY_USERNAME) } - if (userName.isNullOrEmpty()) { + if (StringUtils.isEmpty(userName)) { userName = sessionManager!!.userName } inAppCameraLocationPermissionLauncher = @@ -157,8 +161,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL controller?.locationPermissionCallback?.onLocationPermissionGranted() } else { activity?.let { currentActivity -> - if (currentActivity.shouldShowRequestPermissionRationale( - permission.ACCESS_FINE_LOCATION)) { + if (currentActivity.shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { controller?.handleShowRationaleFlowCameraLocation( currentActivity, inAppCameraLocationPermissionLauncher, // Pass launcher @@ -166,8 +169,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL ) } else { controller?.locationPermissionCallback?.onLocationPermissionDenied( - currentActivity.getString( - R.string.in_app_camera_location_permission_denied) + currentActivity.getString(R.string.in_app_camera_location_permission_denied) ) } } @@ -187,7 +189,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL contributionsListPresenter!!.onAttachView(this) binding!!.fabCustomGallery.setOnClickListener { v: View? -> launchCustomSelector() } binding!!.fabCustomGallery.setOnLongClickListener { view: View? -> - showShortToast(context, R.string.custom_selector_title) + showShortToast(context, fr.free.nrw.commons.R.string.custom_selector_title) true } @@ -197,7 +199,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL } else { binding!!.tvContributionsOfUser.visibility = View.VISIBLE binding!!.tvContributionsOfUser.text = - getString(R.string.contributions_of_user, userName) + getString(fr.free.nrw.commons.R.string.contributions_of_user, userName) binding!!.fabLayout.visibility = View.GONE } @@ -235,10 +237,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL } private fun initAdapter() { - adapter = ContributionsListAdapter(this, - mediaClient!!, - mediaDataExtractor!!, - compositeDisposable) + adapter = ContributionsListAdapter(this, mediaClient!!, mediaDataExtractor!!, compositeDisposable) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -313,7 +312,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { if (e.action == MotionEvent.ACTION_DOWN) { if (isFabOpen) { - animateFAB(true) + animateFAB(isFabOpen) } } return false @@ -345,20 +344,14 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL } private fun getSpanCount(orientation: Int): Int { - return if (orientation == Configuration.ORIENTATION_LANDSCAPE) - spanCountLandscape - else - spanCountPortrait + return if (orientation == Configuration.ORIENTATION_LANDSCAPE) SPAN_COUNT_LANDSCAPE else SPAN_COUNT_PORTRAIT } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) // check orientation binding!!.fabLayout.orientation = - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) - LinearLayout.HORIZONTAL - else - LinearLayout.VERTICAL + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL rvContributionsList ?.setLayoutManager( GridLayoutManager(context, getSpanCount(newConfig.orientation)) @@ -366,10 +359,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL } private fun initializeAnimations() { - fabOpen = AnimationUtils.loadAnimation(activity, R.anim.fab_open) - fabClose = AnimationUtils.loadAnimation(activity, R.anim.fab_close) - rotateForward = AnimationUtils.loadAnimation(activity, R.anim.rotate_forward) - rotateBackward = AnimationUtils.loadAnimation(activity, R.anim.rotate_backward) + fab_open = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_open) + fab_close = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_close) + rotate_forward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_forward) + rotate_backward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_backward) } private fun setListeners() { @@ -385,7 +378,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL binding!!.fabCamera.setOnLongClickListener { view: View? -> showShortToast( context, - R.string.add_contribution_from_camera + fr.free.nrw.commons.R.string.add_contribution_from_camera ) true } @@ -394,7 +387,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL animateFAB(isFabOpen) } binding!!.fabGallery.setOnLongClickListener { view: View? -> - showShortToast(context, R.string.menu_from_gallery) + showShortToast(context, fr.free.nrw.commons.R.string.menu_from_gallery) true } } @@ -402,7 +395,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL /** * Launch Custom Selector. */ - private fun launchCustomSelector() { + protected fun launchCustomSelector() { controller!!.initiateCustomGalleryPickWithPermission( requireActivity(), customSelectorLauncherForResult @@ -418,18 +411,18 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL this.isFabOpen = !isFabOpen if (binding!!.fabPlus.isShown) { if (isFabOpen) { - binding!!.fabPlus.startAnimation(rotateBackward) - binding!!.fabCamera.startAnimation(fabClose) - binding!!.fabGallery.startAnimation(fabClose) - binding!!.fabCustomGallery.startAnimation(fabClose) + binding!!.fabPlus.startAnimation(rotate_backward) + binding!!.fabCamera.startAnimation(fab_close) + binding!!.fabGallery.startAnimation(fab_close) + binding!!.fabCustomGallery.startAnimation(fab_close) binding!!.fabCamera.hide() binding!!.fabGallery.hide() binding!!.fabCustomGallery.hide() } else { - binding!!.fabPlus.startAnimation(rotateForward) - binding!!.fabCamera.startAnimation(fabOpen) - binding!!.fabGallery.startAnimation(fabOpen) - binding!!.fabCustomGallery.startAnimation(fabOpen) + binding!!.fabPlus.startAnimation(rotate_forward) + binding!!.fabCamera.startAnimation(fab_open) + binding!!.fabGallery.startAnimation(fab_open) + binding!!.fabCustomGallery.startAnimation(fab_open) binding!!.fabCamera.show() binding!!.fabGallery.show() binding!!.fabCustomGallery.show() @@ -441,9 +434,9 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL /** * Shows welcome message if user has no contributions yet i.e. new user. */ - override fun showWelcomeTip(numberOfUploads: Boolean) { + override fun showWelcomeTip(shouldShow: Boolean) { binding!!.noContributionsYet.visibility = - if (numberOfUploads) View.VISIBLE else View.GONE + if (shouldShow) View.VISIBLE else View.GONE } /** @@ -463,22 +456,22 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - val layoutManager = rvContributionsList?.layoutManager as GridLayoutManager? + val layoutManager = rvContributionsList + ?.getLayoutManager() as GridLayoutManager? outState.putParcelable(RV_STATE, layoutManager!!.onSaveInstanceState()) } override fun onViewStateRestored(savedInstanceState: Bundle?) { super.onViewStateRestored(savedInstanceState) if (null != savedInstanceState) { - val savedRecyclerLayoutState = - BundleCompat.getParcelable(savedInstanceState, RV_STATE, Parcelable::class.java) + val savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE) rvContributionsList!!.layoutManager!!.onRestoreInstanceState(savedRecyclerLayoutState) } } - override fun openMediaDetail(contribution: Int, isWikipediaPageExists: Boolean) { + override fun openMediaDetail(position: Int, isWikipediaButtonDisplayed: Boolean) { if (null != callback) { //Just being safe, ideally they won't be called when detached - callback!!.showDetail(contribution, isWikipediaPageExists) + callback!!.showDetail(position, isWikipediaButtonDisplayed) } } @@ -490,8 +483,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL override fun addImageToWikipedia(contribution: Contribution?) { showAlertDialog( requireActivity(), - getString(R.string.add_picture_to_wikipedia_article_title), - getString(R.string.add_picture_to_wikipedia_article_desc), + getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_title), + getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_desc), { if (contribution != null) { showAddImageToWikipediaInstructions(contribution) @@ -505,18 +498,16 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL * @param contribution */ private fun showAddImageToWikipediaInstructions(contribution: Contribution) { - val fragmentManager = this.parentFragmentManager + val fragmentManager = fragmentManager val fragment = newInstance(contribution) fragment.callback = - WikipediaInstructionsDialogFragment.Callback { - contribution: Contribution?, - copyWikicode: Boolean -> - onConfirmClicked( + WikipediaInstructionsDialogFragment.Callback { contribution: Contribution?, copyWikicode: Boolean -> + this.onConfirmClicked( contribution, copyWikicode ) } - fragment.show(fragmentManager, "WikimediaFragment") + fragment.show(fragmentManager!!, "WikimediaFragment") } @@ -543,7 +534,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL val url = languageWikipediaSite!!.mobileUrl() + "/wiki/" + (contribution!!.wikidataPlace ?.getWikipediaPageTitle()) - handleWebUrl(requireContext(), url.toUri()) + handleWebUrl(requireContext(), Uri.parse(url)) } fun getContributionStateAt(position: Int): Int { diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt index d481017b2..5c2c44ab5 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt @@ -153,7 +153,21 @@ after opening the app. } } setUpPager() - + /** + * Ask the user for media location access just after login + * so that location in the EXIF metadata of the images shared by the user + * is retained on devices running Android 10 or above + */ +// if (VERSION.SDK_INT >= VERSION_CODES.Q) { +// ActivityCompat.requestPermissions(this, +// new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, 0); +// PermissionUtils.checkPermissionsAndPerformAction( +// this, +// () -> {}, +// R.string.media_location_permission_denied, +// R.string.add_location_manually, +// permission.ACCESS_MEDIA_LOCATION); +// } checkAndResumeStuckUploads() } } @@ -324,7 +338,7 @@ after opening the app. ) .subscribeOn(Schedulers.io()) .blockingGet() - Timber.d("Resuming %d uploads...", stuckUploads.size) + Timber.d("Resuming " + stuckUploads.size + " uploads...") if (!stuckUploads.isEmpty()) { for (contribution in stuckUploads) { contribution.state = Contribution.STATE_QUEUED diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt index 8e899fcba..06c31fede 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/SetWallpaperWorker.kt @@ -45,10 +45,10 @@ class SetWallpaperWorker(context: Context, params: WorkerParameters) : } } - override fun onFailureImpl(dataSource: DataSource?>) { + override fun onFailureImpl(dataSource: DataSource>?) { Timber.d("Error getting bitmap from image url %s", imageUrl.toString()) showNotification(context, "Setting Wallpaper Failed", "Failed to download image.") - dataSource.close() + dataSource?.close() } }, CallerThreadExecutor.getInstance()) diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt index 4bf295f4c..ec08f6f73 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Folder.kt @@ -39,11 +39,4 @@ data class Folder( return true } - - override fun hashCode(): Int { - var result = bucketId.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + images.hashCode() - return result - } } diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt index a172f28e2..a2965fb5d 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/model/Image.kt @@ -1,7 +1,6 @@ package fr.free.nrw.commons.customselector.model import android.net.Uri -import android.os.Build import android.os.Parcel import android.os.Parcelable @@ -49,12 +48,7 @@ data class Image( this( parcel.readLong(), parcel.readString()!!, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - parcel.readParcelable(Uri::class.java.classLoader, Uri::class.java)!! - } else { - @Suppress("DEPRECATION") - parcel.readParcelable(Uri::class.java.classLoader)!! - }, + parcel.readParcelable(Uri::class.java.classLoader)!!, parcel.readString()!!, parcel.readLong(), parcel.readString()!!, @@ -127,16 +121,4 @@ data class Image( override fun newArray(size: Int): Array = arrayOfNulls(size) } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + bucketId.hashCode() - result = 31 * result + name.hashCode() - result = 31 * result + uri.hashCode() - result = 31 * result + path.hashCode() - result = 31 * result + bucketName.hashCode() - result = 31 * result + sha1.hashCode() - result = 31 * result + date.hashCode() - return result - } } diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt index c3ef4a784..62a440ff4 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/adapter/ImageAdapter.kt @@ -168,7 +168,8 @@ class ImageAdapter( // Getting selected index when switch is off } else if (actionableImagesMap.size > position) { - ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position]) + ImageHelper + .getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position]) // For any other case return -1 } else { @@ -347,14 +348,8 @@ class ImageAdapter( numberOfSelectedImagesMarkedAsNotForUpload-- } notifyItemChanged(position, ImageUnselected()) - // Notify listener of deselection to update UI - imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } else { - // Prevent adding the same image multiple times - val image = if (showAlreadyActionedImages) images[position] else ArrayList(actionableImagesMap.values)[position] - if (selectedImages.contains(image)) { - return // Image already selected, ignore additional clicks - } + val image = images[position] scope.launch(ioDispatcher) { val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher) withContext(Dispatchers.Main) { @@ -378,6 +373,7 @@ class ImageAdapter( } selectedImages.add(image) notifyItemChanged(position, ImageSelectedOrUpdated()) + imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload) } } @@ -636,4 +632,4 @@ class ImageAdapter( fun setSingleSelection(single: Boolean) { singleSelection = single } -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt index a5182fe62..4f37106cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/customselector/ui/selector/ImageFragment.kt @@ -9,6 +9,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ProgressBar +import android.widget.Switch import androidx.appcompat.app.AlertDialog import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.view.isVisible @@ -19,7 +20,6 @@ import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.switchmaterial.SwitchMaterial import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao @@ -47,7 +47,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch -import timber.log.Timber import java.util.TreeMap import javax.inject.Inject import kotlin.collections.ArrayList @@ -82,7 +81,7 @@ class ImageFragment : */ private var selectorRV: RecyclerView? = null private var loader: ProgressBar? = null - private var switch: SwitchMaterial? = null + private var switch: Switch? = null lateinit var filteredImages: ArrayList /** @@ -212,12 +211,8 @@ class ImageFragment : savedInstanceState: Bundle?, ): View? { _binding = FragmentCustomSelectorBinding.inflate(inflater, container, false) - - // ensures imageAdapter is initialized - if (!::imageAdapter.isInitialized) { - imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!) - Timber.d("Initialized imageAdapter in onCreateView") - } + imageAdapter = + ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!) // Set single selection mode if needed val singleSelection = (activity as? CustomSelectorActivity)?.intent?.getBooleanExtra(CustomSelectorActivity.EXTRA_SINGLE_SELECTION, false) == true imageAdapter.setSingleSelection(singleSelection) @@ -375,12 +370,7 @@ class ImageFragment : * notifyDataSetChanged, rebuild the holder views to account for deleted images. */ override fun onResume() { - if (::imageAdapter.isInitialized) { - imageAdapter.notifyDataSetChanged() - Timber.d("Notified imageAdapter in onResume") - } else { - Timber.w("imageAdapter not initialized in onResume") - } + imageAdapter.notifyDataSetChanged() super.onResume() } @@ -390,19 +380,14 @@ class ImageFragment : * Save the Image Fragment state. */ override fun onDestroy() { - if (::imageAdapter.isInitialized) { - imageAdapter.cleanUp() - Timber.d("Cleaned up imageAdapter in onDestroy") - } else { - Timber.w("imageAdapter not initialized in onDestroy, skipping cleanup") - } + imageAdapter.cleanUp() val position = - (selectorRV?.layoutManager as? GridLayoutManager) - ?.findFirstVisibleItemPosition() ?: -1 + (selectorRV?.layoutManager as GridLayoutManager) + .findFirstVisibleItemPosition() - // check for valid position and non-empty image list - if (position != -1 && filteredImages.isNotEmpty() && ::imageAdapter.isInitialized) { + // Check for empty RecyclerView. + if (position != -1 && filteredImages.size > 0) { context?.let { context -> context .getSharedPreferences( @@ -411,57 +396,34 @@ class ImageFragment : )?.let { prefs -> prefs.edit()?.let { editor -> editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply() - Timber.d("Saved last visible item ID: %d", imageAdapter.getImageIdAt(position)) } } } - } else { - Timber.d("Skipped saving item ID: position=%d, filteredImages.size=%d, imageAdapter initialized=%b", - position, filteredImages.size, ::imageAdapter.isInitialized) } super.onDestroy() } override fun onDestroyView() { _binding = null - selectorRV = null - loader = null - switch = null - progressLayout = null super.onDestroyView() } override fun refresh() { - if (::imageAdapter.isInitialized) { - imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) - Timber.d("Refreshed imageAdapter") - } else { - Timber.w("imageAdapter not initialized in refresh") - } + imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) } /** * Removes the image from the actionable image map */ fun removeImage(image: Image) { - if (::imageAdapter.isInitialized) { - imageAdapter.removeImageFromActionableImageMap(image) - Timber.d("Removed image from actionable image map") - } else { - Timber.w("imageAdapter not initialized in removeImage") - } + imageAdapter.removeImageFromActionableImageMap(image) } /** * Clears the selected images */ fun clearSelectedImages() { - if (::imageAdapter.isInitialized) { - imageAdapter.clearSelectedImages() - Timber.d("Cleared selected images") - } else { - Timber.w("imageAdapter not initialized in clearSelectedImages") - } + imageAdapter.clearSelectedImages() } /** @@ -472,15 +434,6 @@ class ImageFragment : selectedImages: ArrayList, shouldRefresh: Boolean, ) { - if (::imageAdapter.isInitialized) { - imageAdapter.setSelectedImages(selectedImages) - if (shouldRefresh) { - imageAdapter.refresh(filteredImages, allImages, getUploadingContributions()) - } - Timber.d("Passed %d selected images to imageAdapter, shouldRefresh=%b", selectedImages.size, shouldRefresh) - } else { - Timber.w("imageAdapter not initialized in passSelectedImages") - } } /** @@ -490,7 +443,6 @@ class ImageFragment : if (!progressDialog.isShowing) { progressDialogLayout.progressDialogText.text = text progressDialog.show() - Timber.d("Showing mark/unmark progress dialog: %s", text) } } @@ -500,7 +452,6 @@ class ImageFragment : fun dismissMarkUnmarkProgressDialog() { if (progressDialog.isShowing) { progressDialog.dismiss() - Timber.d("Dismissed mark/unmark progress dialog") } } @@ -510,4 +461,4 @@ class ImageFragment : listOf(Contribution.STATE_IN_PROGRESS, Contribution.STATE_FAILED, Contribution.STATE_QUEUED, Contribution.STATE_PAUSED), )?.subscribeOn(Schedulers.io()) ?.blockingGet() ?: emptyList() -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index b1f1b7f9b..89d43845b 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -150,7 +150,7 @@ class DescriptionEditActivity : this, getString(titleStringID), getString(messageStringId), - getString(R.string.ok), + getString(android.R.string.ok), null ) } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt index bc8f9cfaa..ea96b50a3 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt @@ -64,9 +64,6 @@ class ExploreFragment : CommonsDaggerSupportFragment() { override fun onPageScrollStateChanged(state: Int) = Unit override fun onPageSelected(position: Int) { binding!!.viewPager.canScroll = position != 2 - if (position == 2) { - mapRootFragment?.requestLocationIfNeeded() - } } }) setTabs() @@ -174,12 +171,14 @@ class ExploreFragment : CommonsDaggerSupportFragment() { // if on Map tab, show all menu options, else only show search binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit - override fun onPageScrollStateChanged(state: Int) = Unit + override fun onPageSelected(position: Int) { - binding!!.viewPager.canScroll = position != 2 - others.setVisible(position == 2) - if (position == 2) { - mapRootFragment?.requestLocationIfNeeded() + others.setVisible((position == 2)) + } + + override fun onPageScrollStateChanged(state: Int) { + if (state == ViewPager.SCROLL_STATE_IDLE && binding!!.viewPager.currentItem == 2) { + onPageSelected(2) } } }) @@ -195,6 +194,7 @@ class ExploreFragment : CommonsDaggerSupportFragment() { */ override fun onOptionsItemSelected(item: MenuItem): Boolean { // Handle item selection + when (item.itemId) { R.id.action_search -> { startActivityWithFlags(requireActivity(), SearchActivity::class.java) @@ -224,4 +224,6 @@ class ExploreFragment : CommonsDaggerSupportFragment() { retainInstance = true } } -} \ No newline at end of file +} + + diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt index d405709a8..af65834eb 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.kt @@ -193,20 +193,9 @@ class ExploreMapRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider binding = null } - fun requestLocationIfNeeded() { - mapFragment?.requestLocationIfNeeded() - } - - override fun setUserVisibleHint(isVisibleToUser: Boolean) { - super.setUserVisibleHint(isVisibleToUser) - if (isVisibleToUser) { - requestLocationIfNeeded() - } - } - companion object { fun newInstance(): ExploreMapRootFragment = ExploreMapRootFragment().apply { retainInstance = true } } -} \ No newline at end of file +} diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt index d025fdfe1..32af67e95 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt @@ -112,8 +112,8 @@ class WikidataItemDetailsActivity : BaseActivity(), MediaDetailProvider, Categor viewPagerAdapter!!.setTabs( R.string.title_for_media to depictionImagesListFragment!!, - R.string.title_for_child_classes to childDepictionsFragment, - R.string.title_for_parent_classes to parentDepictionsFragment + R.string.title_for_subcategories to childDepictionsFragment, + R.string.title_for_parent_categories to parentDepictionsFragment ) binding!!.viewPager.offscreenPageLimit = 2 viewPagerAdapter!!.notifyDataSetChanged() diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt index a1bae09fb..e64f12db3 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt @@ -140,8 +140,8 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi requireActivity(), requireActivity().getString(R.string.location_permission_title), requireActivity().getString(R.string.location_permission_rationale_explore), - requireActivity().getString(R.string.ok), - requireActivity().getString(R.string.cancel), + requireActivity().getString(android.R.string.ok), + requireActivity().getString(android.R.string.cancel), { askForLocationPermission() }, null, null @@ -269,60 +269,31 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi override fun onZoom(event: ZoomEvent?): Boolean = false }) - // removed tha permission check here to prevent it from running on fragment creation + if (!locationPermissionsHelper!!.checkLocationPermission(requireActivity())) { + askForLocationPermission() + } } override fun onResume() { super.onResume() binding!!.mapView.onResume() presenter!!.attachView(this) - locationManager.addLocationListener(this) - if (broadcastReceiver != null) { - requireActivity().registerReceiver(broadcastReceiver, intentFilter) + registerNetworkReceiver() + if (isResumed) { + if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) { + performMapReadyActions() + } else { + startMapWithoutPermission() + } } - setSearchThisAreaButtonVisibility(false) } override fun onPause() { super.onPause() // unregistering the broadcastReceiver, as it was causing an exception and a potential crash unregisterNetworkReceiver() - locationManager.unregisterLocationManager() - locationManager.removeLocationListener(this) } - fun requestLocationIfNeeded() { - if (!isVisible) return // skips if not visible to user - if (locationPermissionsHelper!!.checkLocationPermission(requireActivity())) { - if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) { - locationManager.registerLocationManager() - drawMyLocationMarker() - } else { - locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text) - } - } else { - locationPermissionsHelper!!.requestForLocationAccess( - R.string.location_permission_title, - R.string.location_permission_rationale - ) - } - } - - private fun drawMyLocationMarker() { - val location = locationManager.getLastLocation() - if (location != null) { - val geoPoint = GeoPoint(location.latitude, location.longitude) - val startMarker = Marker(binding!!.mapView).apply { - setPosition(geoPoint) - setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) - icon = ContextCompat.getDrawable(requireContext(), R.drawable.current_location_marker) - title = "Your Location" - textLabelFontSize = 24 - } - binding!!.mapView.overlays.add(startMarker) - binding!!.mapView.invalidate() - } - } /** * Unregisters the networkReceiver @@ -965,17 +936,13 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi if (geoPoint != null) { binding!!.mapView.controller.setCenter(geoPoint) val overlays = binding!!.mapView.overlays - // collects the indices of items to remove - val indicesToRemove = mutableListOf() for (i in overlays.indices) { - if (overlays[i] is Marker || overlays[i] is ScaleDiskOverlay) { - indicesToRemove.add(i) + if (overlays[i] is Marker) { + binding!!.mapView.overlays.removeAt(i) + } else if (overlays[i] is ScaleDiskOverlay) { + binding!!.mapView.overlays.removeAt(i) } } - // removes the items in reverse order to avoid index shifting - indicesToRemove.sortedDescending().forEach { index -> - binding!!.mapView.overlays.removeAt(index) - } val diskOverlay = ScaleDiskOverlay( requireContext(), geoPoint, 2000, GeoConstants.UnitOfMeasure.foot @@ -985,6 +952,7 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi this.style = Paint.Style.STROKE this.strokeWidth = 2f }) + setCirclePaint1(Paint().apply { setColor(Color.argb(40, 128, 128, 128)) this.style = Paint.Style.FILL_AND_STROKE @@ -993,6 +961,7 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi setDisplaySizeMax(1700) } binding!!.mapView.overlays.add(diskOverlay) + val startMarker = Marker( binding!!.mapView ).apply { @@ -1110,24 +1079,7 @@ class ExploreMapFragment : CommonsDaggerSupportFragment(), ExploreMapContract.Vi override fun onLocationPermissionDenied(toastMessage: String) = Unit - override fun onLocationPermissionGranted() { - if (locationPermissionsHelper!!.isLocationAccessToAppsTurnedOn()) { - locationManager.registerLocationManager() - drawMyLocationMarker() - } else { - locationPermissionsHelper!!.showLocationOffDialog(requireActivity(), R.string.location_off_dialog_text) - } - onLocationChanged(LocationChangeType.PERMISSION_JUST_GRANTED, null) - } - - fun onLocationChanged(locationChangeType: LocationChangeType, location: Location?) { - if (locationChangeType == LocationChangeType.PERMISSION_JUST_GRANTED) { - val curLatLng = locationManager.getLastLocation() ?: getMapCenter() - populatePlaces(curLatLng) - } else { - presenter!!.updateMap(locationChangeType) - } - } + override fun onLocationPermissionGranted() = Unit companion object { fun newInstance(): ExploreMapFragment { diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt index d16d250dd..e1d0740de 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesDao.kt @@ -163,19 +163,11 @@ class RecentSearchesDao @Inject constructor( * @param cursor * @return RecentSearch object */ - fun fromCursor(cursor: Cursor): RecentSearch { - var query = cursor.getString(COLUMN_NAME) - - if (query == null) { - query = "" - } - - return RecentSearch( - uriForId(cursor.getInt(COLUMN_ID)), - query, - Date(cursor.getLong(COLUMN_LAST_USED)) - ) - } + fun fromCursor(cursor: Cursor): RecentSearch = RecentSearch( + uriForId(cursor.getInt(COLUMN_ID)), + cursor.getString(COLUMN_NAME), + Date(cursor.getLong(COLUMN_LAST_USED)) + ) /** * This class contains the database table architechture for recent searches, diff --git a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt index e7903c9ed..c0f1bd5db 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/recentsearches/RecentSearchesFragment.kt @@ -67,10 +67,10 @@ class RecentSearchesFragment : CommonsDaggerSupportFragment() { private fun showDeleteRecentAlertDialog(context: Context) { AlertDialog.Builder(context) .setMessage(getString(R.string.delete_recent_searches_dialog)) - .setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int -> + .setPositiveButton(android.R.string.yes) { dialog: DialogInterface, _: Int -> setDeleteRecentPositiveButton(context, dialog) } - .setNegativeButton(R.string.no, null) + .setNegativeButton(android.R.string.no, null) .setCancelable(false) .create() .show() @@ -102,7 +102,7 @@ class RecentSearchesFragment : CommonsDaggerSupportFragment() { setDeletePositiveButton(context, dialog, position) } ) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(android.R.string.cancel, null) .setCancelable(false) .create() .show() diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt index 47b4165ad..fefb59adb 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt @@ -64,8 +64,8 @@ class LocationPermissionsHelper( activity, activity.getString(dialogTitleResource), activity.getString(dialogTextResource), - activity.getString(R.string.ok), - activity.getString(R.string.cancel), + activity.getString(android.R.string.ok), + activity.getString(android.R.string.cancel), { ActivityCompat.requestPermissions( activity, diff --git a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt index 08dee587b..e4fedf2e4 100644 --- a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt @@ -46,7 +46,6 @@ import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Compani import fr.free.nrw.commons.utils.DialogUtil import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets -import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets import fr.free.nrw.commons.utils.handleGeoCoordinates import io.reactivex.android.schedulers.AndroidSchedulers @@ -343,10 +342,6 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { } private fun setupMapView() { - - val mapBottomLayout: ConstraintLayout = findViewById(R.id.map_bottom_layout) - mapBottomLayout.applyEdgeToEdgeBottomPaddingInsets() - requestLocationPermissions() //If location metadata is available, move map to that location. diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index 41e65ae4e..e20a75c0f 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -541,7 +541,6 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C } ) binding.progressBarEdit.visibility = View.GONE - binding.descriptionEdit.visibility = View.VISIBLE } override fun onConfigurationChanged(newConfig: Configuration) { @@ -1027,12 +1026,12 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C val message: String = if (result) { context.getString( R.string.send_thank_success_message, - media!!.user + media!!.displayTitle ) } else { context.getString( R.string.send_thank_failure_message, - media!!.user + media!!.displayTitle ) } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java index 323f9756f..db2c1f5d9 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/CheckBoxTriStates.java @@ -44,7 +44,7 @@ public class CheckBoxTriStates extends AppCompatCheckBox { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { switch (state) { case UNKNOWN: - setState(UNCHECKED); + setState(UNCHECKED);; break; case UNCHECKED: setState(CHECKED); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java index 53e9970a6..b5f760c9f 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterSearchRecyclerViewAdapter.java @@ -91,7 +91,6 @@ public class NearbyFilterSearchRecyclerViewAdapter label.setSelected(!label.isSelected()); holder.placeTypeLayout.setSelected(label.isSelected()); - NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels)); callback.filterByMarkerType(selectedLabels, 0, false, false); }); } @@ -153,7 +152,6 @@ public class NearbyFilterSearchRecyclerViewAdapter label.setSelected(false); selectedLabels.remove(label); } - NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels)); notifyDataSetChanged(); } @@ -165,7 +163,6 @@ public class NearbyFilterSearchRecyclerViewAdapter selectedLabels.add(label); } } - NearbyFilterState.setSelectedLabels(new ArrayList<>(selectedLabels)); notifyDataSetChanged(); } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java index d0aec96af..d3ece9bfa 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyFilterState.java @@ -9,7 +9,7 @@ public class NearbyFilterState { private int checkBoxTriState; private ArrayList Never ask this again Ask for location permission Ask for location permission when needed for nearby notification card view feature. - Something went wrong, and we could not fetch achievements + Something went wrong, We could not fetch achievements You\'ve made so many contributions our achievements calculation system can\'t cope. This is the ultimate achievement. Ends on: Display campaigns See the ongoing campaigns - Show deletion button - Enable the \"Delete Folder\" button in the custom picker Allow the app to fetch location in case the camera does not record it. Some device cameras do not record location. In such cases, letting the app fetch and attach location to it makes your contribution more useful. You may change this any time from the Settings Allow Dismiss @@ -492,12 +485,12 @@ Upload your first media by tapping on the add button. Could not request category check for %1$s Requesting category check for %1$s Done - Sending thanks: Success - Sent thanks to %1$s - Failed to send thanks to %1$s - Sending thanks: Failure + Sending Thanks: Success + Successfully sent thanks to %1$s + Failed to send thanks %1$s + Sending Thanks: Failure - Sending thanks for %1$s + Sending Thanks for %1$s Does this follow the rules of copyright? Is this correctly categorized? Is this in-scope? @@ -529,14 +522,15 @@ Upload your first media by tapping on the add button. Error occurred while picking images Please wait… + Featured pictures are images from highly skilled photographers and illustrators that the Wikimedia Commons community has chosen as some of the highest quality on the site. Images Uploaded via Nearby places are the images which are uploaded by discovering places on the map. This feature allows editors to send a Thank you notification to users who make useful edits – by using a small thank link on the history page or diff page. - Copy to the next items + Copy to subsequent media Copied Examples of good images to upload to Commons Examples of images not to upload Skip this image - Download failed. We cannot download the file without external storage permission. + Download Failed!!. We cannot download the file without external storage permission. Manage EXIF Tags Select which EXIF tags to keep in uploads @@ -549,10 +543,12 @@ Upload your first media by tapping on the add button. Serial Numbers Software + Media location access denied + We may not be able to automatically obtain location data from pictures you upload. Please add the appropriate location for each picture before submitting + Upload photos to Wikimedia Commons directly from your phone. Download the Commons App now: %1$s Share app via... Image Info - Don\'t show this message again No Categories found No Depictions found Cancelled Upload @@ -609,7 +605,7 @@ Upload your first media by tapping on the add button. Share image via You haven\'t made any contributions yet - %1$s has not made any contributions yet + %s has not made any contributions yet Account created! Text copied to clipboard Notification marked as read @@ -618,7 +614,7 @@ Upload your first media by tapping on the add button. Exists Needs Photo Place type: - Bridge, museum, hotel, etc. + Bridge, museum, hotel etc. Something went wrong with log-in. You must reset your password! MEDIA CHILD CLASSES @@ -666,7 +662,7 @@ Upload your first media by tapping on the add button. 5. Paste the wikitext in the appropriate place. 6. Edit the wikitext for appropriate positioning, if necessary. For more information, see <a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images#How_to_place_an_image">here</a>. 7. Publish the article - Copy wikitext to clipboard + Copy wikicode to clipboard pause resume Paused @@ -709,8 +705,6 @@ Upload your first media by tapping on the add button. Please select the appropriate categories. Unlike depictions, categories are only in English. Commons makes your pictures reusable and adapted by everyone. Do you want to waive all rights? Do you want to be attributed? Do you want adaptations to use the same license? Depicts - Label - Description Media License Media Details View category page @@ -725,7 +719,8 @@ Upload your first media by tapping on the add button. Show in map app Edit location The image view of the location picker - The shadow of the image view of the location picker + + The shadow of the image view of the location picker Image Location Check whether location is correct Label @@ -737,7 +732,7 @@ Upload your first media by tapping on the add button. Back Welcome to Custom Picture Selector This picker shows you which pictures you have already uploaded to Commons. - Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded.\n\nTouch and hold for image preview. + Unlike the picture on the left, the picture on the right has the Commons logo indicating it is already uploaded. \n Touch and hold for image preview. Awesome This image has already been uploaded to Commons. For technical reasons, the app can\'t reliably upload more than %1$d pictures at once. The upload limit of %1$d has been exceeded by %2$d. @@ -753,11 +748,9 @@ Upload your first media by tapping on the add button. Wiki Loves Monuments is an international photo contest for monuments organised by Wikimedia Need Permission Nearby maps need to read PHONE STATE to function properly - Please turn on location services to view nearby places. - Location access is needed to show nearby places on the map. - Contributions of User: %1$s - Achievements of User: %1$s + Contributions of User: %s + Achievements of User: %s View user profile Edit depictions Edit categories @@ -768,7 +761,7 @@ Upload your first media by tapping on the add button. Location data helps Wiki editors find your picture, making it much more useful.\nYour recent uploads have no location.\nWe suggest you turn on location in your camera app\'s settings.\nThank you for uploading! No location found How about adding the place where this image was taken?\nLocation data helps Wiki editors find your picture, making it much more useful.\nThank you! - Add Location + Add location Please remove from this email any information that you are not comfortable sharing publicly. Also, please be aware that your email address with which you are posting, and the associated name and profile picture, will be visible publicly. Details Achievements are only available in the prod flavor. Please check the developer documentation. @@ -789,8 +782,8 @@ Upload your first media by tapping on the add button. Unmark as not for upload Marking as not for upload Unmarking as not for upload - Show already handled pictures - Hiding already handled pictures + Show already actioned pictures + Hiding already actioned pictures No more images found This image is already uploaded Can not select this image for upload @@ -826,15 +819,15 @@ Upload your first media by tapping on the add button. Your log-in has expired. Please log in again. No application available to open GPX files File Saved Successfully - Do you want to open the GPX file? - Do you want to open the KML file? - Failed to save the KML file. - Failed to save the GPX file. - Saving as a KML file... - Saving as a GPX file... + Do you want to open GPX file? + Do you want to open KML file? + Failed to save KML file. + Failed to save GPX file. + Saving KML File + Saving GPX File - %1$d image selected - %1$d images selected + %d image selected + %d images selected Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads. Note about multi-uploads @@ -874,6 +867,7 @@ Upload your first media by tapping on the add button. Other wikis File usages + SingleWebViewActivity Account Vanish Account Vanish account warning diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 3b7604026..fb3cb0ca1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -55,13 +55,13 @@ android:defaultValue="false" android:key="displayDeletionButton" app:singleLineTitle="false" - android:summary="@string/show_deletion_button_explanation" - android:title="@string/show_deletion_button" /> + android:summary="Enable the "Delete folder" button in the custom picker" + android:title="Show Deletion Button" /> + android:title="Uploads"> + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/android/text/TextUtils.java b/app/src/test/java/android/text/TextUtils.java index f59ab806a..4d63e77df 100644 --- a/app/src/test/java/android/text/TextUtils.java +++ b/app/src/test/java/android/text/TextUtils.java @@ -21,18 +21,14 @@ public class TextUtils { * mocks TextUtils.equals */ public static boolean equals(CharSequence a, CharSequence b) { - if (a == b) { - return true; - } + if (a == b) return true; int length; if (a != null && b != null && (length = a.length()) == b.length()) { if (a instanceof String && b instanceof String) { return a.equals(b); } else { for (int i = 0; i < length; i++) { - if (a.charAt(i) != b.charAt(i)) { - return false; - } + if (a.charAt(i) != b.charAt(i)) return false; } return true; } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt index 1a9d1500b..9e7891560 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/OkHttpJsonApiClientTests.kt @@ -3,10 +3,6 @@ package fr.free.nrw.commons import com.google.gson.Gson import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.verify -import com.nhaarman.mockitokotlin2.times -import fr.free.nrw.commons.campaigns.CampaignResponseDTO -import fr.free.nrw.commons.campaigns.CampaignConfig -import fr.free.nrw.commons.campaigns.models.Campaign import fr.free.nrw.commons.explore.depictions.DepictsClient import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient @@ -14,22 +10,13 @@ import fr.free.nrw.commons.nearby.model.NearbyQueryParams import okhttp3.Call import okhttp3.HttpUrl import okhttp3.OkHttpClient -import okhttp3.Request import okhttp3.Response -import okhttp3.ResponseBody -import okhttp3.mockwebserver.MockResponse -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNotNull -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.mockito.Mock import org.mockito.Mockito -import org.mockito.Mockito.eq +import org.mockito.Mockito.times import org.mockito.MockitoAnnotations -import java.io.BufferedReader -import java.io.InputStreamReader import java.lang.Exception class OkHttpJsonApiClientTests { @@ -58,43 +45,34 @@ class OkHttpJsonApiClientTests { @Mock lateinit var response: Response - @Mock - lateinit var responseBody: ResponseBody - - private lateinit var mockWebServer: TestWebServer - @Before fun setUp() { MockitoAnnotations.openMocks(this) - mockWebServer = TestWebServer() - mockWebServer.setUp() - okHttpJsonApiClient = OkHttpJsonApiClient( - okhttpClient, - depictsClient, - wikiMediaToolforgeUrl, - sparqlQueryUrl, - mockWebServer.getUrl(), //use the mock server for the campaignsUrl - gson - ) + okHttpJsonApiClient = + OkHttpJsonApiClient( + okhttpClient, + depictsClient, + wikiMediaToolforgeUrl, + sparqlQueryUrl, + campaignsUrl, + gson, + ) Mockito.`when`(okhttpClient.newCall(any())).thenReturn(call) Mockito.`when`(call.execute()).thenReturn(response) - Mockito.`when`(response.isSuccessful).thenReturn(false) - Mockito.`when`(response.message).thenReturn("test") - Mockito.`when`(response.body).thenReturn(responseBody) - Mockito.`when`(responseBody.string()).thenReturn("{\"error\": \"test\"}") } @Test fun testGetNearbyPlacesCustomQuery() { + Mockito.`when`(response.message).thenReturn("test") try { okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, "test") } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } try { okHttpJsonApiClient.getNearbyPlaces(NearbyQueryParams.Rectangular(latLng, latLng), "test", true, "test") } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } verify(okhttpClient, times(2)).newCall(any()) verify(call, times(2)).execute() @@ -102,10 +80,11 @@ class OkHttpJsonApiClientTests { @Test fun testGetNearbyPlaces() { + Mockito.`when`(response.message).thenReturn("test") try { okHttpJsonApiClient.getNearbyPlaces(latLng, "test", 10.0, null) } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } try { okHttpJsonApiClient.getNearbyPlaces( @@ -114,8 +93,9 @@ class OkHttpJsonApiClientTests { true, null ) + } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } try { okHttpJsonApiClient.getNearbyPlaces( @@ -125,7 +105,7 @@ class OkHttpJsonApiClientTests { null ) } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } verify(okhttpClient, times(3)).newCall(any()) verify(call, times(3)).execute() @@ -133,121 +113,18 @@ class OkHttpJsonApiClientTests { @Test fun testGetNearbyItemCount() { + Mockito.`when`(response.message).thenReturn("test") try { okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Radial(latLng, 10f)) } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } try { okHttpJsonApiClient.getNearbyItemCount(NearbyQueryParams.Rectangular(latLng, latLng)) } catch (e: Exception) { - assertEquals("test", e.message) + assert(e.message.equals("test")) } verify(okhttpClient, times(2)).newCall(any()) verify(call, times(2)).execute() } - - @Test - fun testGetCampaignsWithData() { - //loads the json response from resources - val jsonResponse = loadJsonFromResource("campaigns_response_with_data.json") - - //mocks the succesfull response chain - Mockito.`when`(response.isSuccessful).thenReturn(true) - Mockito.`when`(response.message).thenReturn("OK") - Mockito.`when`(response.body).thenReturn(responseBody) - Mockito.`when`(responseBody.string()).thenReturn(jsonResponse) - - val campaignResponse = CampaignResponseDTO().apply { - campaignConfig = CampaignConfig().apply { - showOnlyLiveCampaigns = false - sortBy = "startDate" - } - campaigns = listOf( - Campaign().apply { - title = "Wiki Loves Monuments" - isWLMCampaign = true - }, - Campaign().apply { - title = "Wiki Loves Nature" - isWLMCampaign = false - } - ) - } - - //any() for the string argument and eq() for the class argument. - Mockito.`when`( - gson.fromJson( - any(), - eq(CampaignResponseDTO::class.java) - ) - ).thenReturn(campaignResponse) - - //call the getCampaigns - val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet() - - //verify the results - assertNotNull(result) - assertNotNull(result?.campaigns) - assertEquals(2, result?.campaigns!!.size) - assertEquals("Wiki Loves Monuments", result.campaigns!![0].title) - assertTrue(result.campaigns!![0].isWLMCampaign) - assertEquals("Wiki Loves Nature", result.campaigns!![1].title) - assertEquals(false, result.campaigns!![1].isWLMCampaign) - assertNotNull(result.campaignConfig) - assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns) - assertEquals("startDate", result.campaignConfig!!.sortBy) - } - - @Test - fun testGetCampaignsEmpty() { - //loads the empty json response - val jsonResponse = loadJsonFromResource("campaigns_response_empty.json") - - //mocks the successful response chain - Mockito.`when`(response.isSuccessful).thenReturn(true) - Mockito.`when`(response.message).thenReturn("OK") - Mockito.`when`(response.body).thenReturn(responseBody) - Mockito.`when`(responseBody.string()).thenReturn(jsonResponse) - - val campaignResponse = CampaignResponseDTO().apply { - campaignConfig = CampaignConfig().apply { - showOnlyLiveCampaigns = false - sortBy = "startDate" - } - campaigns = emptyList() - } - - //use any() for the string argument and eq() for the class argument. - Mockito.`when`( - gson.fromJson( - any(), - eq(CampaignResponseDTO::class.java) - ) - ).thenReturn(campaignResponse) - - //calls getCampaigns - val result: CampaignResponseDTO? = okHttpJsonApiClient.getCampaigns().blockingGet() - - //verify the results - assertNotNull(result) - assertNotNull(result?.campaigns) - assertTrue(result?.campaigns!!.isEmpty()) - assertNotNull(result.campaignConfig) - assertFalse(result.campaignConfig!!.showOnlyLiveCampaigns) - assertEquals("startDate", result.campaignConfig!!.sortBy) - } - - fun loadJsonFromResource(fileName: String): String { - val resourcePath = "raw/$fileName" - //uses the classloader to find the resource in the test environment - val inputStream = javaClass.classLoader?.getResourceAsStream(resourcePath) - - if (inputStream != null) { - //reads the entire stream content - return BufferedReader(InputStreamReader(inputStream)).use { it.readText() } - } - //throws an exception with the correct expected path - throw IllegalArgumentException("Resource $fileName not found. Please ensure the file is located in app/src/test/resources/raw/") - } -} \ No newline at end of file +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt index 5b5dfd7dd..be3b7e8e3 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditActivityUnitTest.kt @@ -193,8 +193,8 @@ class DescriptionEditActivityUnitTest { method.isAccessible = true method.invoke( activity, - R.string.ok, - R.string.ok, + android.R.string.ok, + android.R.string.ok, ) val dialog: AlertDialog = ShadowAlertDialog.getLatestDialog() as AlertDialog assertEquals(dialog.isShowing, true) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt index 55b8427e6..75d6b8a4f 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/categories/UploadCategoriesFragmentUnitTests.kt @@ -153,13 +153,6 @@ class UploadCategoriesFragmentUnitTests { fragment.showError(R.string.no_categories_found) } - @Test - @Throws(Exception::class) - fun testShowErrorDialog() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - fragment.showErrorDialog("") - } - @Test @Throws(Exception::class) fun testSetCategoriesCaseNull() { diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt index 0cab47c67..a37bcc927 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt @@ -145,8 +145,6 @@ class UploadMediaDetailFragmentUnitTest { ibExpandCollapse = view.findViewById(R.id.ib_expand_collapse) Whitebox.setInternalState(fragment, "uploadMediaDetailAdapter", uploadMediaDetailAdapter) - Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore) - `when`(defaultKvStore.getString("description_language", "")).thenReturn("en") } @Test @@ -164,8 +162,14 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) - fun testOnCreateView() { + fun testSetImageToBeUploaded() { Shadows.shadowOf(Looper.getMainLooper()).idle() + fragment.setImageToBeUploaded(null, null, location) + } + + @Test + @Throws(Exception::class) + fun testOnCreateView() { fragment.onCreateView(layoutInflater, null, savedInstanceState) } @@ -177,6 +181,34 @@ class UploadMediaDetailFragmentUnitTest { fragment.onViewCreated(view, savedInstanceState) } + @Test + @Throws(Exception::class) + fun testInit() { + Shadows.shadowOf(Looper.getMainLooper()).idle() + Whitebox.setInternalState(fragment, "presenter", presenter) + val method: Method = + UploadMediaDetailFragment::class.java.getDeclaredMethod( + "initializeFragment", + ) + method.isAccessible = true + method.invoke(fragment) + } + + @Test + @Throws(Exception::class) + fun testInitCaseGetIndexInViewFlipperNonZero() { + Shadows.shadowOf(Looper.getMainLooper()).idle() + Whitebox.setInternalState(fragment, "presenter", presenter) + `when`(callback.getIndexInViewFlipper(fragment)).thenReturn(1) + `when`(callback.totalNumberOfSteps).thenReturn(5) + val method: Method = + UploadMediaDetailFragment::class.java.getDeclaredMethod( + "initializeFragment", + ) + method.isAccessible = true + method.invoke(fragment) + } + @Test @Throws(Exception::class) fun testShowInfoAlert() { @@ -285,7 +317,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testShowExternalMap() { - Shadows.shadowOf(Looper.getMainLooper()).idle() + shadowOf(Looper.getMainLooper()).idle() `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) `when`(imageCoordinates.decLatitude).thenReturn(0.0) `when`(imageCoordinates.decLongitude).thenReturn(0.0) @@ -296,7 +328,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testOnCameraPositionCallbackOnMapIconClicked() { - Shadows.shadowOf(Looper.getMainLooper()).idle() + shadowOf(Looper.getMainLooper()).idle() Mockito.mock(LocationPicker::class.java) val intent = Mockito.mock(Intent::class.java) val cameraPosition = Mockito.mock(CameraPosition::class.java) @@ -325,7 +357,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testOnCameraPositionCallbackAddLocationDialog() { - Shadows.shadowOf(Looper.getMainLooper()).idle() + shadowOf(Looper.getMainLooper()).idle() Mockito.mock(LocationPicker::class.java) val intent = Mockito.mock(Intent::class.java) val cameraPosition = Mockito.mock(CameraPosition::class.java) @@ -395,7 +427,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testRememberedZoomLevelOnNull() { - Shadows.shadowOf(Looper.getMainLooper()).idle() + shadowOf(Looper.getMainLooper()).idle() Whitebox.setInternalState(fragment, "defaultKvStore", defaultKvStore) `when`(uploadItem.gpsCoords).thenReturn(null) `when`(defaultKvStore.getString(LAST_ZOOM)).thenReturn("13.0") @@ -411,7 +443,7 @@ class UploadMediaDetailFragmentUnitTest { @Test @Throws(Exception::class) fun testRememberedZoomLevelOnNotNull() { - Shadows.shadowOf(Looper.getMainLooper()).idle() + shadowOf(Looper.getMainLooper()).idle() `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) `when`(imageCoordinates.decLatitude).thenReturn(8.0) `when`(imageCoordinates.decLongitude).thenReturn(-8.0) @@ -424,4 +456,4 @@ class UploadMediaDetailFragmentUnitTest { val shadowIntent: ShadowIntent = shadowOf(startedIntent) Assert.assertEquals(shadowIntent.intentClass, LocationPickerActivity::class.java) } -} \ No newline at end of file +} diff --git a/app/src/test/resources/raw/campaigns_response_empty.json b/app/src/test/resources/raw/campaigns_response_empty.json deleted file mode 100644 index 61903e24c..000000000 --- a/app/src/test/resources/raw/campaigns_response_empty.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "config": { - "showOnlyLiveCampaigns": false, - "sortBy": "startDate" - }, - "campaigns": [] -} \ No newline at end of file diff --git a/app/src/test/resources/raw/campaigns_response_with_data.json b/app/src/test/resources/raw/campaigns_response_with_data.json deleted file mode 100644 index dab818e2a..000000000 --- a/app/src/test/resources/raw/campaigns_response_with_data.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "config": { - "showOnlyLiveCampaigns": false, - "sortBy": "startDate" - }, - "campaigns": [ - { - "title": "Wiki Loves Monuments", - "description": "A campaign to photograph monuments", - "startDate": "2025-09-01", - "endDate": "2025-09-30", - "link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Monuments", - "isWLMCampaign": true - }, - { - "title": "Wiki Loves Nature", - "description": "A campaign to photograph nature", - "startDate": "2025-06-01", - "endDate": "2025-06-30", - "link": "https://commons.wikimedia.org/wiki/Campaign:Wiki_Loves_Nature", - "isWLMCampaign": false - } - ] -} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9a4dd53cb..d26b1a62c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -agp = "8.13.0" +agp = "8.12.0" acra = "5.8.4" activityCompose = "1.9.3" adapterdelegates = "4.3.0" androidmediautil = "v1.0-1" -androidSdkVersion = "11.13.5" +androidSdkVersion = "10.0.1" androidPluginScalebar = "1.0.0" androidxJunitVersion = "1.1.5" annotation = "1.3.0" @@ -27,7 +27,7 @@ dexterVersion = "5.0.0" espresso = "3.6.1" exifinterface = "1.3.7" fragmentTesting = "1.6.2" -frescoVersion = "3.6.0" +frescoVersion = "1.13.0" commonsLang3Version = "3.8.1" glide = "4.12.0" gson = "2.8.5" @@ -126,7 +126,6 @@ dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = # Image loading facebook-fresco = { module = "com.facebook.fresco:fresco", version.ref = "frescoVersion" } -facebook-fresco-middleware = { module = "com.facebook.fresco:middleware", version.ref = "frescoVersion" } glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" } kotlinx-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "kotlinxCoroutinesRx2" } diff --git a/update-license-info/Makefile b/update-license-info/Makefile new file mode 100644 index 000000000..a6c96ee2a --- /dev/null +++ b/update-license-info/Makefile @@ -0,0 +1,14 @@ +.FAKE : build update clean install + +build : ../app/src/main/res/xml/wikimedia_licenses.xml + +../app/src/main/res/xml/wikimedia_licenses.xml : licenses.php mediawiki-extensions-UploadWizard + php licenses.php > ../app/src/main/res/xml/wikimedia_licenses.xml + +mediawiki-extensions-UploadWizard : update + +update : + if [ -d mediawiki-extensions-UploadWizard ]; then (cd mediawiki-extensions-UploadWizard && git pull origin master); else git clone https://gerrit.wikimedia.org/r/mediawiki/extensions/UploadWizard mediawiki-extensions-UploadWizard; fi + +clean : + rm -rf mediawiki-extensions-UploadWizard diff --git a/update-license-info/include-stubs.php b/update-license-info/include-stubs.php new file mode 100644 index 000000000..c0a01a0d6 --- /dev/null +++ b/update-license-info/include-stubs.php @@ -0,0 +1,68 @@ + 'English' ); + } +} +$wgMemc = new FakeMemc(); + +class FakeMessage { + function plain() { + return 'stub-message-plain'; + } + function parse() { + return 'stub-message-parsed'; + } +} + +function wfMessage() { + return new FakeMessage(); +} + +/** + * Converts shorthand byte notation to integer form + * + * @param $string String + * @return Integer + */ +function wfShorthandToInteger( $string = '' ) { + $string = trim( $string ); + if ( $string === '' ) { + return -1; + } + $last = $string[strlen( $string ) - 1]; + $val = intval( $string ); + switch ( $last ) { + case 'g': + case 'G': + $val *= 1024; + // break intentionally missing + case 'm': + case 'M': + $val *= 1024; + // break intentionally missing + case 'k': + case 'K': + $val *= 1024; + } + + return $val; +} + +$wgAPIModules = array(); diff --git a/update-license-info/licenses.php b/update-license-info/licenses.php new file mode 100644 index 000000000..badda1a08 --- /dev/null +++ b/update-license-info/licenses.php @@ -0,0 +1,71 @@ + +// 2013-09-30 + +require 'include-stubs.php'; +$config = require "mediawiki-extensions-UploadWizard/UploadWizard.config.php"; +require "mediawiki-extensions-UploadWizard/UploadWizard.i18n.php"; +$licenseList = array(); + +foreach ( $config['licenses'] as $key => $license ) { + // Determine template -> license mappings + if ( isset( $license['templates'] ) ) { + $templates = $license['templates']; + } else { + $templates = array( $key ); + } + + if ( count( $templates ) < 1 ) { + throw new Exception("No templates for $key, this is wrong."); + } + if ( count( $templates ) > 1 ) { + //echo "Skipping multi-template license: $key\n"; + continue; + } + $template = $templates[0]; + if ( preg_match( '/^subst:/i', $template ) ) { + //echo "Skipping subst license: $key\n"; + continue; + } + + $msg = $messages['en'][$license['msg']]; + + $licenseInfo = array( + 'desc' => $msg, + 'template' => $template + ); + if ( isset( $license['url'] ) ) { + $url = $license['url']; + if ( substr( $url, 0, 2 ) == '//' ) { + $url = 'https:' . $url; + } + if ( isset( $license['languageCodePrefix'] ) ) { + $url .= $license['languageCodePrefix'] . '$lang'; + } + $licenseInfo['url'] = $url; + } + $licenseList[$key] = $licenseInfo; +} + +//var_dump( $licenseList ); + +echo "\n"; +echo "\n"; +foreach( $licenseList as $key => $licenseInfo ) { + $encId = htmlspecialchars( $key ); + echo " \n"; + +} +echo "\n"; + \ No newline at end of file