Compare commits

...

734 commits
v4.2.0 ... main

Author SHA1 Message Date
Ritika Pahwa
63f621cb56
Update contributor list in README.md
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-26 14:10:08 +05:30
Eric Pan
e81f916626
Part of issue #5996: Fix IDE warnings in ContributionsListFragment (#6542)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Part of issue #5996: Fix IDE warnings in ContributionsListFragment (naming, null-safety, deprecations)

* Part of issue #5996: Clean final IDE warnings (parameter name alignment, remove redundant toggle)

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-26 08:30:17 +09:00
Ted
28fa7b1a20
Display specific, user-friendly error message when upload categories search API call returns an error (#6540)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Make OkHttpConnectionFactory raise MwIOException when a non-suppressed API call returns an error

* Add AlertDialog displaying specific error message when categories search API call returns an error

* Add test for error alert dialog to UploadCategoriesFragment unit tests

* Add error handling when API call fails to CategoriesPresenter.onAttachViewWithMedia
2025-10-25 23:24:39 +09:00
translatewiki.net
aae9d4a387
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-10-23 14:02:44 +02:00
Amir E. Aharoni
6873f63cf8
Remove an unused element from layout/fragment_media_detail.xml (#6536)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
I noticed this issue years ago because it used a hard-to-understand
string that needed better documentation (see #688). I forgot it,
but recently, I started to explore the app much more deeply and
came back to it.

It looks like this string is only used in this layout element,
but the element itself is not used anywhere. It usage appears to
have been removed in #634.
2025-10-23 09:41:32 +09:00
Ritika Pahwa
2d0255e5fb
Disable hardware acceleration and keyboard animation (#6535)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Disable hardware acceleration and keyboard animation

This is a temporary commit to see if it fixes issue #3364

* Remove unused import

* Bump up version code and modify version name

* Modify handleKeyboardInsets to handle insets correctly

* Refactor handleKeyboardInsets()

* Refactor handleKeyboardInsets()

* Fix inset in login activity
2025-10-22 16:44:06 +05:30
translatewiki.net
32ae406cca
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-22 10:23:13 +02:00
translatewiki.net
3e04a1f036
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-10-20 14:02:26 +02:00
translatewiki.net
6487191394
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-20 06:29:31 +02:00
Amir E. Aharoni
beaf211f39
Fix three Java lint errors (#6531)
* Add braces to conditions.
* Remove an unnecessary semicolon.
* Remove an unnecessary constructor.

This fixes all the Java lint errors of these types.
2025-10-20 10:39:05 +09:00
Amir E. Aharoni
3549789cdf
Delete outdated localization files (#6533)
Strings files for he, id, yi were replaced with iw, in, and ji in 2016.
Those files cause build warnings, and they aren't used,
so it's OK to just remove them.
2025-10-20 10:18:13 +09:00
VoidRaven
def33552f9
Test/2819 add campaigns api tests (#6529)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* test:add mock JSON resource files for campaigns API responses

* feat:make campaign model fields mutable to allow for correct deserialization

* test:implement unit tests for fetching campaigns and fix DTO mocking logic

* test:implement unit tests for fetching campaigns and fix DTO mocking logic

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-19 22:58:14 +09:00
Amir E. Aharoni
3a55583460
Disable linting for icon hiding code in preferences (#6519)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-10-18 13:51:46 +09:00
Amir E. Aharoni
717a855149
Fix lint warning about Timber (#6521)
Change trivial string formatting and function calls
for Timber logging.

This resolves all the lint warnings in the
Android/Lint/Correctness/Messages group.
2025-10-18 13:45:35 +09:00
Amir E. Aharoni
29b6d0f8fe
Replace Switch with SwitchMaterial (#6522)
Lint recommended replacing Switch with SwitchMaterial.
This was a very simple replacement, and I tested it in
the custom selector, where it is used, and it works as
it worked previously.
2025-10-18 13:43:37 +09:00
Xinyu Yang
b5b5d8a8e4
I didn’t look at the code carefully before and directly modified the contents of strings.xml. After reviewing it, I found that the issue was actually in WikidataItemDetailsActivity.kt, where the wrong label was selected. After correcting this, there should no longer be any problems. (#6524)
Co-authored-by: frank <u7896083@anu.edu.au>
2025-10-18 11:31:49 +09:00
Aneesh Hebbar
714e5f8a4b
fix(i18n): Correct capitalization for 'Sending thanks' status messages (#6515) (#6518)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-17 11:40:48 +09:00
Dmitriy
7d96e94689
Fix crash for bookmarks without descriptions/thumbnails (#6488)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-16 19:56:41 +09:00
Xinyu Yang
7a865df909
fix the bug of map reset (#6509)
Co-authored-by: Chengxu Yang <u7954427@anu.edu.au>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-16 19:34:17 +09:00
Amir E. Aharoni
864884e7b2
Update alternative texts for the welcome screen (#6512)
* Update alternative texts for the welcome screen

I've also updated their documentation for translators (qq)
in transltaewiki itself.

Resolves #689.

* Fixed typo

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-16 19:22:02 +09:00
Amir E. Aharoni
1ecaf09f21
Remove wikimedia_licenses.xml and files that use it (#6513)
This file doesn't seem to be used.

Resolves #6504.
2025-10-16 19:20:18 +09:00
Amir E. Aharoni
1ff2a28326
Replace tab with space in an XML layout file (#6514)
I was working on this file recently, and Android Studio
showed a warning that it has tabs instead of spaces,
so here's it's fixed.

A minor thing, but prevents distractions.
2025-10-16 19:19:30 +09:00
Amir E. Aharoni
b48905a153
Change all parameters to numbered parameters (#6516)
This will solve these errors:
"Format string is not a valid format string so it should not be passed to String.format"
2025-10-16 19:19:02 +09:00
Amir E. Aharoni
09c8d987e1
Simplify android:gravity in two layouts (#6506)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
The "Inspect Code" linter complained that these two files
had Right-to-left text compatibility issues. I couldn't
really see any problems neither in English nor in Hebrew,
but the linter's suggestion still made sense, so I cleaned it up.

This fixes all the errors of the type
"Android Lint: Internationalization / Right-to-left text compatibility issues".
2025-10-15 13:52:05 +09:00
Amir E. Aharoni
2e52adbef8
Clean up empty tags in XML files (#6505)
This resolves all the "XML empty tags" lint errors.
2025-10-15 07:37:17 +09:00
Amir E. Aharoni
61c9de6fcc
Add a missing comma to a message (#6477)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
There should be a comma before "etc." in a list,
and there already is a comma before "etc."
in the string depicts_search_text_hint, so it should be
in this string to for consistency.
2025-10-14 21:42:09 +09:00
Amir E. Aharoni
41d95814c9
Remove the string SingleWebViewActivity (#6494)
Resolves issue #6492.

Remove the title of a web activity and the accompanying
string resource.

This was not a real translatable message, but something that
looks more like an identifier that shouldn't be translated.
As far as I can tell, it's not seen anywhere in the interface
because the actual title is set in the code that calls it.
2025-10-14 21:41:31 +09:00
Amir E. Aharoni
c4cb65fc3c
Improve the grammar of messages about GPX and KML files (#6497)
Add articles, fix capitalization, add ellipses.
2025-10-14 21:40:38 +09:00
Amir E. Aharoni
a1c5974e93
Fix depicts and categories pickers for RTL languages (#6503)
This fixes the layouts to work in both left to right (LTR)
and right to left (RTL) languages.

Also replace two hard-coded strings in the depicts picker
with proper string resources.

Fixes #6502.
2025-10-14 17:54:54 +09:00
Amir E. Aharoni
0c244f369c
Replace android.R.string.* with R.string (#6499)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
All these messages are not really necessary because
the app has its own localizations, and android.R.string
sometimes doesn't display the localized string.

Resolves #6470.
2025-10-13 21:54:32 +09:00
translatewiki.net
b6014b017c
Localisation updates from https://translatewiki.net. 2025-10-13 14:03:10 +02:00
Amir E. Aharoni
91ea4a6e7b
Rephrase images_featured_explanation (#6484)
Make the text of the panel consistent with its title.
The title is "Featured images", so the text should
use the same term.

Also move this resource next to the title, to make it easier
for the translators.
2025-10-13 18:43:59 +09:00
Amir E. Aharoni
1e51c4c5d0
Remove the arrow next to "Add location" (#6491)
This resolves #6489 using the "remove arrow" method.
2025-10-13 18:13:22 +09:00
Amir E. Aharoni
fbd28a0564
Change capitalization of "Add Location" (#6493)
This makes it consistent with "Edit Location" and
"Edit Image", which are used in the same screen.
2025-10-13 18:09:43 +09:00
Amir E. Aharoni
d0965206cd
Cleanup whitespace in the custom_selector_info_text2 string (#6496)
In the current state, it appears confusingly on translatewiki,
with a space in the beginning of a line.

This patch changes it to just two linebreaks.
2025-10-13 18:05:26 +09:00
Amir E. Aharoni
bb330c1771
Change "actioned" to "handled" in translatable strings (#6498)
"actioned" is not so standard in English as a verb.

"handled" sounds more appropriate.
2025-10-13 17:57:27 +09:00
Rohit Verma
14d6c80241
fix: remove location manager and update listener on pause (#6483)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-11 23:33:44 +09:00
VoidRaven
4c621364c9
Fix/6404 app crashes theme change multi upload (#6429)
* Prevent IndexOutOfBoundsException in setUploadMediaDetails by validating index and list size (#6404)

* fixed UninitializedPropertyAccessException by safely initializing and accessing imageAdapter (#6404)

* fixed indexOutOfBoundsException by safely handling saved state and index in onViewCreated (#6404)

* resolve Unresolved reference by replacing setImageToBeUploaded with direct field assignments (#6404)

* Fix test compilation by removing obsolete testSetImageToBeUploaded and adding tha testInitializeFragmentWithUploadItem (#6404)

* Fix test compilation by removing testInitializeFragmentWithUploadItem with unresolved onImageProcessed (#6404)

* fix: test failures in UploadMediaDetailFragmentUnitTest by removing obsolete tests and initializing defaultKvStore (#6404)

* Fixed all the typos

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-11 21:38:07 +09:00
Amir E. Aharoni
2a9d5db51e
Consistent spelling of "screenshots" in the issue template (#6481)
"Screenshot" is written as one word without a hyphen everywhere
else in this app's code, and generally in the English language.
2025-10-11 21:33:54 +09:00
Amir E. Aharoni
b8d340fbe8
Rephrase the string copy_image_caption_description (#6472)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
I was going over all the strings and documenting them (see #6457),
and I had a very hard time understand what this message does.
I read the code and finally figured it out. I added qq documentation
for it so now it's clearer, but I also think that the English
message can be clearer:
* "subsequent" changed to "the next" - shorter, easier word.
* "media" changed to "item" - "media" could mean a lot of things,
  and "item" is clearer in this context.
2025-10-11 14:54:40 +09:00
Amir E. Aharoni
dd1814c793
Change filename to username in toasts about sending thanks (#6467)
This fixes #6466.

Also fix the messages themselves a bit:
* Removed "successfully" from the success message. This word
  is usually redundant, because the message already says that it
  was done. (In MediaWiki, there's a specific convention about it:
  https://www.mediawiki.org/wiki/Help:System_message#Avoid_jargon_and_slanghttps://www.mediawiki.org/wiki/Help:System_message#Avoid_jargon_and_slang
)
* Added a missing preposition to the failure message.

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-11 14:53:57 +09:00
VoidRaven
adb6181e9f
fix: map crash (fixes #6432) (#6479)
* fix: map crash (fixes #6432)

* Fix typos in comments in ExploreMapFragment.kt

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-11 14:38:07 +09:00
Jason-Whitmore
0a4b179db5
Fixes Issue 6436: getString(...) must not be null (#6474)
* DatabaseUtils.kt: change getString() to allow null returns

Before this change, a call to getString() would assume that the specified column
name actually exists. A bad String input would cause a null value to be returned
to getString(), which would then throw a NPE because getString() can only return
non null Strings.

This change expands the getString() method to check if the column name exists.
If it does exist, the String is retrieved normally. Else, a null value is
returned. The method signature is changed to allow null return values.

* *Dao.kt: change some usages of getString()

Before this change, the getString() method in DatabaseUtils.kt was changed
to allow returning a null value upon method failure. All usages of getString()
were not changed.

This change updates all usages of getString() which require non null return
values. If null is returned, an empty string is used instead.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-11 14:12:19 +09:00
Amir E. Aharoni
e78db7fa08
Remove the unused message "statistics" (#6478)
Its usage was removed from the file
app/src/main/res/layout/fragment_achievements.xml in a8387f0,
but the message remained in the strings file.

Resolves #6456.
2025-10-11 13:58:19 +09:00
Amir E. Aharoni
7be615bacb
Fix comma splice in a translatable string (#6465)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-10 23:46:17 +09:00
Amir E. Aharoni
95d58023c7
Fix punctuation in the string download_failed_we_cannot_download_the_file_without_storage_permission (#6473)
The double exclamation point is really unnecessary.
2025-10-10 23:26:30 +09:00
Amir E. Aharoni
7b8fbc239b
Remove commented out code and associated strings (#6475)
As I was documenting undocumented strings (see #6457), I noticed
that two messages are only used once in a few lines of code that
were commented out in 2023.

To clean up the messages, I am removing them from the strings
list and deleting the commented-out code.
2025-10-10 23:25:46 +09:00
Amir E. Aharoni
30d1107cef
Change "wikicode" to "wikitext" in a message (#6476)
The usual English term is "wikitext". "Wikicode" is used in French
and perhaps some other language, but English uses "wikitext".
2025-10-10 23:05:19 +09:00
Amir E. Aharoni
fe16c44caa
Change Android "OK" string to app's own localization (#6471)
Addressed one instance described in #6470.
2025-10-10 22:40:00 +10:00
translatewiki.net
4ed9ad5085
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-09 14:02:46 +02:00
Amir E. Aharoni
755d8311dc
Make some hardcoded strings translatable (#6459)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-10-09 10:07:19 +09:00
Amir E. Aharoni
b6457cc6b9
Rename an identifier with a non-ASCII character (#6460)
Android Studio reported that there's an identifier
with a non-ASCII letter. It was nearbyFılterStateInstance,
with a Turkish dotless i. I renamed to ASCII dotted i.

This brings the number of Internationalization
issues in Inspect Code to zero.
2025-10-09 10:06:23 +09:00
Rohit Verma
2d51a7ce9a
chore: upgrade native libraries for 16KB page size compatibility (#6445)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* chore: bump maplibre-native for 16KB page size compatibility

Also, bump AGP

* chore: bump freso for 16KB page size compatibility and fix build issues

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
2025-10-08 23:25:20 +09:00
Amir E. Aharoni
0ade0705e2
Remove leading space from English messages (#6449)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-10-06 08:23:00 +09:00
Ben
6bc25ccd9b
Fix kotling warnings for Image.kt and Folder.kt (part of Issue #5996) (#6441)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* added hash code to folder.kt and image.kt to pair with equals

* fixed deprecation in readParcelable function

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-10-05 22:15:39 +09:00
Amir E. Aharoni
ed7007fc8c
Change a hardcoded string to a translatable message (#6444)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
Follow-up to #6443. Noticed this one after that pull request
was already merged.
2025-10-04 14:10:00 +09:00
Amir E. Aharoni
71ad6a2ce5
Change hardcoded preferences strings to translatable messages (#6443) 2025-10-04 10:43:54 +09:00
Amir E. Aharoni
e9a1af0f52
Change hardcoded strings in the language search dialog to messages (#6440)
Another comment: While working on this, I also noticed that
"Recent Searches" is hardcoded in the XML file, and
I'm not sure where does it actually appear. I fixed it, too,
but perhaps it can be completely removed.

Fixes #6439.
2025-10-04 10:43:10 +09:00
translatewiki.net
10c384ffa7
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-10-02 14:02:46 +02:00
VoidRaven
4e51977fb6
Fix Location Permission Prompt on "Uploaded via Mobile" Tab (#6425)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Added location prompt at Map screen

* Update menu visibility logic to reflect Map tab selection

* Fix location prompt by deferring permission request to Map tab visibility

* Fix: Restrict location permission prompt to Map tab in Explore section
2025-10-02 09:44:37 +09:00
translatewiki.net
d632c268ae
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-29 14:02:35 +02:00
translatewiki.net
be371e5236
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-25 14:03:06 +02:00
Rickey H.
25d3068faf
added padding inset for mapview (#6427)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-09-24 23:18:02 +09:00
translatewiki.net
179c7c1855
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-18 14:02:55 +02:00
translatewiki.net
8018000584
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-15 14:02:29 +02:00
VoidRaven
657af4fe04
Fix #6409: Add listener call in ImageAdapter to update UI and upload (#6420)
* Fix #6409: Add listener call in ImageAdapter to update UI and upload button on deselection

* Fix image deselection issue in ImageAdapter to update UI correctly (#6409)

* Prevent duplicate image selections on multiple taps in ImageAdapter when showAlreadyActionedImages is off (#6409)

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-09-15 18:14:59 +09:00
translatewiki.net
219fcd3dd8
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-11 14:02:52 +02:00
LeopoldoDelgadillo
2e9726b84f
Added VISIBLE flag to descriptionEdit inside onResume function at MediaDetailFragment.kt (#6421)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-09-10 22:56:26 +09:00
translatewiki.net
64c6b0c8d0
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-08 14:01:58 +02:00
Ritika Pahwa
fcc63b9f09 Add v6.0.2 to CHANGELOG.md
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-09-07 13:13:46 +05:30
Ritika Pahwa
a283ffe2bc Bump up version code and name for the patch release (v6.0.2) 2025-09-07 13:05:55 +05:30
Rohit Verma
2811b181b7
Fix: enable H/W acceleration for UploadActivity to resolve keyboard not showing on Upload Screen (#6418)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* fix: enable h/w acceleration for UploadActivity to resolve ime issue

* fix(upload): handle keyboard insets for bottom buttons at Depicts step

* fix(upload): handle keyboard insets for buttons at select category step

* fix(upload): hide keyboard before navigating to Media License screen

This solves keyboard opened at the License screen issue, if we proceed by pressing next at the Upload Categories screen when the keyboard is opened
2025-09-07 00:35:47 +05:30
Jason-Whitmore
730f314200
Fixes Issue #6384: java.lang.NullPointerException in ReviewActivity (#6394)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* activity_review.xml: add new GUI elements to replace old ones

Before this commit, the info icon shared the same GUI element with the "Skip this image" text.
This made the Kotlin code to handle taps on the info icon difficult to write, and would crash
with a NPE when the user used a language that is read right to left and the info icon was pressed.

This commit creates new GUI elements. Notably, the info icon has it's own element. A LinearLayout
is used to place the skip button and the info icon button together. Kotlin code can now be
simplified and the NPE bug can be fixed.

* ReviewActivity.kt: simplify info icon code to work with some languages

Before this commit, if the language was set to a language that is read right to left,
pressing the info icon would crash the app with a NPE. This was because the Kotlin
code assumed that the icon would always be on the right of the skip button
(index 2 in the drawable array). When a right to left language was used, the icon
would be on the left and index 2 would be null.

This commit builds upon prior GUI changes. The info icon now has its own button.
Kotlin changes now remove the use of the drawable array to find the info icon and
instead directly references the new info icon button. The info icon button now works
properly for both left-to-right and right-to-left languages while maintaining correct
positioning.

* activity_review.xml: fix xml to be more readable

This commit moves around some lines in the XML to make it more readable.

* activity_review.xml: change button configuration

This change simplifies the button configuration XML and makes the info icon button slightly smaller

---------

Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-09-05 15:36:47 +09:00
translatewiki.net
81da5c9a1a
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-09-04 14:02:19 +02:00
VoidRaven
a59bf64677
Added the wiki prefix to titles in GlobalFileUsage for issue #6416 (#6417) 2025-09-04 19:47:53 +09:00
VoidRaven
e2c8f85a5b
Make "File usages" items clickable with correct URLs #6307 (#6405)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* Fix: URL generation for GlobalFileUsage in FileUsagesUiModel.kt for issue #6307

* Add clickable functionality to 'Usages on Other Wikis' in FileUsagesContainer for issue #6307

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-09-02 17:03:28 +09:00
translatewiki.net
dd96c64182
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-09-01 14:02:53 +02:00
Ritika Pahwa
9ba702eaa9
Add v6.0.1 to CHANGELOG.md
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-08-30 12:41:52 +05:30
Ritika Pahwa
296b4c1f52 Bump up version code and name for the patch release (v6.0.1) 2025-08-30 12:34:47 +05:30
Chris Danis
48e7effd0a
fix: add User-Agent to NetworkingModule http client (#6415)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* fix: add User-Agent to NetworkingModule http client

* CommonHeaderRequestInterceptor: publicize

* fix import & style
2025-08-30 07:52:08 +09:00
translatewiki.net
b9f353bb5a
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-08-28 14:02:26 +02:00
translatewiki.net
c22e8447b3
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-08-25 14:03:00 +02:00
Ritika Pahwa
f810a2d49b Bump up version code to 1056 for v6.0.0 release
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-08-23 12:37:19 +05:30
Rohit Verma
4f3f7b97fd
fix: use context instead of requireContext() for backward compatibility (#6403)
It fixes crash when opening certain screens like Contribution Details, Bookmark, etc. on lower Android versions

Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
2025-08-23 12:33:30 +05:30
Rohit Verma
718c466505
Bump target sdk to API 35 and make the app UI compatible with edge to edge (#6393)
* chore: upgrade target SDK and refactor function signatures to resolve build issues

* chore: bump android gradle plugin version

* chore(ui): add extension functions for applying edge to edge insets

* fix: apply system bar top and bottom insets for edge to edge

* fix: force edge to edge for backward compatibility and consistent UI

* fix: apply top bar insets as padding and make the status bar color white

Since the toolbars have primary color as bg, we should make the status bar white

* chore: bump robolectric version for API 35 compatibility

* fix: preserve existing margins when adding new insets

* feat(customselector): improve RecyclerView edge-to-edge inset handling

It allows the last item to sits above the navigation bar while preserving edge-to-edge appearance.

* feat(notification): improve RecyclerView edge-to-edge insets handling

Also, refactor LocationPicker and DescriptionEdit activities to use extension functions and reduce duplication

* fix(quiz): enable and handle edge-to-edge insets and status icon colors

* fix: bottom insets not dispatched on all API versions consistently

Upgraded core-ktx version installCompatInsetsDispatch wasn't available on current version

* fix: return fallback value when versionName is null

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix: resolve compilation errors

* docs: add KDoc for edge-to-edge insets utility functions

* fix(SearchActivity): apply insets for system bars

* fix(util): add utility function to handle keyboard insets with animation

* fix(upload): handle keyboard insets for upload media detail card view

* fix(login): hadle IME insets and make edge-to-edge backward compatible

---------

Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-23 12:27:37 +05:30
translatewiki.net
b8a558303b
Localisation updates from https://translatewiki.net. 2025-08-21 14:02:13 +02:00
Ritika Pahwa
a892aa6dee
6357: Fix java.lang.SecurityException for multi-uploads (#6402)
* Fix java.lang.SecurityException for ACTION_OPEN_DOCUMENT

* Handle SecurityException in case of multi-upload

* Remove unused import

* Clean up code

* Clean up code

* Handle SecurityException for other upload methods

* Release persisted URI permissions for successful uploads

* Remove persistable permission for custom picker as it's not required

* Remove persistable permission for in-app camera as it's not required
2025-08-20 01:46:02 +10:00
translatewiki.net
5a6b3cbf09
Localisation updates from https://translatewiki.net. 2025-08-18 14:02:18 +02:00
Rohit Verma
5bdfbf5f6f
fix: NPE when changing theme while on profile screen (#6398) 2025-08-16 15:06:57 +05:30
translatewiki.net
1d7d2801e4
Localisation updates from https://translatewiki.net. 2025-08-14 14:01:59 +02:00
translatewiki.net
5201af70cd
Localisation updates from https://translatewiki.net. 2025-08-11 14:01:53 +02:00
translatewiki.net
d0e95bc3c2
Localisation updates from https://translatewiki.net. 2025-08-07 14:02:05 +02:00
Sonal Yadav
ffb9af1f1c
Support both "label" and "itemLabel" for NearbyResultItem mapping (#6386)
* Support both label and itemLabel for robust NearbyResultItem mapping.

* fix code style

* Add getOriginalLabel() for Wikidata edits to avoid fallback issues with itemLabel

* Fix Wikidata edit failure by resetting hasInvalidLocation flag on upload confirmation

---------

Co-authored-by: Sonal Yadav <sonalyadav@Sonals-MacBook-Air.local>
2025-08-05 13:13:40 +09:00
translatewiki.net
6dcce45c59
Localisation updates from https://translatewiki.net. 2025-08-04 14:02:18 +02:00
Paul Hawke
6f36cae767
Convert explore package to kotlin (#6389)
* Convert WikidataItemDetailsActivity to kotlin

* Convert RecentSearchesDao to kotlin

* Convert RecentSearchesFragment to kotlin

* Convert ExploreListRootFragment to kotlin

* Convert the ParentViewPager to kotlin

* Convert ExploreMapRootFragment to kotlin

* Convert SearchActivity to kotlin

* Convert ExploreFragment to kotlin

* Convert ExploreMapCalls and ExploreMapContract to kotlin

* Convert ExploreMapController to kotlin

* Convert the map presenter to kotlin

* Convert the ExploreMapFragment to kotlin

* Fix import issue
2025-08-04 11:44:00 +09:00
Ritika Pahwa
516039c91d
Add v5.6.1 to CHANGELOG.md 2025-08-02 12:34:34 +05:30
Paul Hawke
8de57304bf
Convert bookmarks package to kotlin (#6387)
* Convert BookmarkItemsController to kotlin

* Split BookmarkItemsDao apart and converted to Kotlin

* Convert and cleanup content providers

* Convert BookmarkItemsFragment to kotlin

* Convert BookmarkPicturesFragment to kotlin

* Convert BookmarkPicturesDao to kotlin and share some useful DB methods

* Convert BookmarkPicturesController to kotlin

* Convert BookmarkFragment to kotlin

* Convert BookmarksPagerAdapter to kotlin

* Convert BookmarkListRootFragment to kotlin
2025-08-01 08:26:16 +09:00
translatewiki.net
869371b485
Localisation updates from https://translatewiki.net. 2025-07-31 14:02:22 +02:00
translatewiki.net
929711da98
Localisation updates from https://translatewiki.net. 2025-07-28 14:01:44 +02:00
Ritika Pahwa
b2816e1459 Bump up version code to 1055 for v5.6.1 release
Revert SPARQL optimisation
2025-07-26 12:32:54 +05:30
Ritika Pahwa
532bd8baa6 Revert "Optimise SPARQL query for single entity metadata using wikibase:label (#6376)"
This reverts commit e5dbcfc2a1.
2025-07-26 12:32:48 +05:30
Sonal Yadav
90ab7a2766
Correct NearbyResultItem label mapping for place name display (#6382)
* Fix  Nearby place name missing in Nearby Place Found popup

* minor change

---------

Co-authored-by: Sonal Yadav <sonalyadav@Sonals-MacBook-Air.local>
2025-07-25 23:35:14 +09:00
translatewiki.net
ee33a9350f
Localisation updates from https://translatewiki.net. 2025-07-24 14:02:07 +02:00
translatewiki.net
f1e6f1ad31
Localisation updates from https://translatewiki.net. 2025-07-21 14:02:00 +02:00
Ritika Pahwa
11e3e37263 Bump up version code to 1054 for v5.6.0 release 2025-07-19 14:46:01 +05:30
translatewiki.net
da694022ac
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-07-17 14:02:00 +02:00
VoidRaven
29ade1e5b7
Fix email verification input (#6367)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Set imeOptions to actionDone and enforce singleLine for xlarge

* Set imeOptions to actionDone and enforce singleLine for landscape

* Set imeOptions to actionDone and enforce singleLine

* Update askUserForTwoFactorAuth for IME_ACTION_DONE to trigger performLogin

* Fix email verification: Set imeOptions to actionDone and replace singleLine with maxLines=1

* Fix email verification: Set imeOptions to actionDone and replace singleLine with maxLines=1

* Fix email verification: Set imeOptions to actionDone and replace singleLine with maxLines=1

* feat: Set imeOptions to actionNext for login_password to improve focus transition

* feat: Enhance keyboard visibility for email verification code input
2025-07-17 09:41:28 +09:00
Shravya K Suresh
88565b70c5
updated the strange wording (#6378)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-07-16 23:11:21 +09:00
Sonal Yadav
e5dbcfc2a1
Optimise SPARQL query for single entity metadata using wikibase:label (#6376)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Optimize SPARQL query for single entity metadata using wikibase:label service

- Use SERVICE wikibase:label for efficient retrieval of labels and descriptions in preferred language
- Remove redundant label/description fetching logic
- Prevent Cartesian product and improve query performance for

* appeded all possible Wikidata languages

* Remove duplicate 'en'

* Update query_for_item.rq

* formatting, comments

---------

Co-authored-by: Sonal Yadav <sonalyadav@Sonals-MacBook-Air.local>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-07-16 17:22:10 +09:00
Copilot
0cda8e4d70
Show author/uploader names in Media Details for Commons licensing compliance (#6375)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Initial plan

* Implement author/uploader display in Media Details

Co-authored-by: nicolas-raoul <99590+nicolas-raoul@users.noreply.github.com>

* Enhanced implementation to use getAttributedAuthor() for better attribution coverage

Co-authored-by: nicolas-raoul <99590+nicolas-raoul@users.noreply.github.com>

* Move Author/Uploader fields to be positioned after License field

Co-authored-by: nicolas-raoul <99590+nicolas-raoul@users.noreply.github.com>

* Remove unnecessary comment lines as requested

Co-authored-by: nicolas-raoul <99590+nicolas-raoul@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nicolas-raoul <99590+nicolas-raoul@users.noreply.github.com>
2025-07-15 23:42:55 +09:00
translatewiki.net
7500b6d374
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-07-14 14:01:46 +02:00
Ritika Pahwa
a4c7a9c4f7
Fix java.lang.SecurityException for ACTION_OPEN_DOCUMENT (#6370)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-07-14 08:56:35 +05:30
Paul Hawke
8fc7e1039b
Convert media package to kotlin (#6369)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Convert Caption to kotlin

* Convert CaptionListViewAdapter to kotlin

* Convert CaptionListViewAdapter to kotlin

* Removed unused class

* Converted MwParseResult / MwParseResponse to kotlin

* Convert CustomOkHttpNetworkFetcher to kotlin

* Break up MediaDetailPagerFragment to make it easier to convert to kotlin

* Convert MediaDetailProvider to kotlin

* Convert the MediaDetailAdapter to kotlin

* Convert MediaDetailPagerFragment to kotlin
2025-07-12 11:11:20 +09:00
translatewiki.net
79f52db929
Localisation updates from https://translatewiki.net. 2025-07-10 14:02:16 +02:00
translatewiki.net
13048cc2fd
Localisation updates from https://translatewiki.net. 2025-07-07 14:01:56 +02:00
Paul Hawke
66395b9871
convert top level classes to kotlin (#6368)
* Converted welcome activity / pager to kotlin

* Removed unused interface

* Convert ViewPagerAdapter to kotlin and enforce that all tabs must have a title that comes from strings.xml

* Convert OkHttpConnectionFactory and remove an exception class nobody was using

* Convert MapController to kotlin along with fixing nullability in a few places
2025-07-07 09:50:16 +09:00
VoidRaven
65f41beed8
Update bug-report.yml to use Type:bug label as per issue #6356 (#6363)
* Update bug-report.yml to use Type:bug label as per issue #6356

* Update bug-report.yml to use type: bug and labels: ['Type: bug'] as per issue #6356

* Update bug-report.yml to use type: Bug and revert labels to ['bug'] as per mentor's feedback for issue #6356

* Remove labels field from bug-report.yml as per feedback for issue #6356

---------

Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
2025-07-05 11:35:54 +05:30
Sonal Yadav
f98b49608e
fix popup from appearing in nearby (#6359)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-07-05 13:15:57 +09:00
Paul Hawke
3bd0ec4466
Convert top level "Utils" class to kotlin (#6364)
* Unused class removed

* Convert BasePresenter to kotlin

* Removed redundent class

* Move the Utils class into the utils package

* Inline the creation of a page title object

* Move license utilities into their own file

* Inline app rating since its only ever used in 1 place

* Moved GeoCoordinates utilities into their own class

* Moved Monuments related utils into their own class

* Moved screen capture into its own util class

* Moved handleWebUrl to its own utility class

* Moved fixExtension to its own class

* Moved clipboard copy into its own utility class

* Renames class to match remaining utility method

* Convert UnderlineUtils to kotlin

* Converted the copy-to-clipboard utility to kotlin

* Converted license name and url lookup to kotlin

* Converted fixExtension to kotlin

* Convert handleGeoCoordinates to kotlin

* Monument utils converted to kotlin

* Convert then inline screeen capture in kotlin

* Convert handleWebUrl to kotlin
2025-07-04 20:18:52 +09:00
translatewiki.net
4befff8f42
Localisation updates from https://translatewiki.net. 2025-07-03 14:02:08 +02:00
Ritika Pahwa
89436b0a75
Add v5.5.0 to CHANGELOG.md 2025-07-01 11:52:08 +05:30
translatewiki.net
6de5a07e0d
Localisation updates from https://translatewiki.net. 2025-06-30 14:01:39 +02:00
translatewiki.net
27b9d70333
Localisation updates from https://translatewiki.net. 2025-06-29 20:27:27 +02:00
Jason-Whitmore
9a94dc2548
Fixes Issue 6312: GPS has huge error and does not update (in Nearby) (#6352)
* NearbyParentFragment.kt: add helper methods for user location overlays and accuracy data.

Before this commit, the code used to create the user location overlays was in multiple places.
Additionally, there was no easy way to access the location accuracy.

This commit places the user location overlay creation code into helper methods, as well as adding
a new location accuracy getter method. These methods can now be used to refactor other parts of the file.

* NearbyParentFragment.kt: create method to update user location overlays

Before this commit, there was no easy way to update the user location overlays.

This commit adds the updateUserLocationOverlays() method, which will properly replace
the old user location overlays with new ones. It will also add the overlays if they
do not already exist.

* NearbyParentFragment.kt: replace old code with calls to updateUserLocationOverlays()

This commit completes the refactor and fixes the issue of the user overlays not
updating. The new method updateUserLocationOverlays is called to refactor and simplify
old code.

* Removal of file is not related to the issue, but is needed for project to compile and run.

* NearbyParentFragment.kt: fix bug where multiple user location overlays would appear

Before this commit, the user could see multiple user location overlays if they paused the app and reopened it when
there are no Places/pins on the map. This was caused by a linear search failing to identify the target overlay
because it compared Drawables between two Overlays, which was unreliable.

This commit contains a better solution for replacing existing user location overlays by adding 2 instance variables
to keep track of the overlays. The position of these overlays in the overlay list can then be found by using indexOf()
with these instance variables rather than the linear search that was implemented before. Some refactoring was also done.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-06-29 18:30:10 +09:00
translatewiki.net
b1a8308aaf
Localisation updates from https://translatewiki.net. 2025-06-26 14:02:13 +02:00
Rohit Verma
ad7dddaac4
Fix infinite loading circular progress bar after nominating for deletion (#6324)
* fix: infinite loading progress bar after nominating for deletion

* add logs for testing

* refactor: use globalFileUsage instead of achievement to append in reason

Fetching achievements is a time consuming operation and globalFileUsage gives the similar result in optimal time

* test(ReasonBuilder): fix tests according to new behavior

* refactor: remove logs added for testing

* test: await for async getReason method call

---------

Co-authored-by: Neel Doshi <neeldoshi147@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-06-25 12:24:03 +09:00
Rohit Verma
5d7f42d127
Fix/file usage not working (#6354)
* chore: add R8 rules to prevent obfuscating file usage classes

* chore: upgrade lifecycle-runtime dependency to resolve lint errors

* remove invalid resource directory
2025-06-24 21:56:00 +09:00
translatewiki.net
d9e8917418
Localisation updates from https://translatewiki.net. 2025-06-23 14:01:45 +02:00
Sonal Yadav
09da7b8d68
Skip image upload to Wikidata (nearby -> green pins) (#6349)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* Skip image upload to Wikidata if item already has image

* Re-run CI

* no more Failed to update Wikidata for green pins
2025-06-22 22:40:15 +09:00
Ritika Pahwa
ca5c7ec966
Bump up version code to 1053
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-21 13:21:05 +05:30
translatewiki.net
9eff9e8e82
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-19 14:01:44 +02:00
Rohit Verma
5665bc7f93
fix: make userName private to prevent conflict when passing arguments (#6339)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-17 17:33:43 +09:00
translatewiki.net
20e5df7d49
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-16 14:01:42 +02:00
translatewiki.net
d3ae925567
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-12 14:01:42 +02:00
translatewiki.net
af82cb2123
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-12 06:17:24 +02:00
translatewiki.net
7df52e3f9c
Localisation updates from https://translatewiki.net. 2025-06-12 06:06:17 +02:00
translatewiki.net
6b40560dfc
Localisation updates from https://translatewiki.net. 2025-06-12 05:27:59 +02:00
translatewiki.net
54bb789461
Localisation updates from https://translatewiki.net. 2025-06-12 05:20:46 +02:00
translatewiki.net
7979be17c1
Localisation updates from https://translatewiki.net. 2025-06-12 05:05:50 +02:00
translatewiki.net
91564a1dff
Localisation updates from https://translatewiki.net. 2025-06-12 04:36:42 +02:00
Sonal Yadav
2b5f0e4ac9
drop down menu in the Upload Wizard now show the language in which the pin's label is shown (#6346)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-11 21:11:49 +09:00
translatewiki.net
9b04031c91
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-09 14:01:52 +02:00
Ritika Pahwa
8ff52e6815
Fix crash on app startup by bumping up room database version
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-08 19:16:01 +05:30
Ritika Pahwa
c41b5cc9da
Add v5.4.1 to CHANGELOG.md
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-08 12:38:14 +05:30
Ritika Pahwa
767b625289
Bump up version code to 1052 2025-06-08 12:35:24 +05:30
Sonal Yadav
f45f26e602
Implement single selection logic in custom image picker (#6341)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* build failure cause

* Fix image selector logic in custom picker
2025-06-07 23:09:47 +09:00
translatewiki.net
06a613e855
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-05 14:02:03 +02:00
Jason-Whitmore
62c5231dc9
ExploreMapFragment.java: modify Overlay onItemSingleTapUp code to place Overlay above other Overlays (#6334)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Before these changes, when a user tapped on an icon in the Explore Map, the icon and label would often
appear underneath other icons, making it difficult or impossible to read the text on the label.

These changes add two methods that search for and move the icon/label Overlays above all other icons and overlays.
These two methods are called when the user taps on an icon.

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-06-05 17:06:55 +09:00
Saifuddin Adenwala
7a224a9120
Fix invalid resource directory causing test failure (#6337) 2025-06-05 16:47:09 +09:00
translatewiki.net
593335aea3
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-06-03 17:59:39 +02:00
Dev Jadiya
6edc6a22e4
Refactor long log line in SingleWebViewActivity to comply with code style (#6333)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-03 09:30:52 +09:00
translatewiki.net
230604f5ef
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-06-02 14:02:04 +02:00
Jason-Whitmore
73f5200c2d
ExploreMapFragment.java: fix removeMarker code to correctly find the specified marker (#6325)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
Before this change, removeMarker() would search for the correct Overlay by doing a linear
search and matching the BaseMarker's Place name with the Overlay's title.
This stopped working once the Overlay's title text was changed to be more user friendly.

This change implements a more robust solution. A map is used to directly associate
BaseMarkers with the matching Overlay. The overlay can now be removed from the
overlay list without using any of the information contained within the BaseMarker
or Overlay.

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-31 12:05:33 +09:00
translatewiki.net
95b8ac74b9
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-29 14:01:52 +02:00
rayane
cfc2cfcca1
Fix Kotlin warnings (related to issue #5996) (#6320)
* refactor: replace unused exception variable with underscore

* refactor: code style fix

* fix: remove unnecessary latLng variable and return null directly

* fix: use explicit true/false instead of isFABsExpanded

* refactor: simplify marker update logic by reducing redundant conditions

* refactor: use data object instead of object

* fix: check placeBindings for null instead of checking each destructured value

* fix: check placeBindings for null instead of checking each destructured value

* docs: fix KDoc for contentUri property by removing incorrect @param tag

* docs: correct @see link in KDoc for showBadgesWithCount

* docs: fix KDoc tag from @property to @param for context in BottomSheetAdapter

* docs: comment out KDoc for disabled method to avoid unresolved @param warning

* docs: fix KDoc for onLongPress by removing invalid @param imageUri

* docs: clean up invalid KDoc tags on property and add docs to isEmpty

* docs: remove invalid @param originalImageCoordinates from findOtherImages KDoc

* docs: fix incorrect @param tag in checkDuplicateImage KDoc

* docs: fix incorrect @param in onLongPress KDoc

* docs: fix invalid @param and @return tags on author property

* docs: fix incorrect @param in provideWikidataMediaInterface KDoc

* docs: fix incorrect @param name in getWikiText KDoc

* docs: escape wikilinks with [[ ]] to avoid KDoc resolution warnings

* docs: fix KDoc by adding missing param descriptions to startActivityWithFlags

* docs: fix KDoc by replacing @param with @property for contentUri

* docs: fix malformed KDoc in startYourself, remove invalid @param line

* docs: remove invalid @param tag in createDialogsAndHandleLocationPermissions

* docs: remove invalid @param tags to @property

* @docs: remove invalid @property tags

* docs: clean up KDoc by removing invalid @param and @return tags

* docs: fix incorrect @param name in removeBlocklisted KDoc

* docs: fix incorrect @param name in checkDuplicateImage KDoc

* docs: fix incorrect @param tag

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-29 20:25:00 +09:00
Rohit Verma
ed1485ca22
Migrated from Groovy to Kotlin DSL and upgrade AGP version (#6322)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* chore: migrate groovy build files to Kotlin DSL and upgrade AGP

chore: use version catalog to manage dependencies

chore: move plugins to version catalog

remove deprecated way of enabling build-config and redundant code

chore: setup triplet-play plugin configuration

refactor: move dependency block and tasks to the end of build file

refactor: move dependency block and mark redundant dependencies

* cleanup: remove redundant dependencies from build file

* cleanup: remove git utils file as the functions moved to build file

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-28 20:43:13 +09:00
Sonal Yadav
c49c85e68b
Fix : UninitializedPropertyAccessException (#6248)
* Fix crash when uploading a duplicate file

* Fix: app crash

* added Kdoc

* remove line b/w kdoc and function

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-28 17:53:28 +09:00
translatewiki.net
91ca2e6672
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-26 14:01:50 +02:00
Sonal Yadav
8849f8984b
Fix: Use concise Wikidata feedback message while keeping full UI text (#6318)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-05-25 23:17:04 +10:00
translatewiki.net
bb21e4bdcd
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-22 14:02:27 +02:00
Jason-Whitmore
eb617ae8ca
Fixes Issue 6308: Explore map shows image at my location rather than at shown location (#6315)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* ExploreMapFragment.java: add helper methods and fields to enable proper Explore map behavior

Before this commit, there was no way to tell if the user had arrived from the Nearby and
before the Nearby map center location had been searched for markers.

This commit adds a boolean flag to indicate this situation. Access, modification, and
initialization methods were added for this boolean value. Additionally, a helper
method to retrieve the Nearby map center LatLng was added as a convienience.

* ExploreMapPresenter.java: fix map update code to search for Nearby LatLng when appropriate

Before this commit, when the user selected "Show in explore" in Nearby when no pins were
on the map, Explore would only search for markers at the user's current GPS location,
rather than those at the Nearby map center.

After this commit, code was added to check if the user recently came from the Nearby map.
If so, the stored coordinates of the Nearby map is searched rather than the user's current
GPS coordinates. Additionally, the boolean that indicates that the user recently came
from the Nearby map is set to false. This ensures that the stored Nearby map center
coordinates are not used when the user taps the icon to focus the map on their
current location.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-21 16:54:45 +09:00
translatewiki.net
b3c1474b31
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-19 14:01:55 +02:00
Ritika Pahwa
21ffcb56fd Bump up version code to 1051
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-18 12:44:37 +05:30
translatewiki.net
f977e16774
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-12 14:01:59 +02:00
Jason-Whitmore
012020735f
Fixes Issue 6262: [Bug]: Error occurred while loading images (#6291)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* ImageInfo.kt: remove call to updateThumbURL()

Before this commit, the updateThumbURL() method would attempt to derive a larger resolution thumbnail URL
from the current thumbnail's width and height. This derived thumbnail URL would sometimes not match
an actual URL. Even creating this thumbnail URL would sometimes fail when doing string manipulation.
Both errors can cause issues, with the second one crashing the thread and preventing images from being
loaded.

This commit simply removes the call to updateThumbURL(). Images with their thumbnails now load correctly,
especially with panoramic images.

* MediaInterface.kt: fix MediaWiki API call to retrieve thumbnail URLs properly

Before this change, the API calls to MediaWiki would request thumbnails with atleast a specific width,
rather than height. These thumbnails would often be extremely low resolution on very wide images,
such as panoramas.

This change instead requests thumbnails with atleast a specific height. Panorama thumbnails are now
at a higher resolution than before. Additionally, the height parameter is now represented as an
integer which can be changed more easily.

* ViewPagerAdapter.java: create new constructor with behavior parameter

Before this commit, ViewPagerAdapter only had one constructor which used the default
behavior where more than the current fragment would be in the Resume state.

This commit adds a constructor where the behavior parameter can be specified.

* ExploreFragment.java: modify onCreateView to use new ViewPagerAdapter constructor

Before this change, onCreateView would use the old ViewPagerAdapter constructor, which
had a default behavior where fragments not currently visible would be in the Resume
state.

After this change, the new ViewPagerAdapter constructor is used with a non-default
behavior where only the visible fragment would be in the Resume state. This has
performance benefits for most users since the Fragments will only be active
when they want to view them.

* ExploreMapFragment.java: remove redundant method call

Before this commit, performMapReadyActions() was called in both onViewCreated and onResume.
In effect, the method is called twice before the user can even see the map, leading to
performance issues.

After this commit, the call in onViewCreated is removed. The method is now called only once
when the user switches to the ExploreMapFragment.

* ExploreMapFragment.java: remove method call that causes recursive loop

Before this commit, there was a call loop that included onScroll and animateTo
on the map. These two method calls caused performance issues in
ExploreMapFragment.

This commit breaks the call loop by removing moveCameraToPosition from getMapCenter.
Also, the removed code did not fit the purpose of getMapCenter.

* ExploreMapFragment.java: fix performMapReadyActions to center to user's last known location

Before this commit, fixing a previous bug caused performMapReadyActions to center the map
on a default location rather than the available last known location.

This commit adds code which will attempt to get the last known user location. If it cannot
be retrieved, the default location is used. The map now centers properly.

* MediaInterface.kt: adjust thumbnail height parameter

Before this commit, the thumbnail height parameter was too small, causing low resolution thumbnails to render.

This commit more than doubles the parameter, increasing the thumbnail resolution.

---------

Co-authored-by: Ritika Pahwa <83745993+RitikaPahwa4444@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-05-10 20:04:57 +09:00
Rohit Verma
3f2077a6db
tests: move to androidTest source-set (#6302)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-05-09 23:36:34 +09:00
translatewiki.net
f06ae4ebfe
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-05-08 14:02:08 +02:00
translatewiki.net
865824a8e3
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-05 14:01:48 +02:00
Ifeoluwa Andrew Omole
4d2170257a
Nearby List: Only show place cards with loaded names (#6301)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-05-04 15:38:18 +05:30
translatewiki.net
0024e72a2e
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-05-01 14:01:50 +02:00
translatewiki.net
60aca9a5e3
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-04-28 14:01:51 +02:00
translatewiki.net
d0f6c16878
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-04-24 14:02:11 +02:00
Yusuke Matsubara
8fded5ef6e
Change back some variable names that were accidentally changed (#6297) 2025-04-24 20:16:03 +09:00
Yusuke Matsubara
329a68216e
Improve credit line in image list (#6295)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
- When author is not uploader, show both.
- When failing to parse author from HTML, use structured data.
2025-04-23 23:23:09 +09:00
translatewiki.net
30762971db
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-04-21 14:01:47 +02:00
Khushbu Khemchandani
7479d96675
Code Enhancement (Explore Map) (#6293)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
* Exclude past locations (P585) from Nearby query

* "Send" button text should be white

* Custom picker: logic

* Revert back changes

* Enhancement :- Explore Map information

* Enhancement :- Explore Map information

* Style

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-04-21 14:49:15 +09:00
translatewiki.net
ed42d85f67
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-04-17 14:01:53 +02:00
Sonal Yadav
78d29bcf20
FIX : Custom picker detect images that is already in commons (#6288)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* Fix: Exclude specific text from being posted in WikidataFeedback

* Add detection logic for images already on Commons in custom picker
2025-04-15 13:53:26 +10:00
Ritika Pahwa
1a13cb3383
Add v5.3.0 to CHANGELOG.md
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-15 02:43:33 +05:30
Khushbu Khemchandani
9289dcc42c
UI enhancement Issue(#6285) (#6287)
* Exclude past locations (P585) from Nearby query

* "Send" button text should be white
2025-04-14 22:58:28 +09:00
translatewiki.net
efdc9c5548
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-14 14:01:44 +02:00
Khushbu Khemchandani
69b3544107
Exclude past locations (P585) from Nearby query (#6284)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-14 10:54:29 +09:00
Ritika Pahwa
5b5aeead88
Bump up version code to 1050
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-13 13:09:19 +05:30
Prinuel
4bacac1f8b
BookmarkLocationsFragment.kt:fix android studio warnings for this file, : Eliminated three unused imports, and changed calls to object: FilePicker.HandleActivityResult to use lamda (#6283)
Co-authored-by: bethel-m <bethelcletus87@gmail.com>
2025-04-13 14:39:14 +09:00
samimshoaib01
6aeb3c07cc
ui: make recenter FAB theme-aware using Material attributes (#6281)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-12 21:05:42 +05:30
Sonal Yadav
2c41176a6e
Mark for closed locations (P3999) in Nearby (#6273)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
* Exclude closed locations (P3999) from Nearby query

* feat: Show  for P3999 items (official closure)

* revert changes

* Add P3999 (date of closure) support for non-existent places

* Typo fixing

* fix-typo

* .

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-04-11 18:00:47 +09:00
translatewiki.net
e3dd00bcfa
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-10 14:01:48 +02:00
Jason-Whitmore
262efe4d8c
ExploreMapFragment.java: fix removeMarker() to remove the correct marker (#6279)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Before this change, the removeMarker() method would determine the correct overlay to remove
by comparing an overlay's Place coordinates with the target BaseMarker's Place coordinates.
If two different markers had the same Place coordinates, the incorrect marker would be removed,
leading to more than one green label appearing on the screen.

After this change, the removeMarker() method now compares an overlay's title with the BaseMarker's
Place name. removeMarker() will work properly as long as all BaseMarker's Place names are unique.
Also, null checks were added to removeMarker().
2025-04-10 14:19:31 +09:00
Dmitry Brant
2eed441462
Enable EmailAuth support. (#6277)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-04-08 20:59:28 +09:00
translatewiki.net
56fa8ceb5a
Localisation updates from https://translatewiki.net.
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
2025-04-07 14:01:50 +02:00
Rohit Verma
7bf9276d1a
fix: resolve IndexOutOfBounds error when removing images from top card (#6124)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
replace deprecated onBackPressed with onBackPressedCallback

remove unit test for deprecated onBackPressed method

remove if-check before deleting picture to prevent hiding top thumbnail card

hide the thumbnail card on fragments other than MediaDetailFragment

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-04-05 22:47:27 +09:00
Prinuel
51da9e4dd6
FooterAdapter.kt: changed enum access of FooterItem, from FooterItem.values to FooterItem.entries, this is the more efficient way of accessing Enum values as introduced in kotlin 1.9 (#6271)
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
Co-authored-by: bethel-m <bethelcletus87@gmail.com>
2025-04-03 21:10:41 +09:00
translatewiki.net
731ff62faf
Localisation updates from https://translatewiki.net. 2025-04-03 14:01:45 +02:00
translatewiki.net
fdfd7781e9
Localisation updates from https://translatewiki.net.
Some checks failed
Android CI / Run tests and generate APK (push) Has been cancelled
2025-03-31 14:01:52 +02:00
Jason-Whitmore
6e090c8d7a
ExploreMapFragment.java: fix marker labels in Explore map fragment to display the username (#6260)
Before this change, the labels that would appear on the marker when tapped did not include
the author or username. Instead, it displayed "Unknown".

After this change, the labels now display the author name. If the author name is not
available, the username will be displayed. If both are unavailable, the default value
of "Unknown" will be displayed. To improve the readability of the text, any HTML text
is removed from the username/author.
2025-03-31 17:49:06 +09:00
Ritika Pahwa
44966645ca
Add v5.2.0 to CHANGELOG.md 2025-03-29 13:21:03 +05:30
translatewiki.net
669f3043ae
Localisation updates from https://translatewiki.net. 2025-03-27 13:01:54 +01:00
translatewiki.net
5a5e660a43
Localisation updates from https://translatewiki.net. 2025-03-24 13:01:43 +01:00
Ritika Pahwa
2e05a58e8b
Bump up version code to 1049 2025-03-22 14:07:51 +05:30
translatewiki.net
f1f4e8baff
Localisation updates from https://translatewiki.net. 2025-03-20 13:01:41 +01:00
Sonal Yadav
828f69fc46
Update Privacy Policy Link to GitHub.io (#6255)
* "moved privacy policy"

* Update PRIVACY_POLICY_URL to https://commons-app.github.io/privacy-policy as suggested

* Use BuildConfig.PRIVACY_POLICY_URL in launchPrivacyPolicy function
2025-03-20 07:21:33 +09:00
Ritika Pahwa
954a7aee91
Bump up version code and name (#6250)
* Bump up version code and name

* Remove unintended change
2025-03-18 23:28:19 +09:00
translatewiki.net
fa0bdf5747
Localisation updates from https://translatewiki.net. 2025-03-17 13:01:37 +01:00
Sonal Yadav
67ac92ff57
Fix NullPointerException in onBackPressed() (#6249) 2025-03-17 07:53:58 +09:00
Sonal Yadav
e1466c866b
Fix NullPointerException in UploadCategoriesFragment (#6246) 2025-03-14 16:16:14 +10:00
translatewiki.net
ba89894dc4
Localisation updates from https://translatewiki.net. 2025-03-13 13:01:53 +01:00
translatewiki.net
c46c1d2353
Localisation updates from https://translatewiki.net. 2025-03-10 13:01:11 +01:00
Saifuddin Adenwala
30322707fc
Migrated media/zoomControllers package to kotlin (#6204)
* Rename .java to .kt

* Migrated media/zoomControllers package to kotlin

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-03-08 22:47:23 +09:00
Parneet Singh
972bf785f1
Fix empty username (#6209)
* revert changes from #5860

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* read author prop instead

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* Use user prop if author is empty or null

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-03-08 22:26:41 +09:00
Rohit Verma
32d485cc51
fix: null context issue by removing context getter override (#6218)
Refactored related code to resolve build issues
2025-03-08 22:02:28 +09:00
Parneet Singh
d11439f85d
add cancel button listener (#6216)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-03-07 09:52:52 +09:00
Rohit Verma
681881f4f6
fix: crash when opening leader board tab (#6220) 2025-03-07 08:21:31 +09:00
Nicolas Raoul
939d01b267 Bumped to 5.1.3 to reflect feature parity but the real 5.1.3 release was performed in the google_play_safe branch 2025-03-06 23:00:45 +09:00
translatewiki.net
d233de6103
Localisation updates from https://translatewiki.net. 2025-03-06 13:01:40 +01:00
Parneet Singh
139a296bd3
Comment in PR with generated builds. (#6226)
* revert prior attempt

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* trigger workflows_run event after uploading artifacts

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-03-03 21:44:08 +09:00
translatewiki.net
6b56075df8
Localisation updates from https://translatewiki.net. 2025-03-03 13:01:29 +01:00
Rohit Verma
218476acbc
fix: rename isDarkTheme property to prevent recursion (#6231) 2025-03-02 06:19:43 +10:00
translatewiki.net
fa24b93830
Localisation updates from https://translatewiki.net. 2025-02-27 13:01:51 +01:00
Parneet Singh
b2f655522e
bump to action's v4 (#6225)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-02-27 10:11:32 +09:00
Nicolas Raoul
88eedc3506 Revert previous commit, wrong branch
This reverts commit aa84dedd64.
2025-02-26 18:03:24 +09:00
Nicolas Raoul
aa84dedd64 Removed Explore, Peer review, because we are at risk of being removed from Google Play tomorrow 2025-02-26 18:00:48 +09:00
Saifuddin Adenwala
1c7dce9e12
Migrated bookmarks locations to Kotlin and adapt room database (#6148)
* Rename .java to .kt

* Refactor: Migrate bookmark location logic to Kotlin

This commit migrates the bookmark location logic to Kotlin, enhancing code maintainability and readability.

-   Removes the `BookmarkLocationsContentProvider`, `BookmarkLocationsController`, and `BookmarkLocationsDao` Java classes.
-   Creates `BookmarkLocationsDao.kt` and `BookmarkLocationsContentProvider.kt` in Kotlin.
-   Migrates the logic from `BookmarkLocationsFragment.java` to `BookmarkLocationsFragment.kt`.
-   Updates test files to reflect these changes.
-   Addresses associated code review comments.

* Refactor: Migrate to Room Database for Bookmark Locations

This commit migrates the bookmark locations functionality from a custom content provider to Room database.

Key changes:

*   **Removal of `BookmarkLocationsContentProvider`:** This class, which previously handled data storage, has been removed.
*   **Introduction of `BookmarksLocations`:** This data class now represents a bookmarked location, serving as the Room entity.
*   **Creation of `BookmarkLocationsDao`:** This Room DAO handles database interactions for bookmark locations, including:
    *   Adding, deleting, and querying bookmarked locations.
    *   Checking if a location is already bookmarked.
    *   Updating the bookmark status of a location.
    *   Retrieving all bookmarked locations as `Place` objects.
*   **`BookmarkLocationsViewModel`:** Added to manage the data layer for bookmark locations
*   **`NearbyUtil`:** Created a Util class for Nearby to manage the bookmark locations.
*   **Updates in `PlaceAdapter` and `PlaceAdapterDelegate`:** These classes have been modified to work with the new Room-based data layer.
*   **Updates in `AppDatabase`:** The database now includes `BookmarksLocations` as an entity and exposes the `bookmarkLocationsDao`.
*   **Updates in `FragmentBuilderModule` and `CommonsApplicationModule`**: for DI
*   **Removal of `DBOpenHelper` upgrade for locations**: as it is no longer needed
* **Updates in `NearbyParentFragmentPresenter`**: refactored the logic to use the dao functions
* **Updates in `NearbyParentFragment`**: refactored the logic to use the util and dao functions
* **Update in `BookmarkLocationsController`**: removed as its no longer needed
* **Add `toPlace` and `toBookmarksLocations`**: extension functions to map between data class and entities
* **Update in `CommonsApplication`**: to remove old db table.

* Refactor: Improve bookmark location handling and update database version

This commit includes the following changes:

-   Updates the database version to 20.
-   Refactors bookmark location handling within `NearbyParentFragment` to improve logic and efficiency.
-   Introduces `getBookmarkLocationExists` in `NearbyUtil` to directly update bookmark icons in the bottom sheet adapter.
-   Removes unused provider.
-   Adjusts `BookmarkLocationsFragment`'s `PlaceAdapter` to use `lifecycleScope` for better coroutine management.
-   Refactors `updateBookmarkLocation` in `NearbyParentFragmentPresenter`.

* Toggle bookmark icon in BottomSheetAdapter instead of finding the location each time in the bookmark

* Update bookmark button image in `BottomSheetAdapter`
* Add new toggle function to `BottomSheetAdapter`
* Call the toggle function in `NearbyParentFragment`

* Refactor: Load bookmarked locations using Flow

*   `BookmarkLocationsController`: Changed to use `Flow` to load bookmarked locations.
*   `BookmarkLocationsDao`: Changed to return `Flow` of bookmark location instead of a list.
*   `BookmarkLocationsFragment`: Used `LifecycleScope` to collect data from flow and display it.
*   Removed unused `getAllBookmarkLocations()` from `BookmarkLocationsViewModel`.
*   Used `map` in `BookmarkLocationsDao` to convert from `BookmarksLocations` to `Place` list.

* Loading locations data in fragment

*   BookmarkLocationsController: Changed `loadFavoritesLocations` to be a suspend function.
*   BookmarkLocationsFragment: Updated the `initList` function to call the controller's suspend function.
*   BookmarkLocationsDao: Changed `getAllBookmarksLocations` and `getAllBookmarksLocationsPlace` to be suspend functions.

* Refactor BookmarkLocationControllerTest and related files to use coroutines

*  Migrated `bookmarkDao!!.getAllBookmarksLocations()` to `bookmarkDao!!.getAllBookmarksLocationsPlace()` and added `runBlocking`
*  Added `runBlocking` for `loadBookmarkedLocations()` and `testInitNonEmpty()`
*  Added `runBlocking` for `getAllBookmarksLocations()` and update `updateMapMarkers`

These changes improve the test structure by ensuring the database functions are executed within a coroutine context.

* Refactor BookmarkLocationsFragment and add tests for BookmarkLocationsDao

*   Moved `initList` to `BookmarkLocationsFragment` for better lifecycle handling.
*   Added test case for `BookmarkLocationsFragment`'s onResume.
*   Added test cases for `BookmarkLocationsDao` operations like adding, retrieving, finding, deleting and updating bookmarks.

* Refactor BookmarkLocationsFragment to load favorites only when view is not null

* BookmarkLocationsFragment: load favorites locations only when view is not null.
* BookmarkLocationsFragmentTest: added spy and verify to test onResume() call initList() method.

* Refactor database and add migration

*   `AppDatabase`: Updated to use room migration.
*   `CommonsApplicationModule`: added migration from version 19 to 20 to `appDatabase`

* Rename .java to .kt

* Refactor: Migrate bookmark location logic to Kotlin

This commit migrates the bookmark location logic to Kotlin, enhancing code maintainability and readability.

-   Removes the `BookmarkLocationsContentProvider`, `BookmarkLocationsController`, and `BookmarkLocationsDao` Java classes.
-   Creates `BookmarkLocationsDao.kt` and `BookmarkLocationsContentProvider.kt` in Kotlin.
-   Migrates the logic from `BookmarkLocationsFragment.java` to `BookmarkLocationsFragment.kt`.
-   Updates test files to reflect these changes.
-   Addresses associated code review comments.

* Refactor: Migrate to Room Database for Bookmark Locations

This commit migrates the bookmark locations functionality from a custom content provider to Room database.

Key changes:

*   **Removal of `BookmarkLocationsContentProvider`:** This class, which previously handled data storage, has been removed.
*   **Introduction of `BookmarksLocations`:** This data class now represents a bookmarked location, serving as the Room entity.
*   **Creation of `BookmarkLocationsDao`:** This Room DAO handles database interactions for bookmark locations, including:
    *   Adding, deleting, and querying bookmarked locations.
    *   Checking if a location is already bookmarked.
    *   Updating the bookmark status of a location.
    *   Retrieving all bookmarked locations as `Place` objects.
*   **`BookmarkLocationsViewModel`:** Added to manage the data layer for bookmark locations
*   **`NearbyUtil`:** Created a Util class for Nearby to manage the bookmark locations.
*   **Updates in `PlaceAdapter` and `PlaceAdapterDelegate`:** These classes have been modified to work with the new Room-based data layer.
*   **Updates in `AppDatabase`:** The database now includes `BookmarksLocations` as an entity and exposes the `bookmarkLocationsDao`.
*   **Updates in `FragmentBuilderModule` and `CommonsApplicationModule`**: for DI
*   **Removal of `DBOpenHelper` upgrade for locations**: as it is no longer needed
* **Updates in `NearbyParentFragmentPresenter`**: refactored the logic to use the dao functions
* **Updates in `NearbyParentFragment`**: refactored the logic to use the util and dao functions
* **Update in `BookmarkLocationsController`**: removed as its no longer needed
* **Add `toPlace` and `toBookmarksLocations`**: extension functions to map between data class and entities
* **Update in `CommonsApplication`**: to remove old db table.

* Refactor: Improve bookmark location handling and update database version

This commit includes the following changes:

-   Updates the database version to 20.
-   Refactors bookmark location handling within `NearbyParentFragment` to improve logic and efficiency.
-   Introduces `getBookmarkLocationExists` in `NearbyUtil` to directly update bookmark icons in the bottom sheet adapter.
-   Removes unused provider.
-   Adjusts `BookmarkLocationsFragment`'s `PlaceAdapter` to use `lifecycleScope` for better coroutine management.
-   Refactors `updateBookmarkLocation` in `NearbyParentFragmentPresenter`.

* Toggle bookmark icon in BottomSheetAdapter instead of finding the location each time in the bookmark

* Update bookmark button image in `BottomSheetAdapter`
* Add new toggle function to `BottomSheetAdapter`
* Call the toggle function in `NearbyParentFragment`

* Refactor: Load bookmarked locations using Flow

*   `BookmarkLocationsController`: Changed to use `Flow` to load bookmarked locations.
*   `BookmarkLocationsDao`: Changed to return `Flow` of bookmark location instead of a list.
*   `BookmarkLocationsFragment`: Used `LifecycleScope` to collect data from flow and display it.
*   Removed unused `getAllBookmarkLocations()` from `BookmarkLocationsViewModel`.
*   Used `map` in `BookmarkLocationsDao` to convert from `BookmarksLocations` to `Place` list.

* Loading locations data in fragment

*   BookmarkLocationsController: Changed `loadFavoritesLocations` to be a suspend function.
*   BookmarkLocationsFragment: Updated the `initList` function to call the controller's suspend function.
*   BookmarkLocationsDao: Changed `getAllBookmarksLocations` and `getAllBookmarksLocationsPlace` to be suspend functions.

* Refactor BookmarkLocationControllerTest and related files to use coroutines

*  Migrated `bookmarkDao!!.getAllBookmarksLocations()` to `bookmarkDao!!.getAllBookmarksLocationsPlace()` and added `runBlocking`
*  Added `runBlocking` for `loadBookmarkedLocations()` and `testInitNonEmpty()`
*  Added `runBlocking` for `getAllBookmarksLocations()` and update `updateMapMarkers`

These changes improve the test structure by ensuring the database functions are executed within a coroutine context.

* Refactor BookmarkLocationsFragment and add tests for BookmarkLocationsDao

*   Moved `initList` to `BookmarkLocationsFragment` for better lifecycle handling.
*   Added test case for `BookmarkLocationsFragment`'s onResume.
*   Added test cases for `BookmarkLocationsDao` operations like adding, retrieving, finding, deleting and updating bookmarks.

* Refactor BookmarkLocationsFragment to load favorites only when view is not null

* BookmarkLocationsFragment: load favorites locations only when view is not null.
* BookmarkLocationsFragmentTest: added spy and verify to test onResume() call initList() method.

* Refactor database and add migration

*   `AppDatabase`: Updated to use room migration.
*   `CommonsApplicationModule`: added migration from version 19 to 20 to `appDatabase`

* Resolve conflicts and attach the `commons.db` with `common_room.db` during the migration to persist the data

* Refactor database migration to handle null values and use direct insertion

*   Modify the database migration from version 19 to 20 to properly handle null values and use direct data insertion.
*   Create a new table `bookmarks_locations` with `NOT NULL` constraints.
*   Directly insert data into the new table from old database, safely handling `NULL` values by using `DEFAULT` empty string value.
*   Close old database cursor and the database connection.
* Drop the old `bookmarksLocations` table.

* Refactor database schema to delete `bookmarksLocations` table and migrate data

*   Delete `bookmarksLocations` table from the database during schema upgrade
*   Migrate data from `bookmarksLocations` to `bookmarks_locations` in new schema
*   Add `INSERT OR REPLACE` to prevent duplication during migration
*   Delete `CONTRIBUTIONS_TABLE` and `BOOKMARKS_LOCATIONS` in the application start-up to have a fresh DB for first start-up after update

* Update sqlite-based database version to 22.

* Update sqlite-based database version to 22.

* Refactor CommonsApplicationModule to utilize application context

* Initialize and use application context directly

* Utilize lateinit for application context

* Added line breaks for improved readability.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-02-24 22:58:26 +09:00
translatewiki.net
1c4797d3aa
Localisation updates from https://translatewiki.net. 2025-02-24 13:01:49 +01:00
Sujal
b2927483fa
Update android-ci-comment.yml (#6200) 2025-02-24 16:19:35 +09:00
Sujal
fda87b7823
Fixed Build Status Badge (#6203)
* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md
2025-02-24 15:52:35 +09:00
Nicolas Raoul
71d3d12020
limit to the best issues for newcomers 2025-02-24 10:56:41 +09:00
Parneet Singh
50eb13a850
delete file (#6205)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-02-23 22:36:52 +09:00
Sujal
a8e38f4329
Updated Icon & Added Animation for Nearby (#6201)
* Applied better animation in nearby

* Refactor: Reduce Duration of Rotate Animation

Decreased the duration of the rotate animation from 1000ms to 500ms in `rotate.xml`, resulting in a faster rotation speed.
2025-02-23 22:36:07 +09:00
Sujal
d32ab15d42
Optimize Image Handling and Open Wikidata Media within app (#6187)
* implementing

* implementing

* implementing

* implementing

* implementing

* make new changes

* done

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-02-22 13:18:12 +09:00
Sujal
8dd1091608
Separate Workflow for Commenting on PR with APK Links (#6199)
* Update android.yml

* Create android-ci-comment.yml

* Update android.yml
2025-02-22 13:11:57 +09:00
translatewiki.net
44f69fcabd
Localisation updates from https://translatewiki.net. 2025-02-20 13:01:50 +01:00
Sujal
8d0da86569
Automatically Link APKs in PR Discussion After Building (#6195)
* Update android.yml

* Update android.yml

* Update android.yml

* Update android.yml

* Update android.yml
2025-02-19 21:43:58 +09:00
Jason-Whitmore
98b25acab9
Fixes Issue 5933: Nearby: Display of all nearby pins makes the app sluggish, leads to crashes (#6181)
* Place.java: change getWikiDataEntityID() method to increase speed

Before this commit, this method would perform the String replace method on the Wikidata link every time
getWikiDataEntityID() was called. Also, getWikiDataLink() was called. This caused poor performance
since both method calls are slow.

This commit changes the method to only run the slow methods if the entityID field is empty or
null. Once the field is populated, the method simply returns the field. This change allows
getWikiDataEntityID() to run much faster.

* NearbyParentFragmentPresenter.kt: change async and place update parameters

Before this commit, the parameters that configure the async and place update code contributed to
the slow loading of the red and green map markers.

This commit changes the parameters such that the red and green map markers load much faster.

These parameters may need further tuning. This commit's changes are simply an educated
guess at a good parameter set.

* NearbyParentFragment.kt: rewrite Java Drawable caching code to Kotlin

Before this commit, the code which cached Drawables was written in Java.

This commit rewrites that code into the new Kotlin file, which replaces the Java file.

* NearbyParentFragmentPresenter.kt: change loadPlacesDataAsync to retry HTTP requests after failure

Before this commit, when an HTTP request failed, the entire batch of Places would not get updated,
even if it was only one Place in the batch that caused the failure.

This commit changes the code such that upon HTTP request failure, new HTTP requests are sent with
one Place per request. This allows more Places to be fetched from the server.
2025-02-19 15:42:24 +09:00
Sujal
40241b4142
Fix #6188 (Nearby upload not being linked from Wikidata), though it introduces issue #6191
* Make neccesary changes

* Make neccesary changes

* spacing

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-02-17 23:02:45 +09:00
translatewiki.net
7a685b1241
Localisation updates from https://translatewiki.net. 2025-02-17 13:01:49 +01:00
translatewiki.net
34943542bf
Localisation updates from https://translatewiki.net. 2025-02-13 13:01:47 +01:00
Sujal
6345fef6bf
Migrated nearby parent fragment file to kotlin (#6177)
* Rename .java to .kt

* Migrated

* Migrated

* Migrated
2025-02-12 16:51:22 +09:00
translatewiki.net
a529ba8032
Localisation updates from https://translatewiki.net. 2025-02-10 13:01:39 +01:00
Sonal Yadav
e9e2697369
Fix: Fix crash when adding location after removing a picture (#6175)
* Fix: Resolved the Crash in UploadMediaDetailFragment

* Fix uninitialized basicKvStoreFactory in UploadMediaPresenter

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-02-07 23:10:18 +09:00
Sujal
12cadd0186
Migrated contributions folder Files from java to kotlin (#6176)
* Rename .java to .kt

* Migrated ContributionController

* Rename .java to .kt

* Migrated ContributionDao

* Rename .java to .kt

* Migrated ContributionsContract,ContributionFragment,ContributionListAdapter,ContributionsListContract from java to Kotlin

* Rename .java to .kt

* converted/Migrated

* converted/Migrated

* Rename .java to .kt

* Migrated ContributionController

* Rename .java to .kt

* Migrated ContributionDao

* Rename .java to .kt

* Migrated ContributionsContract,ContributionFragment,ContributionListAdapter,ContributionsListContract from java to Kotlin

* Rename .java to .kt

* Show placeholder and display depiction section when no depictions are available (#6163) (#6165)

* corrected

* corrected

* Update MediaDetailFragment.kt

Spelling correction

* Migrated AboutActivity from Java to Kotlin (#6158)

* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage

* Refactor Dialog View Initialization with Null-Safe Calls

This PR refactors the dialog setup code in CustomSelectorActivity to improve safety and readability by replacing explicit casts with null-safe generic calls for findViewById.

>Replaced explicit casting (as Button and as TextView) with the generic findViewById<T>() method for improved type safety.
>Added null-safety (?.) to avoid potential crashes if a view is not found in the dialog layout.

why changed:-
>Prevents runtime crashes caused by NullPointerException when a view is missing in the layout.

* Refactor Unit Test: Replace Unsafe Casting with Type-Safe Mocking for findViewById

>PR refactors the unit test for NearbyParentFragment by replacing unsafe casting in the findViewById mocking statements with type-safe

>Ensured all findViewById mocks now use a consistent, type-safe format (findViewById<View>(...)) to reduce verbosity and potential casting errors.

>Verified the functionality of prepareViewsForSheetPosition remains unchanged, ensuring no regression in test behavior.

* Update NearbyParentFragmentUnitTest.kt

* Refactor: Rename Constants to Follow CamelCase Naming Convention

>Updated all constant variable names to follow the camelCase naming convention, removing underscores in the middle or end.

>Ensured variable names remain descriptive and align with code readability best practices.

* Replace private val with const val for URL constants in QuizController

* Renaming the constant to use UPPER_SNAKE_CASE

* Renaming the constant to use UPPER_SNAKE_CASE

* Update Done

* **Refactor: Convert `minimumThresholdForSwipe` to a compile-time constant**

* Convert AboutActivity from Java to Kotlin

This PR converts the AboutActivity class from Java to Kotlin

>Testing:
>Verified all functionalities of the AboutActivity, including toolbar setup, intent launches, and dialog interactions, to ensure behavior remains consistent post-conversion.
>Successfully ran unit tests for AboutActivity to confirm the correctness of methods and logic.

* Thank you for the suggestion! Since these methods all take a single View parameter, replacing them with method references is a great way to simplify the code and improve readability. I'll updated the code accordingly. Added a TODO in the code as a reminder to refactor this in the future.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* Localisation updates from https://translatewiki.net.

* Feat: Make it smoother to switch between nearby and explore maps (#6164)

* Nearby: Add 'Show in Explore' 3-dots menu item

* MainActivity: Add methods to pass extras between Nearby and Explore

* MainActivity: Extend loadFragment() to support passing fragment arguments

* Nearby: Add ability to navigate to Explore fragment on 'Show in Explore' click

* Explore: Read fragment arguments for Nearby map data and update Explore map if present

* Explore: Add 'Show in Nearby' 3-dots menu item. Only visible when Map tab is selected

* Explore: On 'Show in Nearby' click, navigate to Nearby fragment, passing map data as fragment args

* Nearby: Read fragment arguments for Explore map data and update Nearby map if present

* MainActivity: Fix memory leaks when navigating between bottom nav destinations

* Explore: Fix crashes caused by unattached map fragment

* Refactor code to pass unit tests

* Explore: Format javadocs

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* Localisation updates from https://translatewiki.net.

* enhance spammy category filter (#6167)

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* Localisation updates from https://translatewiki.net.

* correction

* correction

* correction

* GitHub workflow to build betaDebug (#6174)

* [Bug fix] Check if duplicate exist using both original and modified file's checksum (#6169)

* check original file's SHA too along with modified one

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* Add multiline input for caption and description (#6173)

* allow multiple lines for description/caption

* make caption multiline too

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* correction

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
Co-authored-by: Akshay Komar <146421342+Akshaykomar890@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
Co-authored-by: translatewiki.net <l10n-bot@translatewiki.net>
Co-authored-by: Ifeoluwa Andrew Omole <iomole3@gmail.com>
Co-authored-by: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com>
Co-authored-by: Matija Nalis <mnalis-git@voyager.hr>
2025-02-07 10:03:38 +09:00
Matija Nalis
1e77b1457a
Add multiline input for caption and description (#6173)
* allow multiple lines for description/caption

* make caption multiline too

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-02-05 15:56:57 +09:00
Parneet Singh
43dca1dd14
[Bug fix] Check if duplicate exist using both original and modified file's checksum (#6169)
* check original file's SHA too along with modified one

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-02-05 04:20:22 +10:00
Matija Nalis
30a7f702a1
GitHub workflow to build betaDebug (#6174) 2025-02-04 19:34:14 +09:00
translatewiki.net
0293b865b4
Localisation updates from https://translatewiki.net. 2025-02-03 13:01:34 +01:00
Parneet Singh
7566ddf529
enhance spammy category filter (#6167)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-02-01 09:13:17 +09:00
translatewiki.net
e653857437
Localisation updates from https://translatewiki.net. 2025-01-31 09:49:38 +01:00
Ifeoluwa Andrew Omole
7b291535e0
Feat: Make it smoother to switch between nearby and explore maps (#6164)
* Nearby: Add 'Show in Explore' 3-dots menu item

* MainActivity: Add methods to pass extras between Nearby and Explore

* MainActivity: Extend loadFragment() to support passing fragment arguments

* Nearby: Add ability to navigate to Explore fragment on 'Show in Explore' click

* Explore: Read fragment arguments for Nearby map data and update Explore map if present

* Explore: Add 'Show in Nearby' 3-dots menu item. Only visible when Map tab is selected

* Explore: On 'Show in Nearby' click, navigate to Nearby fragment, passing map data as fragment args

* Nearby: Read fragment arguments for Explore map data and update Nearby map if present

* MainActivity: Fix memory leaks when navigating between bottom nav destinations

* Explore: Fix crashes caused by unattached map fragment

* Refactor code to pass unit tests

* Explore: Format javadocs

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-30 21:58:00 +09:00
translatewiki.net
9dc9a3b8ab
Localisation updates from https://translatewiki.net. 2025-01-30 13:01:33 +01:00
Akshay Komar
5d4474ead6
Migrated AboutActivity from Java to Kotlin (#6158)
* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage

* Refactor Dialog View Initialization with Null-Safe Calls

This PR refactors the dialog setup code in CustomSelectorActivity to improve safety and readability by replacing explicit casts with null-safe generic calls for findViewById.

>Replaced explicit casting (as Button and as TextView) with the generic findViewById<T>() method for improved type safety.
>Added null-safety (?.) to avoid potential crashes if a view is not found in the dialog layout.

why changed:-
>Prevents runtime crashes caused by NullPointerException when a view is missing in the layout.

* Refactor Unit Test: Replace Unsafe Casting with Type-Safe Mocking for findViewById

>PR refactors the unit test for NearbyParentFragment by replacing unsafe casting in the findViewById mocking statements with type-safe

>Ensured all findViewById mocks now use a consistent, type-safe format (findViewById<View>(...)) to reduce verbosity and potential casting errors.

>Verified the functionality of prepareViewsForSheetPosition remains unchanged, ensuring no regression in test behavior.

* Update NearbyParentFragmentUnitTest.kt

* Refactor: Rename Constants to Follow CamelCase Naming Convention

>Updated all constant variable names to follow the camelCase naming convention, removing underscores in the middle or end.

>Ensured variable names remain descriptive and align with code readability best practices.

* Replace private val with const val for URL constants in QuizController

* Renaming the constant to use UPPER_SNAKE_CASE

* Renaming the constant to use UPPER_SNAKE_CASE

* Update Done

* **Refactor: Convert `minimumThresholdForSwipe` to a compile-time constant**

* Convert AboutActivity from Java to Kotlin

This PR converts the AboutActivity class from Java to Kotlin

>Testing:
>Verified all functionalities of the AboutActivity, including toolbar setup, intent launches, and dialog interactions, to ensure behavior remains consistent post-conversion.
>Successfully ran unit tests for AboutActivity to confirm the correctness of methods and logic.

* Thank you for the suggestion! Since these methods all take a single View parameter, replacing them with method references is a great way to simplify the code and improve readability. I'll updated the code accordingly. Added a TODO in the code as a reminder to refactor this in the future.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-30 16:41:27 +09:00
Sujal
36f844a709
Show placeholder and display depiction section when no depictions are available (#6163) (#6165)
* corrected

* corrected

* Update MediaDetailFragment.kt

Spelling correction
2025-01-29 22:50:29 +09:00
Tanmay Gupta
e01ecb20fa
maps intent: preserve zoom and show red pin (#6160)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-28 11:36:35 +09:00
yuvraj-coder1
41170d81d9
fix: logout user after account deletion by navigating to login screen (#6159) 2025-01-27 22:39:32 +10:00
translatewiki.net
7400872f87
Localisation updates from https://translatewiki.net. 2025-01-27 13:01:31 +01:00
Tanmay Gupta
aedcd7f9b9
Review: Load thumbnail instead of original image (#6153)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-24 16:02:33 +09:00
Tanmay Gupta
bb974f8935
Android CI: Update upload-artifact version (#6157) 2025-01-24 09:52:09 +09:00
translatewiki.net
77bad3380c
Localisation updates from https://translatewiki.net. 2025-01-23 13:01:40 +01:00
translatewiki.net
3570377678
Localisation updates from https://translatewiki.net. 2025-01-20 13:01:41 +01:00
Akshay Komar
d4ababc0a5
Refactor: Rename Constants to Follow CamelCase Naming Convention (#6126)
* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage

* Refactor Dialog View Initialization with Null-Safe Calls

This PR refactors the dialog setup code in CustomSelectorActivity to improve safety and readability by replacing explicit casts with null-safe generic calls for findViewById.

>Replaced explicit casting (as Button and as TextView) with the generic findViewById<T>() method for improved type safety.
>Added null-safety (?.) to avoid potential crashes if a view is not found in the dialog layout.

why changed:-
>Prevents runtime crashes caused by NullPointerException when a view is missing in the layout.

* Refactor Unit Test: Replace Unsafe Casting with Type-Safe Mocking for findViewById

>PR refactors the unit test for NearbyParentFragment by replacing unsafe casting in the findViewById mocking statements with type-safe

>Ensured all findViewById mocks now use a consistent, type-safe format (findViewById<View>(...)) to reduce verbosity and potential casting errors.

>Verified the functionality of prepareViewsForSheetPosition remains unchanged, ensuring no regression in test behavior.

* Update NearbyParentFragmentUnitTest.kt

* Refactor: Rename Constants to Follow CamelCase Naming Convention

>Updated all constant variable names to follow the camelCase naming convention, removing underscores in the middle or end.

>Ensured variable names remain descriptive and align with code readability best practices.

* Replace private val with const val for URL constants in QuizController

* Renaming the constant to use UPPER_SNAKE_CASE

* Renaming the constant to use UPPER_SNAKE_CASE

* Update Done

* **Refactor: Convert `minimumThresholdForSwipe` to a compile-time constant**

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-18 23:06:52 +09:00
yuvraj-coder1
1c6ebafb29
feat: show the proper message in the custom picker when all images are either uploaded or marked. (#6095)
* feat: show the message "Congratulations, all pictures in this album have been either uploaded or marked as not for upload." in the custom picker when all images are either uploaded or marked.

* refactor: fix indentation in the code

* refactor: replace LiveData with StateFlow

* fix: fixed the bug that was causing one ImageFragment testcase to fail.

* fix: fixed the Congratulation message being shown for a brief moment while switching off the switch

* refactor: move hardcoded string to strings.xml.

* docs: add comment to clarify visibility logic in ImageFragment

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-18 23:04:28 +09:00
Sujal
23e1f01783
fix/handle-shared-files-and-error-handling (#6141) 2025-01-18 03:19:16 +10:00
Tanmay Gupta
ef032b0f93
Disable vanishing option when logged out (#6135) 2025-01-18 03:19:00 +10:00
Yusuke Matsubara
35a2fe87db
Use variant-dependent variable to make URLs for vanishing (#6117) 2025-01-17 17:33:50 +09:00
Rohit Verma
9f1fe8737f
Fix crash when opening in-app Camera for the very first time (#6139)
* fix: correctly handle permission callbacks on Main thread

The PermissionUtils was incorrectly executing permission callbacks on a background thread, leading to a Handler error. Now, it is using Main dispatcher.

* fix crash when opening camera while having partial storage access
2025-01-17 15:37:07 +09:00
translatewiki.net
2d6583fea6
Localisation updates from https://translatewiki.net. 2025-01-16 13:01:53 +01:00
Tanmay Gupta
1e64acdf1d
If depicted Wikidata item has no associated Commons category property, then suggest categories from its P18 (#6130)
* Fix NPE with UploadMediaDetails.captionText

* Store P18 instead of processed image url in DepictedItem

* Add routes for fetching category info from titles

* Consider depict's P18 when suggesting categories

* Add tests

* Corrected DepictedItem constructor arguments

* Add test for DepictedItem::primaryImage
2025-01-16 18:04:04 +09:00
Tanmay Gupta
1f33926ed5
Share login state with SingleWebViewActivity (#6136) 2025-01-16 17:49:09 +09:00
Ifeoluwa Andrew Omole
16ac08fe21
change status bar color to grey in dark mode (#6120)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-16 17:41:37 +09:00
Sujal
70291a0cb2
Migrated Java profile package to kotlin (#6119)
* Rename .java to .kt

* Migrate Profile Package to Kotlin #6118 #5928

* Migrate Profile Package to Kotlin #6118 #5928

* Migrate Profile Package to Kotlin #6118 #5928
2025-01-16 16:36:02 +09:00
Tanmay Gupta
62136b5b09
Fix NPE with UploadMediaDetails.captionText (#6128)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-15 22:52:35 +09:00
Kaartic Sivaraam
76078cf3b5 Merge remote-tracking branch 'origin/v5.1.0' 2025-01-15 12:52:36 +05:30
Kaartic Sivaraam
be0b1db193 Version v5.1.2 2025-01-15 12:50:53 +05:30
Kaartic Sivaraam
a796a8adcf category: fix category search in explore
This is a change that replicates the fix done via PR #6093.
This is based over v5.1.1 as the fix seems necessary for
the category search to work properly on the explore screen.
2025-01-15 12:32:50 +05:30
Kaartic Sivaraam
efc9ae8fb6 Merge remote-tracking branch 'origin/v5.1.0' 2025-01-15 11:53:59 +05:30
Jason-Whitmore
d4a9bacd91
Fixes Issue #5832: Navigation Banner Appears in Media Details Screen (#6121)
* MediaDetailFragment.kt: add helper method to retrieve ContributionFragment instance

Before this commit, there was no easy way to check for and retrieve the ContributionFragment instance that
was either the parent or grandparent (parent's parent) fragment. A complicated if check was required to
retrieve it.

After this commit, there is a simple helper method which will retrieve the ContributionFragment instance.
Existing code can now be replaced by calling this method.

* MediaDetailFragment.kt: replace code that is meant to hide nearby card

Before this commit, code would attempt to find and hide the nearby card that would appear
when the user was looking at media details. However, this code did not work.

After this commit, the old code has been replaced with code that correctly hides the
nearby card. Also, this new code uses a helper method call and is overall easier to read.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-14 08:17:14 +09:00
Paul Hawke
0e735512bb
Convert upload to kotlin (part 3) (#6104)
* Convert UploadCategoriesFragment to kotlin

* Convert UploadBaseFragment to kotlin

* Convert UploadItem to kotlin

* Convert UploadModel to kotlin

* Convert UploadMediaDetailAdapter to kotlin

* Convert UploadActivity to kotlin

* Convert UploadMediaPresenter to kotlin

* Convert UploadMediaDetailFragment to kotlin

* Fix NPE that broke uploads
2025-01-13 23:04:09 +09:00
translatewiki.net
6d64357d45
Localisation updates from https://translatewiki.net. 2025-01-13 13:01:48 +01:00
Akshay Komar
78666ccbde
Refactor Dialog View Initialization with Null-Safe Calls (#6114)
* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage

* Refactor Dialog View Initialization with Null-Safe Calls

This PR refactors the dialog setup code in CustomSelectorActivity to improve safety and readability by replacing explicit casts with null-safe generic calls for findViewById.

>Replaced explicit casting (as Button and as TextView) with the generic findViewById<T>() method for improved type safety.
>Added null-safety (?.) to avoid potential crashes if a view is not found in the dialog layout.

why changed:-
>Prevents runtime crashes caused by NullPointerException when a view is missing in the layout.
2025-01-11 08:46:19 +09:00
Neel Doshi
39b513da12
feat : Account Vanishing (#6098)
* feat : Account Vanishing

* Added Comment for SingleWebViewActivity

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-09 23:27:08 +09:00
translatewiki.net
18f599b554
Localisation updates from https://translatewiki.net. 2025-01-09 13:01:40 +01:00
Akshay Komar
3e7565c7e3
Refactor variable names to adhere to naming conventions (#6111)
* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml

* Refactor variable names to adhere to naming conventions

Renamed variables to use camel case:
-UPLOAD_COUNT_THRESHOLD → uploadCountThreshold
-REVERT_PERCENTAGE_FOR_MESSAGE → revertPercentageForMessage
-REVERT_SHARED_PREFERENCE → revertSharedPreference
-UPLOAD_SHARED_PREFERENCE → uploadSharedPreference

Renamed variables with uppercase initials to lowercase for alignment with Kotlin conventions:
-Latitude → latitude
-Longitude → longitude
-Accuracy → accuracy

Refactored the following variable names:
-NUMBER_OF_QUESTIONS → numberOfQuestions
-MULTIPLIER_TO_GET_PERCENTAGE → multiplierToGetPercentage
2025-01-09 17:22:42 +09:00
Saifuddin Adenwala
87a453cb72
Migrated concurrency module from Java to Kotiln (#6110)
* Rename .java to .kt

* Migrated concurrency module to Kotlin
2025-01-08 15:42:53 +09:00
Nachiket Jadhav
fdbe504ca9
fix: logo getting cropped in landscape mode of login page (#6106)
* Fixed logo getting cropped in landscape mode of login page

* Indentation added at line 12

* New Dimension created in dimens.xml which is used in land\activity_login.xml
2025-01-08 05:33:41 +10:00
yuvraj-coder1
b2159ed87f
feat: Long-pressing a row in "Uploads" copies the caption to clipboard, and displays a snack saying "Caption copied to clipboard" (#6105)
* feat: Long-pressing a row in "Uploads" copies the caption to clipboard, and displays a snack saying "Caption copied to clipboard

* refactor: using string resources for the text instead of hard coded values
2025-01-07 07:12:24 +10:00
Akshay Komar
ecb19d6984
Rename Constants to Follow Kotlin Naming Conventions (#6107)
* Rename Constants to Follow Kotlin Naming Conventions

>This PR refactors constant names in the project to adhere to Kotlin's UPPERCASE_SNAKE_CASE naming convention, improving code readability and maintaining consistency across the codebase.

>Renamed the following constants in LoginActivity:
>saveProgressDialog → SAVE_PROGRESS_DIALOG
>saveErrorMessage → SAVE_ERROR_MESSAGE
>saveUsername → SAVE_USERNAME
>savePassword → SAVE_PASSWORD

>Updated all references to these constants throughout the project.

* Update Project_Default.xml
2025-01-07 03:00:02 +10:00
translatewiki.net
940c0740b0
Localisation updates from https://translatewiki.net. 2025-01-06 13:01:38 +01:00
Neel Doshi
cebe1c2a1f
chore : lint fix (#6099)
- Removed unused constants which were there in `Pref`
- Removed unused parameter requireActivity from `Setting Fragment`

Revert "chore : lint fix"

This reverts commit 599203343f4c3c050b45e4ccf53fd23097818cc4.
2025-01-06 02:48:05 +10:00
Paul Hawke
1d8d1d6b03
Remove the extra byte buffer copying while creating file chunks (#6091)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2025-01-05 20:44:30 +09:00
Akshay Komar
25e467b3a5
Refactor Nearby Location Tests to Improve Assertion Logic (#6103)
-Refactored testSearchCloseToCurrentLocationWhenFar: Simplified assertion by using assertFalse(!isClose) for better readability and logical clarity.

-Added testSearchCloseToCurrentLocationWhenClose: Created a new test case to validate behavior when the search is close to the current location, ensuring assertTrue(isClose) for correctness.

-Improved Coverage: These changes enhance the test coverage and reliability of the searchCloseToCurrentLocation function.
2025-01-05 17:21:17 +09:00
Parneet Singh
038ae9acd4
fix null pointer exception (#6093)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2025-01-03 22:32:06 +10:00
translatewiki.net
ea20a64b34
Localisation updates from https://translatewiki.net. 2025-01-02 13:01:43 +01:00
Tanmay Gupta
411184fde8
Nearby: Fixed recenter and legend FABs inconsistencies (#6092)
Using app:useCompatPadding=true on both the FABs caused padding issues, replaced that with android:layout_margin. As the minimumSdk version is >=lollipop, I believe useCompatPadding is not required.
2025-01-02 15:09:18 +09:00
translatewiki.net
bf89f11606
Localisation updates from https://translatewiki.net. 2024-12-30 13:01:44 +01:00
Kaartic Sivaraam
391408ed17 CHANGELOG: fix some typos 2024-12-29 22:43:21 +05:30
Kaartic Sivaraam
9bbb1a98db Version 5.1.1 2024-12-29 22:36:39 +05:30
Sujal
faa58a19de
Fixed bug #5876 (#6086) 2024-12-29 16:09:03 +09:00
Tanmay Gupta
d2751595cb
Nearby: Markers now show place via pin base (#6090) 2024-12-29 15:46:29 +09:00
Gautham Mohanraj
46cefa4899
Fix/feedback to map navigation (#6087)
* feat:Added try catch block for the Feedback

* fix:Added finish after the feedback submitted so that we navigate back to the map
2024-12-29 11:03:47 +09:00
Tanmay Gupta
5bc58284aa
Nearby: Avoid reloading entire map upon cache clear (#6089) 2024-12-29 10:44:47 +09:00
Parneet Singh
a6444968fa
add cancel button (#6078)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-29 08:20:32 +09:00
Parneet Singh
dec56a3342
rm file (#6079)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-29 07:53:04 +09:00
Parneet Singh
91e41c4c18 Always show upload icon (#6022)
* fix issue5847 as owner required: make the icon always visible and just adjust the count accordingly,comment the setVisibility method and make pending_upload_icon always visible

* set pending_upload_icon android visible level: visible and tool visible level: gone

* fix issue 5847, The upload icon is now set to always be visible, while the original code has been commented out and retained with a note for potential re-use in the future.

* refactor

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
Co-authored-by: bxy379987 <bxy379987@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-28 23:55:27 +05:30
Neel Doshi
b714b45bfd Migrated exifInterface to androidx exif.interface (#6013) 2024-12-28 23:43:26 +05:30
Parneet Singh
22238f55cd
fix parsing (#6075)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-27 01:27:03 +10:00
Sonal Yadav
4244373a5d
Refactor focus change listener to improve readability and avoid crashes (#6080) 2024-12-27 01:26:08 +10:00
Tanmay Gupta
75ca96a526
Nearby: show cached pins even when internet is unavailable (fixes #6051) (#6081)
* Place: Made location @Embedded

* Nearby: Move handling map scroll to presenter

* PlacesRepository: Add methods for fetching places in geo bounds

* Nearby: add getScreenTopRight/BottomLeft and refactor old code

* PlacesRepository: Add methods for fetching places in map bounds

* Nearby: Complete offline pins implementation

* Nearby offline pins: Add snackbar message

* Nearby offline pins: Add javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-26 23:55:21 +09:00
translatewiki.net
0d71da106f
Localisation updates from https://translatewiki.net. 2024-12-26 13:01:25 +01:00
Parneet Singh
86cdf96f3d
make prop nullable (#6073)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-25 00:55:12 +09:00
Sonal Yadav
e7864ac1dd
Refactor usage of Html.fromHtml to handle deprecation (#6074) 2024-12-25 00:44:25 +09:00
Paul Hawke
a9058d129e
Convert upload to kotlin (part 2) (#6069)
* Convert UploadCategoriesFragment to kotlin

* Convert DepictsFragment to kotlin

* Convert MediaLicensePresenter to kotlin

* Convert MediaLicenseFragment to kotlin

* Converted SimilarImageDialogFragment to kotlin

* Convert ThumbnailsAdapter to kotlin

* Convert UploadPresenter to kotlin

* Convert UploadBaseFragment to kotlin

* Convert UploadMediaDetailInputFilter to kotlin

* Convert UploadItem to kotlin

* Convert UploadController to kotlin

* Fix nullability of the UploadItem
2024-12-24 16:11:46 +09:00
Tanmay Gupta
369e79be5e
Nearby: No longer keeps loading until timeout when map is zoomed out (#6070)
* Nearby: Search for actual map center

* Add query syntax and methods

* Nearby: Added binary search for loading pins

* Add NearbyQueryParams and refactor

* Add unit tests and complete implementation

* Nearby: Increase max radius from 100km to 300km

* Nearby: Centermost pins now appear on top

* getNearbyItemCount: Added javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-24 15:21:45 +09:00
translatewiki.net
c963cd9ea4
Localisation updates from https://translatewiki.net. 2024-12-23 13:02:14 +01:00
Sonal Yadav
7479767266
Fix: Allow back button functionality to dismiss language selection dialog (#6067)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-23 20:38:45 +09:00
Sonal Yadav
b55c61ddb8
Fixed the migrations warning under Kotlin header #13282 (#6060)
* Fixed  Migration warnings under Kotlin header

* suppresses Lint

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-22 23:14:20 +09:00
Neel Doshi
f1e8e48769
Removed deneme8 Timber tag (#6065) 2024-12-22 22:05:25 +09:00
Neel Doshi
d0bde4a3fe
Replaces Log to Timber (#6062) 2024-12-22 14:44:57 +09:00
Tanmay Gupta
6a32454347
Nearby: Fix map moving by itself (#6061)
fixes #6046

the OnFocusChangeListener for nearby place list items sometimes gets invoked when new items aer set, even when the list is hidden, if an item had previously been clicked in it. This in turn causes the onItemClick to be called. This commit adds a check to make sure the list is not hidden when onItemClick is invoked this way.
2024-12-22 08:39:30 +09:00
Tanmay Gupta
4dd16054ca
Nearby: Fix disappearing of pins loaded from cache (#6052) (#6057)
* Nearby Fix disappearing of pins loaded from cache

Fixes #6052

* Remove outdated tests

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-21 20:10:33 +09:00
Mohit Kambli
4b152fc15f
Fix #6054: Rename SAVEPROGRESSDAILOG to SAVEPROGRESSDIALOG (#6058) 2024-12-21 19:55:51 +09:00
Tanmay Gupta
c891c2b0df
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
2024-12-20 22:30:53 +10:00
Parneet Singh
70b4f78a5d
fix multi-line paste (#6050)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-20 22:29:08 +10:00
Parneet Singh
4c9637c821
Add pull down to refresh in Contributions screen (#6041)
* pull down to refresh

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add kdoc

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* only enabled for self user

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-20 10:06:07 +09:00
translatewiki.net
a4b74794cb
Localisation updates from https://translatewiki.net. 2024-12-19 13:01:57 +01:00
Parneet Singh
5500b03976
Feature: Bookmark Categories (#6035)
* database setup and insert/delete working

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add new categories screen in bookmarks page

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add theme

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* set tab layout scrollable

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* cleanup

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* bump database version

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add docs

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-17 21:49:32 +09:00
translatewiki.net
0153cbe0ed
Localisation updates from https://translatewiki.net. 2024-12-16 13:01:57 +01:00
Tanmay Gupta
e8970ab7f2
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
2024-12-15 21:21:30 +09:00
Tanmay Gupta
a933b92efa
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 <nicolas.raoul@gmail.com>
2024-12-14 19:12:44 +09:00
Haoyu Weng
f2d1f7dbbb
[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 <justweng19@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-14 18:45:44 +09:00
Tanmay Gupta
235e8cdba2
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
2024-12-14 17:31:25 +09:00
Parneet Singh
c1acdbe31a
remove method (#6028)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-14 13:15:52 +09:00
Paul Hawke
2c8c441f25
Convert upload to kotlin (part 1) (#6024)
* Convert upload dagger module to kotlin

* Code cleanup and convert the upload contract to kotlin

* Code cleanup and convert CategoriesContract to kotlin

* Code cleanup and convert MediaLicenseContract to kotlin

* Code cleanup and convert UploadMediaDetailsContract to kotlin

* Code cleanup, fixed nullability and converted DepictsContract to kotlin

* Removed unused class

* Convert FileMetadataUtils to kotlin

* Convert EXIFReader to kotlin

* Convert FileUtils to kotlin

* Convert FileUtilsWrapper to kotlin

* Convert ImageProcessingService to kotlin

* Convert PageContentsCreator to kotlin

* Convert PendingUploadsPresenter and contract to Kotlin with some code-cleanup

* Convert ReadFBMD to kotlin

* Convert SimilarImageInterface to kotlin

* Removed unused classes

* Fix merge/rebase issue

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-13 23:15:13 +09:00
Tanmay Gupta
cb007608d9
Issue #5996 - Fix Android Studio warnings in LocationPickerActivity.kt (#6026)
* Replace deprecated zoomLevel with zoomLevelDouble

* Replace deprecated Html.fromHtml call

* LocationPickerActivity: Replace deprecated methods with androidx

* LocationPickerActivity: Reformatted overlong lines

* Renamed package LocationPicker to locationpicker

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-13 22:49:40 +09:00
Tanmay Gupta
8a55b5e613
FolderDeletionHelper: Fix unintentional deletion (#6027)
* fix issue6020: prevent unintentional deletion of subfolders and non-images by custom selector

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-13 22:41:47 +09:00
Gautham Mohanraj
b2810bcef1
Resolve crash when submitting feedback without internet access (#6018)
* fix: resolve crash when submitting feedback without internet access

* feat:Added check for wheather internet connection was available for a network

* feat:Added SnackBar for Retry and User Info

* feat:Made the feedback dialog not leave screen in case of error

* feat:Removed the network checking from the function

* feat:Added try catch block for the Feedback

* feat:Removed Unnecessary imports

* feat:Used Snackbar and timber instead of log and Toast

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-13 22:30:30 +09:00
translatewiki.net
f51b607312
Localisation updates from https://translatewiki.net. 2024-12-12 13:02:18 +01:00
Parneet Singh
3bfa3612c6
Always show upload icon (#6022)
* fix issue5847 as owner required: make the icon always visible and just adjust the count accordingly,comment the setVisibility method and make pending_upload_icon always visible

* set pending_upload_icon android visible level: visible and tool visible level: gone

* fix issue 5847, The upload icon is now set to always be visible, while the original code has been commented out and retained with a note for potential re-use in the future.

* refactor

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
Co-authored-by: bxy379987 <bxy379987@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-11 23:29:50 +09:00
Parneet Singh
9a876fa5e2
make dialog modal (#6015)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-11 15:08:39 +09:00
Saifuddin Adenwala
c175a4ee03
Migrated category module from Java to Kotlin (#6016)
* Rename .java to .kt

* Rebased category PR

* Resolved conflicts

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-11 11:17:05 +09:00
Saifuddin Adenwala
3030a6fca7
Migrated helper modules to kotlin (#6007)
* Rename .java to .kt

* Migrated delete and description module to kotlin (WIP)

* Fix: Unit tests

* Fix: Unit tests

* Rename .java to .kt

* Migrated data, db, and converter module to kotlin

* Fix: Unit tests

* Fix: Unit tests

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-11 07:43:36 +09:00
Paul Hawke
73311970c5
Convert wikidata/mwapi to kotlin (part 4) (#6010)
* Convert ImageDetails to kotlin

* Convert MwException/MwServiceError to kotlin

* Convert ListUserResponse to kotlin

* Convert MwPostResponse to kotlin

* Convert MwQueryResponse to kotlin

* Convert MwQueryResult to kotlin

* Convert MwQueryPage to kotlin

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-10 12:18:42 +09:00
Neel Doshi
56ada36b83
Migrated exifInterface to androidx exif.interface (#6013) 2024-12-10 03:54:11 +10:00
Parneet Singh
85d9aef2f3
Feature: Show where file is being used on Commons & Other wikis (#6006)
* add url to build config

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add network call functions

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* return response asynchronously

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* inject page size in the request

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* rename from Commons..Response.kt to ..Response.kt

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* convert to .kt

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* ui setup working

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix merge conflict

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* cleanup

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix CI

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use suspend function for network calls

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* doc

* doc

* doc

* doc

* doc

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-09 23:07:44 +09:00
translatewiki.net
04a07ed655
Localisation updates from https://translatewiki.net. 2024-12-09 13:02:13 +01:00
Parneet Singh
cc74707894
delete empty files (#6009)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-12-08 14:51:37 +09:00
Neel Doshi
64fd10d00e
Migrate Feedback module from Java to Kt (#5985)
* Rename `.java` to `.kt`

* Migrated FeedbackContentCreator to kotlin

* Rename FeedbackDialog from `java` to `kt`

* Migrated Feedback Dialog from `java` to `kt`

* Renamed OnFeedbackSubmitCallback to kotlij

* Migrated OnFeedbackSubmitCallback to kotlin

* Fixed: TestCase Failure

* Fixed Test : Changed Private Modifier to Public

* Suppressed deprecated and added TODO for lint

* Linked Deprecation with Github Issue

* Rename Feedback from java to kt

* Migrated Feedback Data Class to Kotlin

* Modified the data class to var for mutuability
2024-12-07 16:19:00 +09:00
Paul Hawke
015c5d5c63
Convert wikidata/mwapi to kotlin (part 3) (#6004)
* Convert Edit to kotlin along with deleting unused class

* Converted ExtMetadata to kotlin

* Convert ImageInfo to kotlin

* Removed unused class

* Convert Notification to kotlin

* Convert PageProperties to kotlin

* Convert PageTitle to kotlin

* Convert Namespace to kotlin
2024-12-07 12:20:06 +09:00
Neel Doshi
64354fb9e4
chore : Lint fix (#5995)
* Refactor : Fixed lint issues on EditActivity

- Using 'Log' instead of 'Timber'
- Line is longer than allowed by code style (> 100 columns)
- Use of getter method instead of property access syntax
- Should be replaced with Kotlin function
- Cascade 'if' should be replaced with 'when'

* Suppress Deprecation

* Linked Deprecated with Github Issue

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-07 02:24:22 +10:00
Neel Doshi
a8387f01c9
Bug Fixs & Enhancement of Achievement Screen (#5666)
* Rename AchievementFragment from `.java` to `.kt`

* Migrated AchievementFragment to kotlin

* Revamped Achievement Screen

* fixed AchievementFragment Unit Test

* fixed Level on MoreBottomSheetFragment

* Implemented Badge and Minor Code Refactor

* Fixed the badge issue & made the badge clickable

* Removed Redundant XML Code & Converted badges to green color and added values inside it

* Fixed : showSnackBarWithRetry Test

* Fixed : Theme issues on Light Mode

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-06 23:15:47 +09:00
Paul Hawke
ae52267a27
Convert wikidata/mwapi to kotlin (part 2) (#5999)
* Convert DepictSearchResponse to kotlin

* Convert Entities to kotlin

* Convert WikiSite to kotlin

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-06 17:50:29 +09:00
Saifuddin Adenwala
f8d519e8eb
Migrated filepicker from Java to Kotlin (#5997)
* Rename .java to .kt

* Migrated filepicker module from Java to Kotlin

* Rename .java to .kt

* Migrated filepicker module from Java to Kotlin

* fix: test cases
2024-12-06 17:31:40 +09:00
Paul Hawke
3777f18bf9
Convert mwapi/wikidata to kotlin (part 1) (#5991)
* Convert OkHttpJsonApiClient and CategoryApi to kotlin

* Convert GsonUtil to kotlin

* Convert WikidataConstants to kotlin

* Convert WikidataEditListener to kotlin

* Convert WikidataEditService to kotlin

* work in progress

* Convert RequiredFieldsCheckOnReadTypeAdapterFactory to kotlin

* Converted type adapters

* Convert WikiSiteTypeAdapter to kotlin

* Fixed nullability
2024-12-05 23:13:38 +09:00
translatewiki.net
9dd504e560
Localisation updates from https://translatewiki.net. 2024-12-05 13:01:47 +01:00
Paul Hawke
33548fa57d
Convert profile package to kotlin (#5979)
* Convert ViewModelFactory to kotlin

* Convert UpdateAvatarResponse and related test to Kotlin

* Convert LeaderboardResponse and related test to kotlin

* Convert LeaderboardListAdapter to kotlin

* Convert UserDetailAdapter to kotlin

* Convert LeaderboardListViewModel to kotlin

* Convert DataSourceClass to kotlin

* Convert the LeaderboardFragment to kotlin

* Converted AchievementsFragment to kotlin

* Revert "Converted AchievementsFragment to kotlin"

This reverts commit 4fcbb81e5d.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-12-03 15:47:25 +09:00
Saifuddin Adenwala
8265cc6306
Migrate location and language module from Java to Kotlin (#5988)
* Rename .java to .kt

* Migrated location and language module from Java to Kotlin

* Changed lastLocation visibility
2024-12-03 15:27:11 +09:00
Saifuddin Adenwala
771f370f9a
Migration of locationpicker module from Java to Kotlin (#5981)
* Rename .java to .kt

* Migrated location picker module from Java to Kotlin
2024-12-02 16:54:26 +09:00
Neel Doshi
fb1ef3212d
Migrated Bookmark from Java to Kotlin (#5960)
* Rename Bookmark Pages from `.java` to `.kt`

* Migrated Bookmark Pages to kotlin
2024-11-30 10:51:53 +09:00
Paul Hawke
1e5521b434
Convert dependency inject ("di") package to kotlin (#5976)
* Convert a batch of easier modules

* Convert the NetworkingModule to kotlin

* Converted the ApplicationlessInjection to kotlin

* Convert CommonsDaggerAppCompatActivity to kotlin

* Convert CommonsDaggerContentProvider to kotlin

* Convert CommonsDaggerIntentService to kotlin

* Convert CommonsDaggerService to kotlin

* Convert CommonsDaggerSupportFragment to kotlin

* Convert CommonsDaggerBroadcastReceiver to kotlin

* Convert CommonsApplicationModule to kotlin

* Fix imports and make them consistent
2024-11-30 10:50:42 +09:00
Paul Hawke
dac3657536
Migrate kvstore to kotlin (#5973)
* Get good test around the basic KvStore before starting conversion

* Converted BasicKvStore to kotlin and removed dead code

* Converted JsonKvStore to kotlin

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-29 14:01:29 +09:00
Saifuddin Adenwala
d6c4cab207
Migrated logging module from Java to Kotlin (#5972)
* Migrated logging module from Java to Kotlin

* Rename .java to .kt

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-29 13:50:33 +09:00
Paul Hawke
1afff73c24
Migrate campaigns package to kotlin (#5969)
* Convert ICampaignsView to kotlin along with simple fix

* Convert CampaignView to Kotlin

* Convert CampaignsPresenter to Kotlin

* Convert CampaignsPresenter to kotlin

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-29 13:44:26 +09:00
translatewiki.net
a6152f937e
Localisation updates from https://translatewiki.net. 2024-11-28 13:01:55 +01:00
Rohit Verma
794dbb8f92
Fix modification on bottom sheet's data when coming from Nearby Banner and clicked on other pins (#5937)
* refactor getIconFor method and remove call to highlightNearestPlace()

It ensures single responsibility on getIconFor method

* refactor and fix icon issue when coming from nearby banner

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-28 17:37:56 +09:00
Paul Hawke
0c969c365b
Convert auth package to kotlin (#5966)
* Convert SessionManager to kotlin along with other small fixes

* Convert WikiAccountAuthenticator to kotlin

* Migrate WikiAccountAuthenticatorService to kotlin

* Converted AccountUtil to kotlin

* Convert SignupActivity to kotlin

* Convert LoginActivity to kotlin

* Merge from main
2024-11-28 17:09:25 +09:00
Saifuddin Adenwala
238023056f
Migrated navtab module from Java to Kotlin (#5965)
* Rename .java to .kt

* Migrated navtab module from Java to Kotlin

* Migrated navtab module from Java to Kotlin
2024-11-27 22:58:46 +09:00
Saifuddin Adenwala
381f9eca0c
Migrated notification module from Java to Kotlin (#5955)
* Rename .java to .kt

* Migration of notification module from Java to Kotlin
2024-11-26 21:29:31 +09:00
translatewiki.net
874773b881
Localisation updates from https://translatewiki.net. 2024-11-25 13:01:54 +01:00
Saifuddin Adenwala
00cfd83521
Migrated quiz module from Java to Kotlin (#5952)
* Rename .java to .kt

* Migrated quiz module to Kotlin

* unit test failing fixed

* unit test failing fixed
2024-11-24 19:17:05 +09:00
Saifuddin Adenwala
bafae821e2
Migration of review module from Java to Kotlin (#5950)
* Rename .java to .kt

* Migrated repository module to Kotlin

* Rename .java to .kt

* Migrated review module to Kotlin
2024-11-23 21:45:46 +09:00
Rohit Verma
e070c5dbe8
Fix unit tests (#5947)
* move createLocale() method to companion object and add test dependency

* use mockk() from Mockk library for mocking sealed classes

* change method parameter to null-able String type

* add null check for accessing property from unit tests

* change method signature to match old method's signature

It fixes the NullPointerException when running ImageProcessingUnitTest

* Fix unresolved references and make properties public for unit tests

* fix tests in UploadRepositoryUnitTest by making return type null-able
2024-11-23 08:35:34 +09:00
Saifuddin Adenwala
fe347c21fd
Migrated recentlanguages and repository module from Java to Kotlin (#5948)
* Rename .java to .kt

* Migrated recentlanguages module to Kotlin

* Rename .java to .kt

* Migrated repository module to Kotlin
2024-11-22 22:58:16 +09:00
u7479759
088dd2479e
Changed to data classes, and added immutability (#5905)
Co-authored-by: Jinniu Du <127721018+Donutcheese@users.noreply.github.com>
2024-11-21 21:42:54 +09:00
Saifuddin Adenwala
cf88f9b796
Migrated settings modules from Java to Kotlin (#5944)
* Rename .java to .kt

* Migrated settings module to Kotlin
2024-11-21 21:16:42 +09:00
translatewiki.net
5f1d284309
Localisation updates from https://translatewiki.net. 2024-11-21 13:01:41 +01:00
Saifuddin Adenwala
ed18a37577
Migrated ui and theme modules from Java to Kotlin (#5942)
* Rename .java to .kt

* Migrated ui and theme module to Kotlin
2024-11-20 22:55:13 +09:00
Saifuddin Adenwala
cb4ffd8ca8
Migrated widget module from Java to Kotlin (#5940)
* Rename .java to .kt

* Migrated widget module to Kotlin
2024-11-20 12:41:50 +09:00
Saifuddin Adenwala
0fdb0044b9
Migrated util module from Java to Kotlin (#5938)
* Rename .java to .kt

* Migrated the following files in util module to Kotlin
- AbstractTextWatcher
- ActivityUtils
- CommonsDateUtil
- DateUtil

* Rename .java to .kt

* Migrated the following files in util module to Kotlin
- DeviceInfoUtil
- ExecutorUtils
- FragmentUtils

* Rename .java to .kt

* Migrated the following files in util module to Kotlin
- ImageUtils
- ImageUtilsWrapper
- LangCodeUtils
- LayoutUtils
- LengthUtils
- LocationUtils
- MapUtils

* Rename .java to .kt

* Migrated all remaining files in util module
2024-11-18 22:40:35 +09:00
translatewiki.net
c439143dd3
Localisation updates from https://translatewiki.net. 2024-11-18 13:01:52 +01:00
Saifuddin Adenwala
5c8c4032e9
Migrated util module files from java to kotlin (#5935)
* Rename .java to .kt

* Migrated the following files in util module to Kotlin
- AbstractTextWatcher
- ActivityUtils
- CommonsDateUtil
- DateUtil

* Rename .java to .kt

* Migrated the following files in util module to Kotlin
- DeviceInfoUtil
- ExecutorUtils
- FragmentUtils
2024-11-17 22:35:25 +09:00
translatewiki.net
248c7b0ceb
Localisation updates from https://translatewiki.net. 2024-11-14 13:02:04 +01:00
Alex Gailis
183e84c098
Improve Unique File Name Search (#5877)
* Modified findUniqueFileName() in UploadWorker.kt to use a random 3-digit alphanumeric hash to append to a file name to make it unique. This improves speed over using an incrementing number to append as there are fewer collisions.

* Modified findUniqueFileName() in UploadWorker.kt to use a random 5-digit numeric hash rather than the previous 3-digit alphanumeric hash

* Removed unnecessary variable "chars"

---------

Co-authored-by: Jinniu Du <127721018+Donutcheese@users.noreply.github.com>
Co-authored-by: Zihan Pan <u7726755@anu.edu.au>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-14 11:35:05 +09:00
StrawberryShortcake
634bc3ede1
custom-selector: adds a button to delete the current folder in custom selector (#5925)
* Issue #5811: "Delete folder" menu in custom image selector

* Issue 5811: folder deletion for api < 29.

* Issue 5811: folder deletion for api < 29.

* Issue 5811: folder deletion for api 29.

* Issue 5811: folder deletion

* Issue 5811: fixes merge conflicts, replaces used function onActivityResult with an ActivityResultLauncher

* Update Constants.java

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-13 22:11:38 +09:00
translatewiki.net
17a8845dfd
Localisation updates from https://translatewiki.net. 2024-11-11 13:01:42 +01:00
Saifuddin Adenwala
a70d585df8
Hide edit options for logged-out users in Explore screen (#5920) 2024-11-07 22:59:32 +10:00
translatewiki.net
3bd7b533d4
Localisation updates from https://translatewiki.net. 2024-11-07 13:01:44 +01:00
translatewiki.net
e388f456dc
Localisation updates from https://translatewiki.net. 2024-11-05 15:18:29 +01:00
translatewiki.net
c46928252c
Localisation updates from https://translatewiki.net. 2024-11-04 13:01:44 +01:00
Rohit Verma
091ddb5db1
revert back to MainScope for database and ui updates (#5914)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-11-04 18:40:54 +09:00
Kaartic Sivaraam
9ca285be53 Link to the elaborate changelog in GitHub release 2024-11-04 00:17:33 +05:30
Kaartic Sivaraam
707997bd1f Version v5.1.0 2024-11-04 00:10:15 +05:30
translatewiki.net
72a80f0616 Localisation updates from https://translatewiki.net. 2024-11-04 00:09:11 +05:30
Hisham Akmal
f011abef1d
Improve string formatting in OkHttpJsonApiClient.java (#5916) 2024-11-01 09:20:16 +09:00
translatewiki.net
7c826502b6
Localisation updates from https://translatewiki.net. 2024-10-31 13:01:42 +01:00
translatewiki.net
4a46488e4e Localisation updates from https://translatewiki.net. 2024-10-29 07:24:44 +05:30
translatewiki.net
197855af0e
Localisation updates from https://translatewiki.net. 2024-10-28 13:02:08 +01:00
u7119288
610919ec67 Partial fixes for errors and warnings reported by ./gradlew lint (#5885)
* BaseMarker.kt: removed unneeded cast

* TransformImageImpl.kt: removed unreachable code

* ZoomableActivity.kt: removed Unnecessary safe call on a non-null receiver of type ZoomableDraweeView

* ZoomableActivity.kt: removed Unnecessary safe call on a non-null receiver of type ZoomableDraweeView

* DescriptionEditActivity.kt: removed unnecessary non-null assertion (!!) on a non-null receiver of type DescriptionEditHelper

* Media.kt: Property would not be serialized into a 'Parcel'. Added '@IgnoredOnParcel' annotation to remove the warning

* ZoomableActivity.kt: removed Unnecessary non-null assertion (!!) on a non-null receiver of type ZoomableDraweeView

* CategoryClient.kt: removed condition 'page.categoryInfo() == null' as it's always 'false'

* DescriptionEditActivity.kt: removed unnecessary safe call on a non-null receiver of type SessionManager

* DescriptionEditActivity.kt: removed unnecessary safe call on a non-null receiver of type DescriptionEditHelper

* WikidataFeedback.kt: removed unneeded cast

* FailedUploadsFragment.kt: removed unneeded non-null assertion (!!)

* PendingUploadsFragment.kt: removed unneeded non-null assertion (!!)

* AchievementsFragment.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* ExploreFragment.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* FileUtils.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* AchievementsFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ExploreFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* LocationPickerActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* MediaDetailFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* NearbyFilterSearchRecyclerViewAdapter.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ProfileActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* RecentSearchesFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ReviewActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* SearchActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* UploadMediaPresenter.java: Changed toUpperCase to toLowerCase(Locale.ROOT)

* CategoriesMediaFragment.kt: Changed arguments!! to requireArguments()

* ChildDepictionsFragment.kt: Changed arguments!! to requireArguments()

* DepictedImagesFragment.kt: Changed arguments!! to requireArguments()

* DepictsFragment: Changed Objects.requireNonNull(getView()) to requireViews(), Objects.requireNonNull(getActivity())) to requireActivity()

* ParentCategoriesFragment.kt: Changed arguments!! to requireArguments()

* ParentCategoriesFragment.kt: Changed arguments!! to requireArguments()

* SubCategoriesFragment.kt: Changed arguments!! to requireArguments()

* SubCategoriesFragment.kt: Changed Objects.requireNonNull(getView()) to requireViews(), Objects.requireNonNull(getActivity()) to requireActivity()

* UploadMediaDetailFragment.java: Changed arguments!! to requireArguments()

* WikipediaInstructionsDialogFragment.kt: Changed arguments!! to requireArguments()

* BookmarkItemsDao.java: Added @SuppressLint("Range"), as -1 is expected behavior not index doesn't exist for getColumnIndex()

* BookmarkLocationsDao.java: Added @SuppressLint("Range"), as -1 is expected behavior not index doesn't exist for getColumnIndex()

* AndroidManifest.xml: Removed redundant label  android:label="@string/app_name"

* bs\strings.xml: Added missing few quantity

* hr\strings.xml: Added missing few quantity

* hr\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63

* Revert "hr\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63"

This reverts commit 47232466ab.

* cy\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63

* sr\strings.xml: Added missing few quantities for lines 35 to 70

* ro\strings.xml: Added missing few quantity and removed not needed zero for <plurals name="contributions_subtitle" fuzzy="true">

* cs\strings.xml: Added missing few many missing quantities for lines 33 - 74

* lt\strings.xml: Added missing few, many missing quantities for lines 34 - 58

* lt\strings.xml: Replaced . . . with ...

* ca\strings.xml: Added missing many quantities for lines 21 - 51, replaced . . . with ...

* ser\strings.xml: Added missing few quantities, replaced . . . with ...

* br\strings.xml: Added missing two, few, many quantities

* pt\strings.xml: Added missing many quantity, changed . . . to ... and ignored typo as it is correct for European Portuguese

* it\strings.xml: changed . . . to ...

* pt\strings.xml: fixed many quantity

* ca\strings.xml: fixed many quantity

* sr\strings.xml: fixed many quantity

* cy\strings.xml: corrected quantities for "share_license_summary

* fr\strings.xml: changed . . . to ... and add many quantities using the other quantity

* fr\strings.xml: changed . . . to ... and add many quantities using the other quantity. Fixed some typos, added ignore for correct spellings but has warning

* getColumnIndex(): added @SuppressLint("Range") as -1 is expected result for column name doesn't exist

* values-b+sr+Latn\strings.xml: changed . . . to ...

* Revert "values-b+sr+Latn\strings.xml: changed . . . to ..."

This reverts commit 95b909c29f.

* values-b+roa+tara\strings.xml: changed . . . to ...

* Revert "values-b+roa+tara\strings.xml: changed . . . to ..."

This reverts commit b5db1a3e68.

* values-b+roa+tara\strings.xml: changed . . . to ...

* values-b+sr+Latn\strings.xml: changed . . . to ..., add few based on other quantity. Ignored one ImpliedQuantity warning as it is correct.

* it\strings.xml: changed . . . to ..., add many based on other quantity.

* pt-rBR\strings.xml: changed . . . to ..., add many based on other quantity. Fixed typos, ignored warning for "one" quantity as translation didn't use number

* si\strings.xml: Ignored ImpliedQuantity warning as it uses 1 not %d. Removed not needed zero quantity

* si\strings.xml: Ignored ImpliedQuantity warning as it uses 1 not %d. Removed not needed zero quantity. Fixed wrong %1$d. Changed . . . to ...

* mk\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use 1

* sl\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* ru\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* uk\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* is\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* strings.xml: changed . . . to ...

* af\strings.xml: removed not needed zero quantity

* de\strings.xml: fixed duplicate word typo

* diq\strings.xml: changed - to dash (-)

* hi\strings.xml: removed not needed zero

* in\strings.xml: removed not needed one quantity

* iw\strings.xml: removed not needed many quantity

* ja\strings.xml: removed not needed one quantity

* ko\strings.xml: removed not needed one quantity

* ky\strings.xml: removed not needed one quantity

* mr\strings.xml: removed not needed zero quantity

* my\strings.xml: removed not needed one quantity

* su\strings.xml: removed not needed one quantity

* th\strings.xml: removed not needed one and zero quantity

* zh\strings.xml: removed not needed one quantity

* activity_description_edit.xml: changed android:tint to app:tint, changed layout_alignParentRight to layout_alignParentEnd

* bottom_sheet_details_explore.xml: changed android:tint to app:tint, added focusable, changed to margin layout

* bottom_sheet_item_layout.xml: changed android:tint to app:tint, added focusable

* bottom_sheet_details_explore.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* item_place.xml.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* layout_campagin.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* layout_contribution.xml.xml: changed android:tint to app:tint

* nearby_card_view.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* nearby_row_button.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* toolbar_location_picker.xml: changed android:tint to app:tint

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-28 08:35:50 +05:30
Parneet Singh
d21c63f1db CommonsApplication migrate to kotlin & some lint fixes (#5879)
* convert to kotlin

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use lateinit instead of nullable types

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* instance property access fix

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* refactor constants name with uppercased ones

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove unused

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix imports in test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use mockk for kotlin to fix tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-28 08:35:50 +05:30
Parneet Singh
6adedd9789 fix test (#5893)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-28 08:35:50 +05:30
u7119288
522f1fe192
Partial fixes for errors and warnings reported by ./gradlew lint (#5885)
* BaseMarker.kt: removed unneeded cast

* TransformImageImpl.kt: removed unreachable code

* ZoomableActivity.kt: removed Unnecessary safe call on a non-null receiver of type ZoomableDraweeView

* ZoomableActivity.kt: removed Unnecessary safe call on a non-null receiver of type ZoomableDraweeView

* DescriptionEditActivity.kt: removed unnecessary non-null assertion (!!) on a non-null receiver of type DescriptionEditHelper

* Media.kt: Property would not be serialized into a 'Parcel'. Added '@IgnoredOnParcel' annotation to remove the warning

* ZoomableActivity.kt: removed Unnecessary non-null assertion (!!) on a non-null receiver of type ZoomableDraweeView

* CategoryClient.kt: removed condition 'page.categoryInfo() == null' as it's always 'false'

* DescriptionEditActivity.kt: removed unnecessary safe call on a non-null receiver of type SessionManager

* DescriptionEditActivity.kt: removed unnecessary safe call on a non-null receiver of type DescriptionEditHelper

* WikidataFeedback.kt: removed unneeded cast

* FailedUploadsFragment.kt: removed unneeded non-null assertion (!!)

* PendingUploadsFragment.kt: removed unneeded non-null assertion (!!)

* AchievementsFragment.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* ExploreFragment.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* FileUtils.java: Changed toUpperCase to toUpperCase(Locale.getDefault())

* AchievementsFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ExploreFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* LocationPickerActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* MediaDetailFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* NearbyFilterSearchRecyclerViewAdapter.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ProfileActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* RecentSearchesFragment.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* ReviewActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* SearchActivity.java: Changed toUpperCase to toUpperCase(Locale.ROOT)

* UploadMediaPresenter.java: Changed toUpperCase to toLowerCase(Locale.ROOT)

* CategoriesMediaFragment.kt: Changed arguments!! to requireArguments()

* ChildDepictionsFragment.kt: Changed arguments!! to requireArguments()

* DepictedImagesFragment.kt: Changed arguments!! to requireArguments()

* DepictsFragment: Changed Objects.requireNonNull(getView()) to requireViews(), Objects.requireNonNull(getActivity())) to requireActivity()

* ParentCategoriesFragment.kt: Changed arguments!! to requireArguments()

* ParentCategoriesFragment.kt: Changed arguments!! to requireArguments()

* SubCategoriesFragment.kt: Changed arguments!! to requireArguments()

* SubCategoriesFragment.kt: Changed Objects.requireNonNull(getView()) to requireViews(), Objects.requireNonNull(getActivity()) to requireActivity()

* UploadMediaDetailFragment.java: Changed arguments!! to requireArguments()

* WikipediaInstructionsDialogFragment.kt: Changed arguments!! to requireArguments()

* BookmarkItemsDao.java: Added @SuppressLint("Range"), as -1 is expected behavior not index doesn't exist for getColumnIndex()

* BookmarkLocationsDao.java: Added @SuppressLint("Range"), as -1 is expected behavior not index doesn't exist for getColumnIndex()

* AndroidManifest.xml: Removed redundant label  android:label="@string/app_name"

* bs\strings.xml: Added missing few quantity

* hr\strings.xml: Added missing few quantity

* hr\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63

* Revert "hr\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63"

This reverts commit 47232466ab.

* cy\strings.xml: Added missing zero, two, few, many quantities for lines 23 - 63

* sr\strings.xml: Added missing few quantities for lines 35 to 70

* ro\strings.xml: Added missing few quantity and removed not needed zero for <plurals name="contributions_subtitle" fuzzy="true">

* cs\strings.xml: Added missing few many missing quantities for lines 33 - 74

* lt\strings.xml: Added missing few, many missing quantities for lines 34 - 58

* lt\strings.xml: Replaced . . . with ...

* ca\strings.xml: Added missing many quantities for lines 21 - 51, replaced . . . with ...

* ser\strings.xml: Added missing few quantities, replaced . . . with ...

* br\strings.xml: Added missing two, few, many quantities

* pt\strings.xml: Added missing many quantity, changed . . . to ... and ignored typo as it is correct for European Portuguese

* it\strings.xml: changed . . . to ...

* pt\strings.xml: fixed many quantity

* ca\strings.xml: fixed many quantity

* sr\strings.xml: fixed many quantity

* cy\strings.xml: corrected quantities for "share_license_summary

* fr\strings.xml: changed . . . to ... and add many quantities using the other quantity

* fr\strings.xml: changed . . . to ... and add many quantities using the other quantity. Fixed some typos, added ignore for correct spellings but has warning

* getColumnIndex(): added @SuppressLint("Range") as -1 is expected result for column name doesn't exist

* values-b+sr+Latn\strings.xml: changed . . . to ...

* Revert "values-b+sr+Latn\strings.xml: changed . . . to ..."

This reverts commit 95b909c29f.

* values-b+roa+tara\strings.xml: changed . . . to ...

* Revert "values-b+roa+tara\strings.xml: changed . . . to ..."

This reverts commit b5db1a3e68.

* values-b+roa+tara\strings.xml: changed . . . to ...

* values-b+sr+Latn\strings.xml: changed . . . to ..., add few based on other quantity. Ignored one ImpliedQuantity warning as it is correct.

* it\strings.xml: changed . . . to ..., add many based on other quantity.

* pt-rBR\strings.xml: changed . . . to ..., add many based on other quantity. Fixed typos, ignored warning for "one" quantity as translation didn't use number

* si\strings.xml: Ignored ImpliedQuantity warning as it uses 1 not %d. Removed not needed zero quantity

* si\strings.xml: Ignored ImpliedQuantity warning as it uses 1 not %d. Removed not needed zero quantity. Fixed wrong %1$d. Changed . . . to ...

* mk\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use 1

* sl\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* ru\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* uk\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* is\strings.xml: changed . . . to ... and ignored ImpliedQuantity as it doesn't use %1$d

* strings.xml: changed . . . to ...

* af\strings.xml: removed not needed zero quantity

* de\strings.xml: fixed duplicate word typo

* diq\strings.xml: changed - to dash (-)

* hi\strings.xml: removed not needed zero

* in\strings.xml: removed not needed one quantity

* iw\strings.xml: removed not needed many quantity

* ja\strings.xml: removed not needed one quantity

* ko\strings.xml: removed not needed one quantity

* ky\strings.xml: removed not needed one quantity

* mr\strings.xml: removed not needed zero quantity

* my\strings.xml: removed not needed one quantity

* su\strings.xml: removed not needed one quantity

* th\strings.xml: removed not needed one and zero quantity

* zh\strings.xml: removed not needed one quantity

* activity_description_edit.xml: changed android:tint to app:tint, changed layout_alignParentRight to layout_alignParentEnd

* bottom_sheet_details_explore.xml: changed android:tint to app:tint, added focusable, changed to margin layout

* bottom_sheet_item_layout.xml: changed android:tint to app:tint, added focusable

* bottom_sheet_details_explore.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* item_place.xml.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* layout_campagin.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* layout_contribution.xml.xml: changed android:tint to app:tint

* nearby_card_view.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* nearby_row_button.xml: changed android:tint to app:tint, added focusable, changed margin layout and removed not needed and invalid params

* toolbar_location_picker.xml: changed android:tint to app:tint

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-27 22:59:09 +09:00
Rohit Verma
cdc4f89da5
Database bug fix (#5902)
* make database function calls suspending and update room version

* replace MainScope with coroutineScope for database operations

* add suspend keyword and refactor code
2024-10-27 22:38:40 +09:00
Parneet Singh
bc065c8792
CommonsApplication migrate to kotlin & some lint fixes (#5879)
* convert to kotlin

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use lateinit instead of nullable types

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* instance property access fix

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* refactor constants name with uppercased ones

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove unused

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix imports in test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* use mockk for kotlin to fix tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-26 23:19:34 +09:00
Parneet Singh
7c58891892
fix test (#5893)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-26 13:32:35 +09:00
Noah Vendrig
3e020ed973
Fixes #5806 Implemented "Refresh" button to clear the cache and reload the Nearby map (#5891)
* Changed files required to get the app to run correctly. Removed suspend from affected DAO files and funcs, and changed to (Kotlin v1.9.22) and (Kotlin compiler v1.5.8)

* Created refresh button icon, and added it to the nearby_fragment_menu.xml (header of the nearby page). Created function refresh() in NearbyParentFragment.java to handle refresh functionality.

* Replaced refresh() func with emptyCache() and reloadMap()

* Attempt at reloadMap(), no testing done yet.

* added changes for a possibly working emptyCache implementation (needs testing).

* Tested changes as working, edited emptyCache to correctly clear cache and then reload map

---------

Co-authored-by: MarcusBarta <marcusbarta@icloud.com>
2024-10-25 14:19:07 +09:00
translatewiki.net
becc07d26b
Localisation updates from https://translatewiki.net. 2024-10-24 14:02:18 +02:00
Parneet Singh
1659a4ce22
add dependency (#5887)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-24 19:07:16 +09:00
Parneet Singh
1e7aabad16
Use new result API (#5875)
* remove unused result expectancy for settings screen launch

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* initial refactor to new result api, wip

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* refactor camera launcher

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* revert callback for video handling

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* invoke callbacks when cancelled

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* handle gallery picker result based on preference

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove old method of refactoring for file picker

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove legacy result handling callback

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* request code used for handling result was never used for launching an activity, hence removed

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* extract voice result handling into function

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* refactor test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove unused tests

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* cleanup

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* fix-docs

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* add space after ,

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-23 21:58:39 +09:00
cambo14
f1205c19be
UploadMediaDetailAdapter: made selecting a language deselect all others (#5883)
>> Made it so that selecting a language results in the hashmap storing the currently selected language(s) being cleared. Considered refactoring the hashmap storing this into a single pair storing the language positition index and its code, as only one language should ever be selected, however I am not confident that this would not introduce unintended side-effects
2024-10-23 03:50:37 +10:00
whe128
9c1c95f5cf
Fix for #5846: After uploading via Nearby, I am sent back to Nearby, where I am mislead into thinking that I must upload again #5846 (#5874)
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-23 02:56:02 +10:00
Hanna Truong
ba7348f83f
Fixes issue #5841: Nearby pins: Make it easier to understand what the colors mean (#5881)
* UI design for legend to explain the colors of the nearby pins

* Add listener for the button to toggle the visibility of the legend (make it hideable)

* Change wording for legend and make text localizable

* Fixed typo

* Fixed typo

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-22 21:27:40 +09:00
myyyy
7b0b604834
Fix: Prevent RecyclerView from resetting scroll position after returning from preview (#5873) (#5880)
Resolved an issue where RecyclerView would incorrectly scroll to the top after exiting fullscreen preview. Adjusted scroll behavior to maintain position unless actioned images are filtered. Implemented observer logic adjustments to handle dataset updates more efficiently.

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-21 22:41:32 +09:00
translatewiki.net
014feb54e5
Localisation updates from https://translatewiki.net. 2024-10-21 14:02:00 +02:00
LachlanMajor
56d0beb22a
Fixes #5840 Custom select folder display breaks after exiting media preview (#5866)
* ImageFragment.kt: notifyDataSetChanged() added to update observers about init call in handleResult()

* ImageFragment.kt: unnecessary initialisation after exiting media preview was removed from passSelectedImages
2024-10-20 21:36:15 +10:00
Christo Joby Antony
63f1ed8a2d
Fix for #5808: Update the cached Place Items on the successful association of Wiki Item (#5864)
* (fixes #5855) fetch item label in nearby based on user configured language.

* implement a method to delete Place entity from database

* clear the cache for a item when a wikiItem is updated.

* fix style issue

* Update placesRepository on successful WikiItemEdit

* Revert the delete operations added to the PlaceDAO

* Set name of the place to avoid race condition with NearbyParentFragment. Update Success Notification to show only after the pin has been updated.

* Clean-up

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-19 22:20:35 +09:00
ErenratZeng
c77e5abba7
notification: classify email messages and add mail check prompt (#5865)
This commit adds logic to classify notifications as "email" type when the notification text contains "sent you an email". It also updates the email notification prompt to support localization, ensuring a better user experience across different regions.

### Problem:
1. Previously, email-related notifications from the backend were missing a URL. As a result, when users clicked on these notifications, there was no link to open, and the notifications were categorized as UNKNOWN. This led to a poor user experience since there was no feedback provided when the user clicked on an email notification.
2. Additionally, the existing code used hardcoded English text for the email notification prompt, which did not provide a localized experience for users in different regions.

### Solution:
1. Added logic to categorize email-related notifications as `EMAIL` when the notification text contains "sent you an email".
2. Replaced the hardcoded "Check your email inbox" string with a localized string and added translations for multiple languages, including zh, zh-rhk, zh-rcn, zh-rtw, and ja.

### Changes:
- **NotificationClient**:
  - Modified `WikimediaNotification.toCommonsNotification()` to check if the notification text contains "sent you an email". If it does, the notification is classified as `EMAIL_MESSAGE` instead of the default `UNKNOWN`.

- **NotificationActivity**:
  - In the `NotificatinAdapter` click handler, added a check for `EMAIL_MESSAGE` type. When an email-type notification is clicked, a localized "Check your mail box" prompt is shown using `Snackbar`, instead of attempting to open a URL (which is typically missing for such notifications).
  - Modified to fetch the string using `getString(R.string.check_your_mail_box)` to support localization.

- **NotificationType**:
  - Added a new `EMAIL` type to categorize email-related notifications.

- **Localization**:
  - Added localized translations for "Check your mail box" in zh, zh-rhk, zh-rcn, zh-rtw, and ja.

Co-authored-by: Qiutong Zeng <Qiutong.zeng@anu.edu.au>
2024-10-18 15:31:54 +09:00
Parneet Singh
817e07b921
set permission required only upto android 5.1 (#5863)
Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-18 10:15:56 +09:00
translatewiki.net
82c7dcfe18
Localisation updates from https://translatewiki.net. 2024-10-17 14:02:05 +02:00
Andrew Gardner
0a7fe662d4
(fixes #3464) Replace assert() usages with assertThat() (#5861)
* Updated instances of assert() in WelcomeActivityTest.kt to asserThat().

New imports:
 - org.hamcrest.CoreMatchers.equalTo
 - org.hamcrest.CoreMatchers.assertThat

* Updated instances of assert() in LatLngTest.kt to asserThat().

New imports:
 - org.hamcrest.CoreMatchers.equalTo
 - import org.hamcrest.CoreMatchers.not
 - org.hamcrest.CoreMatchers.assertThat

* Updated instances of assert() in LabelTest.kt to asserThat().

New imports:
 - org.hamcrest.CoreMatchers.equalTo
 - org.hamcrest.CoreMatchers.assertThat

* Corrected sign error typo in LatLngTest.kt
2024-10-16 22:22:48 +09:00
Zhenhao Li
ef3f6b7977
solve the issue 5856 (#5860) 2024-10-16 22:01:14 +09:00
Christo Joby Antony
3ac608c82c
(fixes #5855) fetch item label in nearby based on user configured language. (#5858) 2024-10-16 12:49:38 +09:00
Parneet Singh
f889ed1821
Refactor FilePicker.java intent result handling (#5851)
* remove unnecessary video flag check

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* handle when custom selector operation cancelled

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* dispatch appropriate request code to handle using respective callbacks

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* remove wrong control statements

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

* refactor gallery picker test

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>

---------

Signed-off-by: parneet-guraya <gurayaparneet@gmail.com>
2024-10-16 11:20:00 +09:00
translatewiki.net
95e0a0d143
Localisation updates from https://translatewiki.net. 2024-10-14 14:02:11 +02:00
Nicolas Raoul
dadc4b6a11
Bumped coordinates2country, recognizes more countries 2024-10-13 22:56:53 +09:00
translatewiki.net
ba7a559714
Localisation updates from https://translatewiki.net. 2024-10-10 14:01:43 +02:00
translatewiki.net
c7065e103b
Localisation updates from https://translatewiki.net. 2024-10-07 14:01:46 +02:00
translatewiki.net
f47a0dd49c
Localisation updates from https://translatewiki.net. 2024-10-03 14:01:48 +02:00
tristan
d0e64d7886
Issue 5835 nearby (#5843)
* NearbyParentFragment.java:
OnScroll - removed distance threshold, delay search by 800ms, discard multiple OnScroll within 800ms.
OnDestroy - destroy any queued OnScroll events

* NearbyParentFragment.java:
loadPlacesDataAsync - set batchSize from 50 back to original 3

* comment

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-10-02 13:03:25 +09:00
translatewiki.net
3d49b1f79a
Localisation updates from https://translatewiki.net. 2024-09-30 14:01:51 +02:00
translatewiki.net
16a1375800
Localisation updates from https://translatewiki.net. 2024-09-23 14:01:49 +02:00
translatewiki.net
249e284a93
Localisation updates from https://translatewiki.net. 2024-09-19 14:02:01 +02:00
tristan
2d82a430c4
Issue-5662-kotlinstyle (#5833)
* *.kt: bulk correction of formatting using ktlint --format

* *.kt: replace wildcard imports and second stage auto format ktlint --format

* QuizQuestionTest.kt: modified property names to camel case to meet ktlint standard

* LevelControllerTest.kt: modified property names to camel case to meet ktlint standard

* QuizActivityUnitTest.kt: modified property names to camel case to meet ktlint standard

* MediaDetailFragmentUnitTests.kt: modified property names to camel case to meet ktlint standard

* UploadWorker.kt: modified property names to camel case to meet ktlint standard

* UploadClient.kt: modified property names to camel case to meet ktlint standard

* BasePagingPresenter.kt: modified property names to camel case to meet ktlint standard

* DescriptionEditActivity.kt: modified property names to camel case to meet ktlint standard

* OnSwipeTouchListener.kt: modified property names to camel case to meet ktlint standard

* MediaDetailFragmentUnitTests.kt: corrected excessive line length to meet ktlint standard

* DepictedItem.kt: corrected property name format and catch format to for  ktlint standard

* UploadCategoryAdapter.kt: corrected class definition format to meet ktlint standard

* CustomSelectorActivity.kt: reformatted function names to first letter lowercase to meet ktlint standard

* MediaDetailFragmentUnitTests.kt: fix string literal indentation to meet ktlint standard

* NotForUploadDao.kt: file renamed to match class name, new file NotForUploadStatusDao.kt

* UploadedDao.kt: file renamed to match class name, new file UploadedStatusDao.kt

* Urls.kt: fixed excessive line length for ktLint standard

* Snak_partial.kt & Statement_partial.kt: refactored to remove underscores in class names to meet ktLint standard

* *.kt: fixed consecutive KDOC error for ktLint

* PageableBaseDataSourceTest.kt & UploadPresenterTest.kt: fixed excessive line lengths to meet ktLint standard

* CheckboxTriStatesTest.kt: renamed file to match class name to meet ktLint standard

* .kt: resolved backing-property-naming error in ktLint, made matching properties public, matched names and refactored

* TestConnectionFactory.kt: fixed property naming to adhere to ktLint standard
2024-09-19 13:56:45 +09:00
Jason-Whitmore
950539c55c
Fixes Issue #5713: "Edit location" is hard to use (and confusing) (#5767)
* LocationPickerActivity.java: fix "Show in Map App" bug

Once the "Show in Map App" button is pressed, the Map app will center on the current photo's EXIF location,
if that data is available. If not, the map app will center on where the location picker's map is centered.
Javadoc updated to reflect this small change.

* LocationPickerActivity.java: add methods to easily move the map center

Before this change, any time the map center had to move, several lines of code had to be written.

This change creates several methods which move the map center to a specified location. By using these new methods,
moving the map center only takes one simple method call.

* LocationPickerActivity.java: add null check to method

The original method did not include a null check for the input GeoPoint.

This change includes a null check. If the input Geopoint is null, the method will simply return.

* LocationPickerActivity.java: remove redundant method, renaming method

Before this change, there were two methods with the same behavior.

This change removes the newer method and renames the old method to the descriptive name the newer one had.
Additionally, code which calls this method has been changed to reflect the new name.

* LocationPickerActivity.java: rearrange method calls.

Before this change, a method call to move the map to the device's GPS location was called at the very end of the map setup method.
This would move the map away from the media's EXIF location data (if it was available).

This change places the method call to move the map to the device's GPS location before the method call to move the map to the media's EXIF location (if the data is available).
In short, if no exif data is available, the map attempts to move to the device's GPS location. If the exif location data does exist, the map will move to that location.

* LocationPickerActivity.java: rewrite method scope, name, and documentation

Before this commit, the method name was unclear and the documentation did not fully explain the method's behavior. Additionally, it was a public method.

This commit renames the method (also changing calls to it), adds more documentation, and changes the method scope from public to private.

* LocationPickerActivity.java: create method to move location picker map to device GPS location

Before this method was created, several lines of code were required to move the location picker map to the device's GPS location.

After this method was created, the location picker map can be moved to the GPS location in a single method call.

* LocationPickerActivity.java: add code to move map to either EXIF or device GPS location

Before this change, there was no explicit if check on whether there was EXIF data available before moving the location picker map.

After this change, an explicit if check (which checks the activity name string) will move the location picker map to either
the EXIF location (if that data is available) or to the device's GPS location (if EXIF data is not available).

* LocationPickerActivity.java: refactor showInMapApp method

Before this change, the showInMapApp method had correct behavior, but could be rewritten to become more clear.

After this change, the showInMapApp code has been rewritten.
Specifically, common code in an if statement has been factored out.

* LocationPickerActivity.java: add null checks to showInMapApp

Before this change, there was no null checks when accessing data from an object,
which could create null pointer exceptions at runtime.

After this change, null checks are introduced to make sure null pointer exceptions will not occur.

* LocationPickerActivity.java: rewrite comments to clarify if statement

Before this change, a comment in showInMapApp did not properly describe an if statement's intended behavior.

After this change, the old comment was removed and 2 new comments were added in each part of the if statement
to properly describe the intended behavior.

* LocationPickerActivity.java: replace code with a method call

Before this change, there was several lines of code which moved the map center to the EXIF location.

After this change, the several lines of code have been replaced with a simpler method call which produces
the same result.

* LocationPickerActivity.java: remove redundant code

Before this change, there was two sections of code which moved the map center to the same location. Both of these code sections
occur very close to each other (one in onCreate(), the other in setupMapView())

After this change, the code section in onCreate() has been removed. With the map centering code only in the setupMapView() method.
This change eliminates redundancy, reduces the amount of code in onCreate(), and makes this java file easier to understand.

* content_location_picker.xml: adjust picker pin and shadow location

Before this commit, the location picker pin and shadow graphics were incorrectly located on the screen.
Specifically, the bottom of the pin was not located at the center of the view. Since pressing the
checkmark button saves the location at the center of the view, users would most likely save the wrong location.

After this commit, the location picker pin and shadow graphics have been moved upward. The shadow graphic is
now located at the exact center of the view, and the bottom of the location picker pin is directly over the
center as well. Users may now use the bottom of the pin as an accurate location picker.

* LocationPickerActivity.java: fix bug with permissions menu moving map

Prior to this change, if the menu asking for permissions to access the device's GPS was accepted, the map
would automatically move to the most recent or current GPS location, rather than staying at the uploaded
image's available EXIF location.

After this change, the map will stay centered at the image's available EXIF location, as intended.
The relevant method name and javadoc have been changed to more accurately describe the method's behavior.

* LocationPickerActivity.java: fix map centering bug when editing location on already uploaded media

Before this commit, when the user pressed the edit location icon on media which was already uploaded,
the map would center on the device's current GPS location.

After this commit, pressing the same edit location icon will now center the map on the location metadata.
This is done by checking the activity string to see if the media is already uploaded. If so, a method
is called to center the map on the media's location metadata.

* LocationPickerActivity.java: replace references to EXIF in documentation

Before this commit, there were several mentions of EXIF location data in the javadocs. It is not clear if
this is correct, since many images have location data that is chosen by the user rather than derived from EXIF.

After this commit, the more generic term "location metadata" is used in place of EXIF location data.
Hopefully this change will help avoid confusion in the future.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-09-18 21:18:46 +09:00
Rajat Sarangal
f751ab4a75
Optimize the Layout ViewBinding (#5830) 2024-09-18 11:43:48 +09:00
Rohit Verma
3e915f9848
Upgrade to SDK 34 (#5790)
* change the overridden method signature as per API 34

* add version check condition to compare with API 23 before adding flag

* refactor: add final keywords, fix typo, and remove redundant spaces

For optimized code only

* upgrade: migrate to SDK 34 and upgrade APG

Additionally, add Jetpack Compose to the project

* AndroidManifest: add new permission for API 34

DescriptionActivity should not be exposed

* refactor: permission should not be check on onCreate for some cases

* add method to get correct storage permission and check partial access

Additionally, add final keywords to reduce compiler warnings

* refactor: prevent app from crashing for SDKs >= 34

* add new UI component to allows user to manage partially access photos

Implement using composeView

* change the overridden method signature as per API 34

* add version check condition to compare with API 23 before adding flag

* refactor: add final keywords, fix typo, and remove redundant spaces

For optimized code only

* upgrade: migrate to SDK 34 and upgrade APG

Additionally, add Jetpack Compose to the project

* AndroidManifest: add new permission for API 34

DescriptionActivity should not be exposed

* refactor: permission should not be check on onCreate for some cases

* add method to get correct storage permission and check partial access

Additionally, add final keywords to reduce compiler warnings

* refactor: prevent app from crashing for SDKs >= 34

* add new UI component to allows user to manage partially access photos

Implement using composeView

* replace deprecated circular progress bar with material progress bar

* remove redundant appcompat dependency

* add condition to check for partial access on API >= 34

It prevents invoking photo picker on UploadActivity.

* UploadWorker: add foreground service type

* fix typos in UploadWorker.kt

* add permission to access media location
2024-09-17 23:58:44 +09:00
translatewiki.net
eb027b74ce
Localisation updates from https://translatewiki.net. 2024-09-16 14:01:51 +02:00
ujjwal2900
9393dda9a4
Added date and time to Feedback (#5797)
* Add Date and Time in UTC format to Feedback

* Add UTC date to the Subject instead of adding it to the body

* Change the UTC Date format to yyyy/MM/dd HH:mm:ss

* Minor changes

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-09-13 14:39:02 +09:00
translatewiki.net
c3cd30ce5c
Localisation updates from https://translatewiki.net. 2024-09-12 14:02:01 +02:00
Rohit Verma
9edde234ec
Localization: fix incorrect Unicode for space after bullet points in French language (#5824)
* localization: fix incorrect unicode for space

* Remove bullet string resource as it doesn't need translation
2024-09-10 17:41:45 +09:00
translatewiki.net
76e4c38299
Localisation updates from https://translatewiki.net. 2024-09-09 14:01:50 +02:00
translatewiki.net
65ae5060c5
Localisation updates from https://translatewiki.net. 2024-09-05 14:04:50 +02:00
translatewiki.net
b9bb9bcf34
Localisation updates from https://translatewiki.net. 2024-09-02 14:01:50 +02:00
Kanahia
93f1e1ec29
Added pending uploads screen (#5752)
* Added pending uploads screen

* Added failed uploads fragment

* Improved progress bars

* Implemented pause functionality

* Improved pause feature

* Fixed issue with sorting when adding more pictures during an upload

* Improved Tap to View notification

* Fixed issue with on going upload deletion

* Improved the deletion feature

* Fixed indentations and unit tests

* Fixed bugs

* Fixed failing test

* Added error message in Failed Uploads Fragment

* Improved error notification

* Moved auto-retry from the Main Activity to UploadProgressActivity

* Fixed large uploads issue

* Minor fixes

* Removed HashSet

* Fixed issue with progress bar

* Bug fixes

* Moved Auto Retry to MainActivity

* Fixed conflicts

* Fixed issue with upload icon

* Fixed null ptr issue on changing modes

* Improved recycler view

* Fixed irrelevant network call

* Fixed irrelevant network call

* Fixed constantly failing uploads

* Fixed constantly failing uploads

* Fixed constantly failing uploads

* Added error log

* Fixed refresh icon visibility in light mode

* Changed progress in progress activity

* Fixed progress bar issue

* Improved icons

* Improved deletion and removed cancelledUploads Hashset

* Fixed sorting, list size issue

* Improved current implementation

* Implemented flag for workers

* Implemented flag for workers

* Fixed sorting bug

* Fixed upload icon

* Improved pausing

* Made changes to visibility implementation

* Added image duplicity check on restart of failed image

* minor adjustments

* added javadoc/kdoc and fixed minor bug

* Fixed unit tests

* Added synchronized(lock)

* Added check to prevent multiple uploads starting at once

* Ignored failing test cases

* Temporary commit - Added jcenter

* Temporary commit - Removed library/commented

* Temporary commit - Removed library/commented

* Updated com.jraska.livedata:testing-ktx

* Ignored failing test - UploadControllerTest.kt

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadModelUnitTest

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadPresenterTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing tests - UploadRepositoryUnitTest.kt

* Ignored failing test - UploadRepositoryUnitTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - DepictedItemTest.kt

* Ignored failing test - FilesUtilsTest.kt

* Ignored failing test - WikiBaseClientUnitTest.kt

* Ignored failing test - WikiBaseClientUnitTest.kt

* Ignored failing test - WikiBaseClientUnitTest.kt

* Ignored failing test - WikidataClientTest.kt

* Ignored failing test - WikidataClientTest.kt

* Fixed unit tests

* Updated kdoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-08-30 15:22:54 +09:00
translatewiki.net
62d6dea219
Localisation updates from https://translatewiki.net. 2024-08-29 14:01:37 +02:00
Rohit Verma
190135d36c
Fix failing tests for updateDepictsProperty method (#5795)
* tests: fix failing testUpdateDepictsProperty

* replace deprecated circular progress bar with material progress bar

* refactor: update SettingsActivity to not use custom appCompatDeletegate

It is required because that delegate is automatically handled in new libraries.
2024-08-28 15:29:16 +09:00
Rohit Verma
ec4a6bc0c4
remove asynchronous call to fetch local depictedItems (#5792) 2024-08-27 23:07:12 +09:00
translatewiki.net
39a0b88e3a
Localisation updates from https://translatewiki.net. 2024-08-26 14:02:06 +02:00
Rohit Verma
46df64d208
Prevent deletion of other structured data when editing depicts (#5741)
* restructure :  minor changes to comments to improve readability

* api: remove clear flag to prevent deletion of structured data

* WikiBaseInterface: add new api methods

Get Method: to get claims for an entity
Post method: to delete claims

* WikiBaseClient: add methods to handle response for new APIs

* typo:  update call to method with updated typo

* DepictEditHelper: call update property method with entity id

* refactor: dismiss progress dialog on error

* DepictsDao: remove usage of runBlocking as it was blocking main thread

Refactor methods to perform well with coroutines

* refactor: update usage of method to match changes in DepictsDao

* refactor: use named parameters to improve readability

* claims: add new data classes to represent remove claims

* WikidataEditService: modify update depicts property method

Performs deletion of old claims and creation of new claims

* refactor: make methods more organized
2024-08-26 15:43:50 +09:00
djbloop
31bb1a73c0
First of two fixes for bug #5726: hide nominate for deletion when logged out (#5773) 2024-08-26 13:24:15 +09:00
ujjwal2900
096c075548
Removed duplicate code in addMarkersToMap method (#5783) 2024-08-25 13:01:55 +09:00
Nicolas Raoul
a81d48cc9d
Updating jraska/livedata-testing for GSoC (#5785) 2024-08-22 22:41:27 +09:00
translatewiki.net
a3a5980ebd
Localisation updates from https://translatewiki.net. 2024-08-19 14:02:05 +02:00
translatewiki.net
060d41f973
Localisation updates from https://translatewiki.net. 2024-08-15 14:01:56 +02:00
translatewiki.net
dc6cc82751
Localisation updates from https://translatewiki.net. 2024-08-12 14:01:56 +02:00
translatewiki.net
9a31d2318e
Localisation updates from https://translatewiki.net. 2024-08-08 14:01:40 +02:00
translatewiki.net
862f8f0b04
Localisation updates from https://translatewiki.net. 2024-08-05 14:01:44 +02:00
Kanahia
2d63f351ed
Made Split to Nearby Query into a fast query for coordinates + a details query for each pin (#5731)
* Splitted the query

* Made changes to the query

* Improvised query

* Improvised query by dividing in the batches

* Fixed failing tests

* Improved batches

* Improved sorting

* Fixes issue caused by search this area button

* Fixed failing tests

* Fixed unnecessary reloads on onResume

* Fixed few pins not loading on changing apps

* Improved zoom level and fixed the pins not loading from the center

* Removed toggle chips and changed pin's color

* Fixed wikidata url

* Fixed unit tests

* Implemented retry with delay of 5000ms

* Fixed exception issue and pins issue

* Added change color icon to pin

* Improved pin clicking

* Removed search this area button

* Implemented caching of places

* Fixed unit test

* Factorized methods

* Changed primary key from location to entity id

* Fixed tests

* Fixed conflicts

* Fixed unit test

* Fixed unit test

* Fixed the bug

* Fixed issue with pin loading on the first launch

* Updated javadocs

* Temporary commit - only for testing

* Replaced Temporary commit

* Temporary commit - Added jcenter

* Made minor changes

* Fixed unit tests

* Fixed unit tests

* Fixed minor bug
2024-08-04 09:35:09 +09:00
translatewiki.net
ba6c8fe8d0
Localisation updates from https://translatewiki.net. 2024-08-01 14:01:42 +02:00
translatewiki.net
dbfe3b50f9
Localisation updates from https://translatewiki.net. 2024-07-29 14:02:27 +02:00
translatewiki.net
59da70aca1
Localisation updates from https://translatewiki.net. 2024-07-25 14:01:45 +02:00
translatewiki.net
d2d8eb9153
Localisation updates from https://translatewiki.net. 2024-07-22 14:01:59 +02:00
Kaartic Sivaraam
7d9f8d27bc
menu: rename menu as "User profile" (#5771)
As suggested by @whym on #5754, the name "User page" is ambiguous with
the on-wiki user page. We actually show the leaderboard when the menu
is clicked on. So, rename the menu as "User profile" instead.

Ref: https://github.com/commons-app/apps-android-commons/issues/5754#issuecomment-2196796213
2024-07-20 23:19:16 +10:00
Matija Nalis
7f6b45aeb6
Update allowed recent years to include 2020s (#5761)
* document regex due to #47

* also count 2020s as "recent years"

* clarify that not all years are ignored

* clarify "year" is current year

* original logic fix
from https://github.com/commons-app/apps-android-commons/pull/5761#pullrequestreview-2144120347

* better variale name for ".*0s.*"
as that regex will match e.g. `1920s` and `80s` too, so the original `is20xxsYear` would be confusing name for it

* consolidate duplicated code to spammyCategory

* clarify regexes via variables

* spammyCategory should always be skipped

* return is simple now, so we can get rid of extra val oldDecade

* fix curYearInString

* some clarification comments

* refactor: rename containsYear to isSpammyCategory

This is done as the name containsYear is ambiguous.
It not just checks for year to identify spammy categories.

* refactor: rename containsYear to isSpammyCategory (take 2)

 A continuation of fe74c77ab (refactor: rename containsYear
 to isSpammyCategory, 2024-07-17)

---------

Co-authored-by: Kaartic Sivaraam <kaartic.sivaraam@gmail.com>
2024-07-20 23:16:20 +10:00
Amir E. Aharoni
34addbe33a
Remove unnecessary double quotes from a message (#5777) 2024-07-18 11:47:03 +09:00
Kaartic Sivaraam
5be76044b1
wikidata-feedback: avoid sentence lego (#5775)
Fixes #5763
2024-07-17 16:22:41 +09:00
translatewiki.net
73e9ed8e26
Localisation updates from https://translatewiki.net. 2024-07-15 14:02:06 +02:00
translatewiki.net
4d8a7dc138
Localisation updates from https://translatewiki.net. 2024-07-11 14:02:14 +02:00
Amir E. Aharoni
5bcbaa1beb
Improve the messages about being in a different place (#5764)
Remove parentheses and comma splice,
and replace them with more straightforward grammar.
2024-07-08 23:16:26 +09:00
translatewiki.net
018a924c53
Localisation updates from https://translatewiki.net. 2024-07-08 14:02:00 +02:00
Kanahia
3779cfb6a5
Added Wikitalk Page (#5740)
* Added wikitalk page and improved bottomsheet for landscape mode

* Improved wikitalk page

* Fixed italics

* Fixed little bug

* Improved the wiki talk page

* .

* changed commons url to wikidata url

* changed commons url to wikidata url + 1

* fixed bookmark issue

* Added kdoc and javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-07-08 15:13:57 +09:00
Kaartic Sivaraam
36905711d0 Version v5.0.2 2024-07-07 01:05:55 +05:30
translatewiki.net
05ffd123e4
Localisation updates from https://translatewiki.net. 2024-07-04 14:01:57 +02:00
translatewiki.net
923acd802c
Localisation updates from https://translatewiki.net. 2024-07-01 14:01:39 +02:00
translatewiki.net
f8e1030164
Localisation updates from https://translatewiki.net. 2024-06-27 14:01:37 +02:00
translatewiki.net
63ec5c8433
Localisation updates from https://translatewiki.net. 2024-06-20 14:01:59 +02:00
Kaartic Sivaraam
69cd9c94d1
Make sure to clear cookies on logout (#5727)
* Ensure to clear the cookies when logging out

It turns out that we failed to clear the cookies from the cookie JAR
when logging the user out. As a consequence, the cookie were retained
and it was possible to edit depictions as the previous user even without
logging in to the app (using the retained cookies).

Make sure we properly clear the cookies when we log the user out.

As an aside, the fact that the edit button shouldn't have been shown
is a different issue being tracked in #5726

* session: reuse removeAccount method for log out

The removeAccount method takes care of invoking the non-deprecated
API in applicable API levels. The logout method did not do such a
thing. Avoid redundancy, and reuse the removeAccount method for
logging out.
2024-06-18 22:31:34 +02:00
Kaartic Sivaraam
1808699e89
Make new feedback to be added as a new section to the end of the page (#5753)
* feedback: add the feedback as a new section at end of the page

Addresses feedback on #5542. For auto-archiving of section
to work properly on our feedback page, the new sections need to
be created at the end of the page rather than at the top.

So, adjust the feedback addition logic to make it such that the
feedback is appended to the bottom of the page.

* Replace lambda with a method reference

* feedback: replace edit summary with something more relevant

The summary of the feedback page was unhelpful. Make it more helpful by
using a more helpful summary that at least mentions the version of the
app for which the feedback is posted.

* test: try to fix test case related to feedback change
2024-06-18 21:40:30 +02:00
translatewiki.net
0e39d93721
Localisation updates from https://translatewiki.net. 2024-06-17 14:02:06 +02:00
Adam English
c2ac0f659a
Add code on line 340. Replace the 'Add location' button with 'Edit location' button when user clicks yes in similar image dialog (#5756)
Co-authored-by: starrain <zxyadelaide@gmail.com>
2024-06-15 22:17:28 +02:00
translatewiki.net
68df749ad2
Localisation updates from https://translatewiki.net. 2024-06-13 14:02:35 +02:00
translatewiki.net
5cdfb85a9c
Localisation updates from https://translatewiki.net. 2024-06-11 11:28:25 +02:00
Evangelos Talos
48bd3c07b8
Add ProgressBar to MediaDetailPagerFragment for Image Loading Indicator (#5736)
* Add progress bar to fragment_media_detail_pager.xml

* Add progress bar to MediaDetailPagerFragment.java

* Add javadoc & comments

* Fix tests

---------

Co-authored-by: Giannis Karyotakis <110292528+karyotakisg@users.noreply.github.com>
2024-06-10 20:06:06 +09:00
Jason-Whitmore
3dc7180784
LocationPickerActivity.java: Fix location picker bug with incorrect map centering (#5716)
This commit moves the center of the map to the image's location, if the image has
location EXIF data. If the image does not have location EXIF data, the map will
center on the device's current GPS location.
2024-06-09 21:24:40 +02:00
Kaartic Sivaraam
bb4cfe421a
feedback: add info about where the feedback gets posted (#5748)
* feedback: add info about where the feedback gets posted

Fixes #5747

* feedback: avoid underscore in the link's alternative text

* wording

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-06-09 21:08:38 +02:00
Evangelos Talos
22dd69cabb
Improved Map Marker Visibility Based on App Theme (#5744)
* Update map markers

* Modify code to use different map marker for themes

* Update map markers for bookmarked

* Add 2 tests

---------

Co-authored-by: Giannis Karyotakis <110292528+karyotakisg@users.noreply.github.com>
2024-06-09 21:07:02 +02:00
Zack Chi
bde09072fb
Update Java version from Java 8 to Java 11 (#5732)
Co-authored-by: Zack Chi <zach.chi.email@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-06-09 20:56:38 +02:00
pacha0
8fdfb8e6dc
Fixes bug #5721 (Crash when adding and then removing media details) (#5750)
I replaced uploadMediaDetail.get(position) with a direct reference to uploadMediaDetail in 3 locations to avoid out of bounds errors.

The usage of RecyclerView in this code is still a bit buggy, and maybe a RecyclerView is not necessary here since the number of items is limited. The fix also revealed problems in the logic of adding the "addButton" only to the last item, which couldn't be reproduced before because the app crashed first. I can submit a new bug for that if this fix goes live.

I did not feel very comfortable with the code so I restricted my commit to fixing the first reported crash, which was resolved in my tests.
2024-06-06 15:10:26 +02:00
translatewiki.net
06e25a074c Localisation updates from https://translatewiki.net. 2024-06-03 14:03:12 +02:00
Giannis Karyotakis
fe7a2f2a8c
Fix voice input bug (#5739)
* Fix voice input bug

* Add javadocs

---------

Co-authored-by: vtalos <v.talos23@gmail.com>
2024-06-02 23:06:43 +05:30
translatewiki.net
18e03ed038 Localisation updates from https://translatewiki.net. 2024-05-30 14:02:21 +02:00
pacha0
ff21e73928
Updated skip_login_title string to sound more natural (#5746) 2024-05-30 09:49:03 +09:00
translatewiki.net
cd045a2a2a Localisation updates from https://translatewiki.net. 2024-05-27 14:01:46 +02:00
translatewiki.net
9a2a56c1cf Localisation updates from https://translatewiki.net. 2024-05-23 14:03:04 +02:00
translatewiki.net
041c293808 Localisation updates from https://translatewiki.net. 2024-05-20 14:01:58 +02:00
translatewiki.net
4545059035 Localisation updates from https://translatewiki.net. 2024-05-16 14:03:00 +02:00
translatewiki.net
7e84a447d4 Localisation updates from https://translatewiki.net. 2024-05-09 14:02:10 +02:00
translatewiki.net
c67cf4b07e Localisation updates from https://translatewiki.net. 2024-05-06 14:02:07 +02:00
Kanahia
6b93c34f9e
Changed Unknown to Wikidata Description (#5697)
* Delete app/src/main/res/values-yue-hant directory

* Changed Unknown to description
2024-05-03 07:23:19 +09:00
translatewiki.net
d6ac307f63 Localisation updates from https://translatewiki.net. 2024-05-02 14:02:38 +02:00
Evangelos Talos
c178c5de41
Enhancing Multi-Upload Functionality for Consistent Depiction Categorization (#5700)
* Add AlertDialog for categories and modularize receiveSharedItems

* Improve nearby-place search function for a multi-upload

Enhance the depiction consistency of a multi-upload by ensuring that it corresponds to a single place

* Add javadoc

* Update strings.xml

* Renamed setImageTobeUploaded to setImageToBeUploaded

* Make uploadIsOnPlace private & add a setter

* Rename uploadIsOnPlace to uploadIsOfAPlace

* Use singular when there is only one picture

* Add a 'Do not show again' checkbox on the dialog

* Update strings.xml

---------

Co-authored-by: Giannis Karyotakis <110292528+karyotakisg@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-05-02 20:12:32 +09:00
translatewiki.net
6aa9303d0f Localisation updates from https://translatewiki.net. 2024-04-29 14:02:13 +02:00
Kaartic Sivaraam
af028cbddc Version v5.0.1 (fix release variant of v5.0.0)
The same as v5.0.0 except this fixes the release variants of the
app for v5.0.0.
2024-04-28 16:58:49 +05:30
Kaartic Sivaraam
da0b2c28e3 app: adjust R8 rules to ensure our model classes are not obfuscated
With refactoring of the dataclient module, the model classes now live
within the app's source code itself. So, the existing R8 rules became
obsolete and resulted in the prodRelease version of the app not
working.

So, adjust the R8 rules so that R8 doesn't obsfuscate the model classes
that now live within the app.
2024-04-28 16:21:14 +05:30
Kaartic Sivaraam
4cc5224556 CHANGELOG: add link to the v5.0.0 GitHub release 2024-04-28 15:07:13 +05:30
Kaartic Sivaraam
c692766c41 CHANGELOG: fix diff link to appropriately use the v5.0.0 tag 2024-04-28 15:05:54 +05:30
Kaartic Sivaraam
12ac709aba Rebrand v4.3.0 as v5.0.0
We have a huge quantum of changes since v4.2.1. So, it makes sense
to bump the major release. So, rebrand v4.3.0 as v5.0.0.
2024-04-28 14:46:47 +05:30
Kaartic Sivaraam
9f904a9515 Version v4.3.0 2024-04-27 22:35:06 +05:30
rohit-satya
e7504b8344
rename method setImageTobeUploaded to setImageToBeUploaded (#5706) 2024-04-27 13:35:26 +05:30
translatewiki.net
2e0c57b3ce Localisation updates from https://translatewiki.net. 2024-04-25 14:02:33 +02:00
RedAuburn
e23d03b2db
remove yue-hant again (#5702) 2024-04-23 22:29:51 +09:00
translatewiki.net
feabb6bc20 Localisation updates from https://translatewiki.net. 2024-04-22 14:02:19 +02:00
Kaartic Sivaraam
4eb8a82191 Remove the value-yue-hant file
The file is not properly recognized by Android and we've actually
codemapped it to yue. The translatewiki configuration has been
done in the incorrect file. So, it is still being created.

The following Gerrit change would correct it for updates after that
change is merged.

  1022508

For the time being remove it for the sake of release.
2024-04-21 19:49:59 +05:30
translatewiki.net
4bd48aa432 Localisation updates from https://translatewiki.net. 2024-04-18 14:02:49 +02:00
Shashwat Kedia
04f9ef4819
Fixes Location related flow of the app #5256 , #5461, #5490 (#5494)
* Resolved merged conflicts

* Resolved merge conflicts and updated workflow

* Updated Location Flow and merged conflicts

* Update flow and merge conflicts

* Fixed LocationPicker's location flow

* Removed redundant code from  LocationPermissionsHelper

* Fixed Explore fragment crashing and incorrect behaviour

* Updated LocationPicker Flow

* Fixed Nearby not working as intended

* Final update to location flow of all maps

* Added the reqested changes and fixed bugs

* Resolved requested change in in-app camera location flow

* Fixed In-app camera location flow

* Resolved conflicts in ContributionsListFragment

* Updated java doc as requested

* Resolved nearby card dialog not being shown

* Optimised LocationPermissionsHelper javadoc

* Made requested changes for preference check

* Added javadoc and requested comment for later reference

* Implemented requested code changes

* Fixed failing test due to changes made during PR

* Added string resource for ExploreMapFragment

* Changed string resource for rationale dialog

* Added standard location flow information in LocationPermissionsHelper

* Added javadoc for doNotAskForLocationPermission

* Removed unused import

* Updated javadoc

* Removed values-yue-hant

* Fix some merge conflict errors

* Fix minor errors due to mergre conflicts

* Fix some refactor errors

* Fixed minor bug due to merging conflicts

* Delete app/src/main/res/values-yue-hant directory

* Final changes to NearbyParentFragment

* Fixes #5686 map coordinates set to image coords

* Removed some redundant code from recenterMap

* Removed one test whose method no longer exists

* Removed unused method from contract of nearby

* Removed redundant method from NearbyParentFragment

* nearby: add a FIXME about the possibly redudant code

---------

Co-authored-by: Kaartic Sivaraam <kaartic.sivaraam@gmail.com>
2024-04-17 09:18:34 +05:30
RedAuburn
1f2e31d45b
Add Adaptive Icon (#5687)
* Add Adaptive Icon

allows for better theming on android O and above.

* remove values-yue-hant
2024-04-16 16:51:54 +09:00
translatewiki.net
de95c46627 Localisation updates from https://translatewiki.net. 2024-04-15 14:02:26 +02:00
translatewiki.net
8ce7485153 Localisation updates from https://translatewiki.net. 2024-04-11 14:02:37 +02:00
Shashank Kumar
2d29fbe885
Fix Failing Tests in DescriptionEditActivityUnitTest (#5685)
* initial commit

* Fix No Precise Message After Clicking Review Buttons

* Fix For ThanksClient

* Fix For Depictions

* Fix For Categories

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Mark as Read notifications

* resolve conflicts

* fix merge conflicts

* fix tests

* fix tests

* fix tests

* fix tests
2024-04-11 15:47:36 +09:00
Brooke Vibber
a595500921
Add a .mailmap file to update bvibber's name/email (#5684) 2024-04-09 07:14:11 +09:00
translatewiki.net
200894665b Localisation updates from https://translatewiki.net. 2024-04-08 14:02:11 +02:00
translatewiki.net
9aa1875bc4 Localisation updates from https://translatewiki.net. 2024-04-04 14:03:12 +02:00
Nicolas Raoul
aa39e509c3
Updated contributors gallery 2024-04-02 22:51:47 +09:00
translatewiki.net
6cbeec2119 Localisation updates from https://translatewiki.net. 2024-04-01 14:02:56 +02:00
Neel Doshi
54312fc2ac
Feat : Added a feature for users to add feadback on github (#5578)
* Feat : Added a place for users to add feadback on github

* Code Cleanup : Added Github Issue URL as Const

* Homogenized

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-04-01 17:36:21 +09:00
Shashank Kumar
3d1efecb55
Fix - No Precise Error Message After Error Due to Password Change (#5643)
* initial commit

* Fix No Precise Message After Clicking Review Buttons

* Fix For ThanksClient

* Fix For Depictions

* Fix For Categories

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Description & Coordinates

* Fix For Mark as Read notifications

* resolve conflicts

* fix merge conflicts
2024-04-01 17:23:00 +09:00
Rohit Verma
e56de2c343
Allow only 1 picture to be selected by System Picker through Nearby (#5680)
* SettingsFragment: add a method for creating locale

* SettingsFragmentUnitTests: fix failing tests for createLocale

* NearbyParentFragment: set WLM check to false
2024-04-01 11:54:30 +09:00
Shashank Kumar
82b97fc49f
Fix Crash EditDescriptionActivity when switched dark/light mode (#5503)
* Fix Crash EditDescriptionActivity when switched dark/light mode

* tests added

* fix

* code cleanup

* code cleanup

* Fix

* Fix
2024-03-31 15:36:41 +09:00
Rohit Verma
5a508ae417
Rename be-x-old language code to be-tarask in languages list (#5676)
* SettingsFragment: add a method for creating locale

* SettingsFragmentUnitTests: fix failing tests for createLocale

* languages_list: rename be-x-old to be-tarask
2024-03-31 00:10:59 +09:00
Shashwat Kedia
0a6257b27b
Adds a 'Remove Location' button to the UploadWizard #5247 (#5672)
* Implemented basic flow to remove location

* Fixed and added new tests and enhanced UX
2024-03-30 23:34:55 +09:00
Kanahia
7dd00efa64
Fixes App crashes on clicking Save (EditActivity) (#5670)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Removed mapbox code1

* Removed mapbox code2

* Fixed failing tests

* Fixed failing due to merging

* Added feature to save nearby places as GPX and KML

* Fixed error caused by null

* Improved UX for Nearby Export

* Delete app/src/main/res/values-yue-hant directory

* Fixed internationalization issue

* Fixed crash
2024-03-30 10:29:59 +09:00
Shashank Kumar
5ca3b7834d
Remove Butterknife from Project (#5660) 2024-03-30 10:27:59 +09:00
Shashank Kumar
4c687b4335
Set Wallpaper in background (#5665)
* Worker added for setting up wallpaper

* Fix crash

* Fix test

* Fix test
2024-03-30 09:04:07 +09:00
Neel Doshi
a7a2125e1d
Fix : Dialog Scroll Issue for devices below Android 10 (#5663) 2024-03-29 20:37:43 +09:00
Kanahia
1f064e29fa
Fixes Nearby export: Pins not all around me (#5658)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #4345

* Delete app/src/main/res/values-yue-hant directory

* Added comment explaining the context

* Fixes #5651

* Deleted directory

* Changed query to 10 kilometers

* Fixed issue

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-03-29 19:44:49 +09:00
Kanahia
6d4ba12775
Improved UX for Nearby Export (#5654)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Removed mapbox code1

* Removed mapbox code2

* Fixed failing tests

* Fixed failing due to merging

* Added feature to save nearby places as GPX and KML

* Fixed error caused by null

* Improved UX for Nearby Export

* Delete app/src/main/res/values-yue-hant directory

* Fixed internationalization issue
2024-03-28 23:12:54 +09:00
translatewiki.net
c5c5a52a09 Localisation updates from https://translatewiki.net. 2024-03-28 13:02:56 +01:00
Shashank Kumar
d408134b7e
Enhacement - Add Custom Image Selector FAB Option to Nearby (#5655)
* Fix

* Fix

* Migrate NearbyParentFragment to ViewBinding

* remove unused Imports

* fix crash
2024-03-28 19:34:52 +09:00
Shashank Kumar
4c43bf2bd4
Fix-Peer Review Buttons not get disabled after voting an Image for deletion (#5519)
* Fix-Peer Review Buttons not get disabled after voting an Image for deletion

* Fix
2024-03-27 22:24:44 +09:00
Shashwat Kedia
42641644cb
Fixes #4704: Remove 'Please Wait' dialog and do task in background (#5570)
* Initial changes to the flow, merged conflicts

* Major changes to flow and logic

* Final major changes to the flow and merged conflicts

* Minor changes to thumbnail flow and merge conflicts

* Fixed ImageProcessingServiceTest

* Removed unnecessary file

* Some code cleanup and fixed UploadRepositoryUnitTest

* Minor javadoc changes and null checks

* Fixed UMDFragmentUnitTest

* Fixed and added new tests in UploadMediaPresenterTest

* Optimised code for no connection cases and minor code cleanup

* Minor bug fix

* Fixed minor bug

* Fixed a failing unit test

* Removed values-yue-hant

* Update UploadRepository.java

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-03-27 16:33:28 +09:00
Shashank Kumar
16960a369a
Fix (#5648) 2024-03-27 16:28:16 +09:00
Neel Doshi
ce9dc33045
Refactor : String when achievements not loaded (#5653) 2024-03-27 16:01:28 +09:00
Neel Doshi
c9317d891e
Removed unused butterknife binding import (#5659) 2024-03-27 15:28:00 +09:00
Kanahia
30b8968199
Changed Created by in Neaby export (#5652)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #4345

* Delete app/src/main/res/values-yue-hant directory

* Added comment explaining the context

* Fixes #5651

* Deleted directory

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-03-26 16:28:34 +09:00
Neel Doshi
71a45c02e2
Refactor CampaignView to use ViewBinding. (#5607) 2024-03-25 22:11:24 -05:00
Neel Doshi
21fdd39d7c
Refactor : NotificationActivity uses ViewBinding. (#5606) 2024-03-25 21:39:32 -05:00
Kanahia
c41940241b
Added functionality to export location of nearby missing pictures to GPX file and KML file (#5645)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Removed mapbox code1

* Removed mapbox code2

* Fixed failing tests

* Fixed failing due to merging

* Added feature to save nearby places as GPX and KML

* Fixed error caused by null
2024-03-25 23:22:17 +09:00
translatewiki.net
dae1f2557e Localisation updates from https://translatewiki.net. 2024-03-25 13:02:47 +01:00
Kaartic Sivaraam
01a3e14154
achievements: make sure thanks count is displayed properly (#5647)
We seem to be incorrectly setting the the thanks count in the achievement level
text. This was then being over-written by the actual achievement level value
in the code flow. In the end, the thanks count seems not to have been displayed at all.

Correct this by properly updating the thanks count.

Fixes #5641
2024-03-25 19:30:04 +09:00
Shashank Kumar
2a2780a4d2
Migrated Review Package to View Binding (#5604)
* Migrated Review Package to View Binding
* fix
2024-03-24 23:17:08 -05:00
bugfixer4646
a45ab9cf16
Refactor curlatLng to currentLatLng (#5646)
Co-authored-by: Aahlad <aahladkethineeedi@gmail.com>
2024-03-25 10:35:30 +09:00
Kanahia
cd337b000e
Fixed Blue square appears at end of description of image uploaded (#5616)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #4345

* Delete app/src/main/res/values-yue-hant directory

* Added comment explaining the context

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-03-25 08:29:30 +09:00
Kanahia
724e4db0fd
Made Nearby show all pins that could be presented on the screen, rather than a circle (#5553)
* Changed nearby from circle to rectangle

* Fixed bug

* Removed MAPBOX Token

* Fixed minor issues

* Fixed minor issues

* Changed query files

* Changed monuments query file

* Fixed Unit Tests

* Fixed failing tests

* Fixed errors due to merging
2024-03-24 23:24:39 +09:00
Shashank Kumar
f404ac9b47
Remove Image From Upload Wizard (#5636)
* Added Remove Icon to Image Thumnails

* fix

* fix

* fix tests
2024-03-24 17:32:56 +09:00
Shashank Kumar
78a53fd40d
fix (#5640) 2024-03-24 08:56:47 +09:00
Shashank Kumar
e5c4230f97
Fix - Custom picker sometimes crashing when marking many pictures as "not for upload" towards the bottom (#5639)
* fix crash

* fix crash
2024-03-24 08:39:39 +09:00
Rohit Verma
7e5789d539
Resolve switching languages problem in settings fragment (#5560)
* SettingsFragment: add a method for creating locale

* SettingsFragmentUnitTests: fix failing tests for createLocale
2024-03-22 23:12:45 +09:00
Lawal abiola
751bc6ce6b
fix: thumbnail of rotated image not visible during upload (#5552)
[The image path of rotated image is not save like nornal image.
so imageview cannot load rotated image

check whether the image path is file uri then add imagerequest to load the image]

[fixed: thumbnail not visible when upload rotated image]
2024-03-22 22:51:31 +09:00
Shashank Kumar
2d333a2af0
Custom picker: Show differently pictures that are currently being uploaded (#5618)
* Custom picker: Show differently pictures that are currently being uploaded

* fix tests

* fix test

* fix crash

* handle all cases

* handle all cases

* fix

* Hide Images When showAlreadyActioned is disabled

* Fix Tests

* cleanup
2024-03-22 22:13:19 +09:00
Amir E. Aharoni
72cdb5d0dd
Message spelling and punctuation improvement (#5638)
1. Remove comma splice.
2. Consistent spelling: "log-in" as noun, "log in" as verb.
2024-03-22 11:21:51 +09:00
Shashank Kumar
2a6ab66c11
Precise error message password change (#5544)
* Precise Error Message and Action When Password is Changed

* Make message persistent

* code cleanup and fixes

* removed unnecessary string resource

* removed unnecessary string resource

* fix

* fix

* Upload Client to kotlin

* Remove Redundant Code

* Remove Redundant Code

* code cleanup and Improvements

* Improved Java doc

* Improved Java doc

* Improved Javadoc
2024-03-21 23:21:53 +05:30
Kanahia
152e824aa6
Fixed reproducible crash when marking last pictures as "not for upload" (#5635)
* Fixes #5634

* Delete app/src/main/res/values-yue-hant directory
2024-03-21 22:51:33 +09:00
Kanahia
d112be04db
Removed MapBox related imports (#5631)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Removed mapbox code1

* Removed mapbox code2

* Fixed failing tests

* Fixed failing due to merging
2024-03-21 22:43:22 +09:00
translatewiki.net
9041e1bc0c Localisation updates from https://translatewiki.net. 2024-03-21 13:03:00 +01:00
Shashank Kumar
7e9aa5b72a
Migrated Media Package to View Binding (#5601)
* Migrated Media Detail Fragment to View Binding
* Migrated ZoomableActivity and PagerFragment
* Improved Java doc
* Improved Java doc
2024-03-20 20:29:07 -05:00
Shashank Kumar
8df0055a5a
Migrated CategoryDetailsActivity , MainActivity to View Binding (#5595)
* Butterknife to ViewBinding
* code fix to pass all tests
2024-03-19 19:21:51 -05:00
Rajesh
23492ab11f
#5611 removed MapBox from CREDIS because it has already been replaced (#5617) 2024-03-18 22:41:51 +09:00
translatewiki.net
192a17156c Localisation updates from https://translatewiki.net. 2024-03-18 13:02:41 +01:00
Shashwat Kedia
bd06a74caa
#4664 Moved Settings Activity to ViewBinding (#5506)
* Moved Main Activity and Settings Activity to ViewBinding
* Moving only SettingsActivity for now
* Removed values-yue-hant directory
* Removing previously done changes to main.xml
2024-03-17 09:03:21 -05:00
Shashank Kumar
fec6dba341
Fix Crash in LocationPickerActivity when device configuration is changed (#5500)
* Fix Crash in LocationPickerActivity when device configuration is changed (UI Modes)

* clean up

* fix

* fix

* fix

* fix

* removed faulty test

* cleanup

* fix and tests added
2024-03-17 22:46:13 +09:00
Shashank Kumar
161e2edc31
Migrated Bookmarks Package From Butterknife to ViewBinding (#5594)
* Butterknife to ViewBinding
* code fix to pass all tests
* code cleanup & binding added to tests
2024-03-17 08:01:55 -05:00
Shashank Kumar
3e5424e18d
Fix Custom image selector (#5576)
* Custom Image Selector Fix

* Custom Image Selector Fix

* code cleanup

* fixes

* Renamed yue-hant

* added java docs

* fix
2024-03-16 22:15:21 +09:00
Shashank Kumar
f73c9dc4d3
Migrate Upload Package To View Binding from Butterknife (#5590)
* Butterknife to ViewBinding
* Butterknife to ViewBinding
* code cleanup and tests fixed
* code cleanup and optimised imports
2024-03-15 14:37:28 -05:00
translatewiki.net
1b5df47362 Localisation updates from https://translatewiki.net. 2024-03-14 13:03:25 +01:00
Shashank Kumar
c94f607107
Migrated Contributions Package From Butterknife to ViewBinding (#5593)
* Butterknife to ViewBinding
* code fix to pass all tests
* test fix
* test fix
* test fix
* test fix
* Update ContributionsFragmentUnitTests.kt
* code cleanup and tests improved
* tests fixed
* adjustments and code cleanup
* adjustments and code cleanup
2024-03-12 21:16:55 -05:00
Shashank Kumar
2076bf9b29
Migrated Explore Package From Butterknife to ViewBinding (#5592)
* Butterknife to ViewBinding
* code cleanup and tests migrated to binding
* tests fixed
* adjustments and code cleanup
2024-03-11 15:48:37 -05:00
translatewiki.net
7b63185d5a Localisation updates from https://translatewiki.net. 2024-03-11 13:02:23 +01:00
Shashank Kumar
6ed5deac65
Migrated Profile Package from Butterknife to View Binding (#5591)
* Butterknife to ViewBinding
* code fix to pass all tests
* code cleanup and tests migrated to binding
* fix LoD
2024-03-10 14:44:06 -05:00
Neel Doshi
dbe739e755
CodeCleanup : Removed Unused Import from packages. (#5609)
* CodeCleanup : Removed Unused Import from packages.

* Removed : value-yue-hant directory.
2024-03-08 23:21:27 +09:00
translatewiki.net
b5ce7c735d Localisation updates from https://translatewiki.net. 2024-03-07 13:02:16 +01:00
Neel Doshi
28ea11127c
Refactor : API calls moved out of the LoginActivity (#5599)
* Refactor :API calls separated from activity added to LoginClient
* getLoginToken() modifier set to private
* Code Cleanup : removed non-null from twofactor and Locale import
* Indentation fix
2024-03-06 08:35:50 -06:00
translatewiki.net
2c376da46e Localisation updates from https://translatewiki.net. 2024-03-04 13:02:19 +01:00
translatewiki.net
9e5854766e Localisation updates from https://translatewiki.net. 2024-02-29 13:02:11 +01:00
translatewiki.net
55346c125b Localisation updates from https://translatewiki.net. 2024-02-26 13:02:48 +01:00
Shashank Kumar
f5a5159f8c
Fix Crash Edit Categories Fragment (#5510)
* fix crash

* Tests Added

* Tests and Null Checks Added
2024-02-26 18:42:51 +09:00
Ivan C. Acha
535792390e
Changed line break behaviour. (#5562)
Added line breaks to PageContentsCreator.java, CategoryEditHelperUnitTests.kt, and MediaDetailFragmentUnitTests.kt following the example given on issue #5523.
2024-02-25 23:00:45 +09:00
translatewiki.net
0656c88fbe Localisation updates from https://translatewiki.net. 2024-02-22 13:02:22 +01:00
Paul Hawke
f0a1d036a5
Convert upload client to kotlin (#5568)
* Write unit tests for the UploadClient (with gentle refactoring to make it testable)

* Convert Upload client to kotlin
2024-02-21 15:55:35 +09:00
Paul Hawke
728712c4e1
Convert API clients to kotlin (#5567)
* Convert UserClient to kotlin

* Added tests for WikiBaseClient

* Removed superfluous dao tests in review helper and got the proper ReviewDaoTest running in the unit test suite

* Improved tests for ReviewHelper

* Convert the ReviewHelper to kotlin

* Convert the WikiBaseClient to kotlin

* Convert the WikidataClient to kotlin
2024-02-20 09:23:11 +09:00
translatewiki.net
c43405267a Localisation updates from https://translatewiki.net. 2024-02-19 13:02:18 +01:00
Rohit Verma
9a8b89c19a
nearby: Show 'no images in this area' message instead of generic error (#5541) 2024-02-18 15:02:19 +00:00
tamojitdas
74f2e9cd60
fix: unit tests for switching from MapBox to OpenStreetMap (#5536)
Fixes #5408
2024-02-18 14:06:40 +00:00
Kanahia
26658e91b3
Removed MAPBOX Access token (#5555)
* Update gradle.properties

* Update build.gradle

* Update CommonsApplication.java

* Update LocationServiceManager.java
2024-02-16 20:56:58 +09:00
Shashank Kumar
4c5d690a9e
Fix Crash When Explore Tab is Clicked (#5550) 2024-02-15 22:54:10 +09:00
Kwong Yu Zhou
013bc7db39
Replaced campaignPreference with CAMPAIGNS_DEFAULT_PREFERENCE (#5357)
Co-authored-by: Kwong Yu Zhou <https://github.com/kwong-yu-zhou>
Co-authored-by: Adam Jones <domdomegg+git@gmail.com>
2024-02-14 21:23:23 +00:00
Shashank Kumar
2d5b7cc579
Fix DNG/RAW Upload Error (#5543)
* Fix DNG/RAW Upload Error

* Fix DNG/RAW Upload Error

* supported formats added

* cleanup
2024-02-14 21:09:48 +09:00
Adam Jones
1cbce77d5f
Migrate from Kotlin synthetics to Jetpack view binding (#5546) 2024-02-14 18:29:06 +09:00
Shashwat Kedia
b18117bc07
Resolved Problems in UploadMediaDetails flow and UX #5511 (#5527)
* Fixed arrow flickering issue on zoom

* Resolved issue point 1 and 3

* Resolved issue point 2

* Minor changes

* Update UploadActivity.java

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-02-14 18:07:51 +09:00
Shashank Kumar
a308a1c8b5
Fix Methods Usage Incompatible with Android API Level 21 (#5525) 2024-02-14 09:05:13 +00:00
Shashank Kumar
8df7f66992
Show Labels For FAB in Contribution Page (#5493) 2024-02-13 21:33:03 +00:00
translatewiki.net
d8b7e27b48 Localisation updates from https://translatewiki.net. 2024-02-12 13:02:39 +01:00
Shashank Kumar
1895bb3ec2
Fix - Unable to Access Privacy Policy (#5534)
* Fix - Unable to Access Privacy Policy

* cleanup
2024-02-11 21:11:20 +09:00
Paul Hawke
f9090b0c2c
Consistent api interfaces (#5530)
* Converted CategoryInterface to kotlin

* Converted DepictsInterface to kotlin

* Convert the MediaDetailInterface to kotlin

* Convert MediaInterface to kotlin

* Convert ReviewInterface to kotlin

* Convert the UserInterface to kotlin

* Convert the WikiBaseInterface to kotlin

* Convert WikidataInterface to kotlin

* Convert WikidataMediaInterface to kotlin

* Convert UploadInterface to kotlin
2024-02-09 13:43:01 +09:00
Paul Hawke
c6cb97e199
Add ability to suppress logging of known unsuccessful API calls (#5526) 2024-02-09 06:55:56 +09:00
translatewiki.net
ba11f0d06e Localisation updates from https://translatewiki.net. 2024-02-08 13:02:19 +01:00
translatewiki.net
a534c11bbb Localisation updates from https://translatewiki.net. 2024-02-05 13:03:06 +01:00
Paul Hawke
0c3085257d
Data client simplification / removal (#5507)
* Removed unused code from the data client module

* Move remaining code out of the data-client and remove it
2024-02-03 10:26:06 +09:00
Shashank Kumar
72a6fd2c90
Enhancement - Nearby banner shows Item without image (#5468)
* Enhancement Nearby banner shows Item without image

* spacing

* Enhancement Nearby banner shows Item without image

* spacing

* Fix Crash

* Reverted "Fix Crash"

This reverts commit a41c9362bb.

* Fixed

* Fix null location

* Fix Conflict

* Show Only Items that exists

---------

Co-authored-by: shashankkumar <shashankkumar45556@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-02-01 22:17:52 +09:00
translatewiki.net
60046f4881 Localisation updates from https://translatewiki.net. 2024-02-01 13:02:45 +01:00
Paul Hawke
8db0b54929
Remove the data-client app adapter implementation (#5499)
* logErrorsInsteadOfCrashing only ever returned false, so remove it and simplify logger

* Removed unused getMediaWikiBaseUrl()

* Removed getDesiredLeadImageDp() from commons app adapter, since it's unused

* Inlined the isLoggedIn() method of the app adapter

* Inline the app adapter username/password

* Removed the unused getRestbaseUriFormat() from the commons app adapter

* Remove references to the data-client SharedPreferenceCookieManager

* Manage our own OkHttpClient and remove the AppAdapter implementation
2024-01-31 10:21:43 +09:00
Paul Hawke
ab9e57f5be
Decouple from the data-client service factory, with some code cleanup in the process (#5496) 2024-01-30 07:46:01 +09:00
translatewiki.net
7ec2a22fce Localisation updates from https://translatewiki.net. 2024-01-29 13:03:35 +01:00
Shashank Kumar
8bafee3801
Enhancement-Show Icon Labels When Long Pressed (#5492)
* Enhancement-Show Icon Labels when Long Pressed

* Tests Added
2024-01-29 17:05:43 +09:00
Nicolas Raoul
a8545abff2 Merge remote-tracking branch 'origin/switch-to-commons-toolforge' 2024-01-29 15:35:33 +09:00
Rohit Verma
9f7e407181
DepictsFragment: add null check for media (#5488) 2024-01-29 15:33:36 +09:00
Paul Hawke
84ffffbbe7
Create CsrfTokenClient and LoginClient by injection, along with a little cleanup (#5491) 2024-01-29 12:36:57 +09:00
Shashwat Kedia
5661e8c332
Fixed flickering of nearby banner compass (#5486) 2024-01-29 09:02:30 +09:00
Kaartic Sivaraam
f1276e4697 Fix broken reference to a missing resource ID
Seems like a fallout of the change done in PR #5464

This hopefully should make the instrumentation tests resume.
2024-01-28 17:53:27 +05:30
Kaartic Sivaraam
805076ee6b Update test case for the toolforge URL change 2024-01-28 17:09:43 +05:30
Kaartic Sivaraam
611f40801e Switch to using the commons toolforge instance fully
So far we used both the urbanecmbot instances and also the commons-android-app
toolforge instance for the functioning of the app.

The reality is that the commons-android-app instance itself had the ability
to return all the data necessary for the app. Use the same to get all the
data necessary for the app.

Fixes #5462
2024-01-28 14:27:13 +05:30
Paul Hawke
9e970123fd
Removed references to the data-client Service interface (#5484) 2024-01-28 08:25:56 +09:00
Shashwat Kedia
8222c4a42c
Resolves Issue #5413 Crash when opening Nearby when location permission hasn't been granted yet (#5418)
* Resolves Issue #5413

* Added Javadoc and formatted code
2024-01-27 22:47:36 +09:00
Kanahia
71de19f27b
Fixed javadoc issue (#5481) 2024-01-27 22:40:18 +09:00
Kanahia
96b2608eb1
Replace Mapbox with OSMDroid (Explore Activity) (#5475)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Made UI changes in UploadMediaDetailAdapter

* Added javadoc

* Replaced Mapbox with OSMDroid in explore activity

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-27 21:39:08 +09:00
Paul Hawke
02ce017c98
Convert the LoginClient to kotlin (#5479)
* Convert the result classes to kotlin

* Convert response and callback to kotlin

* Cleanup code-quality warnings before converting

* Converted the LoginClient to kotlin

* Updated the UserExtendedInfoClientTest to be kotlin, and live in the correct spot
2024-01-27 12:39:00 +09:00
Paul Hawke
0541aacdff
Move login client out of the data-client (#5476) 2024-01-26 23:11:44 +09:00
Shashank Kumar
8789879f10
Fix Multi-Upload Wizard only asks Metadata for one picture (#5478)
* Fix Multi-Upload Wizard only asks Metadata for one picture

* javadoc

* typo

---------

Co-authored-by: shashankkumar <shashankkumar45556@gmail.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-26 22:43:07 +09:00
translatewiki.net
5225f3e7f2 Localisation updates from https://translatewiki.net. 2024-01-25 13:03:23 +01:00
Paul Hawke
97a208dcfa
Refactor CSRF token API to move it into the main commons code base (#5472)
* Remove redundent constructor parameter

* Converted the CsrfTokenClient and test to kotlin

* Moved getCsrfTokenCall() out of the data client
2024-01-24 22:15:51 +09:00
Shashank Kumar
e8e87b1d1c
Fix crash upload wizard (#5466)
* Upload Wizard Crash Fix

* Upload Wizard Crash Fix 2

* Fixes

---------

Co-authored-by: shashankkumar <shashankkumar45556@gmail.com>
2024-01-24 18:30:49 +09:00
Paul Hawke
8b8eb84fae
Moved the CSRF token client over into main commons code (#5471) 2024-01-24 10:36:43 +09:00
Kanahia
3d0e65c92c
Fixes on Edit button, there is + sign overlayed over letter E #5388 (#5464)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker

* Fixes #5439 by capitalizing first letter of voice input

* Made UI changes in UploadMediaDetailAdapter

* Added javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-23 23:03:38 +09:00
Rohit Verma
ab3540312a
Resolve displaced icons problem for small screens sizes (#5467)
* UploadMediaDetailInputFilter: added pattern to identify colon

Added hex code of colon for MediaDetailInputFilter and updated test for it.

* styles.xml: removed color styles for floatingActionButton
2024-01-23 22:59:57 +09:00
Srishti Rohatgi
495d001dc9
Send thanks button in more details fragment (#5424)
* Send thanks button in more details fragment

* failing test fix

* suggested fix
2024-01-23 22:55:16 +09:00
Paul Hawke
3c1cdf18a1
Move notification API into main commons codebase (#5465)
* Moved the notification API calls out of the data client

* Converted the NofificationClient to kotlin and improved its test
2024-01-23 22:43:37 +09:00
translatewiki.net
1948bab873 Localisation updates from https://translatewiki.net. 2024-01-22 13:02:37 +01:00
Paul Hawke
ef47b7025e
Move thanks API into main commons codebase (#5463)
* Move thanks API into main commons codebase

* KDoc

* KDoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-22 13:57:01 +09:00
Shashwat Kedia
2c086b3d79
Updating nearby banner on slight location change (#5459) 2024-01-21 18:01:16 +09:00
Shashwat Kedia
cbf022d2f2
Resolves #5445 highlighting nearest place on clicking home nearby banner (#5453)
* Highlighted nearest place when user clicks on home nearby banner

* Fixed incorrect behaviour of home nearby banner on being clicked

* Fixing failure of unit test cases

* spacing

* indentation

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-19 14:16:36 +09:00
Kanahia
b7323d0913
Fixes #5439 by capitalizing first letter of voice input (#5450) 2024-01-19 13:37:39 +09:00
Nicolas Raoul
1ebbe63fc7
Previous key Commons-GitHub2 key was abused on January 6 2024-01-19 13:10:02 +09:00
translatewiki.net
86608ede54 Localisation updates from https://translatewiki.net. 2024-01-18 13:02:47 +01:00
Kanahia
6319da5445
Replaced mapbox to osmdroid (Upload Activity) (#5443)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

* Replaced mapbox to osmdroid in upload activity

* Fixed Unit Tests

* Made selected marker to be fixed on map

* Changed color of map marker
2024-01-18 14:28:53 +09:00
Rohit Verma
11e7b1cde7
UploadMediaDetailInputFilter: added pattern to identify colon (#5451)
Added hex code of colon for MediaDetailInputFilter and updated test for it.
2024-01-18 09:23:50 +09:00
Shashwat Kedia
1aa07f9368
Resolves #2307 make achievements activity more visible (#5442)
* Resolves #2307 by adding user level in menu

* Formatted code as requested

* Start sentence with uppercase

* javadoc

* Fixed my typo

* javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-17 15:49:14 +09:00
Paul Hawke
56e21ab7c8
Removed butterknife and cleaned up the test (#5446) 2024-01-17 15:35:14 +09:00
Zen-Miyajima
87d1255323
added icon.png file (#5447)
I added icon.png file to resolve the grey icon issue.
2024-01-17 10:03:11 +09:00
Shashwat Kedia
38b1a014dd
Resolved #5411 by rotating button initially (#5412)
* Resolved #5411 by rotating button initially

* Renamed the image file to arrow_up
2024-01-15 22:02:26 +09:00
Rohit Verma
dd605fba87
Made Nearby Pins More Visible (#5440)
* NearbyParentFragment : added referer

In file NearbyParentFragment.java, I added header property, i.e., the referer - http://maps.wikimedia.org/
and set tile source to wikimedia.

* Reworded comments

* sdkmanager: added installation command for build-tools-30.0.3

* Revert "sdkmanager: added installation command for build-tools-30.0.3"

This reverts commit b3e5019e36.

* Update android.yml

* Update gradle.properties

* android.yml: removed extra debug commands

Removed some debug commands because they are no longer needed.

* ic_custom_map_marker: changed fill color to make it more visible.

Changed fill color for map marker used for nearby places that needs photo.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-15 21:53:51 +09:00
shashankiitbhu
54c7187bba
Peer Review Fix (#5435)
* Peer Review Fix

* Peer Review Fix #2

---------

Co-authored-by: shashankkumar <shashankkumar45556@gmail.com>
2024-01-15 21:40:33 +09:00
translatewiki.net
a16d9f7473 Localisation updates from https://translatewiki.net. 2024-01-15 13:02:38 +01:00
Shashwat Kedia
b3a4e8731e
Resolved issue of dark icons not visible in bottom NavBar and Achievements Fragment (#5410) 2024-01-15 17:02:23 +09:00
Shashwat Kedia
1f738eb7c4
Resolved issue #4513 vertical scrollbar not visible (#5420)
* Resolved issue #4513

* Localisation updates from https://translatewiki.net.

* NearbyParentFragment : added referer (#5417)

* NearbyParentFragment : added referer

In file NearbyParentFragment.java, I added header property, i.e., the referer - http://maps.wikimedia.org/
and set tile source to wikimedia.

* Reworded comments

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* Localisation updates from https://translatewiki.net.

* Localisation updates from https://translatewiki.net.

* Removed butterknife from the quiz result activity (#5425)

* ConnectRefuseError: removed the proxy from properties.gradle file to allow downloading of build tools during CI. (#5434)

* NearbyParentFragment : added referer

In file NearbyParentFragment.java, I added header property, i.e., the referer - http://maps.wikimedia.org/
and set tile source to wikimedia.

* Reworded comments

* sdkmanager: added installation command for build-tools-30.0.3

* Revert "sdkmanager: added installation command for build-tools-30.0.3"

This reverts commit b3e5019e36.

* Update android.yml

* Update gradle.properties

* android.yml: removed extra debug commands

Removed some debug commands because they are no longer needed.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* Removed butterknife and inlined a couple of tiny methods (#5426)

* Resolves #2239 by adding a compass arrow for direction of nearest item (#5433)

* Resolves issue #2239 by adding an arrow for direction

* Removed unnecessary change in styles.xml

* spacing

* javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>

* Added voice input for caption and description (#5415)

* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text

---------

Co-authored-by: translatewiki.net <l10n-bot@translatewiki.net>
Co-authored-by: Rohit Verma <101377978+rohit9625@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
Co-authored-by: Paul Hawke <paul.hawke@gmail.com>
Co-authored-by: Kanahia <114223204+kanahia1@users.noreply.github.com>
2024-01-15 16:48:10 +09:00
Priyank Shankar
f2e7c7645c
Fixed bitmap too large issue (#5430) 2024-01-15 15:27:24 +09:00
Kanahia
b7090d90c4
Added voice input for caption and description (#5415)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests

* Added voice input feature

* Fixed test cases

* Changed caption and description text
2024-01-15 14:22:25 +09:00
Shashwat Kedia
e5c789e874
Resolves #2239 by adding a compass arrow for direction of nearest item (#5433)
* Resolves issue #2239 by adding an arrow for direction

* Removed unnecessary change in styles.xml

* spacing

* javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-15 13:57:44 +09:00
Paul Hawke
e99ff1c044
Removed butterknife and inlined a couple of tiny methods (#5426) 2024-01-15 11:48:37 +09:00
Rohit Verma
31b7357bdb
ConnectRefuseError: removed the proxy from properties.gradle file to allow downloading of build tools during CI. (#5434)
* NearbyParentFragment : added referer

In file NearbyParentFragment.java, I added header property, i.e., the referer - http://maps.wikimedia.org/
and set tile source to wikimedia.

* Reworded comments

* sdkmanager: added installation command for build-tools-30.0.3

* Revert "sdkmanager: added installation command for build-tools-30.0.3"

This reverts commit b3e5019e36.

* Update android.yml

* Update gradle.properties

* android.yml: removed extra debug commands

Removed some debug commands because they are no longer needed.

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-14 21:48:15 +09:00
Paul Hawke
908d3c43a4
Removed butterknife from the quiz result activity (#5425) 2024-01-10 23:03:08 +09:00
translatewiki.net
94b2f891ab Localisation updates from https://translatewiki.net. 2024-01-08 13:02:48 +01:00
translatewiki.net
54ab87013c Localisation updates from https://translatewiki.net. 2024-01-04 13:02:31 +01:00
Rohit Verma
6a70c90643
NearbyParentFragment : added referer (#5417)
* NearbyParentFragment : added referer

In file NearbyParentFragment.java, I added header property, i.e., the referer - http://maps.wikimedia.org/
and set tile source to wikimedia.

* Reworded comments

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
2024-01-03 22:01:55 +09:00
translatewiki.net
050a06b258 Localisation updates from https://translatewiki.net. 2024-01-01 13:02:52 +01:00
translatewiki.net
4b2d6d3280 Localisation updates from https://translatewiki.net. 2023-12-28 13:02:38 +01:00
translatewiki.net
9a52e7f86a Localisation updates from https://translatewiki.net. 2023-12-25 13:03:08 +01:00
Kanahia
5df18fb4a6
Replaced Mapbox with osmdroid (Nearby activity) (#5403)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces

* Added added basic features to OSM Maps

* Added search location feature

* Added filter to Open Street Maps

* Fixed chipGroup in Open Street Maps

* Removed mapBox code

* Removed mapBox's code

* Reformat code

* Reformatted code

* Removed rotation feature to map

* Removed rotation files and Fixed Marker click problem

* Ignored failing tests
2023-12-24 20:10:39 +09:00
Paul Hawke
3d525d4eb3
Removed butterknife from contributions list fragment (#5396)
* Removed butterknife from contributions list fragment and overhauled its test

* Suggested fix from stack overflow to remove duplicate class error during build
2023-12-23 15:31:10 +09:00
translatewiki.net
8b054926fe Localisation updates from https://translatewiki.net. 2023-12-21 13:02:30 +01:00
Srishti Rohatgi
c818e8b7c2
widget correction (#5402) 2023-12-20 22:48:32 +09:00
translatewiki.net
77dbafaa9b Localisation updates from https://translatewiki.net. 2023-12-18 13:03:00 +01:00
translatewiki.net
26400c930d Localisation updates from https://translatewiki.net. 2023-12-14 13:02:12 +01:00
Ritika Pahwa
d9e41b9d2b
MainActivity.java: resume uploads that got stuck because of app being killed or device being rebooted (#5399) 2023-12-12 22:30:25 +09:00
translatewiki.net
3ae5bb5cd7 Localisation updates from https://translatewiki.net. 2023-12-11 13:02:55 +01:00
translatewiki.net
eef82d04eb Localisation updates from https://translatewiki.net. 2023-12-07 13:03:19 +01:00
translatewiki.net
7e5e17ea9e Localisation updates from https://translatewiki.net. 2023-12-04 13:03:31 +01:00
Srishti Rohatgi
b3c908583e
mapbox token correction (#5398) 2023-12-01 22:04:50 +09:00
translatewiki.net
96431dd01d Localisation updates from https://translatewiki.net. 2023-11-30 13:02:29 +01:00
Pierre Monier
e1e4f9329a
Add background color option for media detail page (#5394)
* feat: add backgroundColor property on media

* feat: add optional menu items for media backgroundColor

* fix: test pass when running in batch

* refactor: remove backgroundColor from media

* refactor: add string for background color menu

* chore: remove useless change

* feat: change media image background color

* feat: pass backgroundColor to ZoomableActivity

* chore: remove extra space
2023-11-30 10:07:53 +09:00
translatewiki.net
9028e0ed32 Localisation updates from https://translatewiki.net. 2023-11-27 13:02:26 +01:00
translatewiki.net
76337454da Localisation updates from https://translatewiki.net. 2023-11-23 13:03:03 +01:00
Srishti Rohatgi
2dafc0d2ad
moves mapbox token inside gradle.properties (#5392) 2023-11-22 23:34:38 +09:00
translatewiki.net
e7b15b1160 Localisation updates from https://translatewiki.net. 2023-11-20 13:03:11 +01:00
u7469570
60764f6f73
#3101: Add image upload limit of 20 to custom selector (#5369)
* Add basic image limit warning to custom selector

* Block upload button when image limit is exceeded

* Complete basic functionality for upload limit: Disabled button, warning sign & toast

* Consolidate functionality between upload limit and not for upload marking

* Upload Limit: write unit tests and optimize control flow

* Upload Limit Tests: improve logic coverage and alter to stay valid if limit is changed

* Upload Limit: polish javadocs and add explanation to warning toast.

* Upload Limit: refactor variable names

* Upload Limit: change warning toast to dialog box, repurposing welcome dialog code & layout

* Upload Limit: remove error icon when the mark as not for upload button is pressed
2023-11-20 18:13:05 +09:00
Paul Hawke
3118a8368b
Removed butterknife from quiz activity (#5383) 2023-11-20 17:48:22 +09:00
Ritika Pahwa
d967279abc
UploadActivity: fix multi-upload bugs (#5389) 2023-11-20 17:46:32 +09:00
translatewiki.net
a85decd2cd Localisation updates from https://translatewiki.net. 2023-11-16 13:02:57 +01:00
translatewiki.net
d87f7dec3c Localisation updates from https://translatewiki.net. 2023-11-13 13:03:19 +01:00
Nicolas Raoul
8be5526b7a
new disposable Mapbox token 2023-11-13 12:11:35 +09:00
Paul Hawke
5e35db159d
Remove butterknife from achievements fragment (#5382)
* Replace butterknife view bindings

* Migrated onClick listeners, and removed Butterknife
2023-11-12 14:39:59 +09:00
Paul Hawke
861d2b9bf6
Removed butterknife from login activity (#5380)
* Removed butterknife view bindings

* Migrated click listeners to view binding

* Migrate onEditorAction to use ViewBinding

* Finally, removed butterknife
2023-11-11 22:54:08 +09:00
Paul Hawke
9620f6eee0
Replaced Butterknife with Viewbinding, and dealt the a whole bunch of code quality warnings (#5379) 2023-11-10 23:03:12 +09:00
translatewiki.net
b113f3c986 Localisation updates from https://translatewiki.net. 2023-11-09 13:02:57 +01:00
Srishti Rohatgi
2ab100d99f
fixes wrong string translation in french (#5376) 2023-11-07 22:46:10 +09:00
Nicolas Raoul
c901140796
Replaced with disposable token "Commons-GitHub", please replace with former token when releasing 2023-11-07 11:23:06 +09:00
translatewiki.net
327541c7b1 Localisation updates from https://translatewiki.net. 2023-11-06 13:02:54 +01:00
translatewiki.net
fb166b52ec Localisation updates from https://translatewiki.net. 2023-11-02 13:02:53 +01:00
Kanahia
187872114c
Fixed Grey empty screen at Upload wizard (#5356)
* Fixed Grey empty screen at Upload wizard caption step after denying files permission

* Empty commit

* Fixed loop issue

* Created docs for earlier commits

* Fixed javadoc

* Fixed spaces
2023-11-02 10:19:47 +09:00
HNYDDDTONY-ANU
6f96e8959b
Fixes #5344 Some categories hidden at top of Upload Wizard suggestions, need to manually scroll up (#5370)
* Fix Issue #5344

* fix update Issue #5344

* Add Comment
2023-11-01 22:25:07 +09:00
translatewiki.net
b7913fa027 Localisation updates from https://translatewiki.net. 2023-10-30 13:02:43 +01:00
Ritika Pahwa
39a2fbe3d5
ensure that cancelled uploads are really getting cancelled (#5367) 2023-10-29 23:21:16 +09:00
translatewiki.net
6166fde7cd Localisation updates from https://translatewiki.net. 2023-10-26 13:03:03 +02:00
Kaartic Sivaraam
14b6c455bf Version v4.2.1 2023-10-25 23:29:06 +05:30
Priyank Shankar
2ddb6b2e5e
[WIP]Lossless Transformations(Rotate feature) (#5252)
* UI setup for the crop feature almost setup

* basic setup of rotate feature done

* Added basic changes for editing feature

* Getting data back from edit activity

* Getting data back from edit activity

* Updated contentUri

* Finally the rotated image is getting uploaded

* Minor Improvements for better testing

* Fixed thumbnail preview

* Fixed loss of exif data

* Copy exif data

* Save exif data

* Added java docs

* Minor fix

* Added Javadoc

* Refactoring

* Formatting fixes

* Minor Formatting Fix

* Fix unit test

* Add test coverage

* Formatting fixes

* Formatting Fixes

---------

Co-authored-by: Priyank Shankar <priyankshankar@changejar.in>
2023-10-24 13:04:21 +09:00
translatewiki.net
6b8954b4a9 Localisation updates from https://translatewiki.net. 2023-10-23 13:03:01 +02:00
Shin0017
6c14163c66
checks items length (#5360)
* checks items length

* Update UploadMediaPresenter.java

* Update UploadMediaPresenter.java

* Update UploadMediaPresenter.java
2023-10-23 11:09:45 +09:00
Zhiyuan Xu
57d7159ee0
Update the SoLoader SDK version (#5361) 2023-10-22 22:15:41 +09:00
Kaartic Sivaraam
c086ff264f
app: avoid R8 from obfuscating our model classes (#5359)
R8 shouldn't obfuscate the classes that we use for serialization /
de-serialization over Gson. This causes issues with app functioning.

Fixes #5358
2023-10-22 14:42:26 +09:00
Nicolas Raoul
1490710a51 Merge branch 'v4.2.0-release' 2023-10-20 13:51:30 +09:00
Nicolas Raoul
d4917b5455 Switch Mapbox account from Vivek to Nicolas 2023-10-20 12:05:02 +09:00
Srishti Rohatgi
c9dfc03a20
fixes unit tests (#5354)
* fixes unit tests

* fixes failing unit tests
2023-10-20 11:50:51 +09:00
translatewiki.net
af581612f6 Localisation updates from https://translatewiki.net. 2023-10-19 13:02:52 +02:00
Nicolas Raoul
04aec20fc9
Switch Mapbox account from Vivek to Nicolas 2023-10-19 17:27:20 +09:00
Tai Ha
988b83dc32
Fix #5212: removing bug causing progress dialog to load forever (#5350)
* Fixes bug causing the progress dialog to load forever (#5212)

* Fixes bug causing the progress dialog to load forever (#5212)

* remove unnecessary new line (#5212)
2023-10-19 16:20:28 +09:00
Alvin Tang
f7164d0b78
Fix #5246: map icon in Upload Wizard indicating if location is included in the EXIF data (#5343)
* Add XML map icons with a tick/question mark for the Upload Wizard
The existing map icon may not be intuitive enough to indicate
whether the location EXIF data will be included
The two new XML map icons are intended to indicate the status of
location sharing with the location data in the Upload Wizard

* Label the map icon in the Upload Wizard if location is included
If an image is capture with the in-app camera, the location in the
image metadata by default
If so, the map icon in the Upload Wizard should be labelled with
a green tick during initialisation of its UploadMediaDetailFragment instance

* Update the map icon in Upload Wizard if location is pin-pointed
If the user selects images from the device storage
to upload, the location EXIF data might originally not be included
The map icon is labelled with a red question mark
After pin-pointing the location manully, the map icon should be
labelled with a green tick instead

* Fix Upload Wizard map icon XML rendering failure
SVG path is invalid, resulting in failure to render the icons
Also imports are required for UploadMediaDetailFragment to
use Drawable objects and R objects

* Add hasLocation() to UploadableFile to indicate existence of EXIF
When an image is chosen from the album to the Upload Wizard,
its EXIF might contain location data. hasLocation() and fix of init()
in UploadMediaDetailFragment ensures that the map icon is shown
correctly

* Fix init() NullPointerException in UploadMediaDetailFragment

* Fix comment typo in UploadMediaDetailFragment
Fix the comment about red and green labels for the map icon

* Use SLF4J logging for try-catch clauses in UploadableFile class
Instead of using printStackTrace(), error directed to logcat

* Use Timber for logging in UploadableFile
Clean up the catch clause in hasLocation() and getDataTimeFromExif()
2023-10-18 22:42:01 +09:00
Srishti Rohatgi
64652b987d
fixes blank screen decrepancy when sharing items from external apps (#5345)
* fixes blank screen decrepancy when sharing items from external apps

* reverts comment removal
2023-10-18 12:16:33 +09:00
JiaYuan Huang
f13085147f
5338 bug fix (#5340)
* fix Bug index out bound

* fix Bug index out bound

* fix Bug index out bound

* fix Bug index out bound

* add some comments for that

* add some comments for that

* add some comments for that

* add some comments for that
2023-10-17 14:50:24 +09:00
Kaartic Sivaraam
26eaa4650d Merge branch 'siva/gradle-version-upgrade' into v4.2.0-release 2023-10-17 07:58:37 +05:30
Kaartic Sivaraam
870d138d11 Empty commit to enable merge 2023-10-17 07:55:04 +05:30
translatewiki.net
f06dedaebc Localisation updates from https://translatewiki.net. 2023-10-16 13:03:39 +02:00
Kaartic Sivaraam
e5e3a6b875 Upgrade robolectric to fix unit tests
This is as per the observation of Nicolas Raoul in the MR. He mentions
this brings down test failures to just 5.

Ref: https://github.com/commons-app/apps-android-commons/pull/5220#issuecomment-1722425627
2023-10-16 00:28:09 +05:30
Kaartic Sivaraam
eae3e312e4 Disable full mode R8 optimizations
Full mode R8 configurations are hindering the working of the app severly.
So, disable the same.

Also, ensure the POJO classes used to hold the API responses aren't optimized
so that we could use the response properly without any issues.
2023-10-16 00:14:21 +05:30
translatewiki.net
8553a96358 Localisation updates from https://translatewiki.net. 2023-10-12 13:03:03 +02:00
Srishti Rohatgi
a708c811d6
fixes image retention when no network is available (#5335) 2023-10-10 23:00:17 +09:00
Srishti Rohatgi
b18bc8ff4b
fixes popup message title and description on cancelling uploading image (#5334) 2023-10-10 22:21:15 +09:00
Srishti Rohatgi
0d90ac3c53
Tick icon in place of number of images selected in custom picker (#5331)
* tick in place of number of images selected in custom picker

* fixes tests, dark mode visibility, bold tick icon for better visibility
2023-10-09 23:08:00 +09:00
translatewiki.net
f69ecde713 Localisation updates from https://translatewiki.net. 2023-10-09 13:03:04 +02:00
Srishti Rohatgi
733c8709fc
Makes depicted place and category items unselectable for nearby place (#5325)
* Makes depicted place and category items unselectable for nearby place

* UploadCategoriesFragmentUnitTests.kt fixes and javadoc addition

* comment fix

* Fixes tests and hidden category appearing and dissapearing issue
2023-10-08 18:26:27 +09:00
Dean Stirrat
05fbfce865
check for exact category match on upload search (#5328) 2023-10-07 22:30:27 +09:00
Srishti Rohatgi
048b78a03a
Fixes location issue in upload wizard (#5329)
* Fixes location issue in upload wizard

* Fixes blue dot disappearance on first install
2023-10-07 22:07:08 +09:00
Srishti Rohatgi
8aee7a680d
Fixes copy to subsequent media functionality for nearby uploaded images (#5326) 2023-10-07 07:10:28 +09:00
translatewiki.net
fd6ba00fbd Localisation updates from https://translatewiki.net. 2023-10-05 13:03:16 +02:00
Kaartic Sivaraam
a629756f3a Retain siva's comment about disabling force = true 2023-10-04 06:56:52 +05:30
Kaartic Sivaraam
62661604ff Merge commit 'refs/pull/5220/head' of github.com:commons-app/apps-android-commons into new-tools 2023-10-04 06:54:48 +05:30
Aryan Arora
373c6201bd
Show progress dialog on mark/unmark photos not for upload (#5322) 2023-10-03 23:17:29 +09:00
Kaartic Sivaraam
ff09c3487f
Try fixing pipeline after Gradle build changes 2023-07-08 23:49:55 +05:30
Siva Subramaniam
1911b8b957 upgrade AGP to 8.0.2, enable gradle build cache and change java version to 17 in github workflow 2023-06-26 12:56:40 +05:30
Siva Subramaniam
c4d4cbeae2 Merge branch 'master' of https://github.com/siva-subramaniam-v/apps-android-commons into gradle-version-upgrade 2023-06-26 12:15:38 +05:30
Siva Subramaniam
a280f79f36 remove redundant comment in module level build.gradle file 2023-05-03 11:50:08 +05:30
Siva Subramaniam
368701ec59 fixes #5208: gradle build fails with android studio flamingo update 2023-05-03 11:12:28 +05:30
1625 changed files with 87771 additions and 94275 deletions

View file

@ -1,7 +1,7 @@
name: "\U0001F41E Bug report"
description: Create a report to help us improve.
title: "[Bug]: "
labels: ["bug"]
type: Bug # Retained to categorize the issue as per organization-level type
body:
- type: markdown
attributes:
@ -70,7 +70,7 @@ body:
required: false
- type: textarea
attributes:
label: Screen-shots
label: Screenshots
description: Add screenshots related to the issue (if available). Can be created by pressing the Volume Down and Power Button at the same time on Android 4.0 and higher.
validations:
required: false

View file

@ -12,17 +12,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: "temurin"
java-version: 11
distribution: 'temurin'
java-version: '17'
- name: Cache packages
id: cache-packages
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
@ -37,7 +37,7 @@ jobs:
- name: AVD cache
if: github.event_name != 'pull_request'
uses: actions/cache@v3
uses: actions/cache@v4
id: avd-cache
with:
path: |
@ -89,7 +89,7 @@ jobs:
run: bash ./gradlew assembleBetaDebug --stacktrace
- name: Upload betaDebug APK
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: betaDebugAPK
path: app/build/outputs/apk/beta/debug/app-*.apk
@ -98,7 +98,17 @@ jobs:
run: bash ./gradlew assembleProdDebug --stacktrace
- name: Upload prodDebug APK
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: prodDebugAPK
path: app/build/outputs/apk/prod/debug/app-*.apk
- name: Create and PR number artifact
run: |
echo "{\"pr_number\": ${{ github.event.pull_request.number || 'null' }}}" > pr_number.json
- name: Upload PR number artifact
uses: actions/upload-artifact@v4
with:
name: pr_number
path: ./pr_number.json

41
.github/workflows/build-beta.yml vendored Normal file
View file

@ -0,0 +1,41 @@
name: Build beta only
on: [workflow_dispatch]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle
- name: Access test login credentials
run: |
echo "TEST_USER_NAME=${{ secrets.TEST_USER_NAME }}" >> local.properties
echo "TEST_USER_PASSWORD=${{ secrets.TEST_USER_PASSWORD }}" >> local.properties
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Set env
run: echo "COMMIT_SHA=$(git log -n 1 --format='%h')" >> $GITHUB_ENV
- name: Generate betaDebug APK
run: ./gradlew assembleBetaDebug --stacktrace
- name: Rename betaDebug APK
run: mv app/build/outputs/apk/beta/debug/app-*.apk app/build/outputs/apk/beta/debug/apps-android-commons-betaDebug-$COMMIT_SHA.apk
- name: Upload betaDebug APK
uses: actions/upload-artifact@v4
with:
name: apps-android-commons-betaDebugAPK-${{ env.COMMIT_SHA }}
path: app/build/outputs/apk/beta/debug/*.apk
retention-days: 30

View file

@ -0,0 +1,96 @@
name: Comment Artifacts on PR
on:
workflow_run:
workflows: [ "Android CI" ]
types: [ completed ]
permissions:
pull-requests: write
contents: read
concurrency:
group: comment-${{ github.event.workflow_run.id }}
cancel-in-progress: true
jobs:
comment:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
steps:
- name: Download and process artifacts
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const runId = context.payload.workflow_run.id;
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
const prNumberArtifact = allArtifacts.data.artifacts.find(artifact => artifact.name === "pr_number");
if (!prNumberArtifact) {
console.log("pr_number artifact not found.");
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: prNumberArtifact.id,
archive_format: 'zip',
});
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));
const { execSync } = require('child_process');
execSync('unzip -q pr_number.zip -d ./pr_number/');
fs.unlinkSync('pr_number.zip');
const prData = JSON.parse(fs.readFileSync('./pr_number/pr_number.json', 'utf8'));
const prNumber = prData.pr_number;
if (!prNumber || prNumber === 'null') {
console.log("No valid PR number found in pr_number.json. Skipping.");
return;
}
const artifactsToLink = allArtifacts.data.artifacts.filter(artifact => artifact.name !== "pr_number");
if (artifactsToLink.length === 0) {
console.log("No artifacts to link found.");
return;
}
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
});
const oldComments = comments.data.filter(comment =>
comment.body.startsWith("✅ Generated APK variants!")
);
for (const comment of oldComments) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
console.log(`Deleted old comment ID: ${comment.id}`);
};
const commentBody = `✅ Generated APK variants!\n` +
artifactsToLink.map(artifact => {
const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/artifacts/${artifact.id}`;
return `- 🤖 [Download ${artifact.name}](${artifactUrl})`;
}).join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
body: commentBody
});

5
.gitignore vendored
View file

@ -43,3 +43,8 @@ app/src/main/jniLibs
#https://docs.opencv.org/3.3.0/
/libraries/opencv/javadoc/
captures/*
# Test and other output
app/jacoco.exec
app/CommonsContributions
app/.*

View file

@ -16,6 +16,7 @@
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="" withSubpackages="true" static="true" />
<emptyLine />
<package name="" withSubpackages="true" static="false" />
@ -39,21 +40,18 @@
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
<option name="SPACE_BEFORE_SUPERCLASS_COLON" value="false" />
</Objective-C>
<Objective-C-extensions>
<extensions>
<pair source="cc" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
<Python>
<option name="USE_CONTINUATION_INDENT_FOR_ARGUMENTS" value="true" />
</Python>
<TypeScriptCodeStyleSettings>
<option name="INDENT_CHAINED_CALLS" value="false" />
</TypeScriptCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<files>
<extensions>
<pair source="cc" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</files>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
@ -318,9 +316,7 @@
<codeStyleSettings language="protobuf">
<option name="RIGHT_MARGIN" value="80" />
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
</code_scheme>

View file

@ -1,16 +1,36 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintNewerVersionAvailable" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ConfusingElse" enabled="true" level="WARNING" enabled_by_default="true">
<option name="reportWhenNoStatementFollow" value="true" />
</inspection_tool>
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ExplicitThis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="FieldMayBeFinal" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="REPORT_VARIABLES" value="true" />
<option name="REPORT_PARAMETERS" value="true" />
@ -24,14 +44,33 @@
<inspection_tool class="OverlyStrongTypeCast" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreInMatchingInstanceof" value="false" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ProblematicWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreSerializable" value="false" />
<option name="ignoreCloneable" value="false" />
</inspection_tool>
<inspection_tool class="RedundantMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="SimplifiableEqualsExpression" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="TypeParameterExtendsFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
@ -47,6 +86,5 @@
<inspection_tool class="UnnecessaryQualifierForThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryThis" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="UnnecessaryToStringCall" enabled="true" level="WARNING" enabled_by_default="true" />
</profile>
</component>

5
.mailmap Normal file
View file

@ -0,0 +1,5 @@
# See: https://git-scm.com/docs/git-shortlog#_mapping_authors
#
Brooke Vibber <bvibber@wikimedia.org>
Brooke Vibber <bvibber@wikimedia.org> <brion@wikimedia.org>
Brooke Vibber <bvibber@wikimedia.org> <brion@pobox.com>

View file

@ -1,5 +1,291 @@
# Wikimedia Commons for Android
## v6.0.2
### What's changed
* Addressed a bug that prevented the keyboard from appearing in various text fields, such as on the upload wizard
* Links in the "File usages" list are now clickable and will take you to the correct page.
* Titles for file usages are now clearer and easier to understand
* Bug fixes and stability improvements
## v6.0.1
### What's changed
* The app now supports Android 15 with an improved user interface
* Enhanced Nearby with robust and more reliable labels
* Bug fixes and stability improvements
## v5.6.1
### What's changed
* The app no longer uploads images to Wikidata if one exists already for a given item
* File usage displays correctly now
* No more infinite circular progress bar on nominating an image for deletion
* Enhanced location updates while using GPS
* Author/uploader names are now available in Media Details for Commons licensing compliance
* Improved usage of popups in Nearby
* Bug fixes and stability improvements
## v5.5.0
### What's changed
* Explore images will now be shown based on the map location and not at your current location
* Enhanced Wikidata feedback message
* Green labels in Explore map will no longer be hidden by other pins thumbnails
* Upload wizard's language drop-down now reflects the language used in the pin label
* Users can now pick only one image at a time while using the custom selector
* Bug fixes and stability improvements
## v5.4.1
### What's changed
* Custom picker now detects images that are already available on Commons
* Improve credit line in image list
* Show place cards with loaded names only in the Nearby list
* Fix the error that occurs while loading images in Explore
## v5.3.0
### What's changed
* Enable EmailAuth support
* Explore map images no longer show "Unknown"
* Fix crash when removing last two images of multiupload
* Mark ❌ for closed locations (P3999) in Nearby
* Fix two pin labels staying visible at the same time in Explore map
* Refactoring and minor UI improvements
## v5.2.0
v5.2.0 boasts several new functionalities like:
* A new refresh button lets you quickly reload the Nearby map
* Bookmarks now support categories
* Improved feedback and consistency in the user interface
* Bug fixes and performance improvements
### What's changed
* Implement "Refresh" button to clear the cache and reload the Nearby map.
* `CommonsApplication` migrate to kotlin & some lint fixes.
* Revert back to MainScope for database and UI updates and make database operations thread safe.
* Hide edit options for logged-out users in Explore screen.
* Introduced a button to delete the current folder in custom selector.
* Improve Unique File Name Search.
* Migration of several modules from Java to Kotlin.
* Fix modification on bottom sheet's data when coming from Nearby Banner and clicked on other pins.
* Bug fixes and enhancement of Achievements screen.
* Show where file is being used on Commons and other wikis.
* Migrate android.media.ExifInterface to androidx.exifinterface.media.ExifInterface as android.media.ExifInterface had security flaws on older devices.
* Make dialogs modal and always show the upload icon.
* Fix unintentional deletion of subfolders and non-images by custom selector.
* Bookmark categories.
* Add pull down to refresh in the Contributions screen.
* Fix race condition and lag when loading pin details, faster overlay management.
* Show cached pins in Nearby even when internet is unavailable
Full changelog with the list of contributors: [`v5.1.2...v5.2.0`](https://github.com/commons-app/apps-android-commons/compare/v5.1.2...v5.2.0).
## v5.1.2
### What's changed
* Fix the broken category search in the explore screen
## v5.1.1
### What's changed
* Use Android's new EXIF interface to mitigate security issues in old
EXIF interface.
* Make the icon that helps view the upload queue always visible as it ensures
that the queue accessible at all times.
## v5.1.0
### What's Changed
* Enhanced **upload queue management** in the Commons app for smoother, sequential
processing, clearer progress tracking, prevention of stuck or duplicate
uploads. As part of this improvement, the "Limited Connection mode" has been
removed.
* Added an option in "Nearby" feature enabling users to **provide feedback on
Wikidata items**. Users can report if an item doesnt exist, is at a different
location, or has other issues, with submissions tagged for easy tracking and
updates.
* Improved the "Nearby" feature by splitting the query into two parts for faster
loading and **better performance, especially in areas with dense amount of
places**. This update also resolves issues with pins overlapping place names.
* Upgraded AGP and **target/compile SDK to 34** and make necessary adjustments to
the app such as adding **"Partial Access" support**. Also includes some minor
refactoring, and replacement of deprecated circular progress bars.
* Fixed an **UI issue where the 'Subcategories' and 'Parent Categories' tabs
appeared blank** in the Category Details screen. Resolved by optimizing view
binding handling in the parent fragments.
* Fixed an issue where editing depictions removed all other structured data from
images. Now, **only depictions are updated, preserving other associated data**.
* Fixed **map centering** in the image upload flow to **use GPS EXIF tag location**
from pictures and ensured "Show in map app" accurately reflects this location.
* Fixed navigation **after uploading via Nearby by directing users to the Uploads
activity** instead of returning to Nearby, preventing confusion about needing to
upload again.
### Bug fixes and various changes
* Improved the "Nearby" feature to fetch labels based on the user's preferred
language instead of defaulting to English.
* Added a legend to the "Nearby" feature indicating pin statuses: red for items
without pictures, green for those with pictures, and grey for items being
checked. A floating action button now allows users to toggle the legend's
visibility.
* Fixed an issue where the "Nominate for deletion" option is shown to logged out
users, preventing app errors and crashes.
* Updated the regex pattern that filters categories with an year in it to also
filter the 2020s.
* Fix an issue where past depictions were not shown as suggestions, despite
being saved correctly.
* Fixed an issue in custom image picker where exiting the media preview showed
only the first image and cleared selections. Now, previously selected images
are restored correctly after exiting the preview. This was contributed.
* Fixed an issue in custom image picker where scrolling behavior did not
maintain position after exiting fullscreen preview, ensuring users remain at
the same point in their image roll unless actioned images are filtered. This
was contributed.
* Fixed Nearby map not showing new pins on map move by removing the 2000m scroll
threshold and adding an 800ms debounce for smoother pin updates when the map
is moved. Queued searches are now canceled on fragment destruction.
* Revised author information retrieval to emphasize the custom author name from
the metadata instead of the default registered username.
* Enhanced notification classification to properly identify "email" type
notifications and prompting users to check their e-mail inbox when such
notifications are clicked.
* Resolved a bug in the language chooser that incorrectly greyed-out previously
selected languages, ensuring only the current language is non-selectable during
image upload.
* Resolved pin color update issue in "Nearby" feature where the pin colour
failed to be updated after a successful image upload.
What's listed here is only a subset of all the changes. Check the full-list of
the changes in [this link](https://github.com/commons-app/apps-android-commons/compare/v5.0.2...v5.1.0).
Alternatively, checkout [this release on GitHub releases page](https://github.com/commons-app/apps-android-commons/releases/tag/v5.1.0)
for an exhaustive list of changes and the various contributors who contributed the same.
## v5.0.2
- Enhanced multi-upload functionality with user prompts to clarify that all images would share the
same category and depictions.
- Show Wikidata description on currently active Nearby pin to provide more useful information.
- Improve the visibility of map markers by dynamically adjusting their colors based on the app's
theme. The map markers will now appear lighter when the app is in dark mode and darker when the
app is in light mode. This change aims to enhance marker visibility and improve the overall user
experience.
- Added information on where user feedback is posted, helping users track existing feedback and
monitor their own submissions.
- Enhanced the edit location screen of the upload screen by centering the map on the picture's
location from metadata when editing, or on the device's GPS location if metadata is unavailable,
improving accuracy and user experience.
- Ensured the 'Add Location' button is renamed to 'Edit Location' when copying the location of a
recently uploaded image, enhancing clarity and user experience.
- Added a ProgressBar to the media detail screen to indicate image loading status, enhancing user
experience by showing loading progress until the image is fully loaded.
- Fixed an issue where caption and description fields would intermittently disappear when using
voice input, ensuring text remains visible and stable across all entries.
- Fixed a crash that occurred when attempting to remove multiple instances of caption/description
fields after initially adding them.
- Improve the text in the prompt shown when skipping login to sound more natural.
- Modified feedback addition logic to append new sections at the bottom of the page, ensuring
auto-archiving of sections functions correctly on the feedback page.
- Resolved issue where the app failed to clear cookies upon logout.
## v5.0.1
Same as v5.0.0 except this fixes some R8 rules to ensure that the release
variants of the app work as intended.
## v5.0.0
### What's Changed
- Redesigned the map feature to **replace Mapbox with the osmdroid library**.
Key elements like pin visualization and user-centered display are still
included in this redesign. This is done to guard against possible misuse of
the Mapbox token and, more crucially, to keep the app from becoming dependent
on a service that charges for usage but offers a free tier.
With this change, the app retrieves the map tiles from [Wikimedia maps](https://maps.wikimedia.org).
- Add the ability to **export locations of nearby missing pictures in GPX and
KML formats**. This allows users to browse the locations with desired radius
for offline use in their favourite map apps like OsmAnd or Maps.me, enhancing
accessibility and offline functionality.
- **Limited the uploads via the custom image picker** to a maximum of 20.
- Added two menu choices for **transparent image backgrounds**, giving users the
option of either a black or white background, increasing adaptability to
various theme settings.
User customization option has been provided with the
ability to save background color selections permanently on a per image basis.
- Implemented functionality to **automatically resume uploads** that become
stuck due to app termination or device reboot.
- Added a **compass arrow in the Nearby banner** shown in the "Contributions"
screen to guide users towards the nearest item, thus providing the missing
directional cues. The arrow dynamically adjusts based on device rotation,
aligning with the calculated bearing towards the target location. Further,
the distance and direction are updated as the user moves.
- Implemented **voice input feature** for caption and description fields,
enabling users to dictate text directly into these fields.
- Improved various flows in the app to **redirect users to the login page** and
display a persistent message **if their session becomes invalid** due to a
password change, enhancing user guidance and security measures.
### Revamps and refactorings
- **Revamped initial upload screen layout and the description edit screen layout**
for enhanced user experience and ensuring better symmetry in the design.
- **Replaced Butterknife with ViewBinding** in various places of the app.
- Transferred essential code from **the redundant data-client module** to the
main Commons app code, enabling its integration and facilitating the removal
of the redundant module. Further, convert various parts of the code to Kotlin.
- **Revamped the various location permission flows** to ensure consistency for
the sake of a better user experience.
### Bug fixes and various changes
- Resolved an issue where paused uploads that were subsequently cancelled were
still being uploaded.
- Fixed an issue where some user information such as upload count were not
displayed in the "Contributions" and "Profile" screens.
- Fixed the long-standing broken *"Picture of the Day" widget* to restore its
usability.
- Resolved an issue where some categories were hidden at the top of Upload
Wizard suggestions.
- Resolved an issue where there was a grey empty screen at Upload wizard when
the app was denied the files permission.
- Implemented logic to bypass media in Peer Review if the current reviewer is
also the user who uploaded the media.
- Corrected arrow image behaviour in the first upload screen: now displays down
arrow when details card is fully visible, aligning with expected user
interaction.
- Updated app icon to improve visibility and recognition on F-Droid.
- Fixed issue causing all pictures to disappear and activity to reload fully in
the custom image selector after marking a picture as 'not for upload', now
ensuring only the selected picture is removed as expected.
What's listed here is only a subset of all the changes. Check the full-list of
the changes in [this link](https://github.com/commons-app/apps-android-commons/compare/v4.2.1...v5.0.0).
Alternatively, checkout [this release on GitHub releases page](https://github.com/commons-app/apps-android-commons/releases/tag/v5.0.0)
for an exhaustive list of changes and the various contributors who contributed the same.
## v4.2.1
- Provide the ability to edit an image to losslessly rotate it while uploading
- Fix a bug in v4.2.0 where the nearby places were not loading
- Fix a bug where editing depictions was showing a progress bar indefinitely
- In the upload screen, use different map icons to indicate if image is being uploaded with location
metadata
- For nearby uploads, it is no longer possible to deselect the item's category and depiction
- The Mapbox account key used by the app has been changed
- Category search now shows exact matches without any discrepancies
- Various bug and crash fixes
## v4.2.0
- Dark mode colour improvements
- Enhancements done to address location metadata loss including the metadata loss that occurs in

View file

@ -53,7 +53,6 @@ their contribution to the product.
* Butterknife
* GSON
* Timber
* MapBox
3rd party open source apps from which significant code has been reused:
* Android Wikipedia app https://github.com/wikimedia/apps-android-wikipedia

View file

@ -1,6 +1,6 @@
# Wikimedia Commons Android app
![GitHub issue custom search](https://img.shields.io/github/issues-search?label=%22good%20first%20issue%22%20issues&query=repo%3Acommons-app%2Fapps-android-commons%20is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22)
[![Build status](https://github.com/commons-app/apps-android-commons/actions/workflows/android.yml/badge.svg?branch=master)](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amaster)
[![Build status](https://github.com/commons-app/apps-android-commons/actions/workflows/android.yml/badge.svg?branch=main)](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amain)
[![Preview the app](https://img.shields.io/badge/Preview-Appetize.io-orange.svg)](https://appetize.io/app/8ywtpe9f8tb8h6bey11c92vkcw)
[![codecov](https://codecov.io/gh/commons-app/apps-android-commons/branch/master/graph/badge.svg)](https://codecov.io/gh/commons-app/apps-android-commons)
@ -29,11 +29,12 @@ Thank you all for your work!
| [<img src="https://avatars.githubusercontent.com/u/3611199?v=4" width="100px;"/><br /><sub><b>misaochan</b></sub>](https://github.com/misaochan) | [<img src="https://avatars.githubusercontent.com/u/24829418?v=4" width="100px;"/><br /><sub><b>translatewiki</b></sub>](https://github.com/translatewiki) | [<img src="https://avatars.githubusercontent.com/u/3127881?v=4" width="100px;"/><br /><sub><b>neslihanturan</b></sub>](https://github.com/neslihanturan) | [<img src="https://avatars.githubusercontent.com/u/30430?v=4" width="100px;"/><br /><sub><b>yuvipanda</b></sub>](https://github.com/yuvipanda) | [<img src="https://avatars.githubusercontent.com/u/99590?v=4" width="100px;"/><br /><sub><b>nicolas-raoul</b></sub>](https://github.com/nicolas-raoul) |
| :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars.githubusercontent.com/u/4953590?v=4" width="100px;"/><br /><sub><b>domdomegg</b></sub>](https://github.com/domdomegg) | [<img src="https://avatars.githubusercontent.com/u/3069373?v=4" width="100px;"/><br /><sub><b>maskaravivek</b></sub>](https://github.com/maskaravivek) | [<img src="https://avatars.githubusercontent.com/u/407647?v=4" width="100px;"/><br /><sub><b>psh</b></sub>](https://github.com/psh) | [<img src="https://avatars.githubusercontent.com/u/103075?v=4" width="100px;"/><br /><sub><b>brion</b></sub>](https://github.com/brion) | [<img src="https://avatars.githubusercontent.com/u/17375274?v=4" width="100px;"/><br /><sub><b>ashishkumar468</b></sub>](https://github.com/ashishkumar468) |
| [<img src="https://avatars.githubusercontent.com/u/10674?v=4" width="100px;"/><br /><sub><b>whym</b></sub>](https://github.com/whym) | [<img src="https://avatars.githubusercontent.com/u/10153800?v=4" width="100px;"/><br /><sub><b>akaita</b></sub>](https://github.com/akaita) | [<img src="https://avatars.githubusercontent.com/u/30932899?v=4" width="100px;"/><br /><sub><b>madhurgupta10</b></sub>](https://github.com/madhurgupta10) | [<img src="https://avatars.githubusercontent.com/u/6900601?v=4" width="100px;"/><br /><sub><b>veyndan</b></sub>](https://github.com/veyndan) | [<img src="https://avatars.githubusercontent.com/u/19607555?v=4" width="100px;"/><br /><sub><b>ujjwalagrawal17</b></sub>](https://github.com/ujjwalagrawal17) |
| [<img src="https://avatars.githubusercontent.com/u/3358282?v=4" width="100px;"/><br /><sub><b>macgills</b></sub>](https://github.com/macgills) | [<img src="https://avatars.githubusercontent.com/u/1682214?v=4" width="100px;"/><br /><sub><b>dbrant</b></sub>](https://github.com/dbrant) | [<img src="https://avatars.githubusercontent.com/u/34261945?v=4" width="100px;"/><br /><sub><b>vanshikaarora</b></sub>](https://github.com/vanshikaarora) | [<img src="https://avatars.githubusercontent.com/u/1345681?v=4" width="100px;"/><br /><sub><b>sandarumk</b></sub>](https://github.com/sandarumk) | [<img src="https://avatars.githubusercontent.com/u/29161745?v=4" width="100px;"/><br /><sub><b>tanvidadu</b></sub>](https://github.com/tanvidadu) |
| [<img src="https://avatars.githubusercontent.com/u/39745544?v=4" width="100px;"/><br /><sub><b>cypherop</b></sub>](https://github.com/cypherop) | [<img src="https://avatars.githubusercontent.com/u/6953323?v=4" width="100px;"/><br /><sub><b>tobias47n9e</b></sub>](https://github.com/tobias47n9e) | [<img src="https://avatars.githubusercontent.com/u/25305892?v=4" width="100px;"/><br /><sub><b>hismaeel</b></sub>](https://github.com/hismaeel) | [<img src="https://avatars.githubusercontent.com/u/12574756?v=4" width="100px;"/><br /><sub><b>tshradheya</b></sub>](https://github.com/tshradheya) | [<img src="https://avatars.githubusercontent.com/u/3308769?v=4" width="100px;"/><br /><sub><b>addshore</b></sub>](https://github.com/addshore) |
| [<img src="https://avatars.githubusercontent.com/u/20313518?v=4" width="100px;"/><br /><sub><b>knight-shade</b></sub>](https://github.com/knight-shade) | [<img src="https://avatars.githubusercontent.com/u/210297?v=4" width="100px;"/><br /><sub><b>siebrand</b></sub>](https://github.com/siebrand) | [<img src="https://avatars.githubusercontent.com/u/12448084?v=4" width="100px;"/><br /><sub><b>sivaraam</b></sub>](https://github.com/sivaraam) | [<img src="https://avatars.githubusercontent.com/u/5329780?v=4" width="100px;"/><br /><sub><b>Bluesir9</b></sub>](https://github.com/Bluesir9) | [<img src="https://avatars.githubusercontent.com/u/44129798?v=4" width="100px;"/><br /><sub><b>kbhardwaj123</b></sub>](https://github.com/kbhardwaj123) |
| [<img src="https://avatars.githubusercontent.com/u/407647?v=4" width="100px;"/><br /><sub><b>psh</b></sub>](https://github.com/psh) | [<img src="https://avatars.githubusercontent.com/u/4953590?v=4" width="100px;"/><br /><sub><b>domdomegg</b></sub>](https://github.com/domdomegg) | [<img src="https://avatars.githubusercontent.com/u/3069373?v=4" width="100px;"/><br /><sub><b>maskaravivek</b></sub>](https://github.com/maskaravivek) | [<img src="https://avatars.githubusercontent.com/u/30932899?v=4" width="100px;"/><br /><sub><b>madhurgupta10</b></sub>](https://github.com/madhurgupta10) | [<img src="https://avatars.githubusercontent.com/u/17375274?v=4" width="100px;"/><br /><sub><b>ashishkumar468</b></sub>](https://github.com/ashishkumar468) |
| [<img src="https://avatars.githubusercontent.com/u/103075?v=4" width="100px;"/><br /><sub><b>bvibber</b></sub>](https://github.com/bvibber) | [<img src="https://avatars.githubusercontent.com/u/10674?v=4" width="100px;"/><br /><sub><b>whym</b></sub>](https://github.com/whym) | [<img src="https://avatars.githubusercontent.com/u/10153800?v=4" width="100px;"/><br /><sub><b>akaita</b></sub>](https://github.com/akaita) | [<img src="https://avatars.githubusercontent.com/u/12448084?v=4" width="100px;"/><br /><sub><b>sivaraam</b></sub>](https://github.com/sivaraam) | [<img src="https://avatars.githubusercontent.com/u/6900601?v=4" width="100px;"/><br /><sub><b>veyndan</b></sub>](https://github.com/veyndan) |
| [<img src="https://avatars.githubusercontent.com/u/19607555?v=4" width="100px;"/><br /><sub><b>ujjwalagrawal17</b></sub>](https://github.com/ujjwalagrawal17) | [<img src="https://avatars.githubusercontent.com/u/3358282?v=4" width="100px;"/><br /><sub><b>macgills</b></sub>](https://github.com/macgills) | [<img src="https://avatars.githubusercontent.com/u/346271?v=4" width="100px;"/><br /><sub><b>amire80</b></sub>](https://github.com/amire80) | [<img src="https://avatars.githubusercontent.com/u/1682214?v=4" width="100px;"/><br /><sub><b>dbrant</b></sub>](https://github.com/dbrant) | [<img src="https://avatars.githubusercontent.com/u/34261945?v=4" width="100px;"/><br /><sub><b>vanshikaarora</b></sub>](https://github.com/vanshikaarora) |
| [<img src="https://avatars.githubusercontent.com/u/83745993?v=4" width="100px;"/><br /><sub><b>RitikaPahwa4444</b></sub>](https://github.com/RitikaPahwa4444) | [<img src="https://avatars.githubusercontent.com/u/71203077?v=4" width="100px;"/><br /><sub><b>Ayan-10</b></sub>](https://github.com/Ayan-10) | [<img src="https://avatars.githubusercontent.com/u/101377978?v=4" width="100px;"/><br /><sub><b>rohit9625</b></sub>](https://github.com/rohit9625) | [<img src="https://avatars.githubusercontent.com/u/126143257?v=4" width="100px;"/><br /><sub><b>shashankiitbhu</b></sub>](https://github.com/shashankiitbhu) | [<img src="https://avatars.githubusercontent.com/u/54663429?v=4" width="100px;"/><br /><sub><b>Pratham2305</b></sub>](https://github.com/Pratham2305) |
| [<img src="https://avatars.githubusercontent.com/u/111801812?v=4" width="100px;"/><br /><sub><b>parneet-guraya</b></sub>](https://github.com/parneet-guraya) | [<img src="https://avatars.githubusercontent.com/u/1345681?v=4" width="100px;"/><br /><sub><b>sandarumk</b></sub>](https://github.com/sandarumk) | [<img src="https://avatars.githubusercontent.com/u/29161745?v=4" width="100px;"/><br /><sub><b>tanvidadu</b></sub>](https://github.com/tanvidadu) | [<img src="https://avatars.githubusercontent.com/u/39745544?v=4" width="100px;"/><br /><sub><b>cypherop</b></sub>](https://github.com/cypherop) | [<img src="https://avatars.githubusercontent.com/u/65972015?v=4" width="100px;"/><br /><sub><b>Prince-kushwaha</b></sub>](https://github.com/Prince-kushwaha) |
.. and [many more](https://github.com/commons-app/apps-android-commons/graphs/contributors).
@ -45,7 +46,7 @@ This software is open source, licensed under the [Apache License 2.0][10].
[1]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons
[2]: https://commons-app.github.io/
[3]: https://github.com/commons-app/apps-android-commons/issues
[3]: https://github.com/commons-app/apps-android-commons/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+-label%3Adebated+label%3Abug+-label%3A%22low+priority%22+-label%3Aupstream
[4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation
[5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation

View file

@ -1,378 +0,0 @@
plugins {
id 'com.github.triplet.play' version '2.7.2' apply false
}
apply from: '../gitutils.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply from: "$rootDir/jacoco.gradle"
def isRunningOnTravisAndIsNotPRBuild = System.getenv("CI") == "true" && file('../play.p12').exists()
if (isRunningOnTravisAndIsNotPRBuild) {
apply plugin: 'com.github.triplet.play'
}
dependencies {
implementation project(':wikimedia-data-client')
// Utils
implementation 'in.yuvi:http.fluent:1.3'
implementation 'com.google.code.gson:gson:2.8.5'
implementation ("com.squareup.okhttp3:okhttp:$OKHTTP_VERSION"){
force = true //API 19 support
}
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.0.0'
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.1.1'
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.1.1'
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
implementation 'com.facebook.fresco:fresco:1.13.0'
implementation 'org.apache.commons:commons-lang3:3.8.1'
// UI
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
implementation 'com.github.pedrovgs:renderers:3.3.3'
implementation "org.maplibre.gl:android-sdk:$MAPLIBRE_VERSION"
implementation 'org.maplibre.gl:android-plugin-scalebar-v9:1.0.0'
implementation 'com.jakewharton.timber:timber:4.7.1'
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
implementation 'com.karumi:dexter:5.0.0'
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:$ADAPTER_DELEGATES_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-pagination:$ADAPTER_DELEGATES_VERSION"
implementation "androidx.paging:paging-runtime-ktx:$PAGING_VERSION"
testImplementation "androidx.paging:paging-common-ktx:$PAGING_VERSION"
implementation "androidx.paging:paging-rxjava2-ktx:$PAGING_VERSION"
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02"
implementation "com.squareup.okhttp3:okhttp-ws:$OKHTTP_VERSION"
// Logging
implementation 'ch.acra:acra-dialog:5.8.4'
implementation 'ch.acra:acra-mail:5.8.4'
implementation 'org.slf4j:slf4j-api:1.7.25'
api('com.github.tony19:logback-android-classic:1.1.1-6') {
exclude group: 'com.google.android', module: 'android'
}
implementation "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
// Dependency injector
implementation "com.google.dagger:dagger-android:$DAGGER_VERSION"
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
annotationProcessor "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
implementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
//Mocking
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
testImplementation 'org.mockito:mockito-inline:2.13.0'
testImplementation 'org.mockito:mockito-core:2.25.1'
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
// Unit testing
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.6.1'
testImplementation 'androidx.test:core:1.4.0'
testImplementation "com.squareup.okhttp3:mockwebserver:$OKHTTP_VERSION"
testImplementation "com.jraska.livedata:testing-ktx:1.1.2"
testImplementation "androidx.arch.core:core-testing:2.1.0"
testImplementation "org.junit.jupiter:junit-jupiter-api:5.7.0"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.7.0"
testImplementation 'com.facebook.soloader:soloader:0.10.1'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.0"
// Android testing
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha04'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.0-alpha04'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.1-alpha04'
androidTestImplementation 'androidx.test:core:1.4.0'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.annotation:annotation:1.3.0'
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.8.0'
androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0"
androidTestUtil 'androidx.test:orchestrator:1.4.1'
// Debugging
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY_VERSION"
// Support libraries
implementation "com.google.android.material:material:1.1.0-alpha04"
implementation "androidx.browser:browser:1.3.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "androidx.exifinterface:exifinterface:1.3.2"
implementation "androidx.core:core-ktx:$CORE_KTX_VERSION"
implementation "androidx.multidex:multidex:2.0.1"
compile 'com.simplecityapps:recyclerview-fastscroll:2.0.1'
//swipe_layout
implementation 'com.daimajia.swipelayout:library:1.2.0@aar'
//Room
implementation "androidx.room:room-runtime:$ROOM_VERSION"
implementation "androidx.room:room-ktx:$ROOM_VERSION"
implementation "androidx.room:room-rxjava2:$ROOM_VERSION"
kapt "androidx.room:room-compiler:$ROOM_VERSION"
// For Kotlin use kapt instead of annotationProcessor
implementation 'com.squareup.retrofit2:retrofit:2.8.1'
testImplementation "androidx.arch.core:core-testing:2.1.0"
// Pref
// Java language implementation
implementation "androidx.preference:preference:$PREFERENCE_VERSION"
// Kotlin
implementation "androidx.preference:preference-ktx:$PREFERENCE_VERSION"
implementation "androidx.multidex:multidex:$MULTIDEX_VERSION"
def work_version = "2.8.1"
// Kotlin + coroutines
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation("androidx.work:work-runtime:$work_version")
testImplementation "androidx.work:work-testing:$work_version"
//Glide
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation("io.github.coordinates2country:coordinates2country-android:1.3") { exclude group: 'com.google.android', module: 'android' }
}
task disableAnimations(type: Exec) {
def adb = "$System.env.ANDROID_HOME/platform-tools/adb"
commandLine "$adb", 'shell', 'settings', 'put', 'global', 'window_animation_scale', '0'
commandLine "$adb", 'shell', 'settings', 'put', 'global', 'transition_animation_scale', '0'
commandLine "$adb", 'shell', 'settings', 'put', 'global', 'animator_duration_scale', '0'
}
project.gradle.taskGraph.whenReady {
connectedBetaDebugAndroidTest.dependsOn disableAnimations
connectedProdDebugAndroidTest.dependsOn disableAnimations
}
android {
compileSdkVersion 33
defaultConfig {
//applicationId 'fr.free.nrw.commons'
versionCode 1035
versionName '4.2.0'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 21
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments clearPackageData: 'true'
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
packagingOptions {
exclude 'META-INF/androidx.*'
exclude 'META-INF/proguard/androidx-annotations.pro'
}
testOptions {
animationsDisabled true
unitTests.returnDefaultValues = true
unitTests.includeAndroidResources = true
unitTests.all {
jvmArgs '-noverify'
}
}
sourceSets {
// use kotlin only in tests (for now)
test.java.srcDirs += 'src/test/kotlin'
// use main assets and resources in test
test.assets.srcDirs += 'src/main/assets'
test.resources.srcDirs += 'src/main/resoures'
}
signingConfigs {
release
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
testProguardFile 'test-proguard-rules.txt'
signingConfig signingConfigs.debug
if (isRunningOnTravisAndIsNotPRBuild) {
signingConfig signingConfigs.release
}
}
debug {
minifyEnabled false
testCoverageEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
testProguardFile 'test-proguard-rules.txt'
versionNameSuffix "-debug-" + getBranchName()
}
}
if (isRunningOnTravisAndIsNotPRBuild) {
// configure keystore based on env vars in Travis for automated alpha builds
signingConfigs.release.storeFile = file("../nr-commons.keystore")
signingConfigs.release.storePassword = System.getenv("keystore_password")
signingConfigs.release.keyAlias = System.getenv("key_alias")
signingConfigs.release.keyPassword = System.getenv("key_password")
}
configurations.all {
resolutionStrategy.force 'androidx.annotation:annotation:1.1.0'
resolutionStrategy.force 'com.jakewharton.timber:timber:4.7.1'
resolutionStrategy.force 'androidx.fragment:fragment:1.3.6'
exclude module: 'okhttp-ws'
}
flavorDimensions 'tier'
productFlavors {
prod {
applicationId 'fr.free.nrw.commons'
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.org\""
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.org/wiki/\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\""
buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\""
buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.explore.recentsearches.contentprovider\""
buildConfigField "String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.recentlanguages.contentprovider\""
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.contentprovider\""
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.locations.contentprovider\""
buildConfigField "String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.items.contentprovider\""
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
buildConfigField "String", "DEPICTS_PROPERTY", "\"P180\""
dimension 'tier'
}
beta {
applicationId 'fr.free.nrw.commons.beta'
// What values do we need to hit the BETA versions of the site / api ?
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
buildConfigField "String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns_beta_active.json\""
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "COMMONS_URL", "\"https://commons.wikimedia.beta.wmflabs.org\""
buildConfigField "String", "WIKIDATA_URL", "\"https://www.wikidata.org\""
buildConfigField "String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\""
buildConfigField "String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\""
buildConfigField "String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\""
buildConfigField "String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\""
buildConfigField "String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\""
buildConfigField "String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\""
buildConfigField "String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\""
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\""
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.beta.categories.contentprovider\""
buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.beta.explore.recentsearches.contentprovider\""
buildConfigField "String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.beta.recentlanguages.contentprovider\""
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.contentprovider\""
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.locations.contentprovider\""
buildConfigField "String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.items.contentprovider\""
buildConfigField "String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\""
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
buildConfigField "String", "DEPICTS_PROPERTY", "\"P245962\""
dimension 'tier'
}
}
lintOptions {
disable 'MissingTranslation'
disable 'ExtraTranslation'
abortOnError false
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildToolsVersion buildToolsVersion
buildFeatures {
viewBinding true
}
}
String getTestUserName() {
def propFile = rootProject.file("./local.properties")
def properties = new Properties()
properties.load(new FileInputStream(propFile))
return properties['TEST_USER_NAME']
}
String getTestPassword() {
def propFile = rootProject.file("./local.properties")
def properties = new Properties()
properties.load(new FileInputStream(propFile))
return properties['TEST_USER_PASSWORD']
}
if (isRunningOnTravisAndIsNotPRBuild) {
play {
track = "alpha"
userFraction = 1
serviceAccountEmail = System.getenv("SERVICE_ACCOUNT_NAME")
serviceAccountCredentials = file("../play.p12")
resolutionStrategy = "auto"
outputProcessor { // this: ApkVariantOutput
versionNameOverride = "$versionNameOverride.$versionCode"
}
}
}
androidExtensions {
experimental = true
}

447
app/build.gradle.kts Normal file
View file

@ -0,0 +1,447 @@
import java.util.Properties
import java.io.ByteArrayOutputStream
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kotlin.kapt)
alias(libs.plugins.kotlin.parcelize)
}
apply(from = "$rootDir/jacoco.gradle")
val isRunningOnTravisAndIsNotPRBuild = System.getenv("CI") == "true" && file("../play.p12").exists()
if (isRunningOnTravisAndIsNotPRBuild) {
apply(plugin = "com.github.triplet.play")
}
android {
namespace = "fr.free.nrw.commons"
compileSdk = 35
defaultConfig {
applicationId = "fr.free.nrw.commons"
minSdk = 21
targetSdk = 35
versionCode = 1059
versionName = "6.1.0"
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true"
multiDexEnabled = true
vectorDrawables {
useSupportLibrary = true
}
}
sourceSets {
getByName("test") {
// Use kotlin only in tests (for now)
java.srcDirs("src/test/kotlin")
// Use main assets and resources in test
assets.srcDirs("src/main/assets")
resources.srcDirs("src/main/resources")
}
}
signingConfigs {
create("release") {
// Configure keystore based on env vars in Travis for automated alpha builds
if(isRunningOnTravisAndIsNotPRBuild) {
storeFile = file("../nr-commons.keystore")
storePassword = System.getenv("keystore_password")
keyAlias = System.getenv("key_alias")
keyPassword = System.getenv("key_password")
}
}
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
testProguardFile("test-proguard-rules.txt")
signingConfig = signingConfigs.getByName("debug")
if (isRunningOnTravisAndIsNotPRBuild) {
signingConfig = signingConfigs.getByName("release")
}
}
debug {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.txt")
testProguardFile("test-proguard-rules.txt")
versionNameSuffix = "-debug-" + getBranchName()
enableUnitTestCoverage = true
enableAndroidTestCoverage = true
}
}
configurations.all {
resolutionStrategy {
force("androidx.annotation:annotation:1.1.0")
force("com.jakewharton.timber:timber:4.7.1")
force("androidx.fragment:fragment:1.3.6")
}
exclude(module = "okhttp-ws")
}
flavorDimensions += "tier"
productFlavors {
create("prod") {
dimension = "tier"
applicationId = "fr.free.nrw.commons"
buildConfigField("String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\"")
buildConfigField("String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\"")
buildConfigField("String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\"")
buildConfigField("String", "WIKIDATA_URL", "\"https://www.wikidata.org\"")
buildConfigField("String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\"")
buildConfigField("String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns.json\"")
buildConfigField("String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\"")
buildConfigField("String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\"")
buildConfigField("String", "COMMONS_URL", "\"https://commons.wikimedia.org\"")
buildConfigField("String", "WIKIDATA_URL", "\"https://www.wikidata.org\"")
buildConfigField("String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.org/wiki/\"")
buildConfigField("String", "MOBILE_META_URL", "\"https://meta.m.wikimedia.org/wiki/\"")
buildConfigField("String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"")
buildConfigField("String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes\"")
buildConfigField("String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.org/wiki/Special:PasswordReset\"")
buildConfigField("String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"")
buildConfigField("String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\"")
buildConfigField("String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons\"")
buildConfigField("String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.contributions.contentprovider\"")
buildConfigField("String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\"")
buildConfigField("String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\"")
buildConfigField("String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.explore.recentsearches.contentprovider\"")
buildConfigField("String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.recentlanguages.contentprovider\"")
buildConfigField("String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.contentprovider\"")
buildConfigField("String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.locations.contentprovider\"")
buildConfigField("String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.items.contentprovider\"")
buildConfigField("String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\"")
buildConfigField("String", "TEST_USERNAME", "\"" + getTestUserName() + "\"")
buildConfigField("String", "TEST_PASSWORD", "\"" + getTestPassword() + "\"")
buildConfigField("String", "DEPICTS_PROPERTY", "\"P180\"")
buildConfigField("String", "CREATOR_PROPERTY", "\"P170\"")
}
create("beta") {
dimension = "tier"
applicationId = "fr.free.nrw.commons.beta"
// What values do we need to hit the BETA versions of the site / api ?
buildConfigField("String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\"")
buildConfigField("String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\"")
buildConfigField("String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\"")
buildConfigField("String", "WIKIDATA_URL", "\"https://www.wikidata.org\"")
buildConfigField("String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\"")
buildConfigField("String", "WIKIMEDIA_CAMPAIGNS_URL", "\"https://raw.githubusercontent.com/commons-app/campaigns/master/campaigns_beta_active.json\"")
buildConfigField("String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\"")
buildConfigField("String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\"")
buildConfigField("String", "COMMONS_URL", "\"https://commons.wikimedia.beta.wmflabs.org\"")
buildConfigField("String", "WIKIDATA_URL", "\"https://www.wikidata.org\"")
buildConfigField("String", "MOBILE_HOME_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/wiki/\"")
buildConfigField("String", "MOBILE_META_URL", "\"https://meta.m.wikimedia.beta.wmflabs.org/wiki/\"")
buildConfigField("String", "SIGNUP_LANDING_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Special:CreateAccount&returnto=Main+Page&returntoquery=welcome%3Dyes\"")
buildConfigField("String", "SIGNUP_SUCCESS_REDIRECTION_URL", "\"https://commons.m.wikimedia.beta.wmflabs.org/w/index.php?title=Main_Page&welcome=yes\"")
buildConfigField("String", "FORGOT_PASSWORD_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/Special:PasswordReset\"")
buildConfigField("String", "PRIVACY_POLICY_URL", "\"https://github.com/commons-app/commons-app-documentation/blob/master/android/Privacy-policy.md\"")
buildConfigField("String", "FILE_USAGES_BASE_URL", "\"https://commons.wikimedia.org/w/api.php?action=query&format=json&formatversion=2\"")
buildConfigField("String", "ACCOUNT_TYPE", "\"fr.free.nrw.commons.beta\"")
buildConfigField("String", "CONTRIBUTION_AUTHORITY", "\"fr.free.nrw.commons.beta.contributions.contentprovider\"")
buildConfigField("String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.beta.modifications.contentprovider\"")
buildConfigField("String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.beta.categories.contentprovider\"")
buildConfigField("String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.beta.explore.recentsearches.contentprovider\"")
buildConfigField("String", "RECENT_LANGUAGE_AUTHORITY", "\"fr.free.nrw.commons.beta.recentlanguages.contentprovider\"")
buildConfigField("String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.contentprovider\"")
buildConfigField("String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.locations.contentprovider\"")
buildConfigField("String", "BOOKMARK_ITEMS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.items.contentprovider\"")
buildConfigField("String", "COMMIT_SHA", "\"" + getBuildVersion().toString() + "\"")
buildConfigField("String", "TEST_USERNAME", "\"" + getTestUserName() + "\"")
buildConfigField("String", "TEST_PASSWORD", "\"" + getTestPassword() + "\"")
buildConfigField("String", "DEPICTS_PROPERTY", "\"P245962\"")
buildConfigField("String", "CREATOR_PROPERTY", "\"P253075\"")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
buildConfig = true
viewBinding = true
compose = true
}
buildToolsVersion = buildToolsVersion
composeOptions {
kotlinCompilerExtensionVersion = "1.5.8"
}
packaging {
jniLibs {
excludes += listOf("META-INF/androidx.*")
}
resources {
excludes += listOf(
"META-INF/androidx.*",
"META-INF/proguard/androidx-annotations.pro",
"/META-INF/LICENSE.md",
"/META-INF/LICENSE-notice.md"
)
}
}
testOptions {
animationsDisabled = true
unitTests {
isReturnDefaultValues = true
isIncludeAndroidResources = true
}
unitTests.all {
it.jvmArgs("-noverify")
}
}
lint {
abortOnError = false
disable += listOf("MissingTranslation", "ExtraTranslation")
}
}
dependencies {
// Utils
implementation(libs.gson)
implementation(libs.okhttp)
implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
implementation(libs.retrofit.adapter.rxjava)
implementation(libs.rxandroid)
implementation(libs.rxjava)
implementation(libs.rxbinding)
implementation(libs.rxbinding.appcompat)
implementation(libs.facebook.fresco)
implementation(libs.facebook.fresco.middleware)
implementation(libs.apache.commons.lang3)
// UI
implementation("${libs.viewpagerindicator.library.get()}@aar")
implementation(libs.photoview)
implementation(libs.android.sdk)
implementation(libs.android.plugin.scalebar)
implementation(libs.timber)
implementation(libs.android.material)
implementation(libs.dexter)
// Jetpack Compose
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.ui.viewbinding)
implementation(libs.androidx.material3)
implementation(libs.androidx.foundation)
implementation(libs.androidx.foundation.layout)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.adapterdelegates4.kotlin.dsl.viewbinding)
implementation(libs.adapterdelegates4.pagination)
implementation(libs.androidx.paging.runtime.ktx)
testImplementation(libs.androidx.paging.common.ktx)
implementation(libs.androidx.paging.rxjava2.ktx)
implementation(libs.androidx.recyclerview)
// Logging
implementation(libs.acra.dialog)
implementation(libs.acra.mail)
implementation(libs.slf4j.api)
implementation(libs.logback.android.classic) {
exclude(group = "com.google.android", module = "android")
}
implementation(libs.logging.interceptor)
// Dependency injector
implementation(libs.dagger.android)
implementation(libs.dagger.android.support)
kapt(libs.dagger.android.processor)
kapt(libs.dagger.compiler)
annotationProcessor(libs.dagger.android.processor)
implementation(libs.kotlin.reflect)
//Mocking
testImplementation(libs.mockito.kotlin)
testImplementation(libs.mockito.core)
testImplementation(libs.powermock.module.junit)
testImplementation(libs.powermock.api.mockito)
testImplementation(libs.mockk)
// Unit testing
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.androidx.test.core)
testImplementation(libs.androidx.runner)
testImplementation(libs.androidx.test.ext.junit)
testImplementation(libs.androidx.test.rules)
testImplementation(libs.mockwebserver)
testImplementation(libs.livedata.testing.ktx)
testImplementation(libs.androidx.core.testing)
testImplementation(libs.junit.jupiter.api)
testRuntimeOnly(libs.junit.jupiter.engine)
testImplementation(libs.soloader)
testImplementation(libs.kotlinx.coroutines.test)
debugImplementation(libs.androidx.fragment.testing)
testImplementation(libs.commons.io)
// Android testing
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.espresso.intents)
androidTestImplementation(libs.androidx.espresso.contrib)
androidTestImplementation(libs.androidx.runner)
androidTestImplementation(libs.androidx.test.rules)
androidTestImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.test.ext.junit)
androidTestImplementation(libs.androidx.annotation)
androidTestImplementation(libs.mockwebserver)
androidTestImplementation(libs.androidx.uiautomator)
// Debugging
debugImplementation(libs.leakcanary.android)
// Support libraries
implementation(libs.androidx.browser)
implementation(libs.androidx.cardview)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.exifinterface)
implementation(libs.recyclerview.fastscroll)
//swipe_layout
implementation(libs.swipelayout.library)
//Room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.rxjava)
kapt(libs.androidx.room.compiler)
// Preferences
implementation(libs.androidx.preference)
implementation(libs.androidx.preference.ktx)
//Android Media
implementation(libs.juanitobananas.androidDmediaUtil)
implementation(libs.androidx.multidex)
// Kotlin + coroutines
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.work.runtime)
implementation(libs.kotlinx.coroutines.rx2)
testImplementation(libs.androidx.work.testing)
//Glide
implementation(libs.glide)
annotationProcessor(libs.glide.compiler)
kaptTest(libs.androidx.databinding.compiler)
kaptAndroidTest(libs.androidx.databinding.compiler)
implementation(libs.coordinates2country.android) {
exclude(group = "com.google.android", module = "android")
}
//OSMDroid
implementation(libs.osmdroid.android)
constraints {
implementation(libs.kotlin.stdlib.jdk7) {
because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib")
}
implementation(libs.kotlin.stdlib.jdk8) {
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
}
}
}
tasks.register<Exec>("disableAnimations") {
val adb = "${System.getenv("ANDROID_HOME")}/platform-tools/adb"
commandLine(adb, "shell", "settings", "put", "global", "window_animation_scale", "0")
commandLine(adb, "shell", "settings", "put", "global", "transition_animation_scale", "0")
commandLine(adb, "shell", "settings", "put", "global", "animator_duration_scale", "0")
}
project.gradle.taskGraph.whenReady {
val connectedBetaDebugAndroidTest = tasks.named("connectedBetaDebugAndroidTest")
val connectedProdDebugAndroidTest = tasks.named("connectedProdDebugAndroidTest")
connectedBetaDebugAndroidTest.configure {
dependsOn("disableAnimations")
}
connectedProdDebugAndroidTest.configure {
dependsOn("disableAnimations")
}
}
fun getTestUserName(): String? {
val propFile = rootProject.file("./local.properties")
val properties = Properties()
propFile.inputStream().use { properties.load(it) }
return properties.getProperty("TEST_USER_NAME")
}
fun getTestPassword(): String? {
val propFile = rootProject.file("./local.properties")
val properties = Properties()
propFile.inputStream().use { properties.load(it) }
return properties.getProperty("TEST_USER_PASSWORD")
}
if (isRunningOnTravisAndIsNotPRBuild) {
configure<com.github.triplet.gradle.play.PlayPublisherExtension> {
track = "alpha"
userFraction = 1.0
serviceAccountEmail = System.getenv("SERVICE_ACCOUNT_NAME")
serviceAccountCredentials = file("../play.p12")
resolutionStrategy = "auto"
outputProcessor { // this: ApkVariantOutput
versionNameOverride = "$versionNameOverride.$versionCode"
}
}
}
fun getBuildVersion(): String? {
return try {
val stdout = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "--short", "HEAD")
standardOutput = stdout
}
stdout.toString().trim()
} catch (e: Exception) {
null
}
}
fun getBranchName(): String? {
return try {
val stdout = ByteArrayOutputStream()
exec {
commandLine("git", "rev-parse", "--abbrev-ref", "HEAD")
standardOutput = stdout
}
stdout.toString().trim()
} catch (e: Exception) {
null
}
}

View file

@ -31,6 +31,17 @@
-keepattributes Signature
# Retain declared checked exceptions for use by a Proxy instance.
-keepattributes Exceptions
# Note: The model package right now seems to include some other classes that
# are not used for serialization / deserialization over Gson. Hopefully
# that's not a problem since it only prevents R8 from avoiding trimming
# of few more classes.
-keepclasseswithmembers class fr.free.nrw.commons.*.model.** { *; }
-keepclasseswithmembers class fr.free.nrw.commons.actions.** { *; }
-keepclasseswithmembers class fr.free.nrw.commons.auth.csrf.** { *; }
-keepclasseswithmembers class fr.free.nrw.commons.auth.login.** { *; }
-keepclasseswithmembers class fr.free.nrw.commons.wikidata.mwapi.** { *; }
# --- /Retrofit ---
# --- OkHttp + Okio ---
@ -55,6 +66,9 @@
# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }
# Prevent R8 from obfuscating project classes used by Gson for parsing
-keep class fr.free.nrw.commons.fileusages.** { *; }
# Prevent proguard from stripping interface information from TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * implements com.google.gson.TypeAdapterFactory

View file

@ -25,7 +25,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AboutActivityTest {
@get:Rule
var activityRule: ActivityTestRule<*> = ActivityTestRule(AboutActivity::class.java)
@ -36,7 +35,8 @@ class AboutActivityTest {
device.setOrientationNatural()
device.freezeRotation()
Intents.init()
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
Intents
.intending(CoreMatchers.not(IntentMatchers.isInternal()))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
@ -47,11 +47,12 @@ class AboutActivityTest {
@Test
fun testBuildNumber() {
Espresso.onView(ViewMatchers.withId(R.id.about_version))
Espresso
.onView(ViewMatchers.withId(R.id.about_version))
.check(
ViewAssertions.matches(
withText(getApplicationContext<CommonsApplication>().getVersionNameWithSha())
)
withText(getApplicationContext<CommonsApplication>().getVersionNameWithSha()),
),
)
}
@ -61,8 +62,8 @@ class AboutActivityTest {
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.WEBSITE_URL)
)
IntentMatchers.hasData(Urls.WEBSITE_URL),
),
)
}
@ -73,8 +74,8 @@ class AboutActivityTest {
CoreMatchers.anyOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.FACEBOOK_WEB_URL),
IntentMatchers.hasPackage(Urls.FACEBOOK_PACKAGE_NAME)
)
IntentMatchers.hasPackage(Urls.FACEBOOK_PACKAGE_NAME),
),
)
}
@ -84,8 +85,8 @@ class AboutActivityTest {
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.GITHUB_REPO_URL)
)
IntentMatchers.hasData(Urls.GITHUB_REPO_URL),
),
)
}
@ -95,8 +96,8 @@ class AboutActivityTest {
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(BuildConfig.PRIVACY_POLICY_URL)
)
IntentMatchers.hasData(BuildConfig.PRIVACY_POLICY_URL),
),
)
}
@ -104,12 +105,12 @@ class AboutActivityTest {
fun testLaunchTranslate() {
Espresso.onView(ViewMatchers.withId(R.id.about_translate)).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(android.R.id.button1)).perform(ViewActions.click())
val langCode = CommonsApplication.getInstance().languageLookUpTable.codes[0]
val langCode = CommonsApplication.instance.languageLookUpTable!!.getCodes()[0]
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData("${Urls.TRANSLATE_WIKI_URL}$langCode")
)
IntentMatchers.hasData("${Urls.TRANSLATE_WIKI_URL}$langCode"),
),
)
}
@ -119,27 +120,30 @@ class AboutActivityTest {
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.CREDITS_URL)
)
IntentMatchers.hasData(Urls.CREDITS_URL),
),
)
}
@Test
fun testLaunchUserGuide() {
Espresso.onView(ViewMatchers.withId(R.id.about_user_guide)).perform(ViewActions.click())
Intents.intended(CoreMatchers.allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.USER_GUIDE_URL)))
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.USER_GUIDE_URL),
),
)
}
@Test
fun testLaunchAboutFaq() {
Espresso.onView(ViewMatchers.withId(R.id.about_faq)).perform(ViewActions.click())
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(Urls.FAQ_URL)
)
IntentMatchers.hasData(Urls.FAQ_URL),
),
)
}
}

View file

@ -18,12 +18,14 @@ import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SignupActivity
import org.hamcrest.CoreMatchers
import org.hamcrest.CoreMatchers.not
import org.junit.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LoginActivityTest {
@get:Rule
var activityRule = ActivityTestRule(LoginActivity::class.java)
@ -49,8 +51,8 @@ class LoginActivityTest {
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasData(BuildConfig.FORGOT_PASSWORD_URL)
)
IntentMatchers.hasData(BuildConfig.FORGOT_PASSWORD_URL),
),
)
}

View file

@ -21,19 +21,22 @@ import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.notification.NotificationActivity
import org.hamcrest.CoreMatchers
import org.hamcrest.Matchers
import org.junit.*
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@LargeTest
@RunWith(AndroidJUnit4::class)
class MainActivityTest {
@get:Rule
var activityRule: ActivityTestRule<*> = ActivityTestRule(LoginActivity::class.java)
@get:Rule
var mGrantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION"
var mGrantPermissionRule: GrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.ACCESS_FINE_LOCATION",
)
private val device: UiDevice =
@ -48,7 +51,8 @@ class MainActivityTest {
UITestHelper.loginUser()
UITestHelper.skipWelcome()
Intents.init()
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
Intents
.intending(CoreMatchers.not(IntentMatchers.isInternal()))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
val context = InstrumentationRegistry.getInstrumentation().targetContext
val storeName = context.packageName + "_preferences"
@ -62,33 +66,37 @@ class MainActivityTest {
@Test
fun testNearby() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
1
1,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.fragmentContainer))
Espresso
.onView(ViewMatchers.withId(R.id.fragmentContainer))
.check(matches(ViewMatchers.isDisplayed()))
UITestHelper.sleep(10000)
val actionMenuItemView2 = Espresso.onView(
val actionMenuItemView2 =
Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.list_sheet), ViewMatchers.withContentDescription("List"),
ViewMatchers.withId(R.id.list_sheet),
ViewMatchers.withContentDescription("List"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.toolbar),
1
1,
),
0
0,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
)
actionMenuItemView2.perform(ViewActions.click())
UITestHelper.sleep(1000)
@ -96,64 +104,70 @@ class MainActivityTest {
@Test
fun testExplore() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
2
2,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.fragmentContainer))
Espresso
.onView(ViewMatchers.withId(R.id.fragmentContainer))
.check(matches(ViewMatchers.isDisplayed()))
UITestHelper.sleep(1000)
}
@Test
fun testContributions() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
0
0,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
Espresso.onView(ViewMatchers.withId(R.id.fragmentContainer))
Espresso
.onView(ViewMatchers.withId(R.id.fragmentContainer))
.check(matches(ViewMatchers.isDisplayed()))
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.contributionImage),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.contributionsList),
0
0,
),
1
1,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
val actionMenuItemView = Espresso.onView(
val actionMenuItemView =
Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.menu_bookmark_current_image),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.toolbar),
1
1,
),
0
0,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
)
actionMenuItemView.perform(ViewActions.click())
UITestHelper.sleep(3000)
@ -161,70 +175,40 @@ class MainActivityTest {
@Test
fun testBookmarks() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
3
3,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
UITestHelper.sleep(1000)
}
@Test
fun testNotifications() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.notifications),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.toolbar),
1
1,
),
1
1,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
Intents.intended(IntentMatchers.hasComponent(NotificationActivity::class.java.name))
Espresso.pressBack()
UITestHelper.sleep(1000)
}
@Test
fun testLimitedConnectionModeToggle() {
val isEnabled = defaultKvStore
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)
Espresso.onView(
Matchers.allOf(
ViewMatchers.withId(R.id.toggle_limited_connection_mode),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.toolbar),
1
),
0
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
UITestHelper.sleep(1000)
if (isEnabled) {
Assert.assertFalse(
defaultKvStore
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)
)
} else {
Assert.assertTrue(
defaultKvStore
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)
)
}
}
}

View file

@ -4,7 +4,6 @@ import android.app.Activity
import android.app.Instrumentation
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.swipeRight
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
@ -26,7 +25,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ProfileActivityTest {
@get:Rule
var activityRule = IntentsTestRule(LoginActivity::class.java)
@ -38,7 +36,8 @@ class ProfileActivityTest {
device.freezeRotation()
UITestHelper.loginUser()
UITestHelper.skipWelcome()
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
Intents
.intending(CoreMatchers.not(IntentMatchers.isInternal()))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
@ -50,20 +49,19 @@ class ProfileActivityTest {
childAtPosition(
childAtPosition(
withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
4
4,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
onView(Matchers.allOf(withId(R.id.more_profile))).perform(
ViewActions.scrollTo(),
ViewActions.click()
ViewActions.click(),
)
device.swipe(1033, 1346, 531, 1346, 20)
UITestHelper.sleep(5000)
Intents.intended(hasComponent(ProfileActivity::class.java.name))
}
}

View file

@ -9,7 +9,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class ReviewActivityTest {
@get:Rule
var activityRule: ActivityTestRule<*> = ActivityTestRule(ReviewActivity::class.java)
@ -17,5 +16,4 @@ class ReviewActivityTest {
fun orientationChange() {
UITestHelper.changeOrientation(activityRule)
}
}

View file

@ -1,74 +0,0 @@
package fr.free.nrw.commons;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import android.content.Context;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import fr.free.nrw.commons.db.AppDatabase;
import fr.free.nrw.commons.review.ReviewDao;
import fr.free.nrw.commons.review.ReviewEntity;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ReviewDaoTest {
private ReviewDao reviewDao;
private AppDatabase database;
/**
* Set up the application database
*/
@Before
public void createDb() {
Context context = ApplicationProvider.getApplicationContext();
database = Room.inMemoryDatabaseBuilder(
context, AppDatabase.class)
.allowMainThreadQueries()
.build();
reviewDao = database.ReviewDao();
}
/**
* Close the database
*/
@After
public void closeDb() {
database.close();
}
/**
* Test insertion
* Also checks isReviewedAlready():
* Case 1: When image has been reviewed/skipped by the user
*/
@Test
public void insert() {
// Insert data
String imageId = "1234";
ReviewEntity reviewEntity = new ReviewEntity(imageId);
reviewDao.insert(reviewEntity);
// Check insertion
// Covers the case where the image exists in the database
// And isReviewedAlready() returns true
Boolean isInserted = reviewDao.isReviewedAlready(imageId);
assertThat(isInserted, equalTo(true));
}
/**
* Test review status of the image
* Case 2: When image has not been reviewed/skipped
*/
@Test
public void isReviewedAlready(){
String imageId = "5856";
Boolean isInserted = reviewDao.isReviewedAlready(imageId);
assertThat(isInserted, equalTo(false));
}
}

View file

@ -16,7 +16,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SearchActivityTest {
@get:Rule
var activityRule = ActivityTestRule(SearchActivity::class.java)
@ -31,20 +30,21 @@ class SearchActivityTest {
@Test
fun exploreActivityTest() {
val searchAutoComplete = Espresso.onView(
val searchAutoComplete =
Espresso.onView(
Matchers.allOf(
UITestHelper.childAtPosition(
Matchers.allOf(
ViewMatchers.withClassName(Matchers.`is`("android.widget.LinearLayout")),
UITestHelper.childAtPosition(
ViewMatchers.withClassName(Matchers.`is`("android.widget.LinearLayout")),
1
)
1,
),
0
),
ViewMatchers.isDisplayed()
)
0,
),
ViewMatchers.isDisplayed(),
),
)
searchAutoComplete.perform(ViewActions.replaceText("cat"), ViewActions.closeSoftKeyboard())
UITestHelper.sleep(5000)

View file

@ -22,7 +22,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsActivityLoggedInTest {
@get:Rule
var activityRule: ActivityTestRule<*> = ActivityTestRule(LoginActivity::class.java)
@ -35,31 +34,32 @@ class SettingsActivityLoggedInTest {
device.freezeRotation()
UITestHelper.loginUser()
UITestHelper.skipWelcome()
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
Intents
.intending(CoreMatchers.not(IntentMatchers.isInternal()))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
@Test
fun testSettings() {
Espresso.onView(
Espresso
.onView(
Matchers.allOf(
ViewMatchers.withContentDescription("More"),
UITestHelper.childAtPosition(
UITestHelper.childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
4
4,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.more_settings))).perform(
ViewActions.scrollTo(),
ViewActions.click()
ViewActions.click(),
)
Intents.intended(IntentMatchers.hasComponent(SettingsActivity::class.java.name))
UITestHelper.sleep(1000)
}
}

View file

@ -23,7 +23,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SettingsActivityTest {
private lateinit var defaultKvStore: JsonKvStore
@get:Rule
@ -44,21 +43,23 @@ class SettingsActivityTest {
fun useAuthorNameTogglesOn() {
// Turn on "Use author name" preference if currently off
if (!defaultKvStore.getBoolean("useAuthorName", false)) {
Espresso.onView(
Espresso
.onView(
allOf(
withId(R.id.recycler_view),
childAtPosition(withId(android.R.id.list_container), 0)
)
childAtPosition(withId(android.R.id.list_container), 0),
),
).perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(6, click())
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(6, click()),
)
}
// Check authorName preference is enabled
Espresso.onView(
Espresso
.onView(
allOf(
withId(R.id.recycler_view),
childAtPosition(withId(android.R.id.list_container), 0)
)
childAtPosition(withId(android.R.id.list_container), 0),
),
).check(matches(isEnabled()))
}

View file

@ -10,10 +10,13 @@ import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.rule.ActivityTestRule
import org.apache.commons.lang3.StringUtils
import org.hamcrest.*
import org.hamcrest.BaseMatcher
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher
import timber.log.Timber
class UITestHelper {
companion object {
fun skipWelcome() {
@ -30,25 +33,29 @@ class UITestHelper {
fun skipLogin() {
try {
// Skip Login
val htmlTextView = onView(
val htmlTextView =
onView(
Matchers.allOf(
ViewMatchers.withId(R.id.skip_login), ViewMatchers.withText("Skip"),
ViewMatchers.isDisplayed()
)
ViewMatchers.withId(R.id.skip_login),
ViewMatchers.withText("Skip"),
ViewMatchers.isDisplayed(),
),
)
htmlTextView.perform(ViewActions.click())
val appCompatButton = onView(
val appCompatButton =
onView(
Matchers.allOf(
ViewMatchers.withId(android.R.id.button1), ViewMatchers.withText("Yes"),
ViewMatchers.withId(android.R.id.button1),
ViewMatchers.withText("Yes"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.buttonPanel),
0
0,
),
3,
),
),
3
)
)
)
appCompatButton.perform(ViewActions.scrollTo(), ViewActions.click())
} catch (ignored: NoMatchingViewException) {
@ -62,13 +69,13 @@ class UITestHelper {
onView(ViewMatchers.withId(R.id.login_username))
.perform(
ViewActions.replaceText(getTestUsername()),
ViewActions.closeSoftKeyboard()
ViewActions.closeSoftKeyboard(),
)
sleep(2000)
onView(ViewMatchers.withId(R.id.login_password))
.perform(
ViewActions.replaceText(getTestUserPassword()),
ViewActions.closeSoftKeyboard()
ViewActions.closeSoftKeyboard(),
)
sleep(2000)
onView(ViewMatchers.withId(R.id.login_button))
@ -76,7 +83,6 @@ class UITestHelper {
sleep(10000)
} catch (ignored: NoMatchingViewException) {
}
}
fun logoutUser() {
@ -87,36 +93,38 @@ class UITestHelper {
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
4
4,
),
ViewMatchers.isDisplayed(),
),
ViewMatchers.isDisplayed()
)
).perform(ViewActions.click())
onView(
Matchers.allOf(
ViewMatchers.withId(R.id.more_logout), ViewMatchers.withText("Logout"),
ViewMatchers.withId(R.id.more_logout),
ViewMatchers.withText("Logout"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.scroll_view_more_bottom_sheet),
0
0,
),
6,
),
),
6
)
)
).perform(ViewActions.scrollTo(), ViewActions.click())
onView(
Matchers.allOf(
ViewMatchers.withId(android.R.id.button1), ViewMatchers.withText("Yes"),
ViewMatchers.withId(android.R.id.button1),
ViewMatchers.withText("Yes"),
childAtPosition(
childAtPosition(
ViewMatchers.withId(R.id.buttonPanel),
0
0,
),
3,
),
),
3
)
)
).perform(ViewActions.scrollTo(), ViewActions.click())
sleep(5000)
} catch (ignored: NoMatchingViewException) {
@ -124,9 +132,9 @@ class UITestHelper {
}
fun childAtPosition(
parentMatcher: Matcher<View>, position: Int
parentMatcher: Matcher<View>,
position: Int,
): Matcher<View> {
return object : TypeSafeMatcher<View>() {
override fun describeTo(description: Description) {
description.appendText("Child at position $position in parent ")
@ -135,8 +143,9 @@ class UITestHelper {
public override fun matchesSafely(view: View): Boolean {
val parent = view.parent
return parent is ViewGroup && parentMatcher.matches(parent)
&& view == parent.getChildAt(position)
return parent is ViewGroup &&
parentMatcher.matches(parent) &&
view == parent.getChildAt(position)
}
}
}
@ -154,14 +163,18 @@ class UITestHelper {
val username = BuildConfig.TEST_USERNAME
if (StringUtils.isEmpty(username) || username == "null") {
throw NotImplementedError("Configure your beta account's username")
} else return username
} else {
return username
}
}
private fun getTestUserPassword(): String {
val password = BuildConfig.TEST_PASSWORD
if (StringUtils.isEmpty(password) || password == "null") {
throw NotImplementedError("Configure your beta account's password")
} else return password
} else {
return password
}
}
fun <T : Activity> changeOrientation(activityRule: ActivityTestRule<T>) {
@ -174,6 +187,7 @@ class UITestHelper {
fun <T> first(matcher: Matcher<T>): Matcher<T>? {
return object : BaseMatcher<T>() {
var isFirst = true
override fun matches(item: Any): Boolean {
if (isFirst && matcher.matches(item)) {
isFirst = false

View file

@ -4,7 +4,10 @@ import android.app.Activity
import android.app.Instrumentation
import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.action.ViewActions.scrollTo
import androidx.test.espresso.contrib.RecyclerViewActions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.matcher.IntentMatchers
@ -15,7 +18,7 @@ import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import androidx.test.uiautomator.UiDevice
import fr.free.nrw.commons.LocationPicker.LocationPickerActivity
import fr.free.nrw.commons.locationpicker.LocationPickerActivity
import fr.free.nrw.commons.UITestHelper.Companion.childAtPosition
import fr.free.nrw.commons.auth.LoginActivity
import org.hamcrest.CoreMatchers
@ -28,7 +31,6 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class UploadCancelledTest {
@Rule
@JvmField
var mActivityTestRule = ActivityTestRule(LoginActivity::class.java)
@ -37,7 +39,7 @@ class UploadCancelledTest {
@JvmField
var mGrantPermissionRule: GrantPermissionRule =
GrantPermissionRule.grant(
"android.permission.WRITE_EXTERNAL_STORAGE"
"android.permission.WRITE_EXTERNAL_STORAGE",
)
private val device: UiDevice =
@ -47,15 +49,15 @@ class UploadCancelledTest {
fun setup() {
try {
Intents.init()
} catch (ex: IllegalStateException) {
} catch (_: IllegalStateException) {
}
device.unfreezeRotation()
device.setOrientationNatural()
device.freezeRotation()
UITestHelper.loginUser()
UITestHelper.skipWelcome()
Intents.intending(CoreMatchers.not(IntentMatchers.isInternal()))
Intents
.intending(CoreMatchers.not(IntentMatchers.isInternal()))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
@ -63,130 +65,137 @@ class UploadCancelledTest {
fun teardown() {
try {
Intents.release()
} catch (ex: IllegalStateException) {
} catch (_: IllegalStateException) {
}
}
@Test
fun uploadCancelledAfterLocationPickedTest() {
val bottomNavigationItemView = onView(
val bottomNavigationItemView =
onView(
allOf(
childAtPosition(
childAtPosition(
withId(R.id.fragment_main_nav_tab_layout),
0
0,
),
1
1,
),
isDisplayed(),
),
isDisplayed()
)
)
bottomNavigationItemView.perform(click())
UITestHelper.sleep(12000)
val actionMenuItemView = onView(
val actionMenuItemView =
onView(
allOf(
withId(R.id.list_sheet),
childAtPosition(
childAtPosition(
withId(R.id.toolbar),
1
1,
),
0
0,
),
isDisplayed(),
),
isDisplayed()
)
)
actionMenuItemView.perform(click())
val recyclerView = onView(
val recyclerView =
onView(
allOf(
withId(R.id.rv_nearby_list),
)
),
)
recyclerView.perform(
RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(
0,
click()
)
click(),
),
)
val linearLayout3 = onView(
val linearLayout3 =
onView(
allOf(
withId(R.id.cameraButton),
childAtPosition(
allOf(
withId(R.id.nearby_button_layout),
),
0
0,
),
isDisplayed(),
),
isDisplayed()
)
)
linearLayout3.perform(click())
val pasteSensitiveTextInputEditText = onView(
val pasteSensitiveTextInputEditText =
onView(
allOf(
withId(R.id.caption_item_edit_text),
childAtPosition(
childAtPosition(
withId(R.id.caption_item_edit_text_input_layout),
0
0,
),
0
0,
),
isDisplayed(),
),
isDisplayed()
)
)
pasteSensitiveTextInputEditText.perform(replaceText("test"), closeSoftKeyboard())
val pasteSensitiveTextInputEditText2 = onView(
val pasteSensitiveTextInputEditText2 =
onView(
allOf(
withId(R.id.description_item_edit_text),
childAtPosition(
childAtPosition(
withId(R.id.description_item_edit_text_input_layout),
0
0,
),
0
0,
),
isDisplayed(),
),
isDisplayed()
)
)
pasteSensitiveTextInputEditText2.perform(replaceText("test"), closeSoftKeyboard())
val appCompatButton2 = onView(
val appCompatButton2 =
onView(
allOf(
withId(R.id.btn_next),
childAtPosition(
childAtPosition(
withId(R.id.ll_container_media_detail),
2
2,
),
1
1,
),
isDisplayed(),
),
isDisplayed()
)
)
appCompatButton2.perform(click())
val appCompatButton3 = onView(
val appCompatButton3 =
onView(
allOf(
withId(android.R.id.button1),
)
),
)
appCompatButton3.perform(scrollTo(), click())
Intents.intended(IntentMatchers.hasComponent(LocationPickerActivity::class.java.name))
val floatingActionButton3 = onView(
val floatingActionButton3 =
onView(
allOf(
withId(R.id.location_chosen_button),
isDisplayed()
)
isDisplayed(),
),
)
UITestHelper.sleep(2000)
floatingActionButton3.perform(click())

View file

@ -19,7 +19,10 @@ import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.Intents.intending
import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
import androidx.test.espresso.intent.matcher.IntentMatchers.hasType
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import androidx.test.rule.ActivityTestRule
@ -29,21 +32,29 @@ import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
import fr.free.nrw.commons.util.MyViewAction
import fr.free.nrw.commons.utils.ConfigUtils
import org.hamcrest.core.AllOf.allOf
import org.junit.*
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Random
@LargeTest
@RunWith(AndroidJUnit4::class)
class UploadTest {
@get:Rule
var permissionRule = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_FINE_LOCATION)!!
var permissionRule =
GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_FINE_LOCATION,
)!!
@get:Rule
var activityRule = ActivityTestRule(LoginActivity::class.java)
@ -60,8 +71,7 @@ class UploadTest {
fun setup() {
try {
Intents.init()
} catch (ex: IllegalStateException) {
} catch (_: IllegalStateException) {
}
UITestHelper.loginUser()
UITestHelper.skipWelcome()
@ -99,7 +109,6 @@ class UploadTest {
onView(allOf<View>(isDisplayed(), withId(R.id.description_item_edit_text)))
.perform(replaceText(commonsFileName))
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())
@ -131,7 +140,8 @@ class UploadTest {
UITestHelper.sleep(10000)
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
val fileUrl =
"https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
commonsFileName.replace(' ', '_') + ".jpg"
Timber.i("File should be uploaded to $fileUrl")
}
@ -200,7 +210,8 @@ class UploadTest {
UITestHelper.sleep(10000)
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
val fileUrl =
"https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
commonsFileName.replace(' ', '_') + ".jpg"
Timber.i("File should be uploaded to $fileUrl")
}
@ -231,16 +242,22 @@ class UploadTest {
onView(withId(R.id.rv_descriptions)).perform(
RecyclerViewActions
.actionOnItemAtPosition<UploadMediaDetailAdapter.ViewHolder>(0,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description")))
.actionOnItemAtPosition<UploadMediaDetailAdapter.ViewHolder>(
0,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Test description"),
),
)
onView(withId(R.id.btn_add_description))
onView(withId(R.id.btn_add))
.perform(click())
onView(withId(R.id.rv_descriptions)).perform(
RecyclerViewActions
.actionOnItemAtPosition<UploadMediaDetailAdapter.ViewHolder>(1,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description")))
.actionOnItemAtPosition<UploadMediaDetailAdapter.ViewHolder>(
1,
MyViewAction.typeTextInChildViewWithId(R.id.description_item_edit_text, "Description"),
),
)
onView(allOf(isDisplayed(), withId(R.id.btn_next)))
.perform(click())
@ -273,7 +290,8 @@ class UploadTest {
UITestHelper.sleep(10000)
val fileUrl = "https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
val fileUrl =
"https://commons.wikimedia.beta.wmflabs.org/wiki/File:" +
commonsFileName.replace(' ', '_') + ".jpg"
Timber.i("File should be uploaded to $fileUrl")
}
@ -306,7 +324,6 @@ class UploadTest {
} catch (e: IOException) {
e.printStackTrace()
}
}
}

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.runners.AndroidJUnit4
@ -18,11 +17,12 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.CoreMatchers.equalTo
@LargeTest
@RunWith(AndroidJUnit4::class)
class WelcomeActivityTest {
@get:Rule
var activityRule: ActivityTestRule<*> = ActivityTestRule(WelcomeActivity::class.java)
@ -61,7 +61,7 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.finishTutorialButton))
.perform(ViewActions.click())
assert(activityRule.activity.isDestroyed)
assertThat(activityRule.activity.isDestroyed, equalTo(true))
}
}
@ -71,10 +71,10 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
assert(true)
assertThat(true, equalTo(true))
}
@Test
@ -86,13 +86,13 @@ class WelcomeActivityTest {
.perform(ViewActions.swipeLeft())
.perform(ViewActions.swipeLeft())
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
.perform(ViewActions.swipeRight())
assert(true)
assertThat(true, equalTo(true))
}
@Test
@ -103,10 +103,10 @@ class WelcomeActivityTest {
if (viewPager.currentItem == 3) {
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeLeft())
assert(true)
assertThat(true, equalTo(true))
onView(withId(R.id.welcomePager))
.perform(ViewActions.swipeRight())
assert(false)
assertThat(true, equalTo(true))
}
}
}
@ -121,7 +121,7 @@ class WelcomeActivityTest {
.perform(ViewActions.click())
onView(withId(R.id.finishTutorialButton))
.perform(ViewActions.click())
assert(activityRule.activity.isDestroyed)
assertThat(activityRule.activity.isDestroyed, equalTo(true))
}
}
}

View file

@ -0,0 +1,271 @@
package fr.free.nrw.commons.contributions
import android.content.res.Configuration
import android.os.Looper
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.lifecycle.Lifecycle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.android.material.floatingactionbutton.FloatingActionButton
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.OkHttpConnectionFactory
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.createTestClient
import fr.free.nrw.commons.upload.WikidataPlace
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
import java.lang.reflect.Method
@RunWith(AndroidJUnit4::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED)
class ContributionsListFragmentUnitTests {
private lateinit var scenario: FragmentScenario<ContributionsListFragment>
private lateinit var fragment: ContributionsListFragment
private val adapter: ContributionsListAdapter = mock()
private val contribution: Contribution = mock()
private val media: Media = mock()
private val wikidataPlace: WikidataPlace = mock()
@Before
fun setUp() {
OkHttpConnectionFactory.CLIENT = createTestClient()
scenario =
launchFragmentInContainer(
initialState = Lifecycle.State.RESUMED,
themeResId = R.style.LightAppTheme,
) {
ContributionsListFragment()
.apply {
contributionsListPresenter = mock()
callback = mock()
}.also {
fragment = it
}
}
scenario.onFragment {
it.adapter = adapter
}
}
@Test
@Throws(Exception::class)
fun checkFragmentNotNull() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
Assert.assertNotNull(fragment)
}
@Test
@Throws(Exception::class)
fun testOnDetach() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onDetach()
}
@Test
@Throws(Exception::class)
fun testGetContributionStateAt() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(adapter.getContributionForPosition(anyInt())).thenReturn(contribution)
fragment.getContributionStateAt(0)
}
@Test
@Throws(Exception::class)
fun testOnScrollToTop() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.rvContributionsList = mock()
fragment.scrollToTop()
verify(fragment.rvContributionsList)?.smoothScrollToPosition(0)
}
@Test
@Throws(Exception::class)
fun testOnConfirmClicked() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(contribution.media).thenReturn(media)
`when`(media.wikiCode).thenReturn("")
`when`(contribution.wikidataPlace).thenReturn(wikidataPlace)
fragment.onConfirmClicked(contribution, true)
}
@Test
@Throws(Exception::class)
fun testGetTotalMediaCount() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.totalMediaCount
}
@Test
@Throws(Exception::class)
fun testGetMediaAtPositionCaseNonNull() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(adapter.getContributionForPosition(anyInt())).thenReturn(contribution)
`when`(contribution.media).thenReturn(media)
fragment.getMediaAtPosition(0)
}
@Test
@Throws(Exception::class)
fun testGetMediaAtPositionCaseNull() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
`when`(adapter.getContributionForPosition(anyInt())).thenReturn(null)
fragment.getMediaAtPosition(0)
}
@Test
@Throws(Exception::class)
fun testShowAddImageToWikipediaInstructions() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"showAddImageToWikipediaInstructions",
Contribution::class.java,
)
method.isAccessible = true
method.invoke(fragment, contribution)
}
@Test
@Throws(Exception::class)
fun testAddImageToWikipedia() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.addImageToWikipedia(contribution)
}
@Test
@Throws(Exception::class)
fun testOpenMediaDetail() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.openMediaDetail(0, true)
}
@Test
@Throws(Exception::class)
fun testOnViewStateRestored() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onViewStateRestored(mock())
}
@Test
@Throws(Exception::class)
fun testOnSaveInstanceState() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.onSaveInstanceState(mock())
}
@Test
@Throws(Exception::class)
fun testShowNoContributionsUI() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.showNoContributionsUI(true)
}
@Test
@Throws(Exception::class)
fun testShowProgress() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.showProgress(true)
}
@Test
@Throws(Exception::class)
fun testShowWelcomeTip() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
fragment.showWelcomeTip(true)
}
@Test
@Throws(Exception::class)
fun testAnimateFAB() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment {
it.requireView().findViewById<FloatingActionButton>(R.id.fab_plus).hide()
}
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"animateFAB",
Boolean::class.java,
)
method.isAccessible = true
method.invoke(fragment, true)
}
@Test
@Throws(Exception::class)
fun testAnimateFABCaseShownAndOpen() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment {
it.requireView().findViewById<FloatingActionButton>(R.id.fab_plus).show()
}
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"animateFAB",
Boolean::class.java,
)
method.isAccessible = true
method.invoke(fragment, true)
}
@Test
@Throws(Exception::class)
fun testAnimateFABCaseShownAndClose() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment {
it.requireView().findViewById<FloatingActionButton>(R.id.fab_plus).show()
}
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"animateFAB",
Boolean::class.java,
)
method.isAccessible = true
method.invoke(fragment, false)
}
@Test
@Throws(Exception::class)
fun testSetListeners() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"setListeners",
)
method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun testInitializeAnimations() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
val method: Method =
ContributionsListFragment::class.java.getDeclaredMethod(
"initializeAnimations",
)
method.isAccessible = true
method.invoke(fragment)
}
@Test
@Throws(Exception::class)
fun testOnConfigurationChanged() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
val newConfig: Configuration = mock()
newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE
fragment.onConfigurationChanged(newConfig)
}
}

View file

@ -0,0 +1,61 @@
package fr.free.nrw.commons.navtab
import android.os.Looper
import androidx.fragment.app.testing.FragmentScenario
import androidx.fragment.app.testing.launchFragmentInContainer
import androidx.lifecycle.Lifecycle
import androidx.test.ext.junit.runners.AndroidJUnit4
import fr.free.nrw.commons.R
import fr.free.nrw.commons.TestCommonsApplication
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Shadows
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode
@RunWith(AndroidJUnit4::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
@LooperMode(LooperMode.Mode.PAUSED)
class MoreBottomSheetLoggedOutFragmentUnitTests {
private lateinit var scenario: FragmentScenario<MoreBottomSheetLoggedOutFragment>
@Before
fun setUp() {
scenario =
launchFragmentInContainer(
initialState = Lifecycle.State.RESUMED,
themeResId = R.style.LightAppTheme,
) {
MoreBottomSheetLoggedOutFragment()
}
}
@Test
@Throws(Exception::class)
fun testOnSettingsClicked() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment { it.onSettingsClicked() }
}
@Test
@Throws(Exception::class)
fun testOnAboutClicked() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment { it.onAboutClicked() }
}
@Test
@Throws(Exception::class)
fun testOnFeedbackClicked() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment { it.onFeedbackClicked() }
}
@Test
@Throws(Exception::class)
fun testOnLogoutClicked() {
Shadows.shadowOf(Looper.getMainLooper()).idle()
scenario.onFragment { it.onLogoutClicked() }
}
}

View file

@ -11,21 +11,24 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PasteSensitiveTextInputEditTextTest {
private var context: Context? = null
private var textView: PasteSensitiveTextInputEditText? = null
@Before
fun setup() {
context = ApplicationProvider.getApplicationContext()
textView = PasteSensitiveTextInputEditText(context)
textView = PasteSensitiveTextInputEditText(context!!)
}
// this test has no real value, just % for test code coverage
@Test
fun extractFormattingAttributeSet() {
val methodExtractFormattingAttribute = textView!!.javaClass.getDeclaredMethod(
"extractFormattingAttribute", Context::class.java, AttributeSet::class.java)
val methodExtractFormattingAttribute =
textView!!.javaClass.getDeclaredMethod(
"extractFormattingAttribute",
Context::class.java,
AttributeSet::class.java,
)
methodExtractFormattingAttribute.isAccessible = true
methodExtractFormattingAttribute.invoke(textView, context, null)
}

View file

@ -9,56 +9,58 @@ import org.hamcrest.Matcher
class MyViewAction {
companion object {
fun typeTextInChildViewWithId(id: Int, textToBeTyped: String): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View>? {
return null
}
fun typeTextInChildViewWithId(
id: Int,
textToBeTyped: String,
): ViewAction =
object : ViewAction {
override fun getConstraints(): Matcher<View>? = null
override fun getDescription(): String {
return "Click on a child view with specified id."
}
override fun getDescription(): String = "Click on a child view with specified id."
override fun perform(uiController: UiController, view: View) {
override fun perform(
uiController: UiController,
view: View,
) {
val v = view.findViewById<View>(id) as EditText
v.setText(textToBeTyped)
}
}
}
fun selectSpinnerItemInChildViewWithId(id: Int, position: Int): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View>? {
return null
}
fun selectSpinnerItemInChildViewWithId(
id: Int,
position: Int,
): ViewAction =
object : ViewAction {
override fun getConstraints(): Matcher<View>? = null
override fun getDescription(): String {
return "Click on a child view with specified id."
}
override fun getDescription(): String = "Click on a child view with specified id."
override fun perform(uiController: UiController, view: View) {
override fun perform(
uiController: UiController,
view: View,
) {
val v = view.findViewById<View>(id) as AppCompatSpinner
v.setSelection(position)
}
}
}
fun clickItemWithId(id: Int, position: Int): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View>? {
return null
}
fun clickItemWithId(
id: Int,
position: Int,
): ViewAction =
object : ViewAction {
override fun getConstraints(): Matcher<View>? = null
override fun getDescription(): String {
return "Click on a child view with specified id."
}
override fun getDescription(): String = "Click on a child view with specified id."
override fun perform(uiController: UiController, view: View) {
override fun perform(
uiController: UiController,
view: View,
) {
val v = view.findViewById<View>(id) as View
v.performClick()
}
}
}
}
}

View file

@ -1,70 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="fr.free.nrw.commons">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Permission needed up to Android 5.1, see https://github.com/commons-app/apps-android-commons/pull/5863 -->
<uses-permission android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22"/>
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"
android:minSdkVersion="33"/>
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"
android:minSdkVersion="34"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<queries>
<!-- Browser -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<!-- Google Maps -->
<package android:name="com.google.android.apps.maps" />
</queries>
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
</queries> <!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<application
android:name=".CommonsApplication"
android:appComponentFactory="commons"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/LightAppTheme"
android:largeHeap="true"
android:supportsRtl="true"
tools:replace="android:appComponentFactory"
android:appComponentFactory="commons"
android:requestLegacyExternalStorage="true"
tools:ignore="GoogleAppIndexingWarning">
android:supportsRtl="true"
android:theme="@style/LightAppTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:appComponentFactory">
<activity
android:name=".activity.SingleWebViewActivity"
android:exported="false" />
<activity
android:name=".nearby.WikidataFeedback"
android:exported="false" />
<activity
android:name=".upload.UploadProgressActivity"
android:exported="false" />
<activity
android:name=".description.DescriptionEditActivity"
android:exported="true" />
<activity android:name="org.acra.dialog.CrashReportDialog"
android:process=":acra"
android:launchMode="singleInstance"
android:exported="true"
android:theme="@style/EditActivityTheme" />
<activity
android:name=".edit.EditActivity"
android:exported="false" />
<activity
android:name="org.acra.dialog.CrashReportDialog"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" />
android:finishOnTaskLaunch="true"
android:launchMode="singleInstance"
android:process=":acra" />
<activity
android:name=".media.ZoomableActivity"
android:label="Zoomable Activity"
android:configChanges="screenSize|keyboard|orientation"
android:label="Zoomable Activity"
android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" />
<activity android:name=".auth.LoginActivity"
<activity
android:name=".auth.LoginActivity"
android:windowSoftInputMode="adjustPan"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
@ -72,21 +92,18 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity android:name=".WelcomeActivity" />
<activity
android:hardwareAccelerated="false"
android:name=".upload.UploadActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboard"
android:exported="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"
>
android:windowSoftInputMode="adjustPan">
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND" />
@ -106,9 +123,9 @@
</activity>
<activity
android:name=".contributions.MainActivity"
android:configChanges="screenSize|keyboard|orientation"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:configChanges="screenSize|keyboard|orientation" />
/>
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" />
@ -116,59 +133,49 @@
android:name=".AboutActivity"
android:label="@string/title_activity_about"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".auth.SignupActivity"
android:configChanges="orientation|screenLayout|screenSize"
android:label="@string/title_activity_signup" />
<activity
android:name=".notification.NotificationActivity"
android:label="@string/navigation_item_notification" />
<activity android:name=".quiz.QuizActivity"
<activity
android:name=".quiz.QuizActivity"
android:label="@string/quiz" />
<activity android:name=".quiz.QuizResultActivity"
<activity
android:name=".quiz.QuizResultActivity"
android:label="@string/result" />
<activity
android:name=".customselector.ui.selector.CustomSelectorActivity"
android:label="@string/title_activity_custom_selector"
android:configChanges="screenSize|keyboard|orientation"
android:label="@string/title_activity_custom_selector"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".category.CategoryDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:label="@string/title_activity_featured_images"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".explore.depictions.WikidataItemDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:label="@string/title_activity_featured_images"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".explore.SearchActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/title_activity_search"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".contributions.MainActivity"
/>
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".profile.ProfileActivity"
android:configChanges="orientation|screenSize|keyboard"
android:label="@string/Profile" />
<activity
android:name=".review.ReviewActivity"
android:label="@string/title_activity_review" />
<activity
android:name=".LocationPicker.LocationPickerActivity"
android:name=".locationpicker.LocationPickerActivity"
android:label="Location Picker" />
<service
@ -178,16 +185,20 @@
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service
android:name="org.acra.sender.SenderService"
android:exported="false"
android:process=":acra" />
<service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync" />
<provider
android:name=".filepicker.ExtendedFileProvider"
android:authorities="${applicationId}.provider"
@ -197,42 +208,30 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name=".category.CategoryContentProvider"
android:authorities="${applicationId}.categories.contentprovider"
android:exported="false"
android:label="@string/provider_categories"
android:syncable="false" />
<provider
android:name=".explore.recentsearches.RecentSearchesContentProvider"
android:authorities="${applicationId}.explore.recentsearches.contentprovider"
android:exported="false"
android:label="@string/provider_searches"
android:syncable="false" />
<provider
android:name=".recentlanguages.RecentLanguagesContentProvider"
android:authorities="${applicationId}.recentlanguages.contentprovider"
android:exported="false"
android:label="@string/provider_recent_languages"
android:syncable="false" />
<provider
android:name=".bookmarks.pictures.BookmarkPicturesContentProvider"
android:authorities="${applicationId}.bookmarks.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks"
android:syncable="false" />
<provider
android:name=".bookmarks.locations.BookmarkLocationsContentProvider"
android:authorities="${applicationId}.bookmarks.locations.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<provider
android:name=".bookmarks.items.BookmarkItemsContentProvider"
android:authorities="${applicationId}.bookmarks.items.contentprovider"
@ -240,7 +239,8 @@
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<receiver android:name=".widget.PicOfDayAppWidget"
<receiver
android:name=".widget.PicOfDayAppWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -251,8 +251,9 @@
android:resource="@xml/pic_of_day_app_widget_info" />
</receiver>
<uses-library android:name="org.apache.http.legacy" android:required="false" />
<uses-library
android:name="org.apache.http.legacy"
android:required="false" />
</application>
</manifest>

View file

@ -1,187 +0,0 @@
package fr.free.nrw.commons;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Spinner;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.databinding.ActivityAboutBinding;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil;
import java.util.Collections;
import java.util.List;
/**
* Represents about screen of this app
*/
public class AboutActivity extends BaseActivity {
/*
This View Binding class is auto-generated for each xml file. The format is usually the name
of the file with PascalCasing (The underscore characters will be ignored).
More information is available at https://developer.android.com/topic/libraries/view-binding
*/
private ActivityAboutBinding binding;
/**
* This method helps in the creation About screen
*
* @param savedInstanceState Data bundle
*/
@Override
@SuppressLint("StringFormatInvalid")
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
Instead of just setting the view with the xml file. We need to use View Binding class.
*/
binding = ActivityAboutBinding.inflate(getLayoutInflater());
final View view = binding.getRoot();
setContentView(view);
setSupportActionBar(binding.toolbarBinding.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
final String aboutText = getString(R.string.about_license);
/*
We can then access all the views by just using the id names like this.
camelCasing is used with underscore characters being ignored.
*/
binding.aboutLicense.setHtmlText(aboutText);
@SuppressLint("StringFormatMatches")
String improveText = String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL);
binding.aboutImprove.setHtmlText(improveText);
binding.aboutVersion.setText(ConfigUtils.getVersionNameWithSha(getApplicationContext()));
Utils.setUnderlinedText(binding.aboutFaq, R.string.about_faq, getApplicationContext());
Utils.setUnderlinedText(binding.aboutRateUs, R.string.about_rate_us, getApplicationContext());
Utils.setUnderlinedText(binding.aboutUserGuide, R.string.user_guide, getApplicationContext());
Utils.setUnderlinedText(binding.aboutPrivacyPolicy, R.string.about_privacy_policy, getApplicationContext());
Utils.setUnderlinedText(binding.aboutTranslate, R.string.about_translate, getApplicationContext());
Utils.setUnderlinedText(binding.aboutCredits, R.string.about_credits, getApplicationContext());
/*
To set listeners, we can create a separate method and use lambda syntax.
*/
binding.facebookLaunchIcon.setOnClickListener(this::launchFacebook);
binding.githubLaunchIcon.setOnClickListener(this::launchGithub);
binding.websiteLaunchIcon.setOnClickListener(this::launchWebsite);
binding.aboutRateUs.setOnClickListener(this::launchRatings);
binding.aboutCredits.setOnClickListener(this::launchCredits);
binding.aboutPrivacyPolicy.setOnClickListener(this::launchPrivacyPolicy);
binding.aboutUserGuide.setOnClickListener(this::launchUserGuide);
binding.aboutFaq.setOnClickListener(this::launchFrequentlyAskedQuesions);
binding.aboutTranslate.setOnClickListener(this::launchTranslate);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
public void launchFacebook(View view) {
Intent intent;
try {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL));
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME);
startActivity(intent);
} catch (Exception e) {
Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL));
}
}
public void launchGithub(View view) {
Intent intent;
try {
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL));
intent.setPackage(Urls.GITHUB_PACKAGE_NAME);
startActivity(intent);
} catch (Exception e) {
Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL));
}
}
public void launchWebsite(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL));
}
public void launchRatings(View view){
Utils.rateApp(this);
}
public void launchCredits(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL));
}
public void launchUserGuide(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL));
}
public void launchPrivacyPolicy(View view) {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL));
}
public void launchFrequentlyAskedQuesions(View view) {
Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_about, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.share_app_icon:
String shareText = String.format(getString(R.string.share_text), Urls.PLAY_STORE_URL_PREFIX + this.getPackageName());
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via)));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void launchTranslate(View view) {
@NonNull List<String> sortedLocalizedNamesRef = CommonsApplication.getInstance().getLanguageLookUpTable().getCanonicalNames();
Collections.sort(sortedLocalizedNamesRef);
final ArrayAdapter<String> languageAdapter = new ArrayAdapter<>(AboutActivity.this,
android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef);
final Spinner spinner = new Spinner(AboutActivity.this);
spinner.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT));
spinner.setAdapter(languageAdapter);
spinner.setGravity(17);
spinner.setPadding(50,0,0,0);
Runnable positiveButtonRunnable = () -> {
String langCode = CommonsApplication.getInstance().getLanguageLookUpTable().getCodes().get(spinner.getSelectedItemPosition());
Utils.handleWebUrl(AboutActivity.this, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode));
};
DialogUtil.showAlertDialog(this,
getString(R.string.about_translate_title),
getString(R.string.about_translate_message),
getString(R.string.about_translate_proceed),
getString(R.string.about_translate_cancel),
positiveButtonRunnable,
() -> {},
spinner,
true);
}
}

View file

@ -0,0 +1,207 @@
package fr.free.nrw.commons
import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.Spinner
import fr.free.nrw.commons.CommonsApplication.Companion.instance
import fr.free.nrw.commons.databinding.ActivityAboutBinding
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import java.util.Collections
import androidx.core.net.toUri
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets
import fr.free.nrw.commons.utils.handleWebUrl
import fr.free.nrw.commons.utils.setUnderlinedText
/**
* Represents about screen of this app
*/
class AboutActivity : BaseActivity() {
/*
This View Binding class is auto-generated for each xml file. The format is usually the name
of the file with PascalCasing (The underscore characters will be ignored).
More information is available at https://developer.android.com/topic/libraries/view-binding
*/
private var binding: ActivityAboutBinding? = null
/**
* This method helps in the creation About screen
*
* @param savedInstanceState Data bundle
*/
@SuppressLint("StringFormatInvalid") //TODO:
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*
Instead of just setting the view with the xml file. We need to use View Binding class.
*/
binding = ActivityAboutBinding.inflate(layoutInflater)
val view: View = binding!!.root
applyEdgeToEdgeTopInsets(binding!!.toolbarLayout)
setContentView(view)
setSupportActionBar(binding!!.toolbarBinding.toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
val aboutText = getString(R.string.about_license)
/*
We can then access all the views by just using the id names like this.
camelCasing is used with underscore characters being ignored.
*/
binding!!.aboutLicense.setHtmlText(aboutText)
@SuppressLint("StringFormatMatches") // TODO:
val improveText =
String.format(getString(R.string.about_improve), Urls.NEW_ISSUE_URL)
binding!!.aboutImprove.setHtmlText(improveText)
binding!!.aboutVersion.text = applicationContext.getVersionNameWithSha()
binding!!.aboutFaq.setUnderlinedText(R.string.about_faq)
binding!!.aboutRateUs.setUnderlinedText(R.string.about_rate_us)
binding!!.aboutUserGuide.setUnderlinedText(R.string.user_guide)
binding!!.aboutPrivacyPolicy.setUnderlinedText(R.string.about_privacy_policy)
binding!!.aboutTranslate.setUnderlinedText(R.string.about_translate)
binding!!.aboutCredits.setUnderlinedText(R.string.about_credits)
/*
To set listeners, we can create a separate method and use lambda syntax.
*/
binding!!.facebookLaunchIcon.setOnClickListener(::launchFacebook)
binding!!.githubLaunchIcon.setOnClickListener(::launchGithub)
binding!!.websiteLaunchIcon.setOnClickListener(::launchWebsite)
binding!!.aboutRateUs.setOnClickListener(::launchRatings)
binding!!.aboutCredits.setOnClickListener(::launchCredits)
binding!!.aboutPrivacyPolicy.setOnClickListener(::launchPrivacyPolicy)
binding!!.aboutUserGuide.setOnClickListener(::launchUserGuide)
binding!!.aboutFaq.setOnClickListener(::launchFrequentlyAskedQuesions)
binding!!.aboutTranslate.setOnClickListener(::launchTranslate)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
fun launchFacebook(view: View?) {
val intent: Intent
try {
intent = Intent(ACTION_VIEW, Urls.FACEBOOK_APP_URL.toUri())
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
handleWebUrl(this, Urls.FACEBOOK_WEB_URL.toUri())
}
}
fun launchGithub(view: View?) {
val intent: Intent
try {
intent = Intent(ACTION_VIEW, Urls.GITHUB_REPO_URL.toUri())
intent.setPackage(Urls.GITHUB_PACKAGE_NAME)
startActivity(intent)
} catch (e: Exception) {
handleWebUrl(this, Urls.GITHUB_REPO_URL.toUri())
}
}
fun launchWebsite(view: View?) {
handleWebUrl(this, Urls.WEBSITE_URL.toUri())
}
fun launchRatings(view: View?) {
try {
startActivity(
Intent(
ACTION_VIEW,
(Urls.PLAY_STORE_PREFIX + packageName).toUri()
)
)
} catch (_: ActivityNotFoundException) {
handleWebUrl(this, (Urls.PLAY_STORE_URL_PREFIX + packageName).toUri())
}
}
fun launchCredits(view: View?) {
handleWebUrl(this, Urls.CREDITS_URL.toUri())
}
fun launchUserGuide(view: View?) {
handleWebUrl(this, Urls.USER_GUIDE_URL.toUri())
}
fun launchPrivacyPolicy(view: View?) {
handleWebUrl(this, BuildConfig.PRIVACY_POLICY_URL.toUri())
}
fun launchFrequentlyAskedQuesions(view: View?) {
handleWebUrl(this, Urls.FAQ_URL.toUri())
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_about, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.share_app_icon -> {
val shareText = String.format(
getString(R.string.share_text),
Urls.PLAY_STORE_URL_PREFIX + this.packageName
)
val sendIntent = Intent()
sendIntent.setAction(Intent.ACTION_SEND)
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText)
sendIntent.setType("text/plain")
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_via)))
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
fun launchTranslate(view: View?) {
val sortedLocalizedNamesRef = instance.languageLookUpTable!!.getCanonicalNames()
Collections.sort(sortedLocalizedNamesRef)
val languageAdapter = ArrayAdapter(
this@AboutActivity,
android.R.layout.simple_spinner_dropdown_item, sortedLocalizedNamesRef
)
val spinner = Spinner(this@AboutActivity)
spinner.layoutParams =
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
spinner.adapter = languageAdapter
spinner.gravity = 17
spinner.setPadding(50, 0, 0, 0)
val positiveButtonRunnable = Runnable {
val langCode = instance.languageLookUpTable!!.getCodes()[spinner.selectedItemPosition]
handleWebUrl(this@AboutActivity, (Urls.TRANSLATE_WIKI_URL + langCode).toUri())
}
showAlertDialog(
this,
getString(R.string.about_translate_title),
getString(R.string.about_translate_message),
getString(R.string.about_translate_proceed),
getString(R.string.about_translate_cancel),
positiveButtonRunnable,
{},
spinner
)
}
}

View file

@ -0,0 +1,63 @@
package fr.free.nrw.commons
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
class BaseMarker {
private var _position: LatLng = LatLng(0.0, 0.0, 0f)
private var _title: String = ""
private var _place: Place = Place()
private var _icon: Bitmap? = null
var position: LatLng
get() = _position
set(value) {
_position = value
}
var title: String
get() = _title
set(value) {
_title = value
}
var place: Place
get() = _place
set(value) {
_place = value
}
var icon: Bitmap?
get() = _icon
set(value) {
_icon = value
}
constructor() {
}
fun fromResource(
context: Context,
drawableResId: Int,
) {
val drawable: Drawable = context.resources.getDrawable(drawableResId)
icon =
if (drawable is BitmapDrawable) {
drawable.bitmap
} else {
val bitmap =
Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888,
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
}
}
}

View file

@ -1,18 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
/**
* Base presenter, enforcing contracts to atach and detach view
*/
public interface BasePresenter<T> {
/**
* Until a view is attached, it is open to listen events from the presenter
*/
void onAttachView(@NonNull T view);
/**
* Detaching a view makes sure that the view no more receives events from the presenter
*/
void onDetachView();
}

View file

@ -0,0 +1,10 @@
package fr.free.nrw.commons
/**
* Base presenter, enforcing contracts to attach and detach view
*/
interface BasePresenter<T> {
fun onAttachView(view: T)
fun onDetachView()
}

View file

@ -10,6 +10,7 @@ object BetaConstants {
* production server where beta server does not work
*/
const val COMMONS_URL = "https://commons.wikimedia.org/"
/**
* Commons production's depicts property which is used in beta for some specific GET calls on
* production server where beta server does not work

View file

@ -0,0 +1,33 @@
package fr.free.nrw.commons
import android.os.Parcel
import android.os.Parcelable
class CameraPosition(
val latitude: Double,
val longitude: Double,
val zoom: Double,
) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readDouble(),
parcel.readDouble(),
parcel.readDouble(),
)
override fun writeToParcel(
parcel: Parcel,
flags: Int,
) {
parcel.writeDouble(latitude)
parcel.writeDouble(longitude)
parcel.writeDouble(zoom)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<CameraPosition> {
override fun createFromParcel(parcel: Parcel): CameraPosition = CameraPosition(parcel)
override fun newArray(size: Int): Array<CameraPosition?> = arrayOfNulls(size)
}
}

View file

@ -1,86 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
import org.wikipedia.AppAdapter;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.json.GsonMarshaller;
import org.wikipedia.json.GsonUnmarshaller;
import org.wikipedia.login.LoginResult;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import okhttp3.OkHttpClient;
public class CommonsAppAdapter extends AppAdapter {
private final int DEFAULT_THUMB_SIZE = 640;
private final String COOKIE_STORE_NAME = "cookie_store";
private final SessionManager sessionManager;
private final JsonKvStore preferences;
CommonsAppAdapter(@NonNull SessionManager sessionManager, @NonNull JsonKvStore preferences) {
this.sessionManager = sessionManager;
this.preferences = preferences;
}
@Override
public String getMediaWikiBaseUrl() {
return BuildConfig.COMMONS_URL;
}
@Override
public String getRestbaseUriFormat() {
return BuildConfig.COMMONS_URL;
}
@Override
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
return OkHttpConnectionFactory.getClient();
}
@Override
public int getDesiredLeadImageDp() {
return DEFAULT_THUMB_SIZE;
}
@Override
public boolean isLoggedIn() {
return sessionManager.isUserLoggedIn();
}
@Override
public String getUserName() {
return sessionManager.getUserName();
}
@Override
public String getPassword() {
return sessionManager.getPassword();
}
@Override
public void updateAccount(@NonNull LoginResult result) {
sessionManager.updateAccount(result);
}
@Override
public SharedPreferenceCookieManager getCookies() {
if (!preferences.contains(COOKIE_STORE_NAME)) {
return null;
}
return GsonUnmarshaller.unmarshal(SharedPreferenceCookieManager.class,
preferences.getString(COOKIE_STORE_NAME, null));
}
@Override
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
preferences.putString(COOKIE_STORE_NAME, GsonMarshaller.marshal(cookies));
}
@Override
public boolean logErrorsInsteadOfCrashing() {
return false;
}
}

View file

@ -1,340 +0,0 @@
package fr.free.nrw.commons;
import static fr.free.nrw.commons.data.DBOpenHelper.CONTRIBUTIONS_TABLE;
import static org.acra.ReportField.ANDROID_VERSION;
import static org.acra.ReportField.APP_VERSION_CODE;
import static org.acra.ReportField.APP_VERSION_NAME;
import static org.acra.ReportField.PHONE_MODEL;
import static org.acra.ReportField.STACK_TRACE;
import static org.acra.ReportField.USER_COMMENT;
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build;
import android.os.Process;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.multidex.BuildConfig;
import androidx.multidex.MultiDexApplication;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.mapbox.mapboxsdk.Mapbox;
import com.mapbox.mapboxsdk.WellKnownTileServer;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
import fr.free.nrw.commons.concurrency.ThreadPoolService;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.logging.FileLoggingTree;
import fr.free.nrw.commons.logging.LogUtils;
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.ConfigUtils;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.functions.Functions;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Named;
import org.acra.ACRA;
import org.acra.annotation.AcraCore;
import org.acra.annotation.AcraDialog;
import org.acra.annotation.AcraMailSender;
import org.acra.data.StringFormat;
import org.wikipedia.AppAdapter;
import org.wikipedia.language.AppLanguageLookUpTable;
import timber.log.Timber;
@AcraCore(
buildConfigClass = BuildConfig.class,
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
reportFormat = StringFormat.KEY_VALUE_LIST,
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL,
STACK_TRACE}
)
@AcraMailSender(
mailTo = "commons-app-android-private@googlegroups.com",
reportAsFile = false
)
@AcraDialog(
resTheme = R.style.Theme_AppCompat_Dialog,
resText = R.string.crash_dialog_text,
resTitle = R.string.crash_dialog_title,
resCommentPrompt = R.string.crash_dialog_comment_prompt
)
public class CommonsApplication extends MultiDexApplication {
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
@Inject
SessionManager sessionManager;
@Inject
DBOpenHelper dbOpenHelper;
@Inject
@Named("default_preferences")
JsonKvStore defaultPrefs;
@Inject
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
/**
* Constants begin
*/
public static final int OPEN_APPLICATION_DETAIL_SETTINGS = 1001;
public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using [[COM:MOA|Commons Mobile App]]";
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App Feedback";
public static final String REPORT_EMAIL = "commons-app-android-private@googlegroups.com";
public static final String REPORT_EMAIL_SUBJECT = "Report a violation";
public static final String NOTIFICATION_CHANNEL_ID_ALL = "CommonsNotificationAll";
public static final String FEEDBACK_EMAIL_TEMPLATE_HEADER = "-- Technical information --";
/**
* Constants End
*/
private static CommonsApplication INSTANCE;
public static CommonsApplication getInstance() {
return INSTANCE;
}
private AppLanguageLookUpTable languageLookUpTable;
public AppLanguageLookUpTable getLanguageLookUpTable() {
return languageLookUpTable;
}
@Inject
ContributionDao contributionDao;
/**
* In-memory list of contributions whose uploads have been paused by the user
*/
public static Map<String, Boolean> pauseUploads = new HashMap<>();
/**
* Used to declare and initialize various components and dependencies
*/
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
ACRA.init(this);
Mapbox.getInstance(this, getString(R.string.mapbox_commons_app_token), WellKnownTileServer.Mapbox);
ApplicationlessInjection
.getInstance(this)
.getCommonsApplicationComponent()
.inject(this);
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
initTimber();
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS);
if (null == defaultExifTagsSet) {
defaultExifTagsSet = new HashSet<>();
}
defaultExifTagsSet.add(getString(R.string.exif_tag_location));
defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet);
}
// Set DownsampleEnabled to True to downsample the image in case it's heavy
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setNetworkFetcher(customOkHttpNetworkFetcher)
.setDownsampleEnabled(true)
.build();
try {
Fresco.initialize(this, config);
} catch (Exception e) {
Timber.e(e);
// TODO: Remove when we're able to initialize Fresco in test builds.
}
createNotificationChannel(this);
languageLookUpTable = new AppLanguageLookUpTable(this);
// This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler.
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer());
// Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
}
/**
* Plants debug and file logging tree. Timber lets you plant your own logging trees.
*/
private void initTimber() {
boolean isBeta = ConfigUtils.isBetaFlavour();
String logFileName =
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
String logDirectory = LogUtils.getLogDirectory();
//Delete stale logs if they have exceeded the specified size
deleteStaleLogs(logFileName, logDirectory);
FileLoggingTree tree = new FileLoggingTree(
Log.VERBOSE,
logFileName,
logDirectory,
1000,
getFileLoggingThreadPool());
Timber.plant(tree);
Timber.plant(new Timber.DebugTree());
}
/**
* Deletes the logs zip file at the specified directory and file locations specified in the
* params
*
* @param logFileName
* @param logDirectory
*/
private void deleteStaleLogs(String logFileName, String logDirectory) {
try {
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
file.delete();
}
} catch (Exception e) {
Timber.e(e);
}
}
public static boolean isRoboUnitTest() {
return "robolectric".equals(Build.FINGERPRINT);
}
private ThreadPoolService getFileLoggingThreadPool() {
return new ThreadPoolService.Builder("file-logging-thread")
.setPriority(Process.THREAD_PRIORITY_LOWEST)
.setPoolSize(1)
.setExceptionHandler(new BackgroundPoolExceptionHandler())
.build();
}
public static void createNotificationChannel(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = manager
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
if (channel == null) {
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL,
context.getString(R.string.notifications_channel_name_all),
NotificationManager.IMPORTANCE_DEFAULT);
manager.createNotificationChannel(channel);
}
}
}
public String getUserAgent() {
return "Commons/" + ConfigUtils.getVersionNameWithSha(this)
+ " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
}
/**
* clears data of current application
*
* @param context Application context
* @param logoutListener Implementation of interface LogoutListener
*/
@SuppressLint("CheckResult")
public void clearApplicationData(Context context, LogoutListener logoutListener) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
if (applicationDirectory.exists()) {
String[] fileNames = applicationDirectory.list();
for (String fileName : fileNames) {
if (!fileName.equals("lib")) {
FileUtils.deleteFile(new File(applicationDirectory, fileName));
}
}
}
sessionManager.logout()
.andThen(Completable.fromAction(() -> {
Timber.d("All accounts have been removed");
clearImageCache();
//TODO: fix preference manager
defaultPrefs.clearAll();
defaultPrefs.putBoolean("firstrun", false);
updateAllDatabases();
}
))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(logoutListener::onLogoutComplete, Timber::e);
}
/**
* Clear all images cache held by Fresco
*/
private void clearImageCache() {
ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.clearCaches();
}
/**
* Deletes all tables and re-creates them.
*/
private void updateAllDatabases() {
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
CategoryDao.Table.onDelete(db);
dbOpenHelper.deleteTable(db,
CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
try {
contributionDao.deleteAll();
} catch (SQLiteException e) {
Timber.e(e);
}
BookmarkPicturesDao.Table.onDelete(db);
BookmarkLocationsDao.Table.onDelete(db);
Table.onDelete(db);
}
/**
* Interface used to get log-out events
*/
public interface LogoutListener {
void onLogoutComplete();
}
}

View file

@ -0,0 +1,417 @@
package fr.free.nrw.commons
import android.annotation.SuppressLint
import android.app.Activity
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.os.Build
import android.os.Process
import android.util.Log
import androidx.multidex.MultiDexApplication
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable
import fr.free.nrw.commons.category.CategoryDao
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler
import fr.free.nrw.commons.concurrency.ThreadPoolService
import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.data.DBOpenHelper
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.language.AppLanguageLookUpTable
import fr.free.nrw.commons.logging.FileLoggingTree
import fr.free.nrw.commons.logging.LogUtils
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher
import fr.free.nrw.commons.settings.Prefs
import fr.free.nrw.commons.upload.FileUtils
import fr.free.nrw.commons.utils.ConfigUtils.getVersionNameWithSha
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.internal.functions.Functions
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.acra.ACRA.init
import org.acra.ReportField
import org.acra.annotation.AcraCore
import org.acra.annotation.AcraDialog
import org.acra.annotation.AcraMailSender
import org.acra.data.StringFormat
import timber.log.Timber
import timber.log.Timber.DebugTree
import java.io.File
import javax.inject.Inject
import javax.inject.Named
@AcraCore(
buildConfigClass = BuildConfig::class,
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
reportFormat = StringFormat.KEY_VALUE_LIST,
reportContent = [ReportField.USER_COMMENT, ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, ReportField.STACK_TRACE]
)
@AcraMailSender(mailTo = "commons-app-android-private@googlegroups.com", reportAsFile = false)
@AcraDialog(
resTheme = R.style.Theme_AppCompat_Dialog,
resText = R.string.crash_dialog_text,
resTitle = R.string.crash_dialog_title,
resCommentPrompt = R.string.crash_dialog_comment_prompt
)
class CommonsApplication : MultiDexApplication() {
@Inject
lateinit var sessionManager: SessionManager
@Inject
lateinit var dbOpenHelper: DBOpenHelper
@Inject
@field:Named("default_preferences")
lateinit var defaultPrefs: JsonKvStore
@Inject
lateinit var cookieJar: CommonsCookieJar
@Inject
lateinit var customOkHttpNetworkFetcher: CustomOkHttpNetworkFetcher
var languageLookUpTable: AppLanguageLookUpTable? = null
private set
@Inject
lateinit var contributionDao: ContributionDao
/**
* Used to declare and initialize various components and dependencies
*/
override fun onCreate() {
super.onCreate()
instance = this
init(this)
ApplicationlessInjection
.getInstance(this)
.commonsApplicationComponent
.inject(this)
initTimber()
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
var defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS)
if (null == defaultExifTagsSet) {
defaultExifTagsSet = HashSet()
}
defaultExifTagsSet.add(getString(R.string.exif_tag_location))
defaultPrefs.putStringSet(Prefs.MANAGED_EXIF_TAGS, defaultExifTagsSet)
}
// Set DownsampleEnabled to True to downsample the image in case it's heavy
val config = ImagePipelineConfig.newBuilder(this)
.setNetworkFetcher(customOkHttpNetworkFetcher)
.setDownsampleEnabled(true)
.build()
try {
Fresco.initialize(this, config)
} catch (e: Exception) {
Timber.e(e)
// TODO: Remove when we're able to initialize Fresco in test builds.
}
createNotificationChannel(this)
languageLookUpTable = AppLanguageLookUpTable(this)
// This handler will catch exceptions thrown from Observables after they are disposed,
// or from Observables that are (deliberately or not) missing an onError handler.
RxJavaPlugins.setErrorHandler(Functions.emptyConsumer())
// Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0")
}
/**
* Plants debug and file logging tree. Timber lets you plant your own logging trees.
*/
private fun initTimber() {
val isBeta = isBetaFlavour
val logFileName =
if (isBeta) "CommonsBetaAppLogs" else "CommonsAppLogs"
val logDirectory = LogUtils.getLogDirectory()
//Delete stale logs if they have exceeded the specified size
deleteStaleLogs(logFileName, logDirectory)
val tree = FileLoggingTree(
Log.VERBOSE,
logFileName,
logDirectory,
1000,
fileLoggingThreadPool
)
Timber.plant(tree)
Timber.plant(DebugTree())
}
/**
* Deletes the logs zip file at the specified directory and file locations specified in the
* params
*
* @param logFileName
* @param logDirectory
*/
private fun deleteStaleLogs(logFileName: String, logDirectory: String) {
try {
val file = File("$logDirectory/zip/$logFileName.zip")
if (file.exists() && file.totalSpace > 1000000) { // In Kbs
file.delete()
}
} catch (e: Exception) {
Timber.e(e)
}
}
private val fileLoggingThreadPool: ThreadPoolService
get() = ThreadPoolService.Builder("file-logging-thread")
.setPriority(Process.THREAD_PRIORITY_LOWEST)
.setPoolSize(1)
.setExceptionHandler(BackgroundPoolExceptionHandler())
.build()
val userAgent: String
get() = ("Commons/" + this.getVersionNameWithSha()
+ " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE)
/**
* clears data of current application
*
* @param context Application context
* @param logoutListener Implementation of interface LogoutListener
*/
@SuppressLint("CheckResult")
fun clearApplicationData(context: Context, logoutListener: LogoutListener) {
val cacheDirectory = context.cacheDir
val applicationDirectory = File(cacheDirectory.parent)
if (applicationDirectory.exists()) {
val fileNames = applicationDirectory.list()
for (fileName in fileNames) {
if (fileName != "lib") {
FileUtils.deleteFile(File(applicationDirectory, fileName))
}
}
}
sessionManager.logout()
.andThen(Completable.fromAction { cookieJar.clear() })
.andThen(Completable.fromAction {
Timber.d("All accounts have been removed")
clearImageCache()
//TODO: fix preference manager
defaultPrefs.clearAll()
defaultPrefs.putBoolean("firstrun", false)
updateAllDatabases()
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ logoutListener.onLogoutComplete() }, { t: Throwable? -> Timber.e(t) })
}
/**
* Clear all images cache held by Fresco
*/
private fun clearImageCache() {
val imagePipeline = Fresco.getImagePipeline()
imagePipeline.clearCaches()
}
/**
* Deletes all tables and re-creates them.
*/
private fun updateAllDatabases() {
dbOpenHelper.readableDatabase.close()
val db = dbOpenHelper.writableDatabase
CategoryDao.Table.onDelete(db)
dbOpenHelper.deleteTable(
db,
DBOpenHelper.CONTRIBUTIONS_TABLE
) //Delete the contributions table in the existing db on older versions
dbOpenHelper.deleteTable(
db,
DBOpenHelper.BOOKMARKS_LOCATIONS
)
try {
contributionDao.deleteAll()
} catch (e: SQLiteException) {
Timber.e(e)
}
BookmarksTable.onDelete(db)
BookmarkItemsTable.onDelete(db)
}
/**
* Interface used to get log-out events
*/
interface LogoutListener {
fun onLogoutComplete()
}
/**
* This listener is responsible for handling post-logout actions, specifically invoking the LoginActivity
* with relevant intent parameters. It does not perform the actual logout operation.
*/
open class BaseLogoutListener : LogoutListener {
var ctx: Context
var loginMessage: String? = null
var userName: String? = null
/**
* Constructor for BaseLogoutListener.
*
* @param ctx Application context
*/
constructor(ctx: Context) {
this.ctx = ctx
}
/**
* Constructor for BaseLogoutListener
*
* @param ctx The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process.
* @param loginMessage Message to be displayed on the login page
* @param loginUsername Username to be pre-filled on the login page
*/
constructor(
ctx: Context, loginMessage: String?,
loginUsername: String?
) {
this.ctx = ctx
this.loginMessage = loginMessage
this.userName = loginUsername
}
override fun onLogoutComplete() {
Timber.d("Logout complete callback received.")
val loginIntent = Intent(ctx, LoginActivity::class.java)
loginIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
if (loginMessage != null) {
loginIntent.putExtra(LOGIN_MESSAGE_INTENT_KEY, loginMessage)
}
if (userName != null) {
loginIntent.putExtra(LOGIN_USERNAME_INTENT_KEY, userName)
}
ctx.startActivity(loginIntent)
}
}
/**
* This class is an extension of BaseLogoutListener, providing additional functionality or customization
* for the logout process. It includes specific actions to be taken during logout, such as handling redirection to the login screen.
*/
class ActivityLogoutListener : BaseLogoutListener {
var activity: Activity
/**
* Constructor for ActivityLogoutListener.
*
* @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity.
* @param ctx The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process.
*/
constructor(activity: Activity, ctx: Context) : super(ctx) {
this.activity = activity
}
/**
* Constructor for ActivityLogoutListener with additional parameters for the login screen.
*
* @param activity The activity context from which the logout is initiated. Used to perform actions such as finishing the activity.
* @param ctx The application context, used for invoking the LoginActivity and passing relevant intent parameters as part of the post-logout process.
* @param loginMessage Message to be displayed on the login page after logout.
* @param loginUsername Username to be pre-filled on the login page after logout.
*/
constructor(
activity: Activity, ctx: Context?,
loginMessage: String?, loginUsername: String?
) : super(activity, loginMessage, loginUsername) {
this.activity = activity
}
override fun onLogoutComplete() {
super.onLogoutComplete()
activity.finish()
}
}
companion object {
const val LOGIN_MESSAGE_INTENT_KEY: String = "loginMessage"
const val LOGIN_USERNAME_INTENT_KEY: String = "loginUsername"
const val IS_LIMITED_CONNECTION_MODE_ENABLED: String = "is_limited_connection_mode_enabled"
/**
* Constants begin
*/
const val OPEN_APPLICATION_DETAIL_SETTINGS: Int = 1001
const val DEFAULT_EDIT_SUMMARY: String = "Uploaded using [[COM:MOA|Commons Mobile App]]"
const val FEEDBACK_EMAIL: String = "commons-app-android@googlegroups.com"
const val FEEDBACK_EMAIL_SUBJECT: String = "Commons Android App Feedback"
const val REPORT_EMAIL: String = "commons-app-android-private@googlegroups.com"
const val REPORT_EMAIL_SUBJECT: String = "Report a violation"
const val NOTIFICATION_CHANNEL_ID_ALL: String = "CommonsNotificationAll"
const val FEEDBACK_EMAIL_TEMPLATE_HEADER: String = "-- Technical information --"
/**
* Constants End
*/
@JvmStatic
lateinit var instance: CommonsApplication
private set
@JvmField
var isPaused: Boolean = false
@JvmStatic
fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = context
.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
var channel = manager
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL)
if (channel == null) {
channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID_ALL,
context.getString(R.string.notifications_channel_name_all),
NotificationManager.IMPORTANCE_DEFAULT
)
manager.createNotificationChannel(channel)
}
}
}
}
}

View file

@ -1,79 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.Nullable;
/**
* represents Licence object
*/
public class License {
private String key;
private String template;
private String url;
private String name;
/**
* Constructs a new instance of License.
*
* @param key license key
* @param template license template
* @param url license URL
* @param name licence name
*
* @throws RuntimeException if License.key or Licence.template is null
*/
public License(String key, String template, String url, String name) {
if (key == null) {
throw new RuntimeException("License.key must not be null");
}
if (template == null) {
throw new RuntimeException("License.template must not be null");
}
this.key = key;
this.template = template;
this.url = url;
this.name = name;
}
/**
* Gets the license key.
* @return license key as a String.
*/
public String getKey() {
return key;
}
/**
* Gets the license template.
* @return license template as a String.
*/
public String getTemplate() {
return template;
}
/**
* Gets the license name. If name is null, return license key.
* @return license name as string. if name null, license key as String
*/
public String getName() {
if (name == null) {
// hack
return getKey();
} else {
return name;
}
}
/**
* Gets the license URL
*
* @param language license language
* @return URL
*/
public @Nullable String getUrl(String language) {
if (url == null) {
return null;
} else {
return url.replace("$lang", language);
}
}
}

View file

@ -1,65 +0,0 @@
package fr.free.nrw.commons.LocationPicker;
import android.app.Activity;
import android.content.Intent;
import com.mapbox.mapboxsdk.camera.CameraPosition;
/**
* Helper class for starting the activity
*/
public final class LocationPicker {
/**
* Getting camera position from the intent using constants
*
* @param data intent
* @return CameraPosition
*/
public static CameraPosition getCameraPosition(final Intent data) {
return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
}
public static class IntentBuilder {
private final Intent intent;
/**
* Creates a new builder that creates an intent to launch the place picker activity.
*/
public IntentBuilder() {
intent = new Intent();
}
/**
* Gets and puts location in intent
* @param position CameraPosition
* @return LocationPicker.IntentBuilder
*/
public LocationPicker.IntentBuilder defaultLocation(
final CameraPosition position) {
intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position);
return this;
}
/**
* Gets and puts activity name in intent
* @param activity activity key
* @return LocationPicker.IntentBuilder
*/
public LocationPicker.IntentBuilder activityKey(
final String activity) {
intent.putExtra(LocationPickerConstants.ACTIVITY_KEY, activity);
return this;
}
/**
* Gets and sets the activity
* @param activity Activity
* @return Intent
*/
public Intent build(final Activity activity) {
intent.setClass(activity, LocationPickerActivity.class);
return intent;
}
}
}

View file

@ -1,554 +0,0 @@
package fr.free.nrw.commons.LocationPicker;
import static com.mapbox.mapboxsdk.style.layers.Property.NONE;
import static com.mapbox.mapboxsdk.style.layers.Property.VISIBLE;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconIgnorePlacement;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;
import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.visibility;
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION;
import static fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.View;
import android.view.Window;
import android.view.animation.OvershootInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.mapbox.geojson.Point;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import com.mapbox.mapboxsdk.camera.CameraPosition.Builder;
import com.mapbox.mapboxsdk.camera.CameraUpdateFactory;
import com.mapbox.mapboxsdk.geometry.LatLng;
import com.mapbox.mapboxsdk.location.LocationComponent;
import com.mapbox.mapboxsdk.location.LocationComponentActivationOptions;
import com.mapbox.mapboxsdk.location.engine.LocationEngineCallback;
import com.mapbox.mapboxsdk.location.engine.LocationEngineResult;
import com.mapbox.mapboxsdk.location.modes.CameraMode;
import com.mapbox.mapboxsdk.location.modes.RenderMode;
import com.mapbox.mapboxsdk.location.permissions.PermissionsManager;
import com.mapbox.mapboxsdk.maps.MapView;
import com.mapbox.mapboxsdk.maps.MapboxMap;
import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraIdleListener;
import com.mapbox.mapboxsdk.maps.MapboxMap.OnCameraMoveStartedListener;
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
import com.mapbox.mapboxsdk.maps.Style;
import com.mapbox.mapboxsdk.maps.UiSettings;
import com.mapbox.mapboxsdk.style.layers.Layer;
import com.mapbox.mapboxsdk.style.layers.SymbolLayer;
import com.mapbox.mapboxsdk.style.sources.GeoJsonSource;
import fr.free.nrw.commons.MapStyle;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.filepicker.Constants;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LocationPermissionsHelper;
import fr.free.nrw.commons.location.LocationPermissionsHelper.Dialog;
import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import javax.inject.Inject;
import javax.inject.Named;
import org.jetbrains.annotations.NotNull;
import timber.log.Timber;
/**
* Helps to pick location and return the result with an intent
*/
public class LocationPickerActivity extends BaseActivity implements OnMapReadyCallback,
OnCameraMoveStartedListener, OnCameraIdleListener, Observer<CameraPosition>, LocationPermissionCallback {
/**
* DROPPED_MARKER_LAYER_ID : id for layer
*/
private static final String DROPPED_MARKER_LAYER_ID = "DROPPED_MARKER_LAYER_ID";
/**
* cameraPosition : position of picker
*/
private CameraPosition cameraPosition;
/**
* markerImage : picker image
*/
private ImageView markerImage;
/**
* mapboxMap : map
*/
private MapboxMap mapboxMap;
/**
* mapView : view of the map
*/
private MapView mapView;
/**
* tvAttribution : credit
*/
private AppCompatTextView tvAttribution;
/**
* activity : activity key
*/
private String activity;
/**
* location : location
*/
private Location location;
/**
* modifyLocationButton : button for start editing location
*/
Button modifyLocationButton;
/**
* showInMapButton : button for showing in map
*/
TextView showInMapButton;
/**
* placeSelectedButton : fab for selecting location
*/
FloatingActionButton placeSelectedButton;
/**
* fabCenterOnLocation: button for center on location;
*/
FloatingActionButton fabCenterOnLocation;
/**
* droppedMarkerLayer : Layer for static screen
*/
private Layer droppedMarkerLayer;
/**
* shadow : imageview of shadow
*/
private ImageView shadow;
/**
* largeToolbarText : textView of shadow
*/
private TextView largeToolbarText;
/**
* smallToolbarText : textView of shadow
*/
private TextView smallToolbarText;
/**
* applicationKvStore : for storing values
*/
@Inject
@Named("default_preferences")
public
JsonKvStore applicationKvStore;
/**
* isDarkTheme: for keeping a track of the device theme and modifying the map theme accordingly
*/
@Inject
SystemThemeUtils systemThemeUtils;
private boolean isDarkTheme;
@Inject
LocationServiceManager locationManager;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
super.onCreate(savedInstanceState);
isDarkTheme = systemThemeUtils.isDeviceInNightMode();
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
setContentView(R.layout.activity_location_picker);
if (savedInstanceState == null) {
cameraPosition = getIntent()
.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
activity = getIntent().getStringExtra(LocationPickerConstants.ACTIVITY_KEY);
}
final LocationPickerViewModel viewModel = new ViewModelProvider(this)
.get(LocationPickerViewModel.class);
viewModel.getResult().observe(this, this);
bindViews();
addBackButtonListener();
addPlaceSelectedButton();
addCredits();
getToolbarUI();
addCenterOnGPSButton();
if ("UploadActivity".equals(activity)) {
placeSelectedButton.setVisibility(View.GONE);
modifyLocationButton.setVisibility(View.VISIBLE);
showInMapButton.setVisibility(View.VISIBLE);
largeToolbarText.setText(getResources().getString(R.string.image_location));
smallToolbarText.setText(getResources().
getString(R.string.check_whether_location_is_correct));
fabCenterOnLocation.setVisibility(View.GONE);
}
mapView.onCreate(savedInstanceState);
mapView.getMapAsync(this);
}
/**
* For showing credits
*/
private void addCredits() {
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
}
/**
* Clicking back button destroy locationPickerActivity
*/
private void addBackButtonListener() {
final ImageView backButton = findViewById(R.id.maplibre_place_picker_toolbar_back_button);
backButton.setOnClickListener(view -> finish());
}
/**
* Binds mapView and location picker icon
*/
private void bindViews() {
mapView = findViewById(R.id.map_view);
markerImage = findViewById(R.id.location_picker_image_view_marker);
tvAttribution = findViewById(R.id.tv_attribution);
modifyLocationButton = findViewById(R.id.modify_location);
showInMapButton = findViewById(R.id.show_in_map);
showInMapButton.setText(getResources().getString(R.string.show_in_map_app).toUpperCase());
shadow = findViewById(R.id.location_picker_image_view_shadow);
}
/**
* Binds the listeners
*/
private void bindListeners() {
mapboxMap.addOnCameraMoveStartedListener(
this);
mapboxMap.addOnCameraIdleListener(
this);
}
/**
* Gets toolbar color
*/
private void getToolbarUI() {
final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
largeToolbarText = findViewById(R.id.location_picker_toolbar_primary_text_view);
smallToolbarText = findViewById(R.id.location_picker_toolbar_secondary_text_view);
toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
}
/**
* Takes action when map is ready to show
* @param mapboxMap map
*/
@Override
public void onMapReady(final MapboxMap mapboxMap) {
this.mapboxMap = mapboxMap;
mapboxMap.setStyle(isDarkTheme ? MapStyle.DARK : MapStyle.STREETS, this::onStyleLoaded);
}
/**
* Initializes dropped marker and layer
* Handles camera position based on options
* Enables location components
*
* @param style style
*/
private void onStyleLoaded(final Style style) {
if (modifyLocationButton.getVisibility() == View.VISIBLE) {
initDroppedMarker(style);
adjustCameraBasedOnOptions();
enableLocationComponent(style);
if (style.getLayer(DROPPED_MARKER_LAYER_ID) != null) {
final GeoJsonSource source = style.getSourceAs("dropped-marker-source-id");
if (source != null) {
source.setGeoJson(Point.fromLngLat(cameraPosition.target.getLongitude(),
cameraPosition.target.getLatitude()));
}
droppedMarkerLayer = style.getLayer(DROPPED_MARKER_LAYER_ID);
if (droppedMarkerLayer != null) {
droppedMarkerLayer.setProperties(visibility(VISIBLE));
markerImage.setVisibility(View.GONE);
shadow.setVisibility(View.GONE);
}
}
} else {
adjustCameraBasedOnOptions();
enableLocationComponent(style);
bindListeners();
}
modifyLocationButton.setOnClickListener(v -> onClickModifyLocation());
showInMapButton.setOnClickListener(v -> showInMap());
}
/**
* Handles onclick event of modifyLocationButton
*/
private void onClickModifyLocation() {
placeSelectedButton.setVisibility(View.VISIBLE);
modifyLocationButton.setVisibility(View.GONE);
showInMapButton.setVisibility(View.GONE);
droppedMarkerLayer.setProperties(visibility(NONE));
markerImage.setVisibility(View.VISIBLE);
shadow.setVisibility(View.VISIBLE);
largeToolbarText.setText(getResources().getString(R.string.choose_a_location));
smallToolbarText.setText(getResources().getString(R.string.pan_and_zoom_to_adjust));
bindListeners();
fabCenterOnLocation.setVisibility(View.VISIBLE);
}
/**
* Show the location in map app
*/
public void showInMap(){
Utils.handleGeoCoordinates(this,
new fr.free.nrw.commons.location.LatLng(cameraPosition.target.getLatitude(),
cameraPosition.target.getLongitude(), 0.0f));
}
/**
* Initialize Dropped Marker and layer without showing
* @param loadedMapStyle style
*/
private void initDroppedMarker(@NonNull final Style loadedMapStyle) {
// Add the marker image to map
loadedMapStyle.addImage("dropped-icon-image", BitmapFactory.decodeResource(
getResources(), R.drawable.map_default_map_marker));
loadedMapStyle.addSource(new GeoJsonSource("dropped-marker-source-id"));
loadedMapStyle.addLayer(new SymbolLayer(DROPPED_MARKER_LAYER_ID,
"dropped-marker-source-id").withProperties(
iconImage("dropped-icon-image"),
visibility(NONE),
iconAllowOverlap(true),
iconIgnorePlacement(true)
));
}
/**
* move the location to the current media coordinates
*/
private void adjustCameraBasedOnOptions() {
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
}
/**
* Enables location components
* @param loadedMapStyle Style
*/
@SuppressWarnings( {"MissingPermission"})
private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
final UiSettings uiSettings = mapboxMap.getUiSettings();
uiSettings.setAttributionEnabled(false);
// Check if permissions are enabled and if not request
if (PermissionsManager.areLocationPermissionsGranted(this)) {
// Get an instance of the component
final LocationComponent locationComponent = mapboxMap.getLocationComponent();
// Activate with options
locationComponent.activateLocationComponent(
LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
// Enable to make component visible
locationComponent.setLocationComponentEnabled(true);
// Set the component's camera mode
locationComponent.setCameraMode(CameraMode.NONE);
// Set the component's render mode
locationComponent.setRenderMode(RenderMode.NORMAL);
// Get the component's location engine to receive user's last location
locationComponent.getLocationEngine().getLastLocation(
new LocationEngineCallback<LocationEngineResult>() {
@Override
public void onSuccess(LocationEngineResult result) {
location = result.getLastLocation();
}
@Override
public void onFailure(@NonNull Exception exception) {
}
});
}
}
/**
* Acts on camera moving
* @param reason int
*/
@Override
public void onCameraMoveStarted(final int reason) {
Timber.v("Map camera has begun moving.");
if (markerImage.getTranslationY() == 0) {
markerImage.animate().translationY(-75)
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
}
}
/**
* Acts on camera idle
*/
@Override
public void onCameraIdle() {
Timber.v("Map camera is now idling.");
markerImage.animate().translationY(0)
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
}
/**
* Takes action on camera position
* @param position position of picker
*/
@Override
public void onChanged(@Nullable CameraPosition position) {
if (position == null) {
position = new Builder()
.target(new LatLng(mapboxMap.getCameraPosition().target.getLatitude(),
mapboxMap.getCameraPosition().target.getLongitude()))
.zoom(16).build();
}
cameraPosition = position;
}
/**
* Select the preferable location
*/
private void addPlaceSelectedButton() {
placeSelectedButton = findViewById(R.id.location_chosen_button);
placeSelectedButton.setOnClickListener(view -> placeSelected());
}
/**
* Return the intent with required data
*/
void placeSelected() {
if (activity.equals("NoLocationUploadActivity")) {
applicationKvStore.putString(LAST_LOCATION,
mapboxMap.getCameraPosition().target.getLatitude()
+ ","
+ mapboxMap.getCameraPosition().target.getLongitude());
applicationKvStore.putString(LAST_ZOOM, mapboxMap.getCameraPosition().zoom + "");
}
final Intent returningIntent = new Intent();
returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION,
mapboxMap.getCameraPosition());
setResult(AppCompatActivity.RESULT_OK, returningIntent);
finish();
}
/**
* Center the camera on the last saved location
*/
private void addCenterOnGPSButton(){
fabCenterOnLocation = findViewById(R.id.center_on_gps);
fabCenterOnLocation.setOnClickListener(view -> getCenter());
}
/**
* Center the map at user's current location
*/
private void getCenter() {
LocationPermissionsHelper.Dialog locationAccessDialog = new Dialog(
R.string.location_permission_title,
R.string.upload_map_location_access
);
LocationPermissionsHelper.Dialog locationOffDialog = new Dialog(
R.string.ask_to_turn_location_on,
R.string.upload_map_location_access
);
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
this, locationManager, this);
locationPermissionsHelper.handleLocationPermissions(locationAccessDialog, locationOffDialog);
}
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions,
@NonNull final int[] grantResults) {
if (requestCode == Constants.RequestCodes.LOCATION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
onLocationPermissionGranted();
} else {
onLocationPermissionDenied("");
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onStart() {
super.onStart();
mapView.onStart();
}
@Override
protected void onResume() {
super.onResume();
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
mapView.onPause();
}
@Override
protected void onStop() {
super.onStop();
mapView.onStop();
}
@Override
protected void onSaveInstanceState(final @NotNull Bundle outState) {
super.onSaveInstanceState(outState);
mapView.onSaveInstanceState(outState);
}
@Override
protected void onDestroy() {
super.onDestroy();
mapView.onDestroy();
}
@Override
public void onLowMemory() {
super.onLowMemory();
mapView.onLowMemory();
}
@Override
public void onLocationPermissionDenied(String toastMessage) {
//do nothing
}
@Override
public void onLocationPermissionGranted() {
fr.free.nrw.commons.location.LatLng currLocation = locationManager.getLastLocation();
if (currLocation != null) {
final CameraPosition position;
position = new CameraPosition.Builder()
.target(new com.mapbox.mapboxsdk.geometry.LatLng(currLocation.getLatitude(),
currLocation.getLongitude(), 0)) // Sets the new camera position
.zoom(mapboxMap.getCameraPosition().zoom) // Same zoom level
.build();
mapboxMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), 1000);
}
}
}

View file

@ -1,19 +0,0 @@
package fr.free.nrw.commons.LocationPicker;
import com.mapbox.mapboxsdk.maps.Style;
/**
* Constants need for location picking
*/
public final class LocationPickerConstants {
public static final String ACTIVITY_KEY
= "location.picker.activity";
public static final String MAP_CAMERA_POSITION
= "location.picker.cameraPosition";
private LocationPickerConstants() {
}
}

View file

@ -1,63 +0,0 @@
package fr.free.nrw.commons.LocationPicker;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.MutableLiveData;
import com.mapbox.mapboxsdk.camera.CameraPosition;
import org.jetbrains.annotations.NotNull;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;
/**
* Observes live camera position data
*/
public class LocationPickerViewModel extends AndroidViewModel implements Callback<CameraPosition> {
/**
* Wrapping CameraPosition with MutableLiveData
*/
private final MutableLiveData<CameraPosition> result = new MutableLiveData<>();
/**
* Constructor for this class
*
* @param application Application
*/
public LocationPickerViewModel(@NonNull final Application application) {
super(application);
}
/**
* Responses on camera position changing
*
* @param call Call<CameraPosition>
* @param response Response<CameraPosition>
*/
@Override
public void onResponse(final @NotNull Call<CameraPosition> call,
final Response<CameraPosition> response) {
if (response.body() == null) {
result.setValue(null);
return;
}
result.setValue(response.body());
}
@Override
public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) {
Timber.e(t);
}
/**
* Gets live CameraPosition
*
* @return MutableLiveData<CameraPosition>
*/
public MutableLiveData<CameraPosition> getResult() {
return result;
}
}

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import java.util.List;
public abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class NearbyPlacesInfo {
public List<Place> placeList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng curLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class ExplorePlacesInfo {
public List<Place> explorePlaceList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng curLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
}

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class NearbyPlacesInfo {
@JvmField
var placeList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media>? = null // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class ExplorePlacesInfo {
@JvmField
var explorePlaceList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media> = emptyList() // Search location for finding this places
}
}

View file

@ -1,12 +0,0 @@
package fr.free.nrw.commons;
import com.mapbox.mapboxsdk.maps.Style;
/**
* Constants for various map styles
*/
public final class MapStyle {
public static final String DARK = Style.getPredefinedStyle("Dark");
public static final String OUTDOORS = Style.getPredefinedStyle("Outdoors");
public static final String STREETS = Style.getPredefinedStyle("Streets");
}

View file

@ -1,11 +1,15 @@
package fr.free.nrw.commons
import android.os.Parcelable
import fr.free.nrw.commons.BuildConfig.COMMONS_URL
import fr.free.nrw.commons.location.LatLng
import kotlinx.android.parcel.Parcelize
import org.wikipedia.dataclient.mwapi.MwQueryPage
import org.wikipedia.page.PageTitle
import java.util.*
import fr.free.nrw.commons.wikidata.model.WikiSite
import fr.free.nrw.commons.wikidata.model.page.PageTitle
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import java.util.Date
import java.util.Locale
import java.util.UUID
@Parcelize
class Media constructor(
@ -15,7 +19,6 @@ class Media constructor(
*/
var pageId: String = UUID.randomUUID().toString(),
var thumbUrl: String? = null,
/**
* Gets image URL
* @return Image URL
@ -27,16 +30,9 @@ class Media constructor(
*/
var filename: String? = null,
/**
* Gets the file description.
* @return file description as a string
*/
// monolingual description on input...
/**
* Sets the file description.
* @param fallbackDescription the new description of the file
* The fallback description of the file, used if no other description is provided.
*/
var fallbackDescription: String? = null,
/**
* Gets the upload date of the file.
* Can be null.
@ -44,28 +40,25 @@ class Media constructor(
*/
var dateUploaded: Date? = null,
/**
* Gets the license name of the file.
* @return license as a String
*/
/**
* Sets the license name of the file.
*
* @param license license name as a String
* The license name of the file.
*/
var license: String? = null,
/**
* The URL corresponding to the license.
*/
var licenseUrl: String? = null,
/**
* Gets the name of the creator of the file.
* @return author name as a String
*/
/**
* Sets the author name of the file.
* @param author creator name as a string
* The name of the creator of the file.
*/
var author: String? = null,
/**
* The username of the uploader.
*/
var user: String? = null,
/**
* The full name of the file's creator, if different from username.
*/
var creatorName: String? = null,
/**
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
@ -79,20 +72,21 @@ class Media constructor(
var captions: Map<String, String> = emptyMap(),
var descriptions: Map<String, String> = emptyMap(),
var depictionIds: List<String> = emptyList(),
var creatorIds: List<String> = emptyList(),
/**
* This field was added to find non-hidden categories
* Stores the mapping of category title to hidden attribute
* Example: "Mountains" => false, "CC-BY-SA-2.0" => true
*/
var categoriesHiddenStatus: Map<String, Boolean> = emptyMap()
var categoriesHiddenStatus: Map<String, Boolean> = emptyMap(),
) : Parcelable {
constructor(
captions: Map<String, String>,
categories: List<String>?,
filename: String?,
fallbackDescription: String?,
author: String?, user:String?
author: String?,
user: String?,
) : this(
filename = filename,
fallbackDescription = fallbackDescription,
@ -100,25 +94,89 @@ class Media constructor(
author = author,
user = user,
categories = categories,
captions = captions
captions = captions,
)
constructor(
captions: Map<String, String>,
categories: List<String>?,
filename: String?,
fallbackDescription: String?,
author: String?,
user: String?,
dateUploaded: Date? = Date(),
license: String? = null,
licenseUrl: String? = null,
imageUrl: String? = null,
thumbUrl: String? = null,
coordinates: LatLng? = null,
descriptions: Map<String, String> = emptyMap(),
depictionIds: List<String> = emptyList(),
categoriesHiddenStatus: Map<String, Boolean> = emptyMap()
) : this(
pageId = UUID.randomUUID().toString(),
filename = filename,
fallbackDescription = fallbackDescription,
dateUploaded = dateUploaded,
author = author,
user = user,
categories = categories,
captions = captions,
license = license,
licenseUrl = licenseUrl,
imageUrl = imageUrl,
thumbUrl = thumbUrl,
coordinates = coordinates,
descriptions = descriptions,
depictionIds = depictionIds,
categoriesHiddenStatus = categoriesHiddenStatus
)
/**
* Returns Author if it's not null or empty, otherwise
* returns user
* @return Author or User
*/
@Deprecated("Use user for uploader username. Use attributedAuthor() for attribution. Note that the uploader may not be the creator/author.")
fun getAuthorOrUser(): String? {
return if (!author.isNullOrEmpty()) {
author
} else{
user
}
}
/**
* Returns author if it's not null or empty, otherwise
* returns creator name
* @return name of author or creator
*/
fun getAttributedAuthor(): String? {
return if (!author.isNullOrEmpty()) {
author
} else{
creatorName
}
}
/**
* Gets media display title
* @return Media title
*/
val displayTitle: String
get() =
if (filename != null)
if (filename != null) {
pageTitle.displayTextWithoutNamespace.replaceFirst("[.][^.]+$".toRegex(), "")
else
} else {
""
}
/**
* Gets file page title
* @return New media page title
*/
val pageTitle: PageTitle get() = Utils.getPageTitle(filename!!)
val pageTitle: PageTitle
get() = PageTitle(filename!!, WikiSite(COMMONS_URL))
/**
* Returns wikicode to use the media file on a MediaWiki site
@ -128,7 +186,8 @@ class Media constructor(
get() = String.format("[[%s|thumb|%s]]", filename, mostRelevantCaption)
val mostRelevantCaption: String
get() = captions[Locale.getDefault().language]
get() =
captions[Locale.getDefault().language]
?: captions.values.firstOrNull()
?: displayTitle
@ -136,9 +195,12 @@ class Media constructor(
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
@IgnoredOnParcel
var addedCategories: List<String>? = null
// TODO added categories should be removed. It is added for a short fix. On category update,
// categories should be re-fetched instead
get() = field // getter
set(value) { field = value } // setter
set(value) {
field = value
} // setter
}

View file

@ -1,9 +1,9 @@
package fr.free.nrw.commons
import androidx.core.text.HtmlCompat
import fr.free.nrw.commons.media.PAGE_ID_PREFIX
import fr.free.nrw.commons.media.IdAndCaptions
import fr.free.nrw.commons.media.IdAndLabels
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.media.PAGE_ID_PREFIX
import io.reactivex.Single
import timber.log.Timber
import javax.inject.Inject
@ -17,42 +17,56 @@ import javax.inject.Singleton
* to the media and may change due to editing.
*/
@Singleton
class MediaDataExtractor @Inject constructor(private val mediaClient: MediaClient) {
class MediaDataExtractor
@Inject
constructor(
private val mediaClient: MediaClient,
) {
fun fetchDepictionIdsAndLabels(media: Media) =
mediaClient.getEntities(media.depictionIds)
mediaClient
.getEntities(media.depictionIds)
.map {
it.entities()
it
.entities()
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
}
.map { it.map { (key, value) -> IdAndCaptions(key, value) } }
}.map { it.map { (key, value) -> IdAndLabels(key, value) } }
.onErrorReturn { emptyList() }
fun checkDeletionRequestExists(media: Media) =
mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
fun fetchCreatorIdsAndLabels(media: Media) =
mediaClient
.getEntities(media.creatorIds)
.map {
it
.entities()
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
}.map { it.map { (key, value) -> IdAndLabels(key, value) } }
.onErrorReturn { emptyList() }
fun checkDeletionRequestExists(media: Media) = mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
fun fetchDiscussion(media: Media) =
mediaClient.getPageHtml(media.filename!!.replace("File", "File talk"))
mediaClient
.getPageHtml(media.filename!!.replace("File", "File talk"))
.map { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() }
.onErrorReturn {
Timber.d("Error occurred while fetching discussion")
""
}
fun refresh(media: Media): Single<Media> {
return Single.ambArray(
mediaClient.getMediaById(PAGE_ID_PREFIX + media.pageId)
fun refresh(media: Media): Single<Media> =
Single.ambArray(
mediaClient
.getMediaById(PAGE_ID_PREFIX + media.pageId)
.onErrorResumeNext { Single.never() },
mediaClient
.getMediaSuppressingErrors(media.filename)
.onErrorResumeNext { Single.never() },
mediaClient.getMedia(media.filename)
.onErrorResumeNext { Single.never() }
)
}
fun getHtmlOfPage(title: String) = mediaClient.getPageHtml(title);
fun getHtmlOfPage(title: String) = mediaClient.getPageHtml(title)
/**
* Fetches wikitext from mediaClient
*/
fun getCurrentWikiText(title: String) = mediaClient.getCurrentWikiText(title);
fun getCurrentWikiText(title: String) = mediaClient.getCurrentWikiText(title)
}

View file

@ -1,8 +0,0 @@
package fr.free.nrw.commons;
/**
* Base interface for all the views
*/
public interface MvpView {
void showMessage(String message);
}

View file

@ -1,114 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
import org.wikipedia.dataclient.okhttp.HttpStatusException;
import timber.log.Timber;
public final class OkHttpConnectionFactory {
private static final String CACHE_DIR_NAME = "okhttp-cache";
private static final long NET_CACHE_SIZE = 64 * 1024 * 1024;
@NonNull private static final Cache NET_CACHE = new Cache(new File(CommonsApplication.getInstance().getCacheDir(),
CACHE_DIR_NAME), NET_CACHE_SIZE);
@NonNull
private static final OkHttpClient CLIENT = createClient();
@NonNull public static OkHttpClient getClient() {
return CLIENT;
}
@NonNull
private static OkHttpClient createClient() {
return new OkHttpClient.Builder()
.cookieJar(SharedPreferenceCookieManager.getInstance())
.cache(NET_CACHE)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(getLoggingInterceptor())
.addInterceptor(new UnsuccessfulResponseInterceptor())
.addInterceptor(new CommonHeaderRequestInterceptor())
.build();
}
private static HttpLoggingInterceptor getLoggingInterceptor() {
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
.setLevel(Level.BASIC);
httpLoggingInterceptor.redactHeader("Authorization");
httpLoggingInterceptor.redactHeader("Cookie");
return httpLoggingInterceptor;
}
private static class CommonHeaderRequestInterceptor implements Interceptor {
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
.build();
return chain.proceed(request);
}
}
public static class UnsuccessfulResponseInterceptor implements Interceptor {
private static final List<String> DO_NOT_INTERCEPT = Collections.singletonList(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1");
private static final String ERRORS_PREFIX = "{\"error";
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Response rsp = chain.proceed(chain.request());
// Do not intercept certain requests and let the caller handle the errors
if(isExcludedUrl(chain.request())) {
return rsp;
}
if (rsp.isSuccessful()) {
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
if (ERRORS_PREFIX.equals(responseBody.string())) {
try (final ResponseBody body = rsp.body()) {
throw new IOException(body.string());
}
}
} catch (final IOException e) {
Timber.e(e);
}
return rsp;
}
throw new HttpStatusException(rsp);
}
private boolean isExcludedUrl(final Request request) {
final String requestUrl = request.url().toString();
for(final String url: DO_NOT_INTERCEPT) {
if(requestUrl.contains(url)) {
return true;
}
}
return false;
}
}
private OkHttpConnectionFactory() {
}
}

View file

@ -0,0 +1,135 @@
package fr.free.nrw.commons
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.wikidata.GsonUtil
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import fr.free.nrw.commons.wikidata.mwapi.MwErrorResponse
import fr.free.nrw.commons.wikidata.mwapi.MwIOException
import fr.free.nrw.commons.wikidata.mwapi.MwLegacyServiceError
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
object OkHttpConnectionFactory {
private const val CACHE_DIR_NAME = "okhttp-cache"
private const val NET_CACHE_SIZE = (64 * 1024 * 1024).toLong()
@VisibleForTesting
var CLIENT: OkHttpClient? = null
fun getClient(cookieJar: CommonsCookieJar): OkHttpClient {
if (CLIENT == null) {
CLIENT = createClient(cookieJar)
}
return CLIENT!!
}
private fun createClient(cookieJar: CommonsCookieJar): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache(
if (CommonsApplication.instance != null) Cache(
File(CommonsApplication.instance.cacheDir, CACHE_DIR_NAME),
NET_CACHE_SIZE
) else null
)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BASIC)
redactHeader("Authorization")
redactHeader("Cookie")
})
.addInterceptor(UnsuccessfulResponseInterceptor())
.addInterceptor(CommonHeaderRequestInterceptor())
.build()
}
}
class CommonHeaderRequestInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.instance.userAgent)
.build()
return chain.proceed(request)
}
}
private const val SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log"
const val SUPPRESS_ERROR_LOG_HEADER: String = "$SUPPRESS_ERROR_LOG: true"
private class UnsuccessfulResponseInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val rq = chain.request()
// If the request contains our special "suppress errors" header, make note of it
// but don't pass that on to the server.
val suppressErrors = rq.headers.names().contains(SUPPRESS_ERROR_LOG)
val request = rq.newBuilder()
.removeHeader(SUPPRESS_ERROR_LOG)
.build()
val rsp = chain.proceed(request)
// Do not intercept certain requests and let the caller handle the errors
if (isExcludedUrl(chain.request())) {
return rsp
}
if (rsp.isSuccessful) {
try {
rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody ->
if (ERRORS_PREFIX == responseBody.string()) {
rsp.body.use { body ->
val bodyString = body!!.string()
throw MwIOException(
"MediaWiki API returned error: $bodyString",
GsonUtil.defaultGson.fromJson(
bodyString,
MwErrorResponse::class.java
).error!!,
)
}
}
}
} catch (e: MwIOException) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error")
} else {
Timber.e(e)
throw e
}
}
return rsp
}
throw IOException("Unsuccessful response")
}
private fun isExcludedUrl(request: Request): Boolean {
val requestUrl = request.url.toString()
for (url in DO_NOT_INTERCEPT) {
if (requestUrl.contains(url)) {
return true
}
}
return false
}
companion object {
val DO_NOT_INTERCEPT = listOf(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1"
)
const val ERRORS_PREFIX = "{\"error"
}
}

View file

@ -10,7 +10,9 @@ internal object Urls {
const val FAQ_URL = "https://github.com/commons-app/commons-app-documentation/blob/master/android/Frequently-Asked-Questions.md"
const val PLAY_STORE_PREFIX = "market://details?id="
const val PLAY_STORE_URL_PREFIX = "https://play.google.com/store/apps/details?id="
const val TRANSLATE_WIKI_URL = "https://translatewiki.net/w/i.php?title=Special:Translate&group=commons-android-strings&filter=%21translated&action=translate&language="
const val TRANSLATE_WIKI_URL =
"https://translatewiki.net/w/i.php?title=Special:Translate" +
"&group=commons-android-strings&filter=%21translated&action=translate&language="
const val FACEBOOK_WEB_URL = "https://www.facebook.com/1921335171459985"
const val FACEBOOK_APP_URL = "fb://page/1921335171459985"
const val FACEBOOK_PACKAGE_NAME = "com.facebook.katana"

View file

@ -1,261 +0,0 @@
package fr.free.nrw.commons;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.text.SpannableString;
import android.text.style.UnderlineSpan;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.content.ContextCompat;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import java.util.Calendar;
import java.util.Date;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.page.PageTitle;
import java.util.Locale;
import java.util.regex.Pattern;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.utils.ViewUtil;
import timber.log.Timber;
import static android.widget.Toast.LENGTH_SHORT;
import static fr.free.nrw.commons.campaigns.CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE;
public class Utils {
public static PageTitle getPageTitle(@NonNull String title) {
return new PageTitle(title, new WikiSite(BuildConfig.COMMONS_URL));
}
/**
* Generates licence name with given ID
* @param license License ID
* @return Name of license
*/
public static int licenseNameFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return R.string.license_name_cc_by;
case Prefs.Licenses.CC_BY_4:
return R.string.license_name_cc_by_four;
case Prefs.Licenses.CC_BY_SA_3:
return R.string.license_name_cc_by_sa;
case Prefs.Licenses.CC_BY_SA_4:
return R.string.license_name_cc_by_sa_four;
case Prefs.Licenses.CC0:
return R.string.license_name_cc0;
}
throw new IllegalStateException("Unrecognized license value: " + license);
}
/**
* Generates license url with given ID
* @param license License ID
* @return Url of license
*/
@NonNull
public static String licenseUrlFor(String license) {
switch (license) {
case Prefs.Licenses.CC_BY_3:
return "https://creativecommons.org/licenses/by/3.0/";
case Prefs.Licenses.CC_BY_4:
return "https://creativecommons.org/licenses/by/4.0/";
case Prefs.Licenses.CC_BY_SA_3:
return "https://creativecommons.org/licenses/by-sa/3.0/";
case Prefs.Licenses.CC_BY_SA_4:
return "https://creativecommons.org/licenses/by-sa/4.0/";
case Prefs.Licenses.CC0:
return "https://creativecommons.org/publicdomain/zero/1.0/";
default:
throw new IllegalStateException("Unrecognized license value: " + license);
}
}
/**
* Adds extension to filename. Converts to .jpg if system provides .jpeg, adds .jpg if no extension detected
* @param title File name
* @param extension Correct extension
* @return File with correct extension
*/
public static String fixExtension(String title, String extension) {
Pattern jpegPattern = Pattern.compile("\\.jpeg$", Pattern.CASE_INSENSITIVE);
// People are used to ".jpg" more than ".jpeg" which the system gives us.
if (extension != null && extension.toLowerCase(Locale.ENGLISH).equals("jpeg")) {
extension = "jpg";
}
title = jpegPattern.matcher(title).replaceFirst(".jpg");
if (extension != null && !title.toLowerCase(Locale.getDefault())
.endsWith("." + extension.toLowerCase(Locale.ENGLISH))) {
title += "." + extension;
}
// If extension is still null, make it jpg. (Hotfix for https://github.com/commons-app/apps-android-commons/issues/228)
// If title has an extension in it, if won't be true
if (extension == null && title.lastIndexOf(".")<=0) {
extension = "jpg";
title += "." + extension;
}
return title;
}
/**
* Launches intent to rate app
* @param context
*/
public static void rateApp(Context context) {
final String appPackageName = context.getPackageName();
try {
context.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(Urls.PLAY_STORE_PREFIX + appPackageName)));
}
catch (android.content.ActivityNotFoundException anfe) {
handleWebUrl(context, Uri.parse(Urls.PLAY_STORE_URL_PREFIX + appPackageName));
}
}
/**
* Opens Custom Tab Activity with in-app browser for the specified URL.
* Launches intent for web URL
* @param context
* @param url
*/
public static void handleWebUrl(Context context, Uri url) {
Timber.d("Launching web url %s", url.toString());
Intent browserIntent = new Intent(Intent.ACTION_VIEW, url);
if (browserIntent.resolveActivity(context.getPackageManager()) == null) {
Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT);
toast.show();
return;
}
final CustomTabColorSchemeParams color = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor))
.setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor))
.build();
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
builder.setDefaultColorSchemeParams(color);
builder.setExitAnimations(context, android.R.anim.slide_in_left, android.R.anim.slide_out_right);
CustomTabsIntent customTabsIntent = builder.build();
// Clear previous browser tasks, so that back/exit buttons work as intended.
customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
customTabsIntent.launchUrl(context, url);
}
/**
* Util function to handle geo coordinates
* It no longer depends on google maps and any app capable of handling the map intent can handle it
* @param context
* @param latLng
*/
public static void handleGeoCoordinates(Context context, LatLng latLng) {
Intent mapIntent = new Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri());
if (mapIntent.resolveActivity(context.getPackageManager()) != null) {
context.startActivity(mapIntent);
} else {
ViewUtil.showShortToast(context, context.getString(R.string.map_application_missing));
}
}
/**
* To take screenshot of the screen and return it in Bitmap format
*
* @param view
* @return
*/
public static Bitmap getScreenShot(View view) {
View screenView = view.getRootView();
screenView.setDrawingCacheEnabled(true);
Bitmap drawingCache = screenView.getDrawingCache();
if (drawingCache != null) {
Bitmap bitmap = Bitmap.createBitmap(drawingCache);
screenView.setDrawingCacheEnabled(false);
return bitmap;
}
return null;
}
/*
*Copies the content to the clipboard
*
*/
public static void copy(String label,String text, Context context){
ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(label, text);
clipboard.setPrimaryClip(clip);
}
/**
* This method sets underlined string text to a TextView
*
* @param textView TextView associated with string resource
* @param stringResourceName string resource name
* @param context
*/
public static void setUnderlinedText(TextView textView, int stringResourceName, Context context) {
SpannableString content = new SpannableString(context.getString(stringResourceName));
content.setSpan(new UnderlineSpan(), 0, content.length(), 0);
textView.setText(content);
}
/**
* For now we are enabling the monuments only when the date lies between 1 Sept & 31 OCt
* @param date
* @return
*/
public static boolean isMonumentsEnabled(final Date date) {
if (date.getMonth() == 8) {
return true;
}
return false;
}
/**
* Util function to get the start date of wlm monument
* For this release we are hardcoding it to be 1st September
* @return
*/
public static String getWLMStartDate() {
return "1 Sep";
}
/***
* Util function to get the end date of wlm monument
* For this release we are hardcoding it to be 31st October
* @return
*/
public static String getWLMEndDate() {
return "30 Sep";
}
/***
* Function to get the current WLM year
* It increments at the start of September in line with the other WLM functions
* (No consideration of locales for now)
* @param calendar
* @return
*/
public static int getWikiLovesMonumentsYear(Calendar calendar) {
int year = calendar.get(Calendar.YEAR);
if (calendar.get(Calendar.MONTH) < Calendar.SEPTEMBER) {
year -= 1;
}
return year;
}
}

View file

@ -1,7 +0,0 @@
package fr.free.nrw.commons;
import android.content.Context;
public interface ViewHolder<T> {
void bindModel(Context context, T model);
}

View file

@ -1,57 +0,0 @@
package fr.free.nrw.commons;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* This adapter will be used to display fragments in a ViewPager
*/
public class ViewPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> fragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
/**
* This method returns the fragment of the viewpager at a particular position
* @param position
*/
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
/**
* This method returns the total number of fragments in the viewpager.
* @return size
*/
@Override
public int getCount() {
return fragmentList.size();
}
/**
* This method sets the fragment and title list in the viewpager
* @param fragmentList List of all fragments to be displayed in the viewpager
* @param fragmentTitleList List of all titles of the fragments
*/
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
this.fragmentList = fragmentList;
this.fragmentTitleList = fragmentTitleList;
}
/**
* This method returns the title of the page at a particular position
* @param position
*/
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
}

View file

@ -0,0 +1,44 @@
package fr.free.nrw.commons
import android.content.Context
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import java.util.Locale
/**
* This adapter will be used to display fragments in a ViewPager
*/
class ViewPagerAdapter : FragmentPagerAdapter {
private val context: Context
private var fragmentList: List<Fragment> = emptyList()
private var fragmentTitleList: List<String> = emptyList()
constructor(context: Context, manager: FragmentManager) : super(manager) {
this.context = context
}
constructor(context: Context, manager: FragmentManager, behavior: Int) : super(manager, behavior) {
this.context = context
}
override fun getItem(position: Int): Fragment = fragmentList[position]
override fun getPageTitle(position: Int): CharSequence = fragmentTitleList[position]
override fun getCount(): Int = fragmentList.size
fun setTabs(vararg titlesToFragments: Pair<Int, Fragment>) {
// Enforce that every title must come from strings.xml and all will consistently be uppercase
fragmentTitleList = titlesToFragments.map {
context.getString(it.first).uppercase(Locale.ROOT)
}
fragmentList = titlesToFragments.map { it.second }
}
companion object {
// Convenience method for Java callers, can be removed when everything is migrated
@JvmStatic
fun pairOf(first: Int, second: Fragment) = first to second
}
}

View file

@ -1,108 +0,0 @@
package fr.free.nrw.commons;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding;
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding;
import fr.free.nrw.commons.quiz.QuizActivity;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
public class WelcomeActivity extends BaseActivity {
private ActivityWelcomeBinding binding;
private PopupForCopyrightBinding copyrightBinding;
private final WelcomePagerAdapter adapter = new WelcomePagerAdapter();
private boolean isQuiz;
private AlertDialog.Builder dialogBuilder;
private AlertDialog dialog;
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWelcomeBinding.inflate(getLayoutInflater());
final View view = binding.getRoot();
setContentView(view);
if (getIntent() != null) {
final Bundle bundle = getIntent().getExtras();
if (bundle != null) {
isQuiz = bundle.getBoolean("isQuiz");
}
} else {
isQuiz = false;
}
// Enable skip button if beta flavor
if (ConfigUtils.isBetaFlavour()) {
binding.finishTutorialButton.setVisibility(View.VISIBLE);
dialogBuilder = new AlertDialog.Builder(this);
copyrightBinding = PopupForCopyrightBinding.inflate(getLayoutInflater());
final View contactPopupView = copyrightBinding.getRoot();
dialogBuilder.setView(contactPopupView);
dialog = dialogBuilder.create();
dialog.show();
copyrightBinding.buttonOk.setOnClickListener(v -> dialog.dismiss());
}
binding.welcomePager.setAdapter(adapter);
binding.welcomePagerIndicator.setViewPager(binding.welcomePager);
binding.finishTutorialButton.setOnClickListener(v -> finishTutorial());
}
/**
* References WelcomePageAdapter to null before the activity is destroyed
*/
@Override
public void onDestroy() {
if (isQuiz) {
final Intent i = new Intent(this, QuizActivity.class);
startActivity(i);
}
super.onDestroy();
}
/**
* Creates a way to change current activity to WelcomeActivity
*
* @param context Activity context
*/
public static void startYourself(final Context context) {
final Intent welcomeIntent = new Intent(context, WelcomeActivity.class);
context.startActivity(welcomeIntent);
}
/**
* Override onBackPressed() to go to previous tutorial 'pages' if not on first page
*/
@Override
public void onBackPressed() {
if (binding.welcomePager.getCurrentItem() != 0) {
binding.welcomePager.setCurrentItem(binding.welcomePager.getCurrentItem() - 1, true);
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity();
} else {
super.onBackPressed();
}
}
}
public void finishTutorial() {
defaultKvStore.putBoolean("firstrun", false);
finish();
}
}

View file

@ -0,0 +1,80 @@
package fr.free.nrw.commons
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding
import fr.free.nrw.commons.quiz.QuizActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
class WelcomeActivity : BaseActivity() {
private var binding: ActivityWelcomeBinding? = null
private var isQuiz = false
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
applyEdgeToEdgeAllInsets(binding!!.welcomePager.rootView)
setContentView(binding!!.root)
isQuiz = intent?.extras?.getBoolean("isQuiz", false) ?: false
// Enable skip button if beta flavor
if (isBetaFlavour) {
binding!!.finishTutorialButton.visibility = View.VISIBLE
val copyrightBinding = PopupForCopyrightBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this)
.setView(copyrightBinding.root)
.setCancelable(false)
.create()
dialog.show()
copyrightBinding.buttonOk.setOnClickListener { v: View? -> dialog.dismiss() }
}
val adapter = WelcomePagerAdapter()
binding!!.welcomePager.adapter = adapter
binding!!.welcomePagerIndicator.setViewPager(binding!!.welcomePager)
binding!!.finishTutorialButton.setOnClickListener { v: View? -> finishTutorial() }
}
public override fun onDestroy() {
if (isQuiz) {
startActivity(Intent(this, QuizActivity::class.java))
}
super.onDestroy()
}
override fun onBackPressed() {
if (binding!!.welcomePager.currentItem != 0) {
binding!!.welcomePager.setCurrentItem(binding!!.welcomePager.currentItem - 1, true)
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity()
} else {
super.onBackPressed()
}
}
}
fun finishTutorial() {
defaultKvStore.putBoolean("firstrun", false)
finish()
}
}
fun Context.startWelcome() {
startActivity(Intent(this, WelcomeActivity::class.java))
}

View file

@ -1,75 +0,0 @@
package fr.free.nrw.commons;
import android.net.Uri;
import android.text.Html;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.viewpager.widget.PagerAdapter;
public class WelcomePagerAdapter extends PagerAdapter {
private static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
};
/**
* Gets total number of layouts
* @return Number of layouts
*/
@Override
public int getCount() {
return PAGE_LAYOUTS.length;
}
/**
* Compares given view with provided object
* @param view Adapter view
* @param object Adapter object
* @return Equality between view and object
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
// If final page
if (position == PAGE_LAYOUTS.length - 1) {
// Add link to more information
TextView moreInfo = layout.findViewById(R.id.welcomeInfo);
Utils.setUnderlinedText(moreInfo, R.string.welcome_help_button_text, container.getContext());
moreInfo.setOnClickListener(view -> Utils.handleWebUrl(
container.getContext(),
Uri.parse("https://commons.wikimedia.org/wiki/Help:Contents")
));
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById(R.id.finishTutorialButton)
.setOnClickListener(view -> ((WelcomeActivity) container.getContext()).finishTutorial());
}
container.addView(layout);
return layout;
}
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object obj) {
container.removeView((View) obj);
}
}

View file

@ -0,0 +1,70 @@
package fr.free.nrw.commons
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.net.toUri
import androidx.viewpager.widget.PagerAdapter
import fr.free.nrw.commons.utils.UnderlineUtils.setUnderlinedText
import fr.free.nrw.commons.utils.handleWebUrl
class WelcomePagerAdapter : PagerAdapter() {
/**
* Gets total number of layouts
* @return Number of layouts
*/
override fun getCount(): Int = PAGE_LAYOUTS.size
/**
* Compares given view with provided object
* @param view Adapter view
* @param obj Adapter object
* @return Equality between view and object
*/
override fun isViewFromObject(view: View, obj: Any): Boolean = (view === obj)
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) =
container.removeView(obj as View)
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val inflater = LayoutInflater.from(container.context)
val layout = inflater.inflate(PAGE_LAYOUTS[position], container, false) as ViewGroup
// If final page
if (position == PAGE_LAYOUTS.size - 1) {
// Add link to more information
val moreInfo = layout.findViewById<TextView>(R.id.welcomeInfo)
setUnderlinedText(moreInfo, R.string.welcome_help_button_text)
moreInfo.setOnClickListener {
handleWebUrl(
container.context,
"https://commons.wikimedia.org/wiki/Help:Contents".toUri()
)
}
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById<View>(R.id.finishTutorialButton)
.setOnClickListener { view: View? -> (container.context as WelcomeActivity).finishTutorial() }
}
container.addView(layout)
return layout
}
companion object {
private val PAGE_LAYOUTS = intArrayOf(
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
)
}
}

View file

@ -0,0 +1,18 @@
package fr.free.nrw.commons.actions
import fr.free.nrw.commons.wikidata.mwapi.MwResponse
/**
* Response of the Thanks API.
* Context:
* The Commons Android app lets you thank other contributors who have uploaded a great picture.
* See https://www.mediawiki.org/wiki/Extension:Thanks
*/
class MwThankPostResponse : MwResponse() {
var result: Result? = null
inner class Result {
var success: Int? = null
var recipient: String? = null
}
}

View file

@ -1,8 +1,9 @@
package fr.free.nrw.commons.actions
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import io.reactivex.Observable
import io.reactivex.Single
import org.wikipedia.csrf.CsrfTokenClient
/**
* This class acts as a Client to facilitate wiki page editing
@ -13,9 +14,8 @@ import org.wikipedia.csrf.CsrfTokenClient
*/
class PageEditClient(
private val csrfTokenClient: CsrfTokenClient,
private val pageEditInterface: PageEditInterface
private val pageEditInterface: PageEditInterface,
) {
/**
* Replace the content of a wiki page
* @param pageTitle Title of the page to edit
@ -23,11 +23,57 @@ class PageEditClient(
* @param summary Edit summary
* @return whether the edit was successful
*/
fun edit(pageTitle: String, text: String, summary: String): Observable<Boolean> {
return try {
pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.tokenBlocking)
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
fun edit(
pageTitle: String,
text: String,
summary: String,
): Observable<Boolean> =
try {
pageEditInterface
.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking())
.map { editResponse ->
editResponse.edit()!!.editSucceeded()
}
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
/**
* Creates a new page with the given title, text, and summary.
*
* @param pageTitle The title of the page to be created.
* @param text The content of the page in wikitext format.
* @param summary The edit summary for the page creation.
* @return An observable that emits true if the page creation succeeded, false otherwise.
* @throws InvalidLoginTokenException If an invalid login token is encountered during the process.
*/
fun postCreate(
pageTitle: String,
text: String,
summary: String,
): Observable<Boolean> =
try {
pageEditInterface
.postCreate(
pageTitle,
summary,
text,
"text/x-wiki",
"wikitext",
true,
true,
csrfTokenClient.getTokenBlocking(),
).map { editResponse ->
editResponse.edit()!!.editSucceeded()
}
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
@ -39,11 +85,19 @@ class PageEditClient(
* @param summary Edit summary
* @return whether the edit was successful
*/
fun appendEdit(pageTitle: String, appendText: String, summary: String): Observable<Boolean> {
return try {
pageEditInterface.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.tokenBlocking)
fun appendEdit(
pageTitle: String,
appendText: String,
summary: String,
): Observable<Boolean> =
try {
pageEditInterface
.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
@ -55,11 +109,45 @@ class PageEditClient(
* @param summary Edit summary
* @return whether the edit was successful
*/
fun prependEdit(pageTitle: String, prependText: String, summary: String): Observable<Boolean> {
return try {
pageEditInterface.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.tokenBlocking)
fun prependEdit(
pageTitle: String,
prependText: String,
summary: String,
): Observable<Boolean> =
try {
pageEditInterface
.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()?.editSucceeded() ?: false }
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
/**
* Appends a new section to the wiki page
* @param pageTitle Title of the page to edit
* @param sectionTitle Title of the new section that needs to be created
* @param sectionText The page content that is to be added to the section
* @param summary Edit summary
* @return whether the edit was successful
*/
fun createNewSection(
pageTitle: String,
sectionTitle: String,
sectionText: String,
summary: String,
): Observable<Boolean> =
try {
pageEditInterface
.postNewSection(pageTitle, summary, sectionTitle, sectionText, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
@ -72,12 +160,25 @@ class PageEditClient(
* @param value label
* @return 1 when the edit was successful
*/
fun setCaptions(summary: String, title: String,
language: String, value: String) : Observable<Int>{
return try {
pageEditInterface.postCaptions(summary, title, language,
value, csrfTokenClient.tokenBlocking).map { it.success }
fun setCaptions(
summary: String,
title: String,
language: String,
value: String,
): Observable<Int> =
try {
pageEditInterface
.postCaptions(
summary,
title,
language,
value,
csrfTokenClient.getTokenBlocking(),
).map { it.success }
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(0)
}
}
@ -87,9 +188,14 @@ class PageEditClient(
* @param title : Name of the file
* @return Observable<MwQueryResult>
*/
fun getCurrentWikiText(title: String): Single<String?> {
return pageEditInterface.getWikiText(title).map {
it.query()?.pages()?.get(0)?.revisions()?.get(0)?.content()
}
fun getCurrentWikiText(title: String): Single<String?> =
pageEditInterface.getWikiText(title).map {
it
.query()
?.pages()
?.get(0)
?.revisions()
?.get(0)
?.content()
}
}

View file

@ -1,12 +1,17 @@
package fr.free.nrw.commons.actions
import fr.free.nrw.commons.wikidata.WikidataConstants.MW_API_PREFIX
import fr.free.nrw.commons.wikidata.model.Entities
import fr.free.nrw.commons.wikidata.model.edit.Edit
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Observable
import io.reactivex.Single
import org.wikipedia.dataclient.Service
import org.wikipedia.dataclient.mwapi.MwQueryResponse
import org.wikipedia.edit.Edit
import org.wikipedia.wikidata.Entities
import retrofit2.http.*
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query
/**
* This interface facilitates wiki commons page editing services to the Networking module
@ -27,13 +32,40 @@ interface PageEditInterface {
*/
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(Service.MW_API_PREFIX + "action=edit")
@POST(MW_API_PREFIX + "action=edit")
fun postEdit(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("text") text: String,
// NOTE: This csrf shold always be sent as the last field of form data
@Field("token") token: String
@Field("token") token: String,
): Observable<Edit>
/**
* This method creates or edits a page for nearby items.
*
* @param title Title of the page to edit. Cannot be used together with pageid.
* @param summary Edit summary. Also used as the section title when section=new and sectiontitle is not set.
* @param text Text of the page.
* @param contentformat Format of the content (e.g., "text/x-wiki").
* @param contentmodel Model of the content (e.g., "wikitext").
* @param minor Whether the edit is a minor edit.
* @param recreate Whether to recreate the page if it does not exist.
* @param token A "csrf" token. This should always be sent as the last field of form data.
*/
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(MW_API_PREFIX + "action=edit")
fun postCreate(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("text") text: String,
@Field("contentformat") contentformat: String,
@Field("contentmodel") contentmodel: String,
@Field("minor") minor: Boolean,
@Field("recreate") recreate: Boolean,
// NOTE: This csrf shold always be sent as the last field of form data
@Field("token") token: String,
): Observable<Edit>
/**
@ -47,12 +79,12 @@ interface PageEditInterface {
*/
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(Service.MW_API_PREFIX + "action=edit")
@POST(MW_API_PREFIX + "action=edit")
fun postAppendEdit(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("appendtext") appendText: String,
@Field("token") token: String
@Field("token") token: String,
): Observable<Edit>
/**
@ -66,36 +98,44 @@ interface PageEditInterface {
*/
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(Service.MW_API_PREFIX + "action=edit")
@POST(MW_API_PREFIX + "action=edit")
fun postPrependEdit(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("prependtext") prependText: String,
@Field("token") token: String
@Field("token") token: String,
): Observable<Edit>
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(Service.MW_API_PREFIX + "action=wbsetlabel&format=json&site=commonswiki&formatversion=2")
@POST(MW_API_PREFIX + "action=edit&section=new")
fun postNewSection(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("sectiontitle") sectionTitle: String,
@Field("text") sectionText: String,
@Field("token") token: String,
): Observable<Edit>
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(MW_API_PREFIX + "action=wbsetlabel&format=json&site=commonswiki&formatversion=2")
fun postCaptions(
@Field("summary") summary: String,
@Field("title") title: String,
@Field("language") language: String,
@Field("value") value: String,
@Field("token") token: String
@Field("token") token: String,
): Observable<Entities>
/**
* Get wiki text for provided file names
* @param titles : Name of the file
* @return Single<MwQueryResult>
* Gets the wiki text for the provided file name.
*
* @param title The title (name) of the file to fetch wiki text for.
* @return A Single emitting the wiki query response.
*/
@GET(
Service.MW_API_PREFIX +
"action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles="
)
@GET(MW_API_PREFIX + "action=query&prop=revisions&rvprop=content|timestamp&rvlimit=1&converttitles=")
fun getWikiText(
@Query("titles") title: String
@Query("titles") title: String,
): Single<MwQueryResponse?>
}

View file

@ -1,11 +1,10 @@
package fr.free.nrw.commons.actions
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import fr.free.nrw.commons.di.NetworkingModule.Companion.NAMED_COMMONS_CSRF
import io.reactivex.Observable
import org.wikipedia.csrf.CsrfTokenClient
import org.wikipedia.dataclient.Service
import org.wikipedia.dataclient.mwapi.MwPostResponse
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
@ -15,22 +14,33 @@ import javax.inject.Singleton
* Thanks are used by a user to show gratitude to another user for their contributions
*/
@Singleton
class ThanksClient @Inject constructor(
class ThanksClient
@Inject
constructor(
@param:Named(NAMED_COMMONS_CSRF) private val csrfTokenClient: CsrfTokenClient,
@param:Named("commons-service") private val service: Service
private val service: ThanksInterface,
) {
/**
* Thanks a user for a particular revision
* @param revisionId The revision ID the user would like to thank someone for
* @return if thanks was successfully sent to intended recipient
*/
fun thank(revisionId: Long): Observable<Boolean> {
return try {
service.thank(revisionId.toString(), null, csrfTokenClient.tokenBlocking, CommonsApplication.getInstance().userAgent)
.map { mwThankPostResponse -> mwThankPostResponse.result.success== 1 }
fun thank(revisionId: Long): Observable<Boolean> =
try {
service
.thank(
revisionId.toString(), // Rev
null, // Log
csrfTokenClient.getTokenBlocking(), // Token
CommonsApplication.instance.userAgent, // Source
).map { mwThankPostResponse ->
mwThankPostResponse.result?.success == 1
}
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
Observable.error(throwable)
} else {
Observable.just(false)
}
}
}

View file

@ -0,0 +1,24 @@
package fr.free.nrw.commons.actions
import fr.free.nrw.commons.wikidata.WikidataConstants.MW_API_PREFIX
import io.reactivex.Observable
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
/**
* Thanks API.
* Context:
* The Commons Android app lets you thank another contributor who has uploaded a great picture.
* See https://www.mediawiki.org/wiki/Extension:Thanks
*/
interface ThanksInterface {
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=thank")
fun thank(
@Field("rev") rev: String?,
@Field("log") log: String?,
@Field("token") token: String,
@Field("source") source: String?,
): Observable<MwThankPostResponse?>
}

View file

@ -0,0 +1,218 @@
package fr.free.nrw.commons.activity
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.webkit.ConsoleMessage
import android.webkit.CookieManager
import android.webkit.WebChromeClient
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener
import fr.free.nrw.commons.R
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import okhttp3.HttpUrl.Companion.toHttpUrl
import timber.log.Timber
import javax.inject.Inject
/**
* SingleWebViewActivity is a reusable activity webView based on a given url(initial url) and
* closes itself when a specified success URL is reached to success url.
*/
class SingleWebViewActivity : ComponentActivity() {
@Inject
lateinit var cookieJar: CommonsCookieJar
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val url = intent.getStringExtra(VANISH_ACCOUNT_URL)
val successUrl = intent.getStringExtra(VANISH_ACCOUNT_SUCCESS_URL)
if (url == null || successUrl == null) {
finish()
return
}
ApplicationlessInjection
.getInstance(applicationContext)
.commonsApplicationComponent
.inject(this)
setCookies(url)
enableEdgeToEdge()
setContent {
Scaffold(
topBar = {
TopAppBar(
modifier = Modifier,
title = { Text(getString(R.string.vanish_account)) },
navigationIcon = {
IconButton(
onClick = {
// Close the WebView Activity if the user taps the back button
finish()
},
) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
// TODO("Add contentDescription)
contentDescription = ""
)
}
}
)
},
content = {
WebViewComponent(
url = url,
successUrl = successUrl,
onSuccess = {
//Redirect the user to login screen like we do when the user logout's
val app = applicationContext as CommonsApplication
app.clearApplicationData(
applicationContext,
ActivityLogoutListener(activity = this, ctx = applicationContext)
)
finish()
},
modifier = Modifier
.fillMaxSize()
.padding(it)
)
}
)
}
}
/**
* @param url The initial URL which we are loading in the WebView.
* @param successUrl The URL that, when reached, triggers the `onSuccess` callback.
* @param onSuccess A callback that is invoked when the current url of webView is successUrl.
* This is used when we want to close when the webView once a success url is hit.
* @param modifier An optional [Modifier] to customize the layout or appearance of the WebView.
*/
@SuppressLint("SetJavaScriptEnabled")
@Composable
private fun WebViewComponent(
url: String,
successUrl: String,
onSuccess: () -> Unit,
modifier: Modifier = Modifier
) {
val webView = remember { mutableStateOf<WebView?>(null) }
AndroidView(
modifier = modifier,
factory = {
WebView(it).apply {
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
javaScriptCanOpenWindowsAutomatically = true
}
webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
request?.url?.let { url ->
Timber.d("URL Loading: $url")
if (url.toString() == successUrl) {
Timber.d("Success URL detected. Closing WebView.")
onSuccess() // Close the activity
return true
}
return false
}
return false
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
setCookies(url.orEmpty())
}
}
webChromeClient = object : WebChromeClient() {
override fun onConsoleMessage(message: ConsoleMessage): Boolean {
Timber.d("%s%s",
"Console: ${message.message()} -- From line ",
"${message.lineNumber()} of ${message.sourceId()}")
return true
}
}
loadUrl(url)
}
},
update = {
webView.value = it
}
)
}
/**
* Sets cookies for the given URL using the cookies stored in the `CommonsCookieJar`.
*
* @param url The URL for which cookies need to be set.
*/
private fun setCookies(url: String) {
CookieManager.getInstance().let {
val cookies = cookieJar.loadForRequest(url.toHttpUrl())
for (cookie in cookies) {
it.setCookie(url, cookie.toString())
}
}
}
companion object {
private const val VANISH_ACCOUNT_URL = "VanishAccountUrl"
private const val VANISH_ACCOUNT_SUCCESS_URL = "vanishAccountSuccessUrl"
/**
* Launch the WebViewActivity with the specified URL and success URL.
* @param context The context from which the activity is launched.
* @param url The initial URL to load in the WebView.
* @param successUrl The URL that triggers the WebView to close when matched.
*/
fun showWebView(
context: Context,
url: String,
successUrl: String
) {
val intent = Intent(
context,
SingleWebViewActivity::class.java
).apply {
putExtra(VANISH_ACCOUNT_URL, url)
putExtra(VANISH_ACCOUNT_SUCCESS_URL, successUrl)
}
context.startActivity(intent)
}
}
}

View file

@ -1,44 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import timber.log.Timber;
public class AccountUtil {
public static final String AUTH_TOKEN_TYPE = "CommonsAndroid";
public AccountUtil() {
}
/**
* @return Account|null
*/
@Nullable
public static Account account(Context context) {
try {
Account[] accounts = accountManager(context).getAccountsByType(BuildConfig.ACCOUNT_TYPE);
if (accounts.length > 0) {
return accounts[0];
}
} catch (SecurityException e) {
Timber.e(e);
}
return null;
}
@Nullable
public static String getUserName(Context context) {
Account account = account(context);
return account == null ? null : account.name;
}
private static AccountManager accountManager(Context context) {
return AccountManager.get(context);
}
}

View file

@ -0,0 +1,24 @@
package fr.free.nrw.commons.auth
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.BuildConfig.ACCOUNT_TYPE
import timber.log.Timber
const val AUTH_TOKEN_TYPE: String = "CommonsAndroid"
fun getUserName(context: Context): String? {
return account(context)?.name
}
@VisibleForTesting
fun account(context: Context): Account? = try {
val accountManager = AccountManager.get(context)
val accounts = accountManager.getAccountsByType(ACCOUNT_TYPE)
if (accounts.isNotEmpty()) accounts[0] else null
} catch (e: SecurityException) {
Timber.e(e)
null
}

View file

@ -1,500 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.AccountAuthenticatorActivity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.ColorRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.app.NavUtils;
import androidx.core.content.ContextCompat;
import com.google.android.material.textfield.TextInputLayout;
import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.Locale;
import org.wikipedia.AppAdapter;
import org.wikipedia.dataclient.ServiceFactory;
import org.wikipedia.dataclient.WikiSite;
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
import org.wikipedia.login.LoginClient;
import org.wikipedia.login.LoginClient.LoginCallback;
import org.wikipedia.login.LoginResult;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnEditorAction;
import butterknife.OnFocusChange;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.SystemThemeUtils;
import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.disposables.CompositeDisposable;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import timber.log.Timber;
import static android.view.KeyEvent.KEYCODE_ENTER;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_WIKI_SITE;
public class LoginActivity extends AccountAuthenticatorActivity {
@Inject
SessionManager sessionManager;
@Inject
@Named(NAMED_COMMONS_WIKI_SITE)
WikiSite commonsWikiSite;
@Inject
@Named("default_preferences")
JsonKvStore applicationKvStore;
@Inject
LoginClient loginClient;
@Inject
SystemThemeUtils systemThemeUtils;
@BindView(R.id.login_button)
Button loginButton;
@BindView(R.id.login_username)
EditText usernameEdit;
@BindView(R.id.login_password)
EditText passwordEdit;
@BindView(R.id.login_two_factor)
EditText twoFactorEdit;
@BindView(R.id.error_message_container)
ViewGroup errorMessageContainer;
@BindView(R.id.error_message)
TextView errorMessage;
@BindView(R.id.login_credentials)
TextView loginCredentials;
@BindView(R.id.two_factor_container)
TextInputLayout twoFactorContainer;
ProgressDialog progressDialog;
private AppCompatDelegate delegate;
private LoginTextWatcher textWatcher = new LoginTextWatcher();
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private Call<MwQueryResponse> loginToken;
final String saveProgressDailog="ProgressDailog_state";
final String saveErrorMessage ="errorMessage";
final String saveUsername="username";
final String savePassword="password";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ApplicationlessInjection
.getInstance(this.getApplicationContext())
.getCommonsApplicationComponent()
.inject(this);
boolean isDarkTheme = systemThemeUtils.isDeviceInNightMode();
setTheme(isDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme);
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ButterKnife.bind(this);
usernameEdit.addTextChangedListener(textWatcher);
passwordEdit.addTextChangedListener(textWatcher);
twoFactorEdit.addTextChangedListener(textWatcher);
if (ConfigUtils.isBetaFlavour()) {
loginCredentials.setText(getString(R.string.login_credential));
} else {
loginCredentials.setVisibility(View.GONE);
}
}
/**
* Hides the keyboard if the user's focus is not on the password (hasFocus is false).
* @param view The keyboard
* @param hasFocus Set to true if the keyboard has focus
*/
@OnFocusChange(R.id.login_password)
void onPasswordFocusChanged(View view, boolean hasFocus) {
if (!hasFocus) {
ViewUtil.hideKeyboard(view);
}
}
@OnEditorAction(R.id.login_password)
boolean onEditorAction(int actionId, KeyEvent keyEvent) {
if (loginButton.isEnabled()) {
if (actionId == IME_ACTION_DONE) {
performLogin();
return true;
} else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
performLogin();
return true;
}
}
return false;
}
@OnClick(R.id.skip_login)
void skipLogin() {
new AlertDialog.Builder(this).setTitle(R.string.skip_login_title)
.setMessage(R.string.skip_login_message)
.setCancelable(false)
.setPositiveButton(R.string.yes, (dialog, which) -> {
dialog.cancel();
performSkipLogin();
})
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
.show();
}
@OnClick(R.id.forgot_password)
void forgotPassword() {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
}
@OnClick(R.id.about_privacy_policy)
void onPrivacyPolicyClicked() {
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL));
}
@OnClick(R.id.sign_up_button)
void signUp() {
Intent intent = new Intent(this, SignupActivity.class);
startActivity(intent);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
if (sessionManager.getCurrentAccount() != null
&& sessionManager.isUserLoggedIn()) {
applicationKvStore.putBoolean("login_skipped", false);
startMainActivity();
}
if (applicationKvStore.getBoolean("login_skipped", false)) {
performSkipLogin();
}
}
@Override
protected void onDestroy() {
compositeDisposable.clear();
try {
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
} catch (Exception e) {
e.printStackTrace();
}
usernameEdit.removeTextChangedListener(textWatcher);
passwordEdit.removeTextChangedListener(textWatcher);
twoFactorEdit.removeTextChangedListener(textWatcher);
delegate.onDestroy();
if(null!=loginClient) {
loginClient.cancel();
}
super.onDestroy();
}
@OnClick(R.id.login_button)
public void performLogin() {
Timber.d("Login to start!");
final String username = usernameEdit.getText().toString();
final String rawUsername = usernameEdit.getText().toString().trim();
final String password = passwordEdit.getText().toString();
String twoFactorCode = twoFactorEdit.getText().toString();
showLoggingProgressBar();
doLogin(username, password, twoFactorCode);
}
private void doLogin(String username, String password, String twoFactorCode) {
progressDialog.show();
loginToken = ServiceFactory.get(commonsWikiSite).getLoginToken();
loginToken.enqueue(
new Callback<MwQueryResponse>() {
@Override
public void onResponse(Call<MwQueryResponse> call,
Response<MwQueryResponse> response) {
loginClient.login(commonsWikiSite, username, password, null, twoFactorCode,
response.body().query().loginToken(), Locale.getDefault().getLanguage(), new LoginCallback() {
@Override
public void success(@NonNull LoginResult result) {
Timber.d("Login Success");
onLoginSuccess(result);
}
@Override
public void twoFactorPrompt(@NonNull Throwable caught,
@Nullable String token) {
Timber.d("Requesting 2FA prompt");
hideProgress();
askUserForTwoFactorAuth();
}
@Override
public void passwordResetPrompt(@Nullable String token) {
Timber.d("Showing password reset prompt");
hideProgress();
showPasswordResetPrompt();
}
@Override
public void error(@NonNull Throwable caught) {
Timber.e(caught);
hideProgress();
showMessageAndCancelDialog(caught.getLocalizedMessage());
}
});
}
@Override
public void onFailure(Call<MwQueryResponse> call, Throwable t) {
Timber.e(t);
showMessageAndCancelDialog(t.getLocalizedMessage());
}
});
}
private void hideProgress() {
progressDialog.dismiss();
}
private void showPasswordResetPrompt() {
showMessageAndCancelDialog(getString(R.string.you_must_reset_your_passsword));
}
/**
* This function is called when user skips the login.
* It redirects the user to Explore Activity.
*/
private void performSkipLogin() {
applicationKvStore.putBoolean("login_skipped", true);
MainActivity.startYourself(this);
finish();
}
private void showLoggingProgressBar() {
progressDialog = new ProgressDialog(this);
progressDialog.setIndeterminate(true);
progressDialog.setTitle(getString(R.string.logging_in_title));
progressDialog.setMessage(getString(R.string.logging_in_message));
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.show();
}
private void onLoginSuccess(LoginResult loginResult) {
if (!progressDialog.isShowing()) {
// no longer attached to activity!
return;
}
compositeDisposable.clear();
sessionManager.setUserLoggedIn(true);
AppAdapter.get().updateAccount(loginResult);
progressDialog.dismiss();
showSuccessAndDismissDialog();
startMainActivity();
}
@Override
protected void onStart() {
super.onStart();
delegate.onStart();
}
@Override
protected void onStop() {
super.onStop();
delegate.onStop();
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
@NonNull
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
public void askUserForTwoFactorAuth() {
progressDialog.dismiss();
twoFactorContainer.setVisibility(VISIBLE);
twoFactorEdit.setVisibility(VISIBLE);
twoFactorEdit.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
showMessageAndCancelDialog(R.string.login_failed_2fa_needed);
}
public void showMessageAndCancelDialog(@StringRes int resId) {
showMessage(resId, R.color.secondaryDarkColor);
if (progressDialog != null) {
progressDialog.cancel();
}
}
public void showMessageAndCancelDialog(String error) {
showMessage(error, R.color.secondaryDarkColor);
if (progressDialog != null) {
progressDialog.cancel();
}
}
public void showSuccessAndDismissDialog() {
showMessage(R.string.login_success, R.color.primaryDarkColor);
progressDialog.dismiss();
}
public void startMainActivity() {
ActivityUtils.startActivityWithFlags(this, MainActivity.class, Intent.FLAG_ACTIVITY_SINGLE_TOP);
finish();
}
private void showMessage(@StringRes int resId, @ColorRes int colorResId) {
errorMessage.setText(getString(resId));
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
errorMessageContainer.setVisibility(VISIBLE);
}
private void showMessage(String message, @ColorRes int colorResId) {
errorMessage.setText(message);
errorMessage.setTextColor(ContextCompat.getColor(this, colorResId));
errorMessageContainer.setVisibility(VISIBLE);
}
private AppCompatDelegate getDelegate() {
if (delegate == null) {
delegate = AppCompatDelegate.create(this, null);
}
return delegate;
}
private class LoginTextWatcher implements TextWatcher {
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
boolean enabled = usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0
&& (BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != VISIBLE);
loginButton.setEnabled(enabled);
}
}
public static void startYourself(Context context) {
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// if progressDialog is visible during the configuration change then store state as true else false so that
// we maintain visibility of progressDailog after configuration change
if(progressDialog!=null&&progressDialog.isShowing()) {
outState.putBoolean(saveProgressDailog,true);
} else {
outState.putBoolean(saveProgressDailog,false);
}
outState.putString(saveErrorMessage,errorMessage.getText().toString()); //Save the errorMessage
outState.putString(saveUsername,getUsername()); // Save the username
outState.putString(savePassword,getPassword()); // Save the password
}
private String getUsername() {
return usernameEdit.getText().toString();
}
private String getPassword(){
return passwordEdit.getText().toString();
}
@Override
protected void onRestoreInstanceState(final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
usernameEdit.setText(savedInstanceState.getString(saveUsername));
passwordEdit.setText(savedInstanceState.getString(savePassword));
if(savedInstanceState.getBoolean(saveProgressDailog)) {
performLogin();
}
String errorMessage=savedInstanceState.getString(saveErrorMessage);
if(sessionManager.isUserLoggedIn()) {
showMessage(R.string.login_success, R.color.primaryDarkColor);
} else {
showMessage(errorMessage, R.color.secondaryDarkColor);
}
}
}

View file

@ -0,0 +1,489 @@
package fr.free.nrw.commons.auth
import android.accounts.AccountAuthenticatorActivity
import android.app.ProgressDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.KeyEvent
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.NavUtils
import androidx.core.content.ContextCompat
import androidx.core.view.WindowCompat
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.login.LoginCallback
import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.auth.login.LoginResult
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.databinding.ActivityLoginBinding
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import fr.free.nrw.commons.utils.AbstractTextWatcher
import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard
import fr.free.nrw.commons.utils.handleKeyboardInsets
import fr.free.nrw.commons.utils.handleWebUrl
import io.reactivex.disposables.CompositeDisposable
import timber.log.Timber
import java.util.Locale
import javax.inject.Inject
import javax.inject.Named
class LoginActivity : AccountAuthenticatorActivity() {
@Inject
lateinit var sessionManager: SessionManager
@Inject
@field:Named("default_preferences")
lateinit var applicationKvStore: JsonKvStore
@Inject
lateinit var loginClient: LoginClient
@Inject
lateinit var systemThemeUtils: SystemThemeUtils
private var binding: ActivityLoginBinding? = null
private var progressDialog: ProgressDialog? = null
private val textWatcher = AbstractTextWatcher(::onTextChanged)
private val compositeDisposable = CompositeDisposable()
private val delegate: AppCompatDelegate by lazy {
AppCompatDelegate.create(this, null)
}
private var lastLoginResult: LoginResult? = null
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ApplicationlessInjection
.getInstance(this.applicationContext)
.commonsApplicationComponent
.inject(this)
val isDarkTheme = systemThemeUtils.isDeviceInNightMode()
setTheme(if (isDarkTheme) R.style.DarkAppTheme else R.style.LightAppTheme)
delegate.installViewFactory()
delegate.onCreate(savedInstanceState)
WindowCompat.getInsetsController(window, window.decorView)
.isAppearanceLightStatusBars = !isDarkTheme
WindowCompat.setDecorFitsSystemWindows(window, false)
binding = ActivityLoginBinding.inflate(layoutInflater)
applyEdgeToEdgeAllInsets(binding!!.root)
binding!!.root.handleKeyboardInsets()
with(binding!!) {
setContentView(root)
loginUsername.addTextChangedListener(textWatcher)
loginPassword.addTextChangedListener(textWatcher)
loginTwoFactor.addTextChangedListener(textWatcher)
skipLogin.setOnClickListener { skipLogin() }
forgotPassword.setOnClickListener { forgotPassword() }
aboutPrivacyPolicy.setOnClickListener { onPrivacyPolicyClicked() }
signUpButton.setOnClickListener { signUp() }
loginButton.setOnClickListener { performLogin() }
loginPassword.setOnEditorActionListener { textView, actionId, keyEvent ->
if (binding!!.loginButton.isEnabled && isTriggerAction(actionId, keyEvent)) {
if (actionId == EditorInfo.IME_ACTION_NEXT && lastLoginResult != null) {
askUserForTwoFactorAuthWithKeyboard()
true
} else {
performLogin()
true
}
} else {
false
}
}
loginPassword.onFocusChangeListener =
View.OnFocusChangeListener(::onPasswordFocusChanged)
if (isBetaFlavour) {
loginCredentials.text = getString(R.string.login_credential)
} else {
loginCredentials.visibility = View.GONE
}
intent.getStringExtra(CommonsApplication.LOGIN_MESSAGE_INTENT_KEY)?.let {
showMessage(it, R.color.secondaryDarkColor)
}
intent.getStringExtra(CommonsApplication.LOGIN_USERNAME_INTENT_KEY)?.let {
loginUsername.setText(it)
}
}
}
@VisibleForTesting
fun askUserForTwoFactorAuthWithKeyboard() {
if (binding == null) {
Timber.w("Binding is null, reinitializing in askUserForTwoFactorAuthWithKeyboard")
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding!!.root)
}
progressDialog!!.dismiss()
if (binding != null) {
with(binding!!) {
twoFactorContainer.visibility = View.VISIBLE
twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
loginTwoFactor.visibility = View.VISIBLE
loginTwoFactor.requestFocus()
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(loginTwoFactor, InputMethodManager.SHOW_IMPLICIT)
loginTwoFactor.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE ||
(event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
performLogin()
true
} else {
false
}
}
}
} else {
Timber.e("Binding is null in askUserForTwoFactorAuthWithKeyboard after reinitialization attempt")
}
showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed))
}
override fun onPostCreate(savedInstanceState: Bundle?) {
super.onPostCreate(savedInstanceState)
delegate.onPostCreate(savedInstanceState)
}
override fun onResume() {
super.onResume()
if (sessionManager.currentAccount != null && sessionManager.isUserLoggedIn) {
applicationKvStore.putBoolean("login_skipped", false)
startMainActivity()
}
if (applicationKvStore.getBoolean("login_skipped", false)) {
performSkipLogin()
}
}
override fun onDestroy() {
compositeDisposable.clear()
try {
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
if (progressDialog?.isShowing == true) {
progressDialog!!.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
}
with(binding!!) {
loginUsername.removeTextChangedListener(textWatcher)
loginPassword.removeTextChangedListener(textWatcher)
loginTwoFactor.removeTextChangedListener(textWatcher)
}
delegate.onDestroy()
loginClient.cancel()
binding = null
super.onDestroy()
}
override fun onStart() {
super.onStart()
delegate.onStart()
}
override fun onStop() {
super.onStop()
delegate.onStop()
}
override fun onPostResume() {
super.onPostResume()
delegate.onPostResume()
}
override fun setContentView(view: View, params: ViewGroup.LayoutParams) {
delegate.setContentView(view, params)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
return true
}
}
return super.onOptionsItemSelected(item)
}
override fun onSaveInstanceState(outState: Bundle) {
// if progressDialog is visible during the configuration change then store state as true else false so that
// we maintain visibility of progressDialog after configuration change
if (progressDialog != null && progressDialog!!.isShowing) {
outState.putBoolean(SAVE_PROGRESS_DIALOG, true)
} else {
outState.putBoolean(SAVE_PROGRESS_DIALOG, false)
}
outState.putString(
SAVE_ERROR_MESSAGE,
binding!!.errorMessage.text.toString()
) //Save the errorMessage
outState.putString(
SAVE_USERNAME,
binding!!.loginUsername.text.toString()
) // Save the username
outState.putString(
SAVE_PASSWORD,
binding!!.loginPassword.text.toString()
) // Save the password
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
binding!!.loginUsername.setText(savedInstanceState.getString(SAVE_USERNAME))
binding!!.loginPassword.setText(savedInstanceState.getString(SAVE_PASSWORD))
if (savedInstanceState.getBoolean(SAVE_PROGRESS_DIALOG)) {
performLogin()
}
val errorMessage = savedInstanceState.getString(SAVE_ERROR_MESSAGE)
if (sessionManager.isUserLoggedIn) {
showMessage(R.string.login_success, R.color.primaryDarkColor)
} else {
showMessage(errorMessage, R.color.secondaryDarkColor)
}
}
/**
* Hides the keyboard if the user's focus is not on the password (hasFocus is false).
* @param view The keyboard
* @param hasFocus Set to true if the keyboard has focus
*/
private fun onPasswordFocusChanged(view: View, hasFocus: Boolean) {
if (!hasFocus) {
hideKeyboard(view)
}
}
private fun onEditorAction(textView: TextView, actionId: Int, keyEvent: KeyEvent?) =
if (binding!!.loginButton.isEnabled && isTriggerAction(actionId, keyEvent)) {
performLogin()
true
} else false
private fun isTriggerAction(actionId: Int, keyEvent: KeyEvent?) =
actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
private fun skipLogin() {
AlertDialog.Builder(this)
.setTitle(R.string.skip_login_title)
.setMessage(R.string.skip_login_message)
.setCancelable(false)
.setPositiveButton(R.string.yes) { dialog: DialogInterface, which: Int ->
dialog.cancel()
performSkipLogin()
}
.setNegativeButton(R.string.no) { dialog: DialogInterface, which: Int ->
dialog.cancel()
}
.show()
}
private fun forgotPassword() =
handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL))
private fun onPrivacyPolicyClicked() =
handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
private fun signUp() =
startActivity(Intent(this, SignupActivity::class.java))
@VisibleForTesting
fun performLogin() {
Timber.d("Login to start!")
val username = binding!!.loginUsername.text.toString()
val password = binding!!.loginPassword.text.toString()
val twoFactorCode = binding!!.loginTwoFactor.text.toString()
showLoggingProgressBar()
loginClient.doLogin(username,
password,
lastLoginResult,
twoFactorCode,
Locale.getDefault().language,
object : LoginCallback {
override fun success(loginResult: LoginResult) = runOnUiThread {
Timber.d("Login Success")
progressDialog!!.dismiss()
onLoginSuccess(loginResult)
}
override fun twoFactorPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
Timber.d("Requesting 2FA prompt")
progressDialog!!.dismiss()
lastLoginResult = loginResult
askUserForTwoFactorAuthWithKeyboard()
}
override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
Timber.d("Requesting email auth prompt")
progressDialog!!.dismiss()
lastLoginResult = loginResult
askUserForTwoFactorAuthWithKeyboard()
}
override fun passwordResetPrompt(token: String?) = runOnUiThread {
Timber.d("Showing password reset prompt")
progressDialog!!.dismiss()
showPasswordResetPrompt()
}
override fun error(caught: Throwable) = runOnUiThread {
Timber.e(caught)
progressDialog!!.dismiss()
showMessageAndCancelDialog(caught.localizedMessage ?: "")
}
}
)
}
private fun showPasswordResetPrompt() =
showMessageAndCancelDialog(getString(R.string.you_must_reset_your_passsword))
/**
* This function is called when user skips the login.
* It redirects the user to Explore Activity.
*/
private fun performSkipLogin() {
applicationKvStore.putBoolean("login_skipped", true)
MainActivity.startYourself(this)
finish()
}
private fun showLoggingProgressBar() {
progressDialog = ProgressDialog(this).apply {
isIndeterminate = true
setTitle(getString(R.string.logging_in_title))
setMessage(getString(R.string.logging_in_message))
setCancelable(false)
}
progressDialog!!.show()
}
private fun onLoginSuccess(loginResult: LoginResult) {
compositeDisposable.clear()
sessionManager.setUserLoggedIn(true)
sessionManager.updateAccount(loginResult)
progressDialog!!.dismiss()
showSuccessAndDismissDialog()
startMainActivity()
}
override fun getMenuInflater(): MenuInflater =
delegate.menuInflater
@VisibleForTesting
fun askUserForTwoFactorAuth() {
if (binding == null) {
Timber.w("Binding is null, reinitializing in askUserForTwoFactorAuth")
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding!!.root)
}
progressDialog!!.dismiss()
if (binding != null) {
with(binding!!) {
twoFactorContainer.visibility = View.VISIBLE
twoFactorContainer.hint = getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.email_auth_code else R.string._2fa_code)
loginTwoFactor.visibility = View.VISIBLE
loginTwoFactor.requestFocus()
loginTwoFactor.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE ||
(event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)) {
performLogin()
true
} else {
false
}
}
}
} else {
Timber.e("Binding is null in askUserForTwoFactorAuth after reinitialization attempt")
}
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY)
showMessageAndCancelDialog(getString(if (lastLoginResult is LoginResult.EmailAuthResult) R.string.login_failed_email_auth_needed else R.string.login_failed_2fa_needed))
}
@VisibleForTesting
fun showMessageAndCancelDialog(@StringRes resId: Int) {
showMessage(resId, R.color.secondaryDarkColor)
progressDialog?.cancel()
}
@VisibleForTesting
fun showMessageAndCancelDialog(error: String) {
showMessage(error, R.color.secondaryDarkColor)
progressDialog?.cancel()
}
@VisibleForTesting
fun showSuccessAndDismissDialog() {
showMessage(R.string.login_success, R.color.primaryDarkColor)
progressDialog!!.dismiss()
}
@VisibleForTesting
fun startMainActivity() {
startActivityWithFlags(this, MainActivity::class.java, Intent.FLAG_ACTIVITY_SINGLE_TOP)
finish()
}
private fun showMessage(@StringRes resId: Int, @ColorRes colorResId: Int) = with(binding!!) {
errorMessage.text = getString(resId)
errorMessage.setTextColor(ContextCompat.getColor(this@LoginActivity, colorResId))
errorMessageContainer.visibility = View.VISIBLE
}
private fun showMessage(message: String?, @ColorRes colorResId: Int) = with(binding!!) {
errorMessage.text = message
errorMessage.setTextColor(ContextCompat.getColor(this@LoginActivity, colorResId))
errorMessageContainer.visibility = View.VISIBLE
}
private fun onTextChanged(text: String) {
val enabled =
binding!!.loginUsername.text!!.length != 0 && binding!!.loginPassword.text!!.length != 0 &&
(BuildConfig.DEBUG || binding!!.loginTwoFactor.text!!.length != 0 || binding!!.loginTwoFactor.visibility != View.VISIBLE)
binding!!.loginButton.isEnabled = enabled
}
companion object {
fun startYourself(context: Context) =
context.startActivity(Intent(context, LoginActivity::class.java))
const val SAVE_PROGRESS_DIALOG: String = "ProgressDialog_state"
const val SAVE_ERROR_MESSAGE: String = "errorMessage"
const val SAVE_USERNAME: String = "username"
const val SAVE_PASSWORD: String = "password"
}
}

View file

@ -1,36 +0,0 @@
package fr.free.nrw.commons.auth;
import org.wikipedia.dataclient.Service;
import org.wikipedia.dataclient.mwapi.MwPostResponse;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import io.reactivex.Observable;
/**
* Handler for logout
*/
@Singleton
public class LogoutClient {
private final Service service;
@Inject
public LogoutClient(@Named("commons-service") Service service) {
this.service = service;
}
/**
* Fetches the CSRF token and uses that to post the logout api call
* @return
*/
public Observable<MwPostResponse> postLogout() {
return service.getCsrfToken().concatMap(tokenResponse -> service.postLogout(
Objects.requireNonNull(Objects.requireNonNull(tokenResponse.query()).csrfToken())));
}
}

View file

@ -1,149 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.wikipedia.login.LoginResult;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import io.reactivex.Completable;
import io.reactivex.Observable;
/**
* Manage the current logged in user session.
*/
@Singleton
public class SessionManager {
private final Context context;
private Account currentAccount; // Unlike a savings account... ;-)
private JsonKvStore defaultKvStore;
@Inject
public SessionManager(Context context,
@Named("default_preferences") JsonKvStore defaultKvStore) {
this.context = context;
this.currentAccount = null;
this.defaultKvStore = defaultKvStore;
}
private boolean createAccount(@NonNull String userName, @NonNull String password) {
Account account = getCurrentAccount();
if (account == null || TextUtils.isEmpty(account.name) || !account.name.equals(userName)) {
removeAccount();
account = new Account(userName, BuildConfig.ACCOUNT_TYPE);
return accountManager().addAccountExplicitly(account, password, null);
}
return true;
}
private void removeAccount() {
Account account = getCurrentAccount();
if (account != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
accountManager().removeAccountExplicitly(account);
} else {
//noinspection deprecation
accountManager().removeAccount(account, null, null);
}
}
}
public void updateAccount(LoginResult result) {
boolean accountCreated = createAccount(result.getUserName(), result.getPassword());
if (accountCreated) {
setPassword(result.getPassword());
}
}
private void setPassword(@NonNull String password) {
Account account = getCurrentAccount();
if (account != null) {
accountManager().setPassword(account, password);
}
}
/**
* @return Account|null
*/
@Nullable
public Account getCurrentAccount() {
if (currentAccount == null) {
AccountManager accountManager = AccountManager.get(context);
Account[] allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE);
if (allAccounts.length != 0) {
currentAccount = allAccounts[0];
}
}
return currentAccount;
}
public boolean doesAccountExist() {
return getCurrentAccount() != null;
}
@Nullable
public String getUserName() {
Account account = getCurrentAccount();
return account == null ? null : account.name;
}
@Nullable
public String getPassword() {
Account account = getCurrentAccount();
return account == null ? null : accountManager().getPassword(account);
}
private AccountManager accountManager() {
return AccountManager.get(context);
}
public boolean isUserLoggedIn() {
return defaultKvStore.getBoolean("isUserLoggedIn", false);
}
void setUserLoggedIn(boolean isLoggedIn) {
defaultKvStore.putBoolean("isUserLoggedIn", isLoggedIn);
}
public void forceLogin(Context context) {
if (context != null) {
LoginActivity.startYourself(context);
}
}
/**
* 1. Clears existing accounts from account manager
* 2. Calls MediaWikiApi's logout function to clear cookies
* @return
*/
public Completable logout() {
AccountManager accountManager = AccountManager.get(context);
Account[] allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE);
return Completable.fromObservable(Observable.fromArray(allAccounts)
.map(a -> accountManager.removeAccount(a, null, null).getResult()))
.doOnComplete(() -> {
currentAccount = null;
});
}
/**
* Return a corresponding boolean preference
*
* @param key
* @return
*/
public boolean getPreference(String key) {
return defaultKvStore.getBoolean(key);
}
}

View file

@ -0,0 +1,95 @@
package fr.free.nrw.commons.auth
import android.accounts.Account
import android.accounts.AccountManager
import android.content.Context
import android.os.Build
import android.text.TextUtils
import fr.free.nrw.commons.BuildConfig.ACCOUNT_TYPE
import fr.free.nrw.commons.auth.login.LoginResult
import fr.free.nrw.commons.kvstore.JsonKvStore
import io.reactivex.Completable
import io.reactivex.Observable
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
/**
* Manage the current logged in user session.
*/
@Singleton
class SessionManager @Inject constructor(
private val context: Context,
@param:Named("default_preferences") private val defaultKvStore: JsonKvStore
) {
private val accountManager: AccountManager get() = AccountManager.get(context)
private var _currentAccount: Account? = null // Unlike a savings account... ;-)
val currentAccount: Account? get() {
if (_currentAccount == null) {
val allAccounts = AccountManager.get(context).getAccountsByType(ACCOUNT_TYPE)
if (allAccounts.isNotEmpty()) {
_currentAccount = allAccounts[0]
}
}
return _currentAccount
}
val userName: String?
get() = currentAccount?.name
var password: String?
get() = currentAccount?.let { accountManager.getPassword(it) }
private set(value) {
currentAccount?.let { accountManager.setPassword(it, value) }
}
val isUserLoggedIn: Boolean
get() = defaultKvStore.getBoolean("isUserLoggedIn", false)
fun updateAccount(result: LoginResult) {
if (createAccount(result.userName!!, result.password!!)) {
password = result.password
}
}
fun doesAccountExist(): Boolean =
currentAccount != null
fun setUserLoggedIn(isLoggedIn: Boolean) =
defaultKvStore.putBoolean("isUserLoggedIn", isLoggedIn)
fun forceLogin(context: Context?) =
context?.let { LoginActivity.startYourself(it) }
fun getPreference(key: String): Boolean =
defaultKvStore.getBoolean(key)
fun logout(): Completable = Completable.fromObservable(
Observable.empty<Any>()
.doOnComplete {
removeAccount()
_currentAccount = null
}
)
private fun createAccount(userName: String, password: String): Boolean {
var account = currentAccount
if (account == null || TextUtils.isEmpty(account.name) || account.name != userName) {
removeAccount()
account = Account(userName, ACCOUNT_TYPE)
return accountManager.addAccountExplicitly(account, password, null)
}
return true
}
private fun removeAccount() {
currentAccount?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
accountManager.removeAccountExplicitly(it)
} else {
accountManager.removeAccount(it, null, null)
}
}
}
}

View file

@ -1,82 +0,0 @@
package fr.free.nrw.commons.auth;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber;
public class SignupActivity extends BaseActivity {
private WebView webView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Timber.d("Signup Activity started");
webView = new WebView(this);
setContentView(webView);
webView.setWebViewClient(new MyWebViewClient());
WebSettings webSettings = webView.getSettings();
/*Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can
trust Wikimedia's site... right?*/
webSettings.setJavaScriptEnabled(true);
webView.loadUrl(BuildConfig.SIGNUP_LANDING_URL);
}
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.equals(BuildConfig.SIGNUP_SUCCESS_REDIRECTION_URL)) {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url);
Toast toast = Toast.makeText(SignupActivity.this,
R.string.account_created, Toast.LENGTH_LONG);
toast.show();
// terminate on task completion.
finish();
return true;
} else {
//If user clicks any other links in the webview
Timber.d("Not overriding URL, URL is: %s", url);
return false;
}
}
}
@Override
public void onBackPressed() {
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
/**
* Known bug in androidx.appcompat library version 1.1.0 being tracked here
* https://issuetracker.google.com/issues/141132133
* App tries to put light/dark theme to webview and crashes in the process
* This code tries to prevent applying the theme when sdk is between api 21 to 25
* @param overrideConfiguration
*/
@Override
public void applyOverrideConfiguration(final Configuration overrideConfiguration) {
if (Build.VERSION.SDK_INT <= 25 &&
(getResources().getConfiguration().uiMode == getApplicationContext().getResources().getConfiguration().uiMode)) {
return;
}
super.applyOverrideConfiguration(overrideConfiguration);
}
}

View file

@ -0,0 +1,77 @@
package fr.free.nrw.commons.auth
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.Toast
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.R
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
import timber.log.Timber
class SignupActivity : BaseActivity() {
private var webView: WebView? = null
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Timber.d("Signup Activity started")
webView = WebView(this)
applyEdgeToEdgeAllInsets(webView!!)
with(webView!!) {
setContentView(this)
webViewClient = MyWebViewClient()
// Needed to refresh Captcha. Might introduce XSS vulnerabilities, but we can
// trust Wikimedia's site... right?
settings.javaScriptEnabled = true
loadUrl(BuildConfig.SIGNUP_LANDING_URL)
}
}
override fun onBackPressed() {
if (webView!!.canGoBack()) {
webView!!.goBack()
} else {
super.onBackPressed()
}
}
/**
* Known bug in androidx.appcompat library version 1.1.0 being tracked here
* https://issuetracker.google.com/issues/141132133
* App tries to put light/dark theme to webview and crashes in the process
* This code tries to prevent applying the theme when sdk is between api 21 to 25
*/
override fun applyOverrideConfiguration(overrideConfiguration: Configuration) {
if (Build.VERSION.SDK_INT <= 25 &&
(resources.configuration.uiMode == applicationContext.resources.configuration.uiMode)
) return
super.applyOverrideConfiguration(overrideConfiguration)
}
private inner class MyWebViewClient : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean =
if (url == BuildConfig.SIGNUP_SUCCESS_REDIRECTION_URL) {
//Signup success, so clear cookies, notify user, and load LoginActivity again
Timber.d("Overriding URL %s", url)
Toast.makeText(
this@SignupActivity, R.string.account_created, Toast.LENGTH_LONG
).show()
// terminate on task completion.
finish()
true
} else {
//If user clicks any other links in the webview
Timber.d("Not overriding URL, URL is: %s", url)
false
}
}
}

View file

@ -1,141 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.BuildConfig;
import static fr.free.nrw.commons.auth.AccountUtil.AUTH_TOKEN_TYPE;
/**
* Handles WikiMedia commons account Authentication
*/
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
private static final String[] SYNC_AUTHORITIES = {BuildConfig.CONTRIBUTION_AUTHORITY, BuildConfig.MODIFICATION_AUTHORITY};
@NonNull
private final Context context;
public WikiAccountAuthenticator(@NonNull Context context) {
super(context);
this.context = context;
}
/**
* Provides Bundle with edited Account Properties
*/
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
Bundle bundle = new Bundle();
bundle.putString("test", "editProperties");
return bundle;
}
@Override
public Bundle addAccount(@NonNull AccountAuthenticatorResponse response,
@NonNull String accountType, @Nullable String authTokenType,
@Nullable String[] requiredFeatures, @Nullable Bundle options)
throws NetworkErrorException {
// account type not supported returns bundle without loginActivity Intent, it just contains "test" key
if (!supportedAccountType(accountType)) {
Bundle bundle = new Bundle();
bundle.putString("test", "addAccount");
return bundle;
}
return addAccount(response);
}
@Override
public Bundle confirmCredentials(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @Nullable Bundle options)
throws NetworkErrorException {
Bundle bundle = new Bundle();
bundle.putString("test", "confirmCredentials");
return bundle;
}
@Override
public Bundle getAuthToken(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @NonNull String authTokenType,
@Nullable Bundle options)
throws NetworkErrorException {
Bundle bundle = new Bundle();
bundle.putString("test", "getAuthToken");
return bundle;
}
@Nullable
@Override
public String getAuthTokenLabel(@NonNull String authTokenType) {
return supportedAccountType(authTokenType) ? AUTH_TOKEN_TYPE : null;
}
@Nullable
@Override
public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @Nullable String authTokenType,
@Nullable Bundle options)
throws NetworkErrorException {
Bundle bundle = new Bundle();
bundle.putString("test", "updateCredentials");
return bundle;
}
@Nullable
@Override
public Bundle hasFeatures(@NonNull AccountAuthenticatorResponse response,
@NonNull Account account, @NonNull String[] features)
throws NetworkErrorException {
Bundle bundle = new Bundle();
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
return bundle;
}
private boolean supportedAccountType(@Nullable String type) {
return BuildConfig.ACCOUNT_TYPE.equals(type);
}
/**
* Provides a bundle containing a Parcel
* the Parcel packs an Intent with LoginActivity and Authenticator response (requires valid account type)
*/
private Bundle addAccount(AccountAuthenticatorResponse response) {
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
@Override
public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
Account account) throws NetworkErrorException {
Bundle result = super.getAccountRemovalAllowed(response, account);
if (result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
&& !result.containsKey(AccountManager.KEY_INTENT)) {
boolean allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
if (allowed) {
for (String auth : SYNC_AUTHORITIES) {
ContentResolver.cancelSync(account, auth);
}
}
}
return result;
}
}

View file

@ -0,0 +1,108 @@
package fr.free.nrw.commons.auth
import android.accounts.AbstractAccountAuthenticator
import android.accounts.Account
import android.accounts.AccountAuthenticatorResponse
import android.accounts.AccountManager
import android.accounts.NetworkErrorException
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.core.os.bundleOf
import fr.free.nrw.commons.BuildConfig
private val SYNC_AUTHORITIES = arrayOf(
BuildConfig.CONTRIBUTION_AUTHORITY, BuildConfig.MODIFICATION_AUTHORITY
)
/**
* Handles WikiMedia commons account Authentication
*/
class WikiAccountAuthenticator(
private val context: Context
) : AbstractAccountAuthenticator(context) {
/**
* Provides Bundle with edited Account Properties
*/
override fun editProperties(
response: AccountAuthenticatorResponse,
accountType: String
) = bundleOf("test" to "editProperties")
// account type not supported returns bundle without loginActivity Intent, it just contains "test" key
@Throws(NetworkErrorException::class)
override fun addAccount(
response: AccountAuthenticatorResponse,
accountType: String,
authTokenType: String?,
requiredFeatures: Array<String>?,
options: Bundle?
) = if (BuildConfig.ACCOUNT_TYPE == accountType) {
addAccount(response)
} else {
bundleOf("test" to "addAccount")
}
@Throws(NetworkErrorException::class)
override fun confirmCredentials(
response: AccountAuthenticatorResponse, account: Account, options: Bundle?
) = bundleOf("test" to "confirmCredentials")
@Throws(NetworkErrorException::class)
override fun getAuthToken(
response: AccountAuthenticatorResponse,
account: Account,
authTokenType: String,
options: Bundle?
) = bundleOf("test" to "getAuthToken")
override fun getAuthTokenLabel(authTokenType: String) =
if (BuildConfig.ACCOUNT_TYPE == authTokenType) AUTH_TOKEN_TYPE else null
@Throws(NetworkErrorException::class)
override fun updateCredentials(
response: AccountAuthenticatorResponse,
account: Account,
authTokenType: String?,
options: Bundle?
) = bundleOf("test" to "updateCredentials")
@Throws(NetworkErrorException::class)
override fun hasFeatures(
response: AccountAuthenticatorResponse,
account: Account, features: Array<String>
) = bundleOf(AccountManager.KEY_BOOLEAN_RESULT to false)
/**
* Provides a bundle containing a Parcel
* the Parcel packs an Intent with LoginActivity and Authenticator response (requires valid account type)
*/
private fun addAccount(response: AccountAuthenticatorResponse): Bundle {
val intent = Intent(context, LoginActivity::class.java)
.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response)
return bundleOf(AccountManager.KEY_INTENT to intent)
}
@Throws(NetworkErrorException::class)
override fun getAccountRemovalAllowed(
response: AccountAuthenticatorResponse?,
account: Account?
): Bundle {
val result = super.getAccountRemovalAllowed(response, account)
if (result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
&& !result.containsKey(AccountManager.KEY_INTENT)
) {
val allowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)
if (allowed) {
for (auth in SYNC_AUTHORITIES) {
ContentResolver.cancelSync(account, auth)
}
}
}
return result
}
}

View file

@ -1,31 +0,0 @@
package fr.free.nrw.commons.auth;
import android.accounts.AbstractAccountAuthenticator;
import android.content.Intent;
import android.os.IBinder;
import androidx.annotation.Nullable;
import fr.free.nrw.commons.di.CommonsDaggerService;
/**
* Handles the Auth service of the App, see AndroidManifests for details
* (Uses Dagger 2 as injector)
*/
public class WikiAccountAuthenticatorService extends CommonsDaggerService {
@Nullable
private AbstractAccountAuthenticator authenticator;
@Override
public void onCreate() {
super.onCreate();
authenticator = new WikiAccountAuthenticator(this);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return authenticator == null ? null : authenticator.getIBinder();
}
}

View file

@ -0,0 +1,22 @@
package fr.free.nrw.commons.auth
import android.accounts.AbstractAccountAuthenticator
import android.content.Intent
import android.os.IBinder
import fr.free.nrw.commons.di.CommonsDaggerService
/**
* Handles the Auth service of the App, see AndroidManifests for details
* (Uses Dagger 2 as injector)
*/
class WikiAccountAuthenticatorService : CommonsDaggerService() {
private var authenticator: AbstractAccountAuthenticator? = null
override fun onCreate() {
super.onCreate()
authenticator = WikiAccountAuthenticator(this)
}
override fun onBind(intent: Intent): IBinder? =
authenticator?.iBinder
}

View file

@ -0,0 +1,217 @@
package fr.free.nrw.commons.auth.csrf
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.login.LoginCallback
import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.auth.login.LoginFailedException
import fr.free.nrw.commons.auth.login.LoginResult
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import retrofit2.Call
import retrofit2.Response
import timber.log.Timber
import java.util.concurrent.Callable
import java.util.concurrent.Executors.newSingleThreadExecutor
class CsrfTokenClient(
private val sessionManager: SessionManager,
private val csrfTokenInterface: CsrfTokenInterface,
private val loginClient: LoginClient,
private val logoutClient: LogoutClient,
) {
private var retries = 0
private var csrfTokenCall: Call<MwQueryResponse?>? = null
@Throws(Throwable::class)
fun getTokenBlocking(): String {
var token = ""
val userName = sessionManager.userName ?: ""
val password = sessionManager.password ?: ""
for (retry in 0 until MAX_RETRIES_OF_LOGIN_BLOCKING) {
try {
if (retry > 0) {
// Log in explicitly
loginClient.loginBlocking(userName, password)
}
// Get CSRFToken response off the main thread.
val response =
newSingleThreadExecutor()
.submit(
Callable {
csrfTokenInterface.getCsrfTokenCall().execute()
},
).get()
if (response
.body()
?.query()
?.csrfToken()
.isNullOrEmpty()
) {
continue
}
token = response.body()!!.query()!!.csrfToken()!!
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
}
break
} catch (e: LoginFailedException) {
throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
} catch (t: Throwable) {
Timber.w(t)
}
}
if (token.isEmpty() || token == ANON_TOKEN) {
throw InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
}
return token
}
@VisibleForTesting
fun request(
service: CsrfTokenInterface,
cb: Callback,
): Call<MwQueryResponse?> =
requestToken(
service,
object : Callback {
override fun success(token: String?) {
if (sessionManager.isUserLoggedIn && token == ANON_TOKEN) {
retryWithLogin(cb) {
InvalidLoginTokenException(ANONYMOUS_TOKEN_MESSAGE)
}
} else {
cb.success(token)
}
}
override fun failure(caught: Throwable?) = retryWithLogin(cb) { caught }
override fun twoFactorPrompt() = cb.twoFactorPrompt()
override fun emailAuthPrompt() = cb.emailAuthPrompt()
},
)
@VisibleForTesting
fun requestToken(
service: CsrfTokenInterface,
cb: Callback,
): Call<MwQueryResponse?> {
val call = service.getCsrfTokenCall()
call.enqueue(
object : retrofit2.Callback<MwQueryResponse?> {
override fun onResponse(
call: Call<MwQueryResponse?>,
response: Response<MwQueryResponse?>,
) {
if (call.isCanceled) {
return
}
cb.success(response.body()!!.query()!!.csrfToken())
}
override fun onFailure(
call: Call<MwQueryResponse?>,
t: Throwable,
) {
if (call.isCanceled) {
return
}
cb.failure(t)
}
},
)
return call
}
private fun retryWithLogin(
callback: Callback,
caught: () -> Throwable?,
) {
val userName = sessionManager.userName
val password = sessionManager.password
if (retries < MAX_RETRIES && !userName.isNullOrEmpty() && !password.isNullOrEmpty()) {
retries++
logoutClient.logout()
login(userName, password, callback) {
Timber.i("retrying...")
cancel()
csrfTokenCall = request(csrfTokenInterface, callback)
}
} else {
callback.failure(caught())
}
}
private fun login(
username: String,
password: String,
callback: Callback,
retryCallback: () -> Unit,
) = loginClient.request(
username,
password,
object : LoginCallback {
override fun success(loginResult: LoginResult) {
if (loginResult.pass) {
sessionManager.updateAccount(loginResult)
retryCallback()
} else {
callback.failure(LoginFailedException(loginResult.message))
}
}
override fun twoFactorPrompt(
loginResult: LoginResult,
caught: Throwable,
token: String?,
) = callback.twoFactorPrompt()
override fun emailAuthPrompt(
loginResult: LoginResult,
caught: Throwable,
token: String?,
) = callback.emailAuthPrompt()
// Should not happen here, but call the callback just in case.
override fun passwordResetPrompt(token: String?) = callback.failure(LoginFailedException("Logged in with temporary password."))
override fun error(caught: Throwable) = callback.failure(caught)
},
)
private fun cancel() {
loginClient.cancel()
if (csrfTokenCall != null) {
csrfTokenCall!!.cancel()
csrfTokenCall = null
}
}
interface Callback {
fun success(token: String?)
fun failure(caught: Throwable?)
fun twoFactorPrompt()
fun emailAuthPrompt()
}
companion object {
private const val ANON_TOKEN = "+\\"
private const val MAX_RETRIES = 1
private const val MAX_RETRIES_OF_LOGIN_BLOCKING = 2
const val INVALID_TOKEN_ERROR_MESSAGE = "Invalid token, or login failure."
const val ANONYMOUS_TOKEN_MESSAGE = "App believes we're logged in, but got anonymous token."
}
}
class InvalidLoginTokenException(
message: String,
) : Exception(message)

View file

@ -0,0 +1,13 @@
package fr.free.nrw.commons.auth.csrf
import fr.free.nrw.commons.wikidata.WikidataConstants.MW_API_PREFIX
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Headers
interface CsrfTokenInterface {
@Headers("Cache-Control: no-cache")
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=csrf")
fun getCsrfTokenCall(): Call<MwQueryResponse?>
}

View file

@ -0,0 +1,12 @@
package fr.free.nrw.commons.auth.csrf
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieStorage
import javax.inject.Inject
class LogoutClient
@Inject
constructor(
private val store: CommonsCookieStorage,
) {
fun logout() = store.clear()
}

View file

@ -0,0 +1,21 @@
package fr.free.nrw.commons.auth.login
interface LoginCallback {
fun success(loginResult: LoginResult)
fun twoFactorPrompt(
loginResult: LoginResult,
caught: Throwable,
token: String?,
)
fun emailAuthPrompt(
loginResult: LoginResult,
caught: Throwable,
token: String?,
)
fun passwordResetPrompt(token: String?)
fun error(caught: Throwable)
}

View file

@ -0,0 +1,276 @@
package fr.free.nrw.commons.auth.login
import android.text.TextUtils
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
import fr.free.nrw.commons.wikidata.WikidataConstants.WIKIPEDIA_URL
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import timber.log.Timber
import java.io.IOException
/**
* Responsible for making login related requests to the server.
*/
class LoginClient(
private val loginInterface: LoginInterface,
) {
private var tokenCall: Call<MwQueryResponse?>? = null
private var loginCall: Call<LoginResponse?>? = null
/**
* userLanguage
* It holds the value of the user's device language code.
* For example, if user's device language is English it will hold En
* The value will be fetched when the user clicks Login Button in the LoginActivity
*/
private var userLanguage = ""
private fun getLoginToken() = loginInterface.getLoginToken()
fun request(
userName: String,
password: String,
cb: LoginCallback,
) {
cancel()
tokenCall = getLoginToken()
tokenCall!!.enqueue(
object : Callback<MwQueryResponse?> {
override fun onResponse(
call: Call<MwQueryResponse?>,
response: Response<MwQueryResponse?>,
) {
login(
userName,
password,
null,
null,
null,
response.body()!!.query()!!.loginToken(),
userLanguage,
cb,
)
}
override fun onFailure(
call: Call<MwQueryResponse?>,
caught: Throwable,
) {
if (call.isCanceled) {
return
}
cb.error(caught)
}
},
)
}
fun login(
userName: String,
password: String,
retypedPassword: String?,
twoFactorCode: String?,
emailAuthCode: String?,
loginToken: String?,
userLanguage: String,
cb: LoginCallback,
) {
this.userLanguage = userLanguage
loginCall =
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty() && retypedPassword.isNullOrEmpty()) {
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
} else {
loginInterface.postLogIn(
userName,
password,
retypedPassword,
twoFactorCode,
emailAuthCode,
loginToken,
userLanguage,
true,
)
}
loginCall!!.enqueue(
object : Callback<LoginResponse?> {
override fun onResponse(
call: Call<LoginResponse?>,
response: Response<LoginResponse?>,
) {
val loginResult = response.body()?.toLoginResult(password)
if (loginResult != null) {
if (loginResult.pass && !loginResult.userName.isNullOrEmpty()) {
// The server could do some transformations on user names, e.g. on some
// wikis is uppercases the first letter.
getExtendedInfo(loginResult.userName, loginResult, cb)
} else if ("UI" == loginResult.status) {
when (loginResult) {
is OAuthResult ->
cb.twoFactorPrompt(
loginResult,
LoginFailedException(loginResult.message),
loginToken,
)
is EmailAuthResult ->
cb.emailAuthPrompt(
loginResult,
LoginFailedException(loginResult.message),
loginToken
)
is ResetPasswordResult -> cb.passwordResetPrompt(loginToken)
is LoginResult.Result ->
cb.error(
LoginFailedException(loginResult.message),
)
}
} else {
cb.error(LoginFailedException(loginResult.message))
}
} else {
cb.error(IOException("Login failed. Unexpected response."))
}
}
override fun onFailure(
call: Call<LoginResponse?>,
t: Throwable,
) {
if (call.isCanceled) {
return
}
cb.error(t)
}
},
)
}
fun doLogin(
username: String,
password: String,
lastLoginResult: LoginResult?,
twoFactorCode: String,
userLanguage: String,
loginCallback: LoginCallback,
) {
getLoginToken().enqueue(
object : Callback<MwQueryResponse?> {
override fun onResponse(
call: Call<MwQueryResponse?>,
response: Response<MwQueryResponse?>,
) = if (response.isSuccessful) {
val loginToken = response.body()?.query()?.loginToken()
loginToken?.let {
login(username, password, null,
if (lastLoginResult is OAuthResult) twoFactorCode else null,
if (lastLoginResult is EmailAuthResult) twoFactorCode else null,
it, userLanguage, loginCallback)
} ?: run {
loginCallback.error(IOException("Failed to retrieve login token"))
}
} else {
loginCallback.error(IOException("Failed to retrieve login token"))
}
override fun onFailure(
call: Call<MwQueryResponse?>,
t: Throwable,
) {
loginCallback.error(t)
}
},
)
}
@Throws(Throwable::class)
fun loginBlocking(
userName: String,
password: String,
twoFactorCode: String? = null,
emailAuthCode: String? = null
) {
val tokenResponse = getLoginToken().execute()
if (tokenResponse
.body()
?.query()
?.loginToken()
.isNullOrEmpty()
) {
throw IOException("Unexpected response when getting login token.")
}
val loginToken = tokenResponse.body()?.query()?.loginToken()
val tempLoginCall =
if (twoFactorCode.isNullOrEmpty() && emailAuthCode.isNullOrEmpty()) {
loginInterface.postLogIn(userName, password, loginToken, userLanguage, WIKIPEDIA_URL)
} else {
loginInterface.postLogIn(
userName,
password,
null,
twoFactorCode,
emailAuthCode,
loginToken,
userLanguage,
true,
)
}
val response = tempLoginCall.execute()
val loginResponse = response.body() ?: throw IOException("Unexpected response when logging in.")
val loginResult = loginResponse.toLoginResult(password) ?: throw IOException("Unexpected response when logging in.")
if ("UI" == loginResult.status) {
if (loginResult is OAuthResult || loginResult is EmailAuthResult) {
// TODO: Find a better way to boil up the warning about 2FA
throw LoginFailedException(loginResult.message)
}
throw LoginFailedException(loginResult.message)
}
if (!loginResult.pass || TextUtils.isEmpty(loginResult.userName)) {
throw LoginFailedException(loginResult.message)
}
}
private fun getExtendedInfo(
userName: String,
loginResult: LoginResult,
cb: LoginCallback,
) = loginInterface
.getUserInfo(userName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response: MwQueryResponse? ->
loginResult.userId = response?.query()?.userInfo()?.id() ?: 0
loginResult.groups =
response?.query()?.getUserResponse(userName)?.getGroups() ?: emptySet()
cb.success(loginResult)
}, { caught: Throwable ->
Timber.e(caught, "Login succeeded but getting group information failed. ")
cb.error(caught)
})
fun cancel() {
tokenCall?.let {
it.cancel()
tokenCall = null
}
loginCall?.let {
it.cancel()
loginCall = null
}
}
}

View file

@ -0,0 +1,5 @@
package fr.free.nrw.commons.auth.login
class LoginFailedException(
message: String?,
) : Throwable(message)

View file

@ -0,0 +1,48 @@
package fr.free.nrw.commons.auth.login
import fr.free.nrw.commons.wikidata.WikidataConstants.MW_API_PREFIX
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Observable
import retrofit2.Call
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.Query
interface LoginInterface {
@Headers("Cache-Control: no-cache")
@GET(MW_API_PREFIX + "action=query&meta=tokens&type=login")
fun getLoginToken(): Call<MwQueryResponse?>
@Headers("Cache-Control: no-cache")
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=clientlogin&rememberMe=")
fun postLogIn(
@Field("username") user: String?,
@Field("password") pass: String?,
@Field("logintoken") token: String?,
@Field("uselang") userLanguage: String?,
@Field("loginreturnurl") url: String?,
): Call<LoginResponse?>
@Headers("Cache-Control: no-cache")
@FormUrlEncoded
@POST(MW_API_PREFIX + "action=clientlogin&rememberMe=")
fun postLogIn(
@Field("username") user: String?,
@Field("password") pass: String?,
@Field("retype") retypedPass: String?,
@Field("OATHToken") twoFactorCode: String?,
@Field("token") emailAuthToken: String?,
@Field("logintoken") loginToken: String?,
@Field("uselang") userLanguage: String?,
@Field("logincontinue") loginContinue: Boolean,
): Call<LoginResponse?>
@GET(MW_API_PREFIX + "action=query&meta=userinfo&list=users&usprop=groups|cancreate")
fun getUserInfo(
@Query("ususers") userName: String,
): Observable<MwQueryResponse?>
}

View file

@ -0,0 +1,64 @@
package fr.free.nrw.commons.auth.login
import com.google.gson.annotations.SerializedName
import fr.free.nrw.commons.auth.login.LoginResult.OAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.EmailAuthResult
import fr.free.nrw.commons.auth.login.LoginResult.ResetPasswordResult
import fr.free.nrw.commons.auth.login.LoginResult.Result
import fr.free.nrw.commons.wikidata.mwapi.MwServiceError
class LoginResponse {
@SerializedName("error")
val error: MwServiceError? = null
@SerializedName("clientlogin")
private val clientLogin: ClientLogin? = null
fun toLoginResult(password: String): LoginResult? = clientLogin?.toLoginResult(password)
}
internal class ClientLogin {
private val status: String? = null
private val requests: List<Request>? = null
private val message: String? = null
@SerializedName("username")
private val userName: String? = null
fun toLoginResult(password: String): LoginResult {
var userMessage = message
if ("UI" == status) {
requests?.forEach { request ->
request.id()?.let {
if (it.endsWith("TOTPAuthenticationRequest")) {
return OAuthResult(status, userName, password, message)
} else if (it.endsWith("EmailAuthAuthenticationRequest")) {
return EmailAuthResult(status, userName, password, message)
} else if (it.endsWith("PasswordAuthenticationRequest")) {
return ResetPasswordResult(status, userName, password, message)
}
}
}
} else if ("PASS" != status && "FAIL" != status) {
// TODO: String resource -- Looks like needed for others in this class too
userMessage = "An unknown error occurred."
}
return Result(status ?: "", userName, password, userMessage)
}
}
internal class Request {
private val id: String? = null
private val required: String? = null
private val provider: String? = null
private val account: String? = null
internal val fields: Map<String, RequestField>? = null
fun id(): String? = id
}
internal class RequestField {
private val type: String? = null
private val label: String? = null
internal val help: String? = null
}

View file

@ -0,0 +1,40 @@
package fr.free.nrw.commons.auth.login
sealed class LoginResult(
val status: String,
val userName: String?,
val password: String?,
val message: String?,
) {
var userId = 0
var groups = emptySet<String>()
val pass: Boolean get() = "PASS" == status
class Result(
status: String,
userName: String?,
password: String?,
message: String?,
) : LoginResult(status, userName, password, message)
class OAuthResult(
status: String,
userName: String?,
password: String?,
message: String?,
) : LoginResult(status, userName, password, message)
class EmailAuthResult(
status: String,
userName: String?,
password: String?,
message: String?,
) : LoginResult(status, userName, password, message)
class ResetPasswordResult(
status: String,
userName: String?,
password: String?,
message: String?,
) : LoginResult(status, userName, password, message)
}

View file

@ -1,111 +0,0 @@
package fr.free.nrw.commons.bookmarks;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentManager;
import com.google.android.material.tabs.TabLayout;
import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.explore.ParentViewPager;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.theme.BaseActivity;
import javax.inject.Inject;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import javax.inject.Named;
public class BookmarkFragment extends CommonsDaggerSupportFragment {
private FragmentManager supportFragmentManager;
private BookmarksPagerAdapter adapter;
@BindView(R.id.viewPagerBookmarks)
ParentViewPager viewPager;
@BindView(R.id.tab_layout)
TabLayout tabLayout;
@BindView(R.id.fragmentContainer)
FrameLayout fragmentContainer;
@Inject
ContributionController controller;
/**
* To check if the user is loggedIn or not.
*/
@Inject
@Named("default_preferences")
public
JsonKvStore applicationKvStore;
@NonNull
public static BookmarkFragment newInstance() {
BookmarkFragment fragment = new BookmarkFragment();
fragment.setRetainInstance(true);
return fragment;
}
public void setScroll(boolean canScroll) {
viewPager.setCanScroll(canScroll);
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View view = inflater.inflate(R.layout.fragment_bookmarks, container, false);
ButterKnife.bind(this, view);
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
supportFragmentManager = getChildFragmentManager();
adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(),
applicationKvStore.getBoolean("login_skipped"));
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
((MainActivity) getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
setupTabLayout();
return view;
}
/**
* This method sets up the tab layout. If the adapter has only one element it sets the
* visibility of tabLayout to gone.
*/
public void setupTabLayout() {
tabLayout.setVisibility(View.VISIBLE);
if (adapter.getCount() == 1) {
tabLayout.setVisibility(View.GONE);
}
}
public void onBackPressed() {
if (((BookmarkListRootFragment) (adapter.getItem(tabLayout.getSelectedTabPosition())))
.backPressed()) {
// The event is handled internally by the adapter , no further action required.
return;
}
// Event is not handled by the adapter ( performed back action ) change action bar.
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}

View file

@ -0,0 +1,98 @@
package fr.free.nrw.commons.bookmarks
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.databinding.FragmentBookmarksBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.theme.BaseActivity
import javax.inject.Inject
import javax.inject.Named
class BookmarkFragment : CommonsDaggerSupportFragment() {
private var adapter: BookmarksPagerAdapter? = null
@JvmField
var binding: FragmentBookmarksBinding? = null
@JvmField
@Inject
var controller: ContributionController? = null
/**
* To check if the user is loggedIn or not.
*/
@JvmField
@Inject
@Named("default_preferences")
var applicationKvStore: JsonKvStore? = null
fun setScroll(canScroll: Boolean) {
binding?.let {
it.viewPagerBookmarks.canScroll = canScroll
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
binding = FragmentBookmarksBinding.inflate(inflater, container, false)
// Activity can call methods in the fragment by acquiring a
// reference to the Fragment from FragmentManager, using findFragmentById()
val supportFragmentManager = childFragmentManager
adapter = BookmarksPagerAdapter(
supportFragmentManager, requireContext(),
applicationKvStore!!.getBoolean("login_skipped")
)
binding!!.viewPagerBookmarks.adapter = adapter
binding!!.tabLayout.setupWithViewPager(binding!!.viewPagerBookmarks)
(requireActivity() as MainActivity).showTabs()
(requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
setupTabLayout()
return binding!!.root
}
/**
* This method sets up the tab layout. If the adapter has only one element it sets the
* visibility of tabLayout to gone.
*/
fun setupTabLayout() {
binding!!.tabLayout.visibility = View.VISIBLE
if (adapter!!.count == 1) {
binding!!.tabLayout.visibility = View.GONE
}
}
fun onBackPressed() {
if (((adapter!!.getItem(binding!!.tabLayout.selectedTabPosition)) as BookmarkListRootFragment).backPressed()) {
// The event is handled internally by the adapter , no further action required.
return
}
// Event is not handled by the adapter ( performed back action ) change action bar.
(requireActivity() as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
companion object {
fun newInstance(): BookmarkFragment = BookmarkFragment().apply {
retainInstance = true
}
}
}

View file

@ -1,256 +0,0 @@
package fr.free.nrw.commons.bookmarks;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
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 butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
import fr.free.nrw.commons.category.CategoryImagesCallback;
import fr.free.nrw.commons.category.GridViewAdapter;
import fr.free.nrw.commons.contributions.MainActivity;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.navtab.NavTab;
import java.util.ArrayList;
import java.util.Iterator;
public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements
FragmentManager.OnBackStackChangedListener,
MediaDetailPagerFragment.MediaDetailProvider,
AdapterView.OnItemClickListener, CategoryImagesCallback {
private MediaDetailPagerFragment mediaDetails;
//private BookmarkPicturesFragment bookmarkPicturesFragment;
private BookmarkLocationsFragment bookmarkLocationsFragment;
public Fragment listFragment;
private BookmarksPagerAdapter bookmarksPagerAdapter;
@BindView(R.id.explore_container)
FrameLayout container;
public BookmarkListRootFragment() {
//empty constructor necessary otherwise crashes on recreate
}
public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) {
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();
if(orderItem == 2) {
listFragment = new BookmarkItemsFragment();
}
}
Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", title);
listFragment.setArguments(featuredArguments);
this.bookmarksPagerAdapter = bookmarksPagerAdapter;
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
ButterKnife.bind(this, view);
return view;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null) {
setFragment(listFragment, mediaDetails);
}
}
public void setFragment(Fragment fragment, Fragment otherFragment) {
if (fragment.isAdded() && otherFragment != null) {
getChildFragmentManager()
.beginTransaction()
.hide(otherFragment)
.show(fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit();
getChildFragmentManager().executePendingTransactions();
} else if (fragment.isAdded() && otherFragment == null) {
getChildFragmentManager()
.beginTransaction()
.show(fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit();
getChildFragmentManager().executePendingTransactions();
} else if (!fragment.isAdded() && otherFragment != null) {
getChildFragmentManager()
.beginTransaction()
.hide(otherFragment)
.add(R.id.explore_container, fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit();
getChildFragmentManager().executePendingTransactions();
} else if (!fragment.isAdded()) {
getChildFragmentManager()
.beginTransaction()
.replace(R.id.explore_container, fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit();
getChildFragmentManager().executePendingTransactions();
}
}
public void removeFragment(Fragment fragment) {
getChildFragmentManager()
.beginTransaction()
.remove(fragment)
.commit();
getChildFragmentManager().executePendingTransactions();
}
@Override
public void onAttach(final Context context) {
super.onAttach(context);
}
@Override
public void onMediaClicked(int position) {
Log.d("deneme8", "on media clicked");
/*container.setVisibility(View.VISIBLE);
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
mediaDetails = new MediaDetailPagerFragment(false, true, position);
setFragment(mediaDetails, bookmarkPicturesFragment);*/
}
/**
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
*
* @param i It is the index of which media object is to be returned which is same as current
* index of viewPager.
* @return Media Object
*/
@Override
public Media getMediaAtPosition(int i) {
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
// not yet ready to return data
return null;
} else {
return (Media) bookmarksPagerAdapter.getMediaAdapter().getItem(i);
}
}
/**
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
* same number of media items as that of media elements in adapter.
*
* @return Total Media count in the adapter
*/
@Override
public int getTotalMediaCount() {
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
return 0;
}
return bookmarksPagerAdapter.getMediaAdapter().getCount();
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* Reload media detail fragment once media is nominated
*
* @param index item position that has been nominated
*/
@Override
public void refreshNominatedMedia(int index) {
if (mediaDetails != null && !listFragment.isVisible()) {
removeFragment(mediaDetails);
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
((BookmarkFragment) getParentFragment()).setScroll(false);
setFragment(mediaDetails, listFragment);
mediaDetails.showImage(index);
}
}
/**
* This method is called on success of API call for featured images or mobile uploads. The
* viewpager will notified that number of items have changed.
*/
@Override
public void viewPagerNotifyDataSetChanged() {
if (mediaDetails != null) {
mediaDetails.notifyDataSetChanged();
}
}
public boolean backPressed() {
//check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
if (mediaDetails != null) {
if (mediaDetails.isVisible()) {
// todo add get list fragment
((BookmarkFragment) getParentFragment()).setupTabLayout();
ArrayList<Integer> removed = mediaDetails.getRemovedItems();
removeFragment(mediaDetails);
((BookmarkFragment) getParentFragment()).setScroll(true);
setFragment(listFragment, mediaDetails);
((MainActivity) getActivity()).showTabs();
if (listFragment instanceof BookmarkPicturesFragment) {
GridViewAdapter adapter = ((GridViewAdapter) ((BookmarkPicturesFragment) listFragment)
.getAdapter());
Iterator i = removed.iterator();
while (i.hasNext()) {
adapter.remove(adapter.getItem((int) i.next()));
}
mediaDetails.clearRemoved();
}
} else {
moveToContributionsFragment();
}
} else {
moveToContributionsFragment();
}
// notify mediaDetails did not handled the backPressed further actions required.
return false;
}
void moveToContributionsFragment() {
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
((MainActivity) getActivity()).showTabs();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("deneme8", "on media clicked");
container.setVisibility(View.VISIBLE);
((BookmarkFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
((BookmarkFragment) getParentFragment()).setScroll(false);
setFragment(mediaDetails, listFragment);
mediaDetails.showImage(position);
}
@Override
public void onBackStackChanged() {
}
}

View file

@ -0,0 +1,226 @@
package fr.free.nrw.commons.bookmarks
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
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
import fr.free.nrw.commons.category.CategoryImagesCallback
import fr.free.nrw.commons.category.GridViewAdapter
import fr.free.nrw.commons.contributions.MainActivity
import fr.free.nrw.commons.databinding.FragmentFeaturedRootBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment.Companion.newInstance
import fr.free.nrw.commons.media.MediaDetailProvider
import fr.free.nrw.commons.navtab.NavTab
import timber.log.Timber
class BookmarkListRootFragment : CommonsDaggerSupportFragment,
FragmentManager.OnBackStackChangedListener, MediaDetailProvider, OnItemClickListener,
CategoryImagesCallback {
private var mediaDetails: MediaDetailPagerFragment? = null
private val bookmarkLocationsFragment: BookmarkLocationsFragment? = null
var listFragment: Fragment? = null
private var bookmarksPagerAdapter: BookmarksPagerAdapter? = null
var binding: FragmentFeaturedRootBinding? = null
constructor()
constructor(bundle: Bundle, bookmarksPagerAdapter: BookmarksPagerAdapter) {
val title = bundle.getString("categoryName")
val order = bundle.getInt("order")
val orderItem = bundle.getInt("orderItem")
when (order) {
0 -> listFragment = BookmarkPicturesFragment()
1 -> listFragment = BookmarkLocationsFragment()
3 -> listFragment = BookmarkCategoriesFragment()
}
if (orderItem == 2) {
listFragment = BookmarkItemsFragment()
}
val featuredArguments = Bundle()
featuredArguments.putString("categoryName", title)
listFragment!!.setArguments(featuredArguments)
this.bookmarksPagerAdapter = bookmarksPagerAdapter
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreate(savedInstanceState)
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
return binding!!.getRoot()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (savedInstanceState == null) {
setFragment(listFragment!!, mediaDetails)
}
}
fun setFragment(fragment: Fragment, otherFragment: Fragment?) {
if (fragment.isAdded() && otherFragment != null) {
getChildFragmentManager()
.beginTransaction()
.hide(otherFragment)
.show(fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit()
getChildFragmentManager().executePendingTransactions()
} else if (fragment.isAdded() && otherFragment == null) {
getChildFragmentManager()
.beginTransaction()
.show(fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit()
getChildFragmentManager().executePendingTransactions()
} else if (!fragment.isAdded() && otherFragment != null) {
getChildFragmentManager()
.beginTransaction()
.hide(otherFragment)
.add(R.id.explore_container, fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit()
getChildFragmentManager().executePendingTransactions()
} else if (!fragment.isAdded()) {
getChildFragmentManager()
.beginTransaction()
.replace(R.id.explore_container, fragment)
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
.commit()
getChildFragmentManager().executePendingTransactions()
}
}
fun removeFragment(fragment: Fragment) {
getChildFragmentManager()
.beginTransaction()
.remove(fragment)
.commit()
getChildFragmentManager().executePendingTransactions()
}
override fun onMediaClicked(position: Int) {
Timber.d("on media clicked")
/*container.setVisibility(View.VISIBLE);
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
mediaDetails = new MediaDetailPagerFragment(false, true, position);
setFragment(mediaDetails, bookmarkPicturesFragment);*/
}
/**
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
*
* @param i It is the index of which media object is to be returned which is same as current
* index of viewPager.
* @return Media Object
*/
override fun getMediaAtPosition(i: Int): Media? =
bookmarksPagerAdapter!!.mediaAdapter?.getItem(i) as Media?
/**
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
* same number of media items as that of media elements in adapter.
*
* @return Total Media count in the adapter
*/
override fun getTotalMediaCount(): Int =
bookmarksPagerAdapter!!.mediaAdapter?.count ?: 0
override fun getContributionStateAt(position: Int): Int? {
return null
}
/**
* Reload media detail fragment once media is nominated
*
* @param index item position that has been nominated
*/
override fun refreshNominatedMedia(index: Int) {
if (mediaDetails != null && !listFragment!!.isVisible()) {
removeFragment(mediaDetails!!)
mediaDetails = newInstance(false, true)
(parentFragment as BookmarkFragment).setScroll(false)
setFragment(mediaDetails!!, listFragment)
mediaDetails!!.showImage(index)
}
}
/**
* This method is called on success of API call for featured images or mobile uploads. The
* viewpager will notified that number of items have changed.
*/
override fun viewPagerNotifyDataSetChanged() {
if (mediaDetails != null) {
mediaDetails!!.notifyDataSetChanged()
}
}
fun backPressed(): Boolean {
//check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
if (mediaDetails != null) {
if (mediaDetails!!.isVisible()) {
// todo add get list fragment
(parentFragment as BookmarkFragment).setupTabLayout()
val removed: ArrayList<Int> = mediaDetails!!.removedItems
removeFragment(mediaDetails!!)
(parentFragment as BookmarkFragment).setScroll(true)
setFragment(listFragment!!, mediaDetails)
(requireActivity() as MainActivity).showTabs()
if (listFragment is BookmarkPicturesFragment) {
val adapter = ((listFragment as BookmarkPicturesFragment)
.getAdapter() as GridViewAdapter?)
val i: MutableIterator<*> = removed.iterator()
while (i.hasNext()) {
adapter!!.remove(adapter.getItem(i.next() as Int))
}
mediaDetails!!.clearRemoved()
}
} else {
moveToContributionsFragment()
}
} else {
moveToContributionsFragment()
}
// notify mediaDetails did not handled the backPressed further actions required.
return false
}
fun moveToContributionsFragment() {
(requireActivity() as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
(requireActivity() as MainActivity).showTabs()
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
Timber.d("on media clicked")
binding!!.exploreContainer.visibility = View.VISIBLE
(parentFragment as BookmarkFragment).binding!!.tabLayout.setVisibility(View.GONE)
mediaDetails = newInstance(false, true)
(parentFragment as BookmarkFragment).setScroll(false)
setFragment(mediaDetails!!, listFragment)
mediaDetails!!.showImage(position)
}
override fun onBackStackChanged() = Unit
override fun onDestroy() {
super.onDestroy()
binding = null
}
}

View file

@ -1,32 +0,0 @@
package fr.free.nrw.commons.bookmarks;
import androidx.fragment.app.Fragment;
/**
* Data class for handling a bookmark fragment and it title
*/
public class BookmarkPages {
private Fragment page;
private String title;
BookmarkPages(Fragment fragment, String title) {
this.title = title;
this.page = fragment;
}
/**
* Return the fragment
* @return fragment object
*/
public Fragment getPage() {
return page;
}
/**
* Return the fragment title
* @return title
*/
public String getTitle() {
return title;
}
}

View file

@ -0,0 +1,8 @@
package fr.free.nrw.commons.bookmarks
import androidx.fragment.app.Fragment
data class BookmarkPages (
val page: Fragment? = null,
val title: String? = null
)

View file

@ -1,88 +0,0 @@
package fr.free.nrw.commons.bookmarks;
import android.content.Context;
import android.os.Bundle;
import android.widget.ListAdapter;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
public class BookmarksPagerAdapter extends FragmentPagerAdapter {
private ArrayList<BookmarkPages> pages;
/**
* Default Constructor
* @param fm
* @param context
* @param onlyPictures is true if the fragment requires only BookmarkPictureFragment
* (i.e. when no user is logged in).
*/
BookmarksPagerAdapter(FragmentManager fm, Context context,boolean onlyPictures) {
super(fm);
pages = new ArrayList<>();
Bundle picturesBundle = new Bundle();
picturesBundle.putString("categoryName", context.getString(R.string.title_page_bookmarks_pictures));
picturesBundle.putInt("order", 0);
pages.add(new BookmarkPages(
new BookmarkListRootFragment(picturesBundle, this),
context.getString(R.string.title_page_bookmarks_pictures)));
if (!onlyPictures) {
// if onlyPictures is false we also add the location fragment.
Bundle locationBundle = new Bundle();
locationBundle.putString("categoryName",
context.getString(R.string.title_page_bookmarks_locations));
locationBundle.putInt("order", 1);
pages.add(new BookmarkPages(
new BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_locations)));
locationBundle.putInt("orderItem", 2);
pages.add(new BookmarkPages(
new BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_items)));
}
notifyDataSetChanged();
}
@Override
public Fragment getItem(int position) {
return pages.get(position).getPage();
}
@Override
public int getCount() {
return pages.size();
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return pages.get(position).getTitle();
}
/**
* Return the Adapter used to display the picture gridview
* @return adapter
*/
public ListAdapter getMediaAdapter() {
BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment);
return fragment.getAdapter();
}
/**
* Update the pictures list for the bookmark fragment
*/
public void requestPictureListUpdate() {
BookmarkPicturesFragment fragment = (BookmarkPicturesFragment)(((BookmarkListRootFragment)pages.get(0).getPage()).listFragment);
fragment.onResume();
}
}

View file

@ -0,0 +1,82 @@
package fr.free.nrw.commons.bookmarks
import android.content.Context
import android.widget.ListAdapter
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import fr.free.nrw.commons.R
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment
class BookmarksPagerAdapter internal constructor(
fm: FragmentManager, context: Context, onlyPictures: Boolean
) : FragmentPagerAdapter(fm) {
private val pages = mutableListOf<BookmarkPages>()
/**
* Default Constructor
* @param fm
* @param context
* @param onlyPictures is true if the fragment requires only BookmarkPictureFragment
* (i.e. when no user is logged in).
*/
init {
pages.add(
BookmarkPages(
BookmarkListRootFragment(
bundleOf(
"categoryName" to context.getString(R.string.title_page_bookmarks_pictures),
"order" to 0
), this
), context.getString(R.string.title_page_bookmarks_pictures)
)
)
if (!onlyPictures) {
// if onlyPictures is false we also add the location fragment.
val locationBundle = bundleOf(
"categoryName" to context.getString(R.string.title_page_bookmarks_locations),
"order" to 1
)
pages.add(
BookmarkPages(
BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_locations)
)
)
locationBundle.putInt("orderItem", 2)
pages.add(
BookmarkPages(
BookmarkListRootFragment(locationBundle, this),
context.getString(R.string.title_page_bookmarks_items)
)
)
}
pages.add(
BookmarkPages(
BookmarkListRootFragment(
bundleOf(
"categoryName" to context.getString(R.string.title_page_bookmarks_categories),
"order" to 3
), this),
context.getString(R.string.title_page_bookmarks_categories)
)
)
notifyDataSetChanged()
}
override fun getItem(position: Int): Fragment = pages[position].page!!
override fun getCount(): Int = pages.size
override fun getPageTitle(position: Int): CharSequence? = pages[position].title
/**
* Return the Adapter used to display the picture gridview
* @return adapter
*/
val mediaAdapter: ListAdapter?
get() = (((pages[0].page as BookmarkListRootFragment).listFragment) as BookmarkPicturesFragment).getAdapter()
}

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