From c1acdbe31aa240534ae2717d9b357fedcf9d530c Mon Sep 17 00:00:00 2001 From: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com> Date: Sat, 14 Dec 2024 09:45:52 +0530 Subject: [PATCH 01/45] remove method (#6028) Signed-off-by: parneet-guraya --- .../upload/UploadMediaDetailAdapter.java | 38 ++---------------- .../UploadMediaDetailAdapterUnitTest.kt | 40 ------------------- 2 files changed, 3 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java index 43331193e..69bd318b4 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java @@ -302,8 +302,9 @@ public class UploadMediaDetailAdapter extends removeButton.setOnClickListener(v -> removeDescription(uploadMediaDetail, position)); captionListener = new AbstractTextWatcher( - captionText -> uploadMediaDetail.setCaptionText(convertIdeographicSpaceToLatinSpace( - removeLeadingAndTrailingWhitespace(captionText)))); + captionText -> uploadMediaDetail.setCaptionText( + convertIdeographicSpaceToLatinSpace(captionText.strip())) + ); descriptionListener = new AbstractTextWatcher( descriptionText -> uploadMediaDetail.setDescriptionText(descriptionText)); captionItemEditText.addTextChangedListener(captionListener); @@ -547,39 +548,6 @@ public class UploadMediaDetailAdapter extends } } - /** - * Removes any leading and trailing whitespace from the source text. - * - * @param source input string - * @return a string without leading and trailing whitespace - */ - public String removeLeadingAndTrailingWhitespace(String source) { - // This method can be replaced with the inbuilt String::strip when updated to JDK 11. - // Note that String::trim does not adequately remove all whitespace chars. - int firstNonWhitespaceIndex = 0; - while (firstNonWhitespaceIndex < source.length()) { - if (Character.isWhitespace(source.charAt(firstNonWhitespaceIndex))) { - firstNonWhitespaceIndex++; - } else { - break; - } - } - if (firstNonWhitespaceIndex == source.length()) { - return ""; - } - - int lastNonWhitespaceIndex = source.length() - 1; - while (lastNonWhitespaceIndex > firstNonWhitespaceIndex) { - if (Character.isWhitespace(source.charAt(lastNonWhitespaceIndex))) { - lastNonWhitespaceIndex--; - } else { - break; - } - } - - return source.substring(firstNonWhitespaceIndex, lastNonWhitespaceIndex + 1); - } - /** * Convert Ideographic space to Latin space * diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt index 794b6e64e..c6bed9bc5 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/UploadMediaDetailAdapterUnitTest.kt @@ -269,50 +269,10 @@ class UploadMediaDetailAdapterUnitTest { verify(adapterView, times(3)).adapter } - @Test - fun testRemoveLeadingAndTrailingWhitespace() { - // empty space - val test1 = " test " - val expected1 = "test" - Assert.assertEquals(expected1, viewHolder.removeLeadingAndTrailingWhitespace(test1)) - - val test2 = " test test " - val expected2 = "test test" - Assert.assertEquals(expected2, viewHolder.removeLeadingAndTrailingWhitespace(test2)) - - // No whitespace - val test3 = "No trailing space" - val expected3 = "No trailing space" - Assert.assertEquals(expected3, viewHolder.removeLeadingAndTrailingWhitespace(test3)) - - // blank string - val test4 = " \r \t " - val expected4 = "" - Assert.assertEquals(expected4, viewHolder.removeLeadingAndTrailingWhitespace(test4)) - } - - @Test - fun testRemoveLeadingAndTrailingInstanceTab() { - val test = "\ttest\t" - val expected = "test" - Assert.assertEquals(expected, viewHolder.removeLeadingAndTrailingWhitespace(test)) - } - - @Test - fun testRemoveLeadingAndTrailingCarriageReturn() { - val test = "\rtest\r" - val expected = "test" - Assert.assertEquals(expected, viewHolder.removeLeadingAndTrailingWhitespace(test)) - } - @Test fun testCaptionJapaneseCharacters() { val test1 = "テスト テスト" val expected1 = "テスト テスト" Assert.assertEquals(expected1, viewHolder.convertIdeographicSpaceToLatinSpace(test1)) - - val test2 = " \r \t テスト \r \t " - val expected2 = "テスト" - Assert.assertEquals(expected2, viewHolder.removeLeadingAndTrailingWhitespace(test2)) } } From 235e8cdba284bb93d9766942ad3a29bc4f26b6c0 Mon Sep 17 00:00:00 2001 From: Tanmay Gupta <119003089+savsch@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:01:25 +0530 Subject: [PATCH 02/45] Fix edit button shown when image has no location (#6029) Changed the commons.filepicker.UploadableFile's hasLocation method to use the existing ImageCoordinates class for detecting the presence of geolocation in an image's exif --- .../java/fr/free/nrw/commons/filepicker/UploadableFile.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt index 1398e7785..d8109cb3d 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/UploadableFile.kt @@ -10,6 +10,7 @@ import android.os.Parcelable import androidx.exifinterface.media.ExifInterface import fr.free.nrw.commons.upload.FileUtils +import fr.free.nrw.commons.upload.ImageCoordinates import java.io.File import java.io.IOException import java.util.Date @@ -87,9 +88,7 @@ class UploadableFile : Parcelable { fun hasLocation(): Boolean { return try { val exif = ExifInterface(file.absolutePath) - val latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) - val longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE) - latitude != null && longitude != null + ImageCoordinates(exif, null).imageCoordsExists } catch (e: IOException) { Timber.tag("UploadableFile").d(e) false From f2d1f7dbbb813dfe1c81734dc28b88750eef5a22 Mon Sep 17 00:00:00 2001 From: Haoyu Weng <68474038+Justweng@users.noreply.github.com> Date: Sat, 14 Dec 2024 17:45:44 +0800 Subject: [PATCH 03/45] [Enhancement] Fix #5042 Add copy link button (#5080) * Add a "copy link" button next to the share button. Add relative English and Simplified Chinese text in strings.xml. * Change the icon from copy to link * Fixed typo * Fixed case --------- Co-authored-by: Justweng Co-authored-by: Nicolas Raoul --- .../nrw/commons/media/MediaDetailPagerFragment.java | 13 +++++++++++++ .../main/res/drawable/menu_ic_copy_link_24dp.xml | 7 +++++++ app/src/main/res/menu/fragment_image_detail.xml | 5 +++++ app/src/main/res/values-zh-rCN/strings.xml | 2 ++ app/src/main/res/values/strings.xml | 2 ++ 5 files changed, 29 insertions(+) create mode 100644 app/src/main/res/drawable/menu_ic_copy_link_24dp.xml diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index 8f52b1ced..0de29a3bf 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -31,6 +31,7 @@ import com.google.android.material.snackbar.Snackbar; import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.bookmarks.models.Bookmark; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider; @@ -211,6 +212,13 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple snackbar.show(); updateBookmarkState(item); return true; + case R.id.menu_copy_link: + String uri = m.getPageTitle().getCanonicalUri(); + Utils.copy("shareLink", uri, requireContext()); + Timber.d("Copied share link to clipboard: %s", uri); + Toast.makeText(requireContext(), getString(R.string.menu_link_copied), + Toast.LENGTH_SHORT).show(); + return true; case R.id.menu_share_current_image: Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); @@ -390,6 +398,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple if (m != null) { // Enable default set of actions, then re-enable different set of actions only if it is a failed contrib menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true); + menu.findItem(R.id.menu_copy_link).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_share_current_image).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_download_current_image).setEnabled(true).setVisible(true); menu.findItem(R.id.menu_bookmark_current_image).setEnabled(true).setVisible(true); @@ -423,6 +432,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple case Contribution.STATE_QUEUED: menu.findItem(R.id.menu_browser_current_image).setEnabled(false) .setVisible(false); + menu.findItem(R.id.menu_copy_link).setEnabled(false) + .setVisible(false); menu.findItem(R.id.menu_share_current_image).setEnabled(false) .setVisible(false); menu.findItem(R.id.menu_download_current_image).setEnabled(false) @@ -440,6 +451,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple } else { menu.findItem(R.id.menu_browser_current_image).setEnabled(false) .setVisible(false); + menu.findItem(R.id.menu_copy_link).setEnabled(false) + .setVisible(false); menu.findItem(R.id.menu_share_current_image).setEnabled(false) .setVisible(false); menu.findItem(R.id.menu_download_current_image).setEnabled(false) diff --git a/app/src/main/res/drawable/menu_ic_copy_link_24dp.xml b/app/src/main/res/drawable/menu_ic_copy_link_24dp.xml new file mode 100644 index 000000000..ab6cb4a96 --- /dev/null +++ b/app/src/main/res/drawable/menu_ic_copy_link_24dp.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/menu/fragment_image_detail.xml b/app/src/main/res/menu/fragment_image_detail.xml index 7ab77b686..b4239ef9d 100644 --- a/app/src/main/res/menu/fragment_image_detail.xml +++ b/app/src/main/res/menu/fragment_image_detail.xml @@ -7,6 +7,11 @@ android:icon="@drawable/menu_ic_round_star_border_24px" android:title="@string/menu_bookmark" app:showAsAction="always" /> + 从图库 拍照 我的上传 + 复制链接 + 链接已复制到剪贴板 分享 在浏览器中查看 标题 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6b5829359..4bf76c405 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,6 +99,8 @@ Take photo Nearby My uploads + Copy link + The link has been copied to the clipboard Share View file page Caption (Required) From a933b92efac6922ad50fe607a5ea3b219e4fe9ad Mon Sep 17 00:00:00 2001 From: Tanmay Gupta <119003089+savsch@users.noreply.github.com> Date: Sat, 14 Dec 2024 15:42:44 +0530 Subject: [PATCH 04/45] Fix caption lost on accepting 'Is this a pic of' (#6030) Fixes issue 5842 by correcting the implementation of onUserConfirmedUploadIsOfPlace in UploadMediaDetailsContract's UserActionListener Co-authored-by: Nicolas Raoul --- .../UploadMediaDetailFragment.java | 6 ++--- .../UploadMediaDetailsContract.kt | 2 +- .../mediaDetails/UploadMediaPresenter.java | 23 ++++++++++--------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index 27556da4a..53ba6b75c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -398,7 +398,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace); if (response) { if (callback != null) { - presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace); + presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment); } } } else { @@ -445,7 +445,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements () -> { // Execute when user confirms the upload is of the specified place UploadActivity.nearbyPopupAnswers.put(place, true); - presenter.onUserConfirmedUploadIsOfPlace(place); + presenter.onUserConfirmedUploadIsOfPlace(place, indexOfFragment); }, () -> { // Execute when user cancels the upload of the specified place @@ -486,7 +486,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements if (UploadActivity.nearbyPopupAnswers.containsKey(nearbyPlace)) { final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace); if (response) { - presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace); + presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment); } } else { showNearbyPlaceFound(nearbyPlace); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt index ca4d8e67b..88dad8b93 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt @@ -117,6 +117,6 @@ interface UploadMediaDetailsContract { fun onEditButtonClicked(indexInViewFlipper: Int) - fun onUserConfirmedUploadIsOfPlace(place: Place?) + fun onUserConfirmedUploadIsOfPlace(place: Place?, uploadItemIndex: Int) } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java index 6c234cdaf..e626ee876 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.java @@ -322,23 +322,24 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt } /** - * Updates the information regarding the specified place for uploads + * Updates the information regarding the specified place for the specified upload item * when the user confirms the suggested nearby place. * * @param place The place to be associated with the uploads. + * @param uploadItemIndex Index of the uploadItem whose detected place has been confirmed */ @Override - public void onUserConfirmedUploadIsOfPlace(final Place place) { - final List uploads = repository.getUploads(); - for (final UploadItem uploadItem : uploads) { - uploadItem.setPlace(place); - final List uploadMediaDetails = uploadItem.getUploadMediaDetails(); - // Update UploadMediaDetail object for this UploadItem - uploadMediaDetails.set(0, new UploadMediaDetail(place)); - } - // Now that all UploadItems and their associated UploadMediaDetail objects have been updated, + public void onUserConfirmedUploadIsOfPlace(final Place place, final int uploadItemIndex) { + final UploadItem uploadItem = repository.getUploads().get(uploadItemIndex); + + uploadItem.setPlace(place); + final List uploadMediaDetails = uploadItem.getUploadMediaDetails(); + // Update UploadMediaDetail object for this UploadItem + uploadMediaDetails.set(0, new UploadMediaDetail(place)); + + // Now that the UploadItem and its associated UploadMediaDetail objects have been updated, // update the view with the modified media details of the first upload item - view.updateMediaDetails(uploads.get(0).getUploadMediaDetails()); + view.updateMediaDetails(uploadMediaDetails); UploadActivity.setUploadIsOfAPlace(true); } From e8970ab7f2866cbba0fa9da93d9f2748b010c805 Mon Sep 17 00:00:00 2001 From: Tanmay Gupta <119003089+savsch@users.noreply.github.com> Date: Sun, 15 Dec 2024 17:51:30 +0530 Subject: [PATCH 05/45] FilePicker: Fix error when multiple images are returned from Documents based picker (#6033) * FilePicker: Correctly handle Documents result * Empty commit to re-trigger CI checks --- .../fr/free/nrw/commons/filepicker/FilePicker.kt | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt index 6bf8a1061..2ed573740 100644 --- a/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt +++ b/app/src/main/java/fr/free/nrw/commons/filepicker/FilePicker.kt @@ -263,17 +263,8 @@ object FilePicker : Constants { ) { if (result.resultCode == Activity.RESULT_OK && !isPhoto(result.data)) { try { - val photoPath = result.data?.data - val photoFile = PickedFiles.pickedExistingPicture(activity, photoPath!!) - callbacks.onImagesPicked( - singleFileList(photoFile), - ImageSource.DOCUMENTS, - restoreType(activity) - ) - - if (configuration(activity).shouldCopyPickedImagesToPublicGalleryAppFolder()) { - PickedFiles.copyFilesInSeparateThread(activity, singleFileList(photoFile)) - } + val files = getFilesFromGalleryPictures(result.data, activity) + callbacks.onImagesPicked(files, ImageSource.DOCUMENTS, restoreType(activity)) } catch (e: Exception) { e.printStackTrace() callbacks.onImagePickerError(e, ImageSource.DOCUMENTS, restoreType(activity)) From 0153cbe0ed92f2ca1a6685798e5f49bd04fe967c Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Mon, 16 Dec 2024 13:01:57 +0100 Subject: [PATCH 06/45] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-ar/strings.xml | 10 ++++++++++ app/src/main/res/values-da/strings.xml | 2 ++ app/src/main/res/values-fr/strings.xml | 2 ++ app/src/main/res/values-iw/strings.xml | 2 ++ app/src/main/res/values-ko/strings.xml | 5 +++++ app/src/main/res/values-lb/strings.xml | 2 ++ app/src/main/res/values-mk/strings.xml | 2 ++ app/src/main/res/values-pa/strings.xml | 3 ++- app/src/main/res/values-pms/strings.xml | 1 + app/src/main/res/values-ru/strings.xml | 5 +++++ app/src/main/res/values-skr/strings.xml | 2 ++ app/src/main/res/values-sr/strings.xml | 2 +- 12 files changed, 36 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 639a268d7..2bdb7d458 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -9,6 +9,7 @@ * Dr-Taher * Dr. Mohammed * FiberAhmed +* Hhaboh162002 * Kassem7899 * LaMagiaaa * Meno25 @@ -153,6 +154,8 @@ التقط صورة بالقرب من هنا مرفوعاتي + نسخ الوصلة + تَمَّ نَّسخ الوصلة إلى الحافظة انشرها عرض صفحة الملف العنوان (مطلوب) @@ -411,11 +414,13 @@ حذف الإنجازات الملف الشخصي + أوسمة إحصاءات تم تلقي الشكر الصور المختارة صور عبر \"الأماكن المجاورة\" المستوى %d + %s (المستوى %s) الصور المرفوعة لم يتم إرجاع الصور الصور المستخدمة @@ -863,4 +868,9 @@ هذا المكان ليس له صورة بعد، اذهب والتقط واحدة! هذا المكان لديه صورة بالفعل. الآن التحقق ما إذا كان هذا المكان لديه صورة. + خطأ أثناء التحميل + لم يتم العثور على حالات استخدام + كومنز + مواقع ويكي أخرى + حالات استخدام الملف diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 38d447dcb..c8e242fa5 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -107,6 +107,8 @@ Tag billede I nærheden Mine uploads + Kopiér link + Linket er blevet kopieret til udklipsholderen Del Vis filside Billedtekst (påkrævet) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 417c23a5a..2dbc3ed2a 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -143,6 +143,8 @@ Prendre une photo À proximité Mes téléversements + Copier le lien + Le lien a été copié dans le presse-papiers Partager Voir la page du fichier Légende (obligatoire) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index b82c84a41..17808697d 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -131,6 +131,8 @@ צילום תמונה בסביבה ההעלאות שלי + העתקת קישור + הקישור הועתק ללוח שיתוף הצגת דף קובץ כיתוב (נדרש) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 9b85a451d..06970e5b0 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -111,6 +111,8 @@ 사진 찍기 근처 내 올린 파일 + 링크 복사 + 이 링크는 클립보드로 복사되었습니다 공유 파일 문서 보기 캡션 (필수) @@ -700,4 +702,7 @@ 이 장소에 아직 사진이 없습니다. 사진을 찍어보세요! 이 장소에 이미 사진이 있습니다. 지금 이 장소에 사진이 있는지 확인 중입니다. + 공용 + 다른 위키 + 이 파일을 사용하는 문서 diff --git a/app/src/main/res/values-lb/strings.xml b/app/src/main/res/values-lb/strings.xml index 94119bbac..fa6790622 100644 --- a/app/src/main/res/values-lb/strings.xml +++ b/app/src/main/res/values-lb/strings.xml @@ -86,6 +86,8 @@ Foto maachen Nobäi Meng eropgeluede Fichieren + Link kopéieren + De Link gouf an den Tëschespäicher kopéiert Deelen Fichierssäit weisen Beschrëftung (obligatoresch) diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index f1c581888..b329dbb31 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -101,6 +101,8 @@ Направи слика Во близина Мои подигања + Копирај врска + Врската е прекопирана во меѓускладот. Сподели Погл. податотечната страница Толкување (задолж.) diff --git a/app/src/main/res/values-pa/strings.xml b/app/src/main/res/values-pa/strings.xml index 2abc1fedd..041a45fc2 100644 --- a/app/src/main/res/values-pa/strings.xml +++ b/app/src/main/res/values-pa/strings.xml @@ -67,12 +67,13 @@ ਤਸਵੀਰ ਲਵੋ ਨੇੜੇ-ਤੇੜੇ ਮੇਰੇ ਅੱਪਲੋਡ + ਕੜੀ ਦੀ ਨਕਲ ਕਰੋ ਸਾਂਝਾ ਕਰੋ ਸੁਰਖੀ (ਲੋੜੀਂਦੀ) ਵੇਰਵਾ ਦਾਖ਼ਲ ਹੋਣ ਵਿੱਚ ਅਸਮਰੱਥ - ਨੈੱਟਵਰਕ ਫੇਲ੍ਹ ਹੋਇਆ ਹੈ ਬਹੁਤ ਸਾਰੀਆਂ ਅਸਫ਼ਲ ਕੋਸ਼ਿਸ਼ਾਂ। ਥੋੜ੍ਹੀ ਦੇਰ ਬਾਅਦ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ। - ਅਫ਼ਸੋਸ, ਇਹ ਵਰਤੋਂਕਾਰ ਕਾਮਨਜ਼ ਉੱਤੇ ਬਲਾਕ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ + ਅਫ਼ਸੋਸ, ਇਹ ਵਰਤੋਂਕਾਰ ਨੂੰ ਕਾਮਨਜ਼ ਤੇ ਰੋਕ ਲਾਈ ਗਈ ਹੈ। ਦਾਖ਼ਲ ਹੋਣਾ ਅਸਫ਼ਲ! ਚੜ੍ਹਾਉ ਇਸ ਸੈੱਟ ਨੂੰ ਨਾਂ ਦਿਓ diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 0468dbd96..3a199484d 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -99,6 +99,7 @@ Fé na fòto Davzin Ij mè cariament + Copié la liura Partagé Vëdde la pàgina dl\'archivi Legenda (obligatòria) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 20a89af9e..b03791dc2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -22,6 +22,7 @@ * MaxBioHazard * McDutchie * Megakott +* Mitte27 * Monirec * Movses * Nastua Buldakova @@ -156,6 +157,8 @@ Сфотографировать Поблизости Мои загрузки + Скопировать ссылку + Ссылка скопирована в буфер обмена Поделиться Просмотреть страницу файла Подпись (Обязательна) @@ -857,4 +860,6 @@ У этого места пока нет фотографий, так что сделайте несколько! У этого места уже есть фотография. Сейчас проверим, есть ли у этого места фотография. + Викисклад + Другие вики diff --git a/app/src/main/res/values-skr/strings.xml b/app/src/main/res/values-skr/strings.xml index 7765151df..f6d88d6b0 100644 --- a/app/src/main/res/values-skr/strings.xml +++ b/app/src/main/res/values-skr/strings.xml @@ -54,6 +54,7 @@ فوٹو چھکو نیڑے میݙے اپ لوڈ + لنک نقل کرو شیئر فائل آلا ورقہ ݙیکھو عنوان (ضروری ہے) @@ -274,4 +275,5 @@ ناکام تھیا مٹاؤ منسوخ + کامنز diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 7101c1200..dd3b84478 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -362,7 +362,7 @@ Захваљивања Изабране слике Слике преко „Места у близини” - Ниво + Ниво %d Отпремљене слике Слике које нису уклоњене Искоришћене слике From 5500b039762b63d18b794bb0bc68d2a84a495166 Mon Sep 17 00:00:00 2001 From: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:19:32 +0530 Subject: [PATCH 07/45] Feature: Bookmark Categories (#6035) * database setup and insert/delete working Signed-off-by: parneet-guraya * add new categories screen in bookmarks page Signed-off-by: parneet-guraya * add theme Signed-off-by: parneet-guraya * set tab layout scrollable Signed-off-by: parneet-guraya * cleanup Signed-off-by: parneet-guraya * fix tests Signed-off-by: parneet-guraya * bump database version Signed-off-by: parneet-guraya * add docs Signed-off-by: parneet-guraya --------- Signed-off-by: parneet-guraya --- .../bookmarks/BookmarkListRootFragment.java | 19 ++- .../bookmarks/BookmarksPagerAdapter.java | 7 + .../category/BookmarkCategoriesDao.kt | 52 +++++++ .../category/BookmarkCategoriesFragment.kt | 143 ++++++++++++++++++ .../category/BookmarksCategoryModal.kt | 15 ++ .../category/CategoryDetailsActivity.kt | 46 ++++++ .../category/CategoryDetailsViewModel.kt | 109 +++++++++++++ .../fr/free/nrw/commons/db/AppDatabase.kt | 8 +- .../commons/di/CommonsApplicationModule.kt | 5 + .../nrw/commons/di/FragmentBuilderModule.kt | 4 + .../main/res/layout/fragment_bookmarks.xml | 2 +- .../res/menu/fragment_category_detail.xml | 6 + app/src/main/res/values/strings.xml | 1 + .../bookmarks/BookmarksPagerAdapterTests.kt | 2 +- .../LoggedOutBookmarksPagerAdapterTests.kt | 2 +- 15 files changed, 410 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesDao.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesFragment.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarksCategoryModal.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsViewModel.kt diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java index 281248ca4..8b66a104a 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarkListRootFragment.java @@ -7,13 +7,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.R; +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment; import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment; import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment; @@ -48,14 +48,21 @@ public class BookmarkListRootFragment extends CommonsDaggerSupportFragment imple String title = bundle.getString("categoryName"); int order = bundle.getInt("order"); final int orderItem = bundle.getInt("orderItem"); - if (order == 0) { - listFragment = new BookmarkPicturesFragment(); - } else { - listFragment = new BookmarkLocationsFragment(); + + switch (order){ + case 0: listFragment = new BookmarkPicturesFragment(); + break; + + case 1: listFragment = new BookmarkLocationsFragment(); + break; + + case 3: listFragment = new BookmarkCategoriesFragment(); + break; + } if(orderItem == 2) { listFragment = new BookmarkItemsFragment(); } - } + Bundle featuredArguments = new Bundle(); featuredArguments.putString("categoryName", title); listFragment.setArguments(featuredArguments); diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java index ea3a9a453..f0620032a 100644 --- a/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapter.java @@ -49,6 +49,13 @@ public class BookmarksPagerAdapter extends FragmentPagerAdapter { new BookmarkListRootFragment(locationBundle, this), context.getString(R.string.title_page_bookmarks_items))); } + final Bundle categoriesBundle = new Bundle(); + categoriesBundle.putString("categoryName", + context.getString(R.string.title_page_bookmarks_categories)); + categoriesBundle.putInt("order", 3); + pages.add(new BookmarkPages( + new BookmarkListRootFragment(categoriesBundle, this), + context.getString(R.string.title_page_bookmarks_categories))); notifyDataSetChanged(); } diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesDao.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesDao.kt new file mode 100644 index 000000000..71a2d1ec9 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesDao.kt @@ -0,0 +1,52 @@ +package fr.free.nrw.commons.bookmarks.category + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +/** + * Bookmark categories dao + * + * @constructor Create empty Bookmark categories dao + */ +@Dao +interface BookmarkCategoriesDao { + + /** + * Insert or Delete category bookmark into DB + * + * @param bookmarksCategoryModal + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(bookmarksCategoryModal: BookmarksCategoryModal) + + + /** + * Delete category bookmark from DB + * + * @param bookmarksCategoryModal + */ + @Delete + suspend fun delete(bookmarksCategoryModal: BookmarksCategoryModal) + + /** + * Checks if given category exist in DB + * + * @param categoryName + * @return + */ + @Query("SELECT EXISTS (SELECT 1 FROM bookmarks_categories WHERE categoryName = :categoryName)") + suspend fun doesExist(categoryName: String): Boolean + + /** + * Get all categories + * + * @return + */ + @Query("SELECT * FROM bookmarks_categories") + fun getAllCategories(): Flow> + +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesFragment.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesFragment.kt new file mode 100644 index 000000000..ef5bc613d --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarkCategoriesFragment.kt @@ -0,0 +1,143 @@ +package fr.free.nrw.commons.bookmarks.category + +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.android.support.DaggerFragment +import fr.free.nrw.commons.R +import fr.free.nrw.commons.category.CategoryDetailsActivity +import javax.inject.Inject + +/** + * Tab fragment to show list of bookmarked Categories + */ +class BookmarkCategoriesFragment : DaggerFragment() { + + @Inject + lateinit var bookmarkCategoriesDao: BookmarkCategoriesDao + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + MaterialTheme( + colorScheme = if (isSystemInDarkTheme()) darkColorScheme( + primary = colorResource(R.color.primaryDarkColor), + surface = colorResource(R.color.main_background_dark), + background = colorResource(R.color.main_background_dark) + ) else lightColorScheme( + primary = colorResource(R.color.primaryColor), + surface = colorResource(R.color.main_background_light), + background = colorResource(R.color.main_background_light) + ) + ) { + val listOfBookmarks by bookmarkCategoriesDao.getAllCategories() + .collectAsStateWithLifecycle(initialValue = emptyList()) + Surface(modifier = Modifier.fillMaxSize()) { + Box(contentAlignment = Alignment.Center) { + if (listOfBookmarks.isEmpty()) { + Text( + text = stringResource(R.string.bookmark_empty), + style = MaterialTheme.typography.bodyMedium, + color = if (isSystemInDarkTheme()) Color(0xB3FFFFFF) + else Color( + 0x8A000000 + ) + ) + } else { + LazyColumn(modifier = Modifier.fillMaxSize()) { + items(items = listOfBookmarks) { bookmarkItem -> + CategoryItem( + categoryName = bookmarkItem.categoryName, + onClick = { + val categoryDetailsIntent = Intent( + requireContext(), + CategoryDetailsActivity::class.java + ).putExtra("categoryName", it) + startActivity(categoryDetailsIntent) + } + ) + } + } + } + } + } + } + } + } + } + + + @Composable + fun CategoryItem( + modifier: Modifier = Modifier, + onClick: (String) -> Unit, + categoryName: String + ) { + Row(modifier = modifier.clickable { + onClick(categoryName) + }) { + ListItem( + leadingContent = { + Image( + modifier = Modifier.size(48.dp), + painter = painterResource(R.drawable.commons), + contentDescription = null + ) + }, + headlineContent = { + Text( + text = categoryName, + maxLines = 2, + color = if (isSystemInDarkTheme()) Color.White else Color.Black, + style = MaterialTheme.typography.bodyMedium, + fontWeight = FontWeight.SemiBold + ) + } + ) + } + } + + @Preview + @Composable + private fun CategoryItemPreview() { + CategoryItem( + onClick = {}, + categoryName = "Test Category" + ) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarksCategoryModal.kt b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarksCategoryModal.kt new file mode 100644 index 000000000..ab679611f --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/bookmarks/category/BookmarksCategoryModal.kt @@ -0,0 +1,15 @@ +package fr.free.nrw.commons.bookmarks.category + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * Data class representing bookmarked category in DB + * + * @property categoryName + * @constructor Create empty Bookmarks category modal + */ +@Entity(tableName = "bookmarks_categories") +data class BookmarksCategoryModal( + @PrimaryKey val categoryName: String +) diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt index ba1fcfdae..a42d26fd6 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt @@ -7,8 +7,12 @@ import android.os.Bundle import android.view.Menu import android.view.MenuItem import android.view.View +import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import fr.free.nrw.commons.Media import fr.free.nrw.commons.R import fr.free.nrw.commons.Utils @@ -19,6 +23,8 @@ import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment import fr.free.nrw.commons.media.MediaDetailPagerFragment import fr.free.nrw.commons.theme.BaseActivity +import kotlinx.coroutines.launch +import javax.inject.Inject /** @@ -38,6 +44,11 @@ class CategoryDetailsActivity : BaseActivity(), private lateinit var binding: ActivityCategoryDetailsBinding + @Inject + lateinit var categoryViewModelFactory: CategoryDetailsViewModel.ViewModelFactory + + private val viewModel: CategoryDetailsViewModel by viewModels { categoryViewModelFactory } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -53,6 +64,15 @@ class CategoryDetailsActivity : BaseActivity(), supportActionBar?.setDisplayHomeAsUpEnabled(true) setTabs() setPageTitle() + + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED){ + viewModel.bookmarkState.collect { + invalidateOptionsMenu() + } + } + } + } /** @@ -73,6 +93,8 @@ class CategoryDetailsActivity : BaseActivity(), categoriesMediaFragment.arguments = arguments subCategoryListFragment.arguments = arguments parentCategoriesFragment.arguments = arguments + + viewModel.onCheckIfBookmarked(categoryName!!) } fragmentList.add(categoriesMediaFragment) titleList.add("MEDIA") @@ -181,6 +203,14 @@ class CategoryDetailsActivity : BaseActivity(), Utils.handleWebUrl(this, Uri.parse(title.canonicalUri)) true } + + R.id.menu_bookmark_current_category -> { + categoryName?.let { + viewModel.onBookmarkClick(categoryName = it) + } + true + } + android.R.id.home -> { onBackPressed() true @@ -189,6 +219,22 @@ class CategoryDetailsActivity : BaseActivity(), } } + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + menu?.run { + val bookmarkMenuItem = findItem(R.id.menu_bookmark_current_category) + if (bookmarkMenuItem != null) { + val icon = if(viewModel.bookmarkState.value){ + R.drawable.menu_ic_round_star_filled_24px + } else { + R.drawable.menu_ic_round_star_border_24px + } + + bookmarkMenuItem.setIcon(icon) + } + } + return super.onPrepareOptionsMenu(menu) + } + /** * This method is called on backPressed of anyFragment in the activity. * If condition is called when mediaDetailFragment is opened. diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsViewModel.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsViewModel.kt new file mode 100644 index 000000000..a50f25669 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsViewModel.kt @@ -0,0 +1,109 @@ +package fr.free.nrw.commons.category + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao +import fr.free.nrw.commons.bookmarks.category.BookmarksCategoryModal +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +/** + * ViewModal for [CategoryDetailsActivity] + */ +class CategoryDetailsViewModel( + private val bookmarkCategoriesDao: BookmarkCategoriesDao +) : ViewModel() { + + private val _bookmarkState = MutableStateFlow(false) + val bookmarkState = _bookmarkState.asStateFlow() + + + /** + * Used to check if bookmark exists for the given category in DB + * based on that bookmark state is updated + * @param categoryName + */ + fun onCheckIfBookmarked(categoryName: String) { + viewModelScope.launch { + val isBookmarked = bookmarkCategoriesDao.doesExist(categoryName) + _bookmarkState.update { + isBookmarked + } + } + } + + /** + * Handles event when bookmark button is clicked from view + * based on that category is bookmarked or removed in/from in the DB + * and bookmark state is update as well + * @param categoryName + */ + fun onBookmarkClick(categoryName: String) { + if (_bookmarkState.value) { + deleteBookmark(categoryName) + _bookmarkState.update { + false + } + } else { + addBookmark(categoryName) + _bookmarkState.update { + true + } + } + } + + + /** + * Add bookmark into DB + * + * @param categoryName + */ + private fun addBookmark(categoryName: String) { + viewModelScope.launch { + val categoryItem = BookmarksCategoryModal( + categoryName = categoryName + ) + + bookmarkCategoriesDao.insert(categoryItem) + } + } + + + /** + * Delete bookmark from DB + * + * @param categoryName + */ + private fun deleteBookmark(categoryName: String) { + viewModelScope.launch { + bookmarkCategoriesDao.delete( + BookmarksCategoryModal( + categoryName = categoryName + ) + ) + } + } + + /** + * View model factory to create [CategoryDetailsViewModel] + * + * @property bookmarkCategoriesDao + * @constructor Create empty View model factory + */ + class ViewModelFactory @Inject constructor( + private val bookmarkCategoriesDao: BookmarkCategoriesDao + ) : ViewModelProvider.Factory { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = + if (modelClass.isAssignableFrom(CategoryDetailsViewModel::class.java)) { + CategoryDetailsViewModel(bookmarkCategoriesDao) as T + } else { + throw IllegalArgumentException("Unknown class name") + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt index 71947fa1a..0c34bbdec 100644 --- a/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt +++ b/app/src/main/java/fr/free/nrw/commons/db/AppDatabase.kt @@ -3,6 +3,8 @@ package fr.free.nrw.commons.db import androidx.room.Database import androidx.room.RoomDatabase import androidx.room.TypeConverters +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao +import fr.free.nrw.commons.bookmarks.category.BookmarksCategoryModal import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.customselector.database.NotForUploadStatus @@ -21,8 +23,8 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao * */ @Database( - entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class], - version = 18, + entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class, BookmarksCategoryModal::class], + version = 19, exportSchema = false, ) @TypeConverters(Converters::class) @@ -38,4 +40,6 @@ abstract class AppDatabase : RoomDatabase() { abstract fun NotForUploadStatusDao(): NotForUploadStatusDao abstract fun ReviewDao(): ReviewDao + + abstract fun bookmarkCategoriesDao(): BookmarkCategoriesDao } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.kt b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.kt index b195674a9..58d9039d5 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.kt +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationModule.kt @@ -15,6 +15,7 @@ import dagger.Provides import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.R import fr.free.nrw.commons.auth.SessionManager +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.database.UploadedStatusDao @@ -221,6 +222,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) { fun providesReviewDao(appDatabase: AppDatabase): ReviewDao = appDatabase.ReviewDao() + @Provides + fun providesBookmarkCategoriesDao (appDatabase: AppDatabase): BookmarkCategoriesDao = + appDatabase.bookmarkCategoriesDao() + @Provides fun providesContentResolver(context: Context): ContentResolver = context.contentResolver diff --git a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.kt b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.kt index bfdb90181..0ef34e355 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.kt +++ b/app/src/main/java/fr/free/nrw/commons/di/FragmentBuilderModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector import fr.free.nrw.commons.bookmarks.BookmarkFragment import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment +import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment @@ -97,6 +98,9 @@ abstract class FragmentBuilderModule { @ContributesAndroidInjector(modules = [BookmarkItemsFragmentModule::class]) abstract fun bindBookmarkItemListFragment(): BookmarkItemsFragment + @ContributesAndroidInjector + abstract fun bindBookmarkCategoriesListFragment(): BookmarkCategoriesFragment + @ContributesAndroidInjector abstract fun bindReviewOutOfContextFragment(): ReviewImageFragment diff --git a/app/src/main/res/layout/fragment_bookmarks.xml b/app/src/main/res/layout/fragment_bookmarks.xml index 930faf592..1ffb7bc5d 100644 --- a/app/src/main/res/layout/fragment_bookmarks.xml +++ b/app/src/main/res/layout/fragment_bookmarks.xml @@ -17,7 +17,7 @@ android:layout_below="@id/toolbar" android:background="?attr/tabBackground" app:tabIndicatorColor="?attr/tabIndicatorColor" - app:tabMode="fixed" + app:tabMode="scrollable" app:tabSelectedTextColor="?attr/tabSelectedTextColor" app:tabTextColor="?attr/tabTextColor" /> diff --git a/app/src/main/res/menu/fragment_category_detail.xml b/app/src/main/res/menu/fragment_category_detail.xml index 386e678fd..0d2624238 100644 --- a/app/src/main/res/menu/fragment_category_detail.xml +++ b/app/src/main/res/menu/fragment_category_detail.xml @@ -2,6 +2,12 @@ + + + No compatible map application could be found on your device. Please install a map application to use this feature. Pictures Locations + Categories Add/Remove to bookmarks Bookmarks You haven\'t added any bookmarks diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt index 9423c9095..bf143ecc0 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/BookmarksPagerAdapterTests.kt @@ -37,7 +37,7 @@ class BookmarksPagerAdapterTests { @Test fun testGetCount() { - Assert.assertEquals(bookmarksPagerAdapter.count, 3) + Assert.assertEquals(bookmarksPagerAdapter.count, 4) } @Test diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt index 4c20bdda9..7ba7d559e 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/LoggedOutBookmarksPagerAdapterTests.kt @@ -53,7 +53,7 @@ class LoggedOutBookmarksPagerAdapterTests { */ @Test fun testGetCount() { - Assert.assertEquals(bookmarksPagerAdapter.count, 1) + Assert.assertEquals(bookmarksPagerAdapter.count, 2) } /** From a4b74794cb3340b5e4920fe4d1fec5255840d5a1 Mon Sep 17 00:00:00 2001 From: "translatewiki.net" Date: Thu, 19 Dec 2024 13:01:57 +0100 Subject: [PATCH 08/45] Localisation updates from https://translatewiki.net. --- app/src/main/res/values-ar/strings.xml | 1 + app/src/main/res/values-da/strings.xml | 1 + app/src/main/res/values-fa/strings.xml | 3 +++ app/src/main/res/values-fr/strings.xml | 1 + app/src/main/res/values-hi/strings.xml | 1 + app/src/main/res/values-iw/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-nl/strings.xml | 8 ++++++++ app/src/main/res/values-pms/strings.xml | 2 ++ app/src/main/res/values-skr/strings.xml | 1 + app/src/main/res/values-sr/strings.xml | 1 + 11 files changed, 21 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 2bdb7d458..a93015e52 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -452,6 +452,7 @@ لا يمكن العثور على تطبيق خرائط متوافق على جهازك; الرجاء تثبيت تطبيق خرائط لاستخدام هذه الميزة. الصور المواقع + التصنيفات إضافة إلى/إزالة من العلامات العلامات أنت لم تضف أي علامات diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index c8e242fa5..acd557751 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -405,6 +405,7 @@ Der blev ikke fundet nogen kompatibel kortapplikation på din enhed. Installer en kortapplikation for at bruge denne funktion. Billeder Placeringer + Kategorier Tilføj/Fjern bogmærker Bogmærker Du har ikke tilføjet nogen bogmærker diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 1be4873f4..3e76618bc 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -106,6 +106,8 @@ گرفتن عکس در نزدیکی بارگذاری‌های من + رونویسی پیوند + پیوند به بریده‌دان منتقل شد به اشتراک‌گذاشتن مشاهدهٔ صفحهٔ پرونده برنگاشت (الزامی) @@ -364,6 +366,7 @@ پرونده‌ای با نام %1$s وجود دارد. آیا اطمینان دارید که می‌خواهید ادامه دهید؟ تصویرها مکان‌ها + رده‌ها افزودن/حذف نشانک‌ها نشانک‌ها شما هیچ bookmarkی اضافه نکرده‌اید diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2dbc3ed2a..b94688600 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -441,6 +441,7 @@ Aucune application cartographique compatible n’a été trouvée sur votre appareil. Veuillez en installer une pour utiliser cette fonctionnalité. Images Lieux + Catégories Ajouter ou supprimer des signets Signets Vous n’avez ajouté aucun signet diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 55a378eaf..69bea9079 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -317,6 +317,7 @@ पिछला चित्र स्थान + श्रेणियाँ बुकमार्क से जोड़ें/हटायें बुकमार्क आपने कोई बुकमार्क नहीं जोड़ा है diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 17808697d..7a61965ed 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -429,6 +429,7 @@ לא נמצא יישום מפה תואם במכשיר שלך. נא להתקין יישום מפה כדי להשתמש בתכונה זו. תמונות מיקומים + קטגוריות הוספה / הסרה של סימנייה סימניות לא הוספת שום סימנייה diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 06970e5b0..73604c4f4 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -404,6 +404,7 @@ 장치에서 호환되는 지도 애플리케이션을 찾지 못했습니다. 이 기능을 사용하려면 지도 애플리케이션을 설치해 주십시오. 사진 위치 + 분류 북마크에 추가/제거 북마크 북마크를 추가하지 않았습니다 diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e0ef57f67..17e4b2556 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -122,6 +122,8 @@ Foto nemen In de buurt Mijn uploads + Koppeling kopiëren + De koppeling is naar het klembord gekopieerd Delen Bestandspagina bekijken Titel (verplicht) @@ -418,6 +420,7 @@ Er is geen compatibele kaarttoepassing gevonden op uw apparaat. Installeer een kaarttoepassing om deze functie te gebruiken. Afbeeldingen Locaties + Categorieën Toevoegen/verwijderen van bladwijzers Bladwijzers U hebt geen bladwijzers toegevoegd @@ -821,4 +824,9 @@ Er is nog geen foto van deze plek, maak er eentje! Er is al een foto van deze plek. We controleren nu of er een foto van deze plek is. + Fout bij het laden + Geen gebruik gevonden + Commons + Andere wiki’s + Bestandsgebruik diff --git a/app/src/main/res/values-pms/strings.xml b/app/src/main/res/values-pms/strings.xml index 3a199484d..57e58ab93 100644 --- a/app/src/main/res/values-pms/strings.xml +++ b/app/src/main/res/values-pms/strings.xml @@ -100,6 +100,7 @@ Davzin Ij mè cariament Copié la liura + La liura a l\'é stàita copià ant ël quàder dij feuj. Partagé Vëdde la pàgina dl\'archivi Legenda (obligatòria) @@ -396,6 +397,7 @@ Gnun-a aplicassion cartogràfica compatìbil a l\'é trovasse an sò angign. Për piasì, ch\'a na anstala un-a për dovré costa fonsionalità. Plance Locassion + Categorìe Gionté/Gavé dij marcapàgine Marcapàgine A l\'ha giontà gnun marcapàgina diff --git a/app/src/main/res/values-skr/strings.xml b/app/src/main/res/values-skr/strings.xml index f6d88d6b0..3b276b95f 100644 --- a/app/src/main/res/values-skr/strings.xml +++ b/app/src/main/res/values-skr/strings.xml @@ -181,6 +181,7 @@ پچھلا تصویراں محل وقوع + ونکیاں کتاب نشان کتاب نشان اپ لوڈ منسوخ کرو diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index dd3b84478..d93c2d43c 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -394,6 +394,7 @@ Није могуће пронаћи апликацију компатибилне мапе на вашем уређају. Инсталирајте апликацију мапе да бисте користили ову функцију. Слике Локације + Категорије Додај/уклони обележивач Обележивачи Нисте додали ниједан обележивач From 4c9637c821f749bd97a65e9fc0e07cff22a060cb Mon Sep 17 00:00:00 2001 From: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com> Date: Fri, 20 Dec 2024 06:36:07 +0530 Subject: [PATCH 09/45] Add pull down to refresh in Contributions screen (#6041) * pull down to refresh Signed-off-by: parneet-guraya * add kdoc Signed-off-by: parneet-guraya * only enabled for self user Signed-off-by: parneet-guraya * fix test Signed-off-by: parneet-guraya --------- Signed-off-by: parneet-guraya --- .../ContributionBoundaryCallback.kt | 38 +++- .../ContributionsListContract.java | 4 + .../ContributionsListFragment.java | 9 + .../ContributionsListPresenter.java | 18 +- .../layout/fragment_contributions_list.xml | 192 +++++++++--------- .../ContributionBoundaryCallbackTest.kt | 7 +- 6 files changed, 160 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt index 3f7bffe91..b5075a21e 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt @@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions import androidx.paging.PagedList.BoundaryCallback import fr.free.nrw.commons.auth.SessionManager -import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD import fr.free.nrw.commons.media.MediaClient import io.reactivex.Scheduler @@ -31,10 +30,7 @@ class ContributionBoundaryCallback * network */ override fun onZeroItemsLoaded() { - if (sessionManager.userName != null) { - mediaClient.resetUserNameContinuation(sessionManager.userName!!) - } - fetchContributions() + refreshList() } /** @@ -52,9 +48,25 @@ class ContributionBoundaryCallback } /** - * Fetches contributions using the MediaWiki API + * Fetch list from network and save it to local DB. + * + * @param onRefreshFinish callback to invoke when operations finishes + * with either error or success. */ - private fun fetchContributions() { + fun refreshList(onRefreshFinish: () -> Unit = {}){ + if (sessionManager.userName != null) { + mediaClient.resetUserNameContinuation(sessionManager.userName!!) + } + fetchContributions(onRefreshFinish) + } + + /** + * Fetches contributions using the MediaWiki API + * + * @param onRefreshFinish callback to invoke when operations finishes + * with either error or success. + */ + private fun fetchContributions(onRefreshFinish: () -> Unit = {}) { if (sessionManager.userName != null) { userName ?.let { userName -> @@ -65,12 +77,15 @@ class ContributionBoundaryCallback Contribution(media = media, state = Contribution.STATE_COMPLETED) } }.subscribeOn(ioThreadScheduler) - .subscribe(::saveContributionsToDB) { error: Throwable -> + .subscribe({ list -> + saveContributionsToDB(list, onRefreshFinish) + },{ error -> + onRefreshFinish() Timber.e( "Failed to fetch contributions: %s", error.message, ) - } + }) }?.let { compositeDisposable.add( it, @@ -83,13 +98,16 @@ class ContributionBoundaryCallback /** * Saves the contributions the the local DB + * + * @param onRefreshFinish callback to invoke when successfully saved to DB. */ - private fun saveContributionsToDB(contributions: List) { + private fun saveContributionsToDB(contributions: List, onRefreshFinish: () -> Unit) { compositeDisposable.add( repository .save(contributions) .subscribeOn(ioThreadScheduler) .subscribe { longs: List? -> + onRefreshFinish() repository["last_fetch_timestamp"] = System.currentTimeMillis() }, ) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java index 58bd2783d..0d0a19436 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.contributions; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import fr.free.nrw.commons.BasePresenter; /** @@ -17,5 +18,8 @@ public class ContributionsListContract { } public interface UserActionListener extends BasePresenter { + + void refreshList(SwipeRefreshLayout swipeRefreshLayout); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index 509d1eb95..df65a91cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -191,6 +191,15 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl initAdapter(); + // pull down to refresh only enabled for self user. + if(Objects.equals(sessionManager.getUserName(), userName)){ + binding.swipeRefreshLayout.setOnRefreshListener(() -> { + contributionsListPresenter.refreshList(binding.swipeRefreshLayout); + }); + } else { + binding.swipeRefreshLayout.setEnabled(false); + } + return binding.getRoot(); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java index 735ff63d4..100c8be03 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java @@ -8,14 +8,15 @@ import androidx.paging.DataSource; import androidx.paging.DataSource.Factory; import androidx.paging.LivePagedListBuilder; import androidx.paging.PagedList; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import fr.free.nrw.commons.contributions.ContributionsListContract.UserActionListener; -import fr.free.nrw.commons.di.CommonsApplicationModule; import io.reactivex.Scheduler; import io.reactivex.disposables.CompositeDisposable; -import java.util.Arrays; import java.util.Collections; import javax.inject.Inject; import javax.inject.Named; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; /** * The presenter class for Contributions @@ -95,4 +96,17 @@ public class ContributionsListPresenter implements UserActionListener { contributionBoundaryCallback.dispose(); } + /** + * It is used to refresh list. + * + * @param swipeRefreshLayout used to stop refresh animation when + * refresh finishes. + */ + @Override + public void refreshList(final SwipeRefreshLayout swipeRefreshLayout) { + contributionBoundaryCallback.refreshList(() -> { + swipeRefreshLayout.setRefreshing(false); + return Unit.INSTANCE; + }); + } } diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml index b490c35ff..41121c92a 100644 --- a/app/src/main/res/layout/fragment_contributions_list.xml +++ b/app/src/main/res/layout/fragment_contributions_list.xml @@ -1,125 +1,129 @@ - + + + android:orientation="vertical"> + + android:id="@+id/noContributionsYet" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_marginEnd="@dimen/tiny_gap" + android:layout_marginRight="@dimen/tiny_gap" + android:gravity="center" + android:text="@string/no_uploads" + android:visibility="gone" /> + android:visibility="gone" /> - + - + + + + android:contentDescription="@string/add_contribution_from_camera" + android:tint="@color/button_blue" + android:visibility="gone" + app:backgroundTint="@color/main_background_light" + app:elevation="@dimen/tiny_margin" + app:fabSize="mini" + app:srcCompat="@drawable/ic_photo_camera_white_24dp" + app:useCompatPadding="true" /> - + - + - - - + - + + diff --git a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt index 19b36521d..11b5fe9a2 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt @@ -19,6 +19,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoInteractions import org.mockito.MockitoAnnotations import java.lang.reflect.Method +import kotlin.reflect.jvm.internal.impl.builtins.functions.FunctionTypeKind /** * The unit test class for ContributionBoundaryCallbackTest @@ -99,9 +100,10 @@ class ContributionBoundaryCallbackTest { val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( "fetchContributions", + Function0::class.java ) method.isAccessible = true - method.invoke(contributionBoundaryCallback) + method.invoke(contributionBoundaryCallback, {}) verify(repository).save(anyList()) verify(mediaClient).getMediaListForUser(anyString()) } @@ -113,9 +115,10 @@ class ContributionBoundaryCallbackTest { val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( "fetchContributions", + Function0::class.java ) method.isAccessible = true - method.invoke(contributionBoundaryCallback) + method.invoke(contributionBoundaryCallback, {}) verifyNoInteractions(repository) verify(mediaClient).getMediaListForUser(anyString()) } From 70b4f78a5d36bd9a8fbff930a79e8c2b0a3759df Mon Sep 17 00:00:00 2001 From: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com> Date: Fri, 20 Dec 2024 17:59:08 +0530 Subject: [PATCH 10/45] fix multi-line paste (#6050) Signed-off-by: parneet-guraya --- .../free/nrw/commons/upload/UploadMediaDetailInputFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailInputFilter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailInputFilter.java index 04498746d..f2c444ee7 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailInputFilter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailInputFilter.java @@ -70,7 +70,7 @@ public class UploadMediaDetailInputFilter implements InputFilter { public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { if (checkBlocklisted(source)) { - if (start == dstart) { + if (start == dstart && dest.length() > 0) { return dest; } From c891c2b0dfbdb4d2abf47140298333b18b4c6dbc Mon Sep 17 00:00:00 2001 From: Tanmay Gupta <119003089+savsch@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:00:53 +0530 Subject: [PATCH 11/45] Nearby: Fix race condition and lag when loading pin details, faster overlay management (#6047) * temporary fixes part one * temporary fixes part two * temporary fixes part three * temporary fixes part four * temporary fixes part five * reformatting * remove code no longer in use * Migrate NearbyParentFragmentPresenter to Kotlin * Partially replace temporary experimental fixes * Replace temporary experimental fixes part 2 * Replace temporary experimental fixes part 3 * Replace temporary fixes completely * Fix caching and loading places in Nearby list * Add place bookmarking logic, Remove all old code * Nearby Presenter: Close channel properly * Nearby pins now load starting from the center Fixes #6049 * Add comments and javadoc for Nearby Presenter * Fix warnings, Fix formatting, Add javadoc * Pass unit tests --- .../nrw/commons/nearby/NearbyController.java | 2 +- .../NearbyParentFragmentContract.java | 6 +- .../fragments/NearbyParentFragment.java | 365 ++--------- .../NearbyParentFragmentPresenter.java | 368 ----------- .../NearbyParentFragmentPresenter.kt | 597 ++++++++++++++++++ .../NearbyParentFragmentPresenterTest.kt | 17 +- 6 files changed, 676 insertions(+), 679 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java index 7bb311961..cc2442db6 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyController.java @@ -20,8 +20,8 @@ import timber.log.Timber; public class NearbyController extends MapController { - private static final int MAX_RESULTS = 1000; private final NearbyPlaces nearbyPlaces; + public static final int MAX_RESULTS = 1000; public static double currentLocationSearchRadius = 10.0; //in kilometers public static LatLng currentLocation; // Users latest fetched location public static LatLng latestSearchLocation; // Can be current and camera target on search this area button is used diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/contract/NearbyParentFragmentContract.java b/app/src/main/java/fr/free/nrw/commons/nearby/contract/NearbyParentFragmentContract.java index bcf8d8421..3b08cf7cb 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/contract/NearbyParentFragmentContract.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/contract/NearbyParentFragmentContract.java @@ -2,11 +2,13 @@ package fr.free.nrw.commons.nearby.contract; import android.content.Context; import androidx.annotation.Nullable; +import androidx.lifecycle.LifecycleCoroutineScope; import fr.free.nrw.commons.BaseMarker; import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; import fr.free.nrw.commons.nearby.Label; +import fr.free.nrw.commons.nearby.MarkerPlaceGroup; import fr.free.nrw.commons.nearby.Place; import java.util.List; @@ -68,7 +70,7 @@ public interface NearbyParentFragmentContract { Context getContext(); - void updateMapMarkers(List BaseMarkers); + void replaceMarkerOverlays(List markerPlaceGroups); void filterOutAllMarkers(); @@ -127,5 +129,7 @@ public interface NearbyParentFragmentContract { void setCheckboxUnknown(); void setAdvancedQuery(String query); + + void toggleBookmarkedStatus(Place place); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index fff4e4ca7..6b21681ab 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -23,8 +23,6 @@ import android.graphics.drawable.Drawable; import android.location.Location; import android.location.LocationManager; import android.net.Uri; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -56,6 +54,7 @@ import androidx.appcompat.app.AlertDialog.Builder; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; +import androidx.lifecycle.LifecycleOwnerKt; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; @@ -110,16 +109,12 @@ import fr.free.nrw.commons.wikidata.WikidataEditListener; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Locale; @@ -155,6 +150,29 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment FragmentNearbyParentBinding binding; + public final MapEventsOverlay mapEventsOverlay = new MapEventsOverlay(new MapEventsReceiver() { + @Override + public boolean singleTapConfirmedHelper(GeoPoint p) { + if (clickedMarker != null) { + clickedMarker.closeInfoWindow(); + } else { + Timber.e("CLICKED MARKER IS NULL"); + } + if (isListBottomSheetExpanded()) { + // Back should first hide the bottom sheet if it is expanded + hideBottomSheet(); + } else if (isDetailsBottomSheetVisible()) { + hideBottomDetailsSheet(); + } + return true; + } + + @Override + public boolean longPressHelper(GeoPoint p) { + return false; + } + }); + @Inject LocationServiceManager locationManager; @Inject @@ -217,7 +235,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment private Runnable searchRunnable; private static final long SCROLL_DELAY = 800; // Delay for debounce of onscroll, in milliseconds. - private List updatedPlacesList; private LatLng updatedLatLng; private boolean searchable; @@ -308,10 +325,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment * WLM URL */ public static final String WLM_URL = "https://commons.wikimedia.org/wiki/Commons:Mobile_app/Contributing_to_WLM_using_the_app"; - /** - * Saves response of list of places for the first time - */ - private List places = new ArrayList<>(); @NonNull public static NearbyParentFragment newInstance() { @@ -327,7 +340,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment view = binding.getRoot(); initNetworkBroadCastReceiver(); - presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao); + presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, nearbyController); progressDialog = new ProgressDialog(getActivity()); progressDialog.setCancelable(false); progressDialog.setMessage("Saving in progress..."); @@ -452,28 +465,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment binding.map.getOverlays().add(scaleBarOverlay); binding.map.getZoomController().setVisibility(Visibility.NEVER); binding.map.getController().setZoom(ZOOM_LEVEL); - binding.map.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - clickedMarker.closeInfoWindow(); - } else { - Timber.e("CLICKED MARKER IS NULL"); - } - if (isListBottomSheetExpanded()) { - // Back should first hide the bottom sheet if it is expanded - hideBottomSheet(); - } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); - } - return true; - } - - @Override - public boolean longPressHelper(GeoPoint p) { - return false; - } - })); + binding.map.getOverlays().add(mapEventsOverlay); binding.map.addMapListener(new MapListener() { @Override @@ -605,8 +597,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return Unit.INSTANCE; }, (place, isBookmarked) -> { - updateMarker(isBookmarked, place, null); - binding.map.invalidate(); + presenter.toggleBookmarkedStatus(place); return Unit.INSTANCE; }, commonPlaceClickActions, @@ -670,19 +661,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment registerNetworkReceiver(); if (isResumed() && ((MainActivity) getActivity()).activeFragment == ActiveFragment.NEARBY) { if (locationPermissionsHelper.checkLocationPermission(getActivity())) { - if (lastFocusLocation == null && lastKnownLocation == null) { - locationPermissionGranted(); - } else{ - if (updatedPlacesList != null) { - if (!updatedPlacesList.isEmpty()) { - loadPlacesDataAsync(updatedPlacesList, updatedLatLng); - } else { - updateMapMarkers(updatedPlacesList, getLastMapFocus(), false); - } - }else { - locationPermissionGranted(); - } - } + locationPermissionGranted(); } else { startMapWithoutPermission(); } @@ -973,7 +952,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void updateListFragment(final List placeList) { - places = placeList; + adapter.clear(); adapter.setItems(placeList); binding.bottomSheetNearby.noResultsMessage.setVisibility( placeList.isEmpty() ? View.VISIBLE : View.GONE); @@ -1367,13 +1346,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment ? getTextBetweenParentheses( updatedPlace.getLongDescription()) : updatedPlace.getLongDescription()); marker.showInfoWindow(); - for (int i = 0; i < updatedPlacesList.size(); i++) { - Place pl = updatedPlacesList.get(i); - if (pl.location == updatedPlace.location) { - updatedPlacesList.set(i, updatedPlace); - savePlaceToDatabase(place); - } - } + presenter.handlePinClicked(updatedPlace); + savePlaceToDatabase(place); Drawable icon = ContextCompat.getDrawable(getContext(), getIconFor(updatedPlace, isBookMarked)); marker.setIcon(icon); @@ -1412,12 +1386,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment setProgressBarVisibility(false); presenter.lockUnlockNearby(false); } else { - updateMapMarkers(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng, - true); + updateMapMarkers(nearbyPlacesInfo.placeList, searchLatLng, true); lastFocusLocation = searchLatLng; lastMapFocus = new GeoPoint(searchLatLng.getLatitude(), searchLatLng.getLongitude()); - loadPlacesDataAsync(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng); } }, throwable -> { @@ -1457,12 +1429,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment // curLatLng is used to calculate distance from the current location to the place // and distance is later on populated to the place - updateMapMarkers(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng, - false); + updateMapMarkers(nearbyPlacesInfo.placeList, searchLatLng, false); lastMapFocus = new GeoPoint(searchLatLng.getLatitude(), searchLatLng.getLongitude()); stopQuery(); - loadPlacesDataAsync(nearbyPlacesInfo.placeList, nearbyPlacesInfo.currentLatLng); } }, throwable -> { @@ -1475,167 +1445,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment })); } - public void loadPlacesDataAsync(List placeList, LatLng curLatLng) { - List places = new ArrayList<>(placeList); - - // Instead of loading all pins in a single SPARQL query, we query in batches. - // This variable controls the number of pins queried per batch. - int batchSize = 3; - - updatedLatLng = curLatLng; - updatedPlacesList = new ArrayList<>(placeList); - - // Sorts the places by distance to ensure the nearest pins are ready for the user as soon - // as possible. - if (VERSION.SDK_INT >= VERSION_CODES.N) { - Collections.sort(places, - Comparator.comparingDouble(place -> place.getDistanceInDouble(getMapFocus()))); - } - stopQuery = false; - processBatchesSequentially(places, batchSize, updatedPlacesList, curLatLng, 0); - } - - /** - * Processes a list of places in batches sequentially. This method handles the asynchronous - * processing of places, updating the map markers and updates the list of updated places accordingly. - * - * @param places The list of Place objects to be processed. - * @param batchSize The size of each batch to be processed. - * @param updatedPlaceList The list of Place objects to be updated. - * @param curLatLng The current location of the user. - * @param startIndex The starting index for the current batch. - */ - @SuppressLint("CheckResult") - private void processBatchesSequentially(List places, int batchSize, - List updatedPlaceList, LatLng curLatLng, int startIndex) { - if (startIndex >= places.size() || stopQuery) { - return; - } - - int endIndex = Math.min(startIndex + batchSize, places.size()); - List batch = places.subList(startIndex, endIndex); - for (int i = 0; i < batch.size(); i++) { - if (i == batch.size() - 1 && batch.get(i).name != "") { - processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng, - endIndex + batchSize); - return; - } - if (batch.get(i).name == "") { - if (i == 0) { - break; - } - processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng, - endIndex + i); - return; - } - } - - Disposable disposable = processBatch(batch, updatedPlaceList) - .subscribe(p -> { - if (stopQuery) { - return; - } - if (!p.isEmpty() && p != updatedPlaceList) { - synchronized (updatedPlaceList) { - updatedPlaceList.clear(); - updatedPlaceList.addAll((Collection) p); - } - } - updateMapMarkers(new ArrayList<>(updatedPlaceList), curLatLng, false); - processBatchesSequentially(places, batchSize, updatedPlaceList, curLatLng, endIndex); - }, throwable -> { - Timber.e(throwable); - showErrorMessage(getString(R.string.error_fetching_nearby_places) + throwable.getLocalizedMessage()); - setFilterState(); - }); - - compositeDisposable.add(disposable); - } - - /** - * Processes a batch of places, updating the provided place list with fetched or updated data. - * This method handles the asynchronous fetching and updating of places from the repository. - * - * @param batch The batch of Place objects to be processed. - * @param placeList The list of Place objects to be updated. - * @return An Observable emitting the updated list of Place objects. - */ - private Observable> processBatch(List batch, List placeList) { - List toBeProcessed = new ArrayList<>(); - - List> placeObservables = new ArrayList<>(); - - for (Place place : batch) { - Observable placeObservable = Observable - .fromCallable(() -> { - Place fetchedPlace = placesRepository.fetchPlace(place.entityID); - return fetchedPlace != null ? fetchedPlace : place; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(placeData -> { - if (placeData.equals(place)) { - toBeProcessed.add(place); - } else { - for (int i = 0; i < placeList.size(); i++) { - Place pl = placeList.get(i); - if (pl.location.equals(place.location)) { - placeList.set(i, placeData); - break; - } - } - } - }); - - placeObservables.add(placeObservable); - } - - return Observable.zip(placeObservables, objects -> toBeProcessed) - .flatMap(processedList -> { - if (processedList.isEmpty()) { - return Observable.just(placeList); - } - return Observable.fromCallable(() -> nearbyController.getPlaces(processedList)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .map(places -> { - if (stopQuery) { - return Collections.emptyList(); - } - if (places == null || places.isEmpty()) { - return Collections.emptyList(); - } else { - List updatedPlaceList = new ArrayList<>(placeList); - for (Place place : places) { - for (Place foundPlace : placeList) { - if (place.siteLinks.getWikidataLink() - .equals(foundPlace.siteLinks.getWikidataLink())) { - place.location = foundPlace.location; - place.distance = foundPlace.distance; - place.setMonument(foundPlace.isMonument()); - int index = updatedPlaceList.indexOf(foundPlace); - if (index != -1) { - updatedPlaceList.set(index, place); - savePlaceToDatabase(place); - } - break; - } - } - } - return updatedPlaceList; - } - }) - .onErrorReturn(throwable -> { - Timber.e(throwable); - showErrorMessage(getString(R.string.error_fetching_nearby_places) + " " - + throwable.getLocalizedMessage()); - setFilterState(); - return Collections.emptyList(); - }); - }); - } - - private void savePlaceToDatabase(Place place) { + public void savePlaceToDatabase(Place place) { compositeDisposable.add(placesRepository .save(place) .subscribeOn(Schedulers.io()) @@ -1661,8 +1471,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment */ private void updateMapMarkers(final List nearbyPlaces, final LatLng curLatLng, final boolean shouldUpdateSelectedMarker) { - presenter.updateMapMarkers(nearbyPlaces, curLatLng, shouldUpdateSelectedMarker); - setFilterState(); + presenter.updateMapMarkers(nearbyPlaces, curLatLng, + LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner())); } @@ -1899,13 +1709,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } } - @Override - public void updateMapMarkers(final List BaseMarkers) { - if (binding.map != null) { - presenter.updateMapMarkersToController(BaseMarkers); - } - } - @Override public void filterOutAllMarkers() { clearAllMarkers(); @@ -1925,8 +1728,11 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment final boolean displayExists = false; final boolean displayNeedsPhoto= false; final boolean displayWlm = false; - // Remove the previous markers before updating them - clearAllMarkers(); + if (selectedLabels == null || selectedLabels.size() == 0) { + replaceMarkerOverlays(NearbyController.markerLabelList); + return; + } + final ArrayList placeGroupsToShow = new ArrayList<>(); for (final MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) { final Place place = markerPlaceGroup.getPlace(); // When label filter is engaged @@ -1967,19 +1773,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } if (shouldUpdateMarker) { - updateMarker(markerPlaceGroup.getIsBookmarked(), place, - NearbyController.currentLocation); + placeGroupsToShow.add( + new MarkerPlaceGroup(markerPlaceGroup.getIsBookmarked(), place) + ); } } - if (selectedLabels == null || selectedLabels.size() == 0) { - ArrayList markerArrayList = new ArrayList<>(); - for (final MarkerPlaceGroup markerPlaceGroup : NearbyController.markerLabelList) { - BaseMarker nearbyBaseMarker = new BaseMarker(); - nearbyBaseMarker.setPlace(markerPlaceGroup.getPlace()); - markerArrayList.add(nearbyBaseMarker); - } - addMarkersToMap(markerArrayList); - } + replaceMarkerOverlays(placeGroupsToShow); } @Override @@ -1987,18 +1786,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return binding.map == null ? null : getMapFocus(); } - /** - * Sets marker icon according to marker status. Sets title and distance. - * - * @param isBookmarked true if place is bookmarked - * @param place - * @param currentLatLng current location - */ - public void updateMarker(final boolean isBookmarked, final Place place, - @Nullable final LatLng currentLatLng) { - addMarkerToMap(place, isBookmarked); - } - /** * Highlights nearest place when user clicks on home nearby banner * @@ -2052,13 +1839,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment ); } - /** - * Adds a marker representing a place to the map with optional bookmark icon. - * - * @param place The Place object containing information about the location. - * @param isBookMarked A Boolean flag indicating whether the place is bookmarked or not. - */ - private void addMarkerToMap(Place place, Boolean isBookMarked) { + public Marker convertToMarker(Place place, Boolean isBookMarked) { Drawable icon = ContextCompat.getDrawable(getContext(), getIconFor(place, isBookMarked)); GeoPoint point = new GeoPoint(place.location.getLatitude(), place.location.getLongitude()); Marker marker = new Marker(binding.map); @@ -2094,22 +1875,27 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); return true; }); - binding.map.getOverlays().add(marker); + return marker; } /** * Adds multiple markers representing places to the map and handles item gestures. * - * @param nearbyBaseMarkers The list of Place objects containing information about the - * locations. + * @param markerPlaceGroups The list of marker place groups containing the places and + * their bookmarked status */ - private void addMarkersToMap(List nearbyBaseMarkers) { - - for(int i = 0; i< nearbyBaseMarkers.size(); i++){ - addMarkerToMap(nearbyBaseMarkers.get(i).getPlace(), false); + @Override + public void replaceMarkerOverlays(final List markerPlaceGroups) { + ArrayList newMarkers = new ArrayList<>(markerPlaceGroups.size()); + for (MarkerPlaceGroup markerPlaceGroup : markerPlaceGroups) { + newMarkers.add( + convertToMarker(markerPlaceGroup.getPlace(), markerPlaceGroup.getIsBookmarked())); } + clearAllMarkers(); + binding.map.getOverlays().addAll(newMarkers); } + /** * Extracts text between the first occurrence of '(' and its corresponding ')' in the input * string. @@ -2400,7 +2186,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment binding.map.invalidate(); GeoPoint geoPoint = mapCenter; if (geoPoint != null) { - List overlays = binding.map.getOverlays(); ScaleDiskOverlay diskOverlay = new ScaleDiskOverlay(this.getContext(), geoPoint, 2000, UnitOfMeasure.foot); @@ -2434,28 +2219,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment scaleBarOverlay.setBackgroundPaint(barPaint); scaleBarOverlay.enableScaleBar(); binding.map.getOverlays().add(scaleBarOverlay); - binding.map.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - clickedMarker.closeInfoWindow(); - } else { - Timber.e("CLICKED MARKER IS NULL"); - } - if (isListBottomSheetExpanded()) { - // Back should first hide the bottom sheet if it is expanded - hideBottomSheet(); - } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); - } - return true; - } - - @Override - public boolean longPressHelper(GeoPoint p) { - return false; - } - })); + binding.map.getOverlays().add(mapEventsOverlay); binding.map.setMultiTouchControls(true); } @@ -2510,21 +2274,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void onBottomSheetItemClick(@Nullable View view, int position) { BottomSheetItem item = dataList.get(position); - boolean isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); switch (item.getImageResourceId()) { case R.drawable.ic_round_star_border_24px: - bookmarkLocationDao.updateBookmarkLocation(selectedPlace); + presenter.toggleBookmarkedStatus(selectedPlace); updateBookmarkButtonImage(selectedPlace); - isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); - updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation()); - binding.map.invalidate(); break; case R.drawable.ic_round_star_filled_24px: - bookmarkLocationDao.updateBookmarkLocation(selectedPlace); + presenter.toggleBookmarkedStatus(selectedPlace); updateBookmarkButtonImage(selectedPlace); - isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); - updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation()); - binding.map.invalidate(); break; case R.drawable.ic_directions_black_24dp: Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation()); diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java deleted file mode 100644 index 00a491e68..000000000 --- a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.java +++ /dev/null @@ -1,368 +0,0 @@ -package fr.free.nrw.commons.nearby.presenter; - -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.CUSTOM_QUERY; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA; -import static fr.free.nrw.commons.nearby.CheckBoxTriStates.CHECKED; -import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNCHECKED; -import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNKNOWN; -import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; - -import android.location.Location; -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; -import fr.free.nrw.commons.location.LocationUpdateListener; -import fr.free.nrw.commons.nearby.CheckBoxTriStates; -import fr.free.nrw.commons.nearby.Label; -import fr.free.nrw.commons.nearby.MarkerPlaceGroup; -import fr.free.nrw.commons.nearby.NearbyController; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; -import fr.free.nrw.commons.utils.LocationUtils; -import fr.free.nrw.commons.wikidata.WikidataEditListener; -import java.lang.reflect.Proxy; -import java.util.List; -import timber.log.Timber; - -public class NearbyParentFragmentPresenter - implements NearbyParentFragmentContract.UserActions, - WikidataEditListener.WikidataP18EditListener, - LocationUpdateListener { - - private boolean isNearbyLocked; - private LatLng currentLatLng; - - private boolean placesLoadedOnce; - - BookmarkLocationsDao bookmarkLocationDao; - - private @Nullable String customQuery; - - private static final NearbyParentFragmentContract.View DUMMY = (NearbyParentFragmentContract.View) Proxy.newProxyInstance( - NearbyParentFragmentContract.View.class.getClassLoader(), - new Class[]{NearbyParentFragmentContract.View.class}, (proxy, method, args) -> { - if (method.getName().equals("onMyEvent")) { - return null; - } else if (String.class == method.getReturnType()) { - return ""; - } else if (Integer.class == method.getReturnType()) { - return Integer.valueOf(0); - } else if (int.class == method.getReturnType()) { - return 0; - } else if (Boolean.class == method.getReturnType()) { - return Boolean.FALSE; - } else if (boolean.class == method.getReturnType()) { - return false; - } else { - return null; - } - } - ); - private NearbyParentFragmentContract.View nearbyParentFragmentView = DUMMY; - - - public NearbyParentFragmentPresenter(BookmarkLocationsDao bookmarkLocationDao) { - this.bookmarkLocationDao = bookmarkLocationDao; - } - - @Override - public void attachView(NearbyParentFragmentContract.View view) { - this.nearbyParentFragmentView = view; - } - - @Override - public void detachView() { - this.nearbyParentFragmentView = DUMMY; - } - - @Override - public void removeNearbyPreferences(JsonKvStore applicationKvStore) { - Timber.d("Remove place objects"); - applicationKvStore.remove(PLACE_OBJECT); - } - - public void initializeMapOperations() { - lockUnlockNearby(false); - updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); - nearbyParentFragmentView.setCheckBoxAction(); - } - - /** - * Sets click listeners of FABs, and 2 bottom sheets - */ - @Override - public void setActionListeners(JsonKvStore applicationKvStore) { - nearbyParentFragmentView.setFABPlusAction(v -> { - if (applicationKvStore.getBoolean("login_skipped", false)) { - // prompt the user to login - nearbyParentFragmentView.displayLoginSkippedWarning(); - } else { - nearbyParentFragmentView.animateFABs(); - } - }); - - nearbyParentFragmentView.setFABRecenterAction(v -> { - nearbyParentFragmentView.recenterMap(currentLatLng); - }); - - } - - @Override - public boolean backButtonClicked() { - if (nearbyParentFragmentView.isAdvancedQueryFragmentVisible()) { - nearbyParentFragmentView.showHideAdvancedQueryFragment(false); - return true; - } else if (nearbyParentFragmentView.isListBottomSheetExpanded()) { - // Back should first hide the bottom sheet if it is expanded - nearbyParentFragmentView.listOptionMenuItemClicked(); - return true; - } else if (nearbyParentFragmentView.isDetailsBottomSheetVisible()) { - nearbyParentFragmentView.setBottomSheetDetailsSmaller(); - return true; - } - return false; - } - - public void markerUnselected() { - nearbyParentFragmentView.hideBottomSheet(); - } - - /** - * Nearby updates takes time, since they are network operations. During update time, we don't - * want to get any other calls from user. So locking nearby. - * - * @param isNearbyLocked true means lock, false means unlock - */ - @Override - public void lockUnlockNearby(boolean isNearbyLocked) { - this.isNearbyLocked = isNearbyLocked; - if (isNearbyLocked) { - nearbyParentFragmentView.disableFABRecenter(); - } else { - nearbyParentFragmentView.enableFABRecenter(); - } - } - - /** - * This method should be the single point to update Map and List. Triggered by location changes - * - * @param locationChangeType defines if location changed significantly or slightly - */ - @Override - public void updateMapAndList(LocationChangeType locationChangeType) { - Timber.d("Presenter updates map and list"); - if (isNearbyLocked) { - Timber.d("Nearby is locked, so updateMapAndList returns"); - return; - } - - if (!nearbyParentFragmentView.isNetworkConnectionEstablished()) { - Timber.d("Network connection is not established"); - return; - } - - LatLng lastLocation = nearbyParentFragmentView.getLastMapFocus(); - if (nearbyParentFragmentView.getMapCenter() != null) { - currentLatLng = nearbyParentFragmentView.getMapCenter(); - } else { - currentLatLng = lastLocation; - } - if (currentLatLng == null) { - Timber.d("Skipping update of nearby places as location is unavailable"); - return; - } - - /** - * Significant changed - Markers and current location will be updated together - * Slightly changed - Only current position marker will be updated - */ - if (locationChangeType.equals(CUSTOM_QUERY)) { - Timber.d("ADVANCED_QUERY_SEARCH"); - lockUnlockNearby(true); - nearbyParentFragmentView.setProgressBarVisibility(true); - LatLng updatedLocationByUser = LocationUtils.deriveUpdatedLocationFromSearchQuery( - customQuery); - if (updatedLocationByUser == null) { - updatedLocationByUser = lastLocation; - } - nearbyParentFragmentView.populatePlaces(updatedLocationByUser, customQuery); - } else if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED) - || locationChangeType.equals(MAP_UPDATED)) { - lockUnlockNearby(true); - nearbyParentFragmentView.setProgressBarVisibility(true); - nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapCenter()); - } else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) { - Timber.d("SEARCH_CUSTOM_AREA"); - lockUnlockNearby(true); - nearbyParentFragmentView.setProgressBarVisibility(true); - nearbyParentFragmentView.populatePlaces(nearbyParentFragmentView.getMapFocus()); - } else { // Means location changed slightly, ie user is walking or driving. - Timber.d("Means location changed slightly"); - } - } - - /** - * Populates places for custom location, should be used for finding nearby places around a - * location where you are not at. - * - * @param nearbyPlaces This variable has placeToCenter list information and distances. - */ - public void updateMapMarkers(List nearbyPlaces, LatLng currentLatLng, - boolean shouldTrackPosition) { - if (null != nearbyParentFragmentView) { - nearbyParentFragmentView.clearAllMarkers(); - List baseMarkers = NearbyController - .loadAttractionsFromLocationToBaseMarkerOptions(currentLatLng, - // Curlatlang will be used to calculate distances - nearbyPlaces); - nearbyParentFragmentView.updateMapMarkers(baseMarkers); - lockUnlockNearby(false); // So that new location updates wont come - nearbyParentFragmentView.setProgressBarVisibility(false); - nearbyParentFragmentView.updateListFragment(nearbyPlaces); - } - } - - /** - * Some centering task may need to wait for map to be ready, if they are requested before map is - * ready. So we will remember it when the map is ready - */ - private void handleCenteringTaskIfAny() { - if (!placesLoadedOnce) { - placesLoadedOnce = true; - nearbyParentFragmentView.centerMapToPlace(null); - } - } - - @Override - public void onWikidataEditSuccessful() { - updateMapAndList(MAP_UPDATED); - } - - @Override - public void onLocationChangedSignificantly(LatLng latLng) { - Timber.d("Location significantly changed"); - updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); - } - - @Override - public void onLocationChangedSlightly(LatLng latLng) { - Timber.d("Location significantly changed"); - updateMapAndList(LOCATION_SLIGHTLY_CHANGED); - } - - @Override - public void onLocationChangedMedium(LatLng latLng) { - Timber.d("Location changed medium"); - } - - @Override - public void filterByMarkerType(List