mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Compare commits
129 commits
v5.5.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63f621cb56 | ||
|
|
e81f916626 | ||
|
|
28fa7b1a20 | ||
|
|
aae9d4a387 | ||
|
|
6873f63cf8 | ||
|
|
2d0255e5fb | ||
|
|
32ae406cca | ||
|
|
3e04a1f036 | ||
|
|
6487191394 | ||
|
|
beaf211f39 | ||
|
|
3549789cdf | ||
|
|
def33552f9 | ||
|
|
3a55583460 | ||
|
|
717a855149 | ||
|
|
29b6d0f8fe | ||
|
|
b5b5d8a8e4 | ||
|
|
714e5f8a4b | ||
|
|
7d96e94689 | ||
|
|
7a865df909 | ||
|
|
864884e7b2 | ||
|
|
1ecaf09f21 | ||
|
|
1ff2a28326 | ||
|
|
b48905a153 | ||
|
|
09c8d987e1 | ||
|
|
2e52adbef8 | ||
|
|
61c9de6fcc | ||
|
|
41d95814c9 | ||
|
|
c4cb65fc3c | ||
|
|
a1c5974e93 | ||
|
|
0c244f369c | ||
|
|
b6014b017c | ||
|
|
91ea4a6e7b | ||
|
|
1e51c4c5d0 | ||
|
|
fbd28a0564 | ||
|
|
d0965206cd | ||
|
|
bb330c1771 | ||
|
|
14d6c80241 | ||
|
|
4c621364c9 | ||
|
|
2a9d5db51e | ||
|
|
b8d340fbe8 | ||
|
|
dd1814c793 | ||
|
|
adb6181e9f | ||
|
|
0a4b179db5 | ||
|
|
e78db7fa08 | ||
|
|
7be615bacb | ||
|
|
95d58023c7 | ||
|
|
7b8fbc239b | ||
|
|
30d1107cef | ||
|
|
fe16c44caa | ||
|
|
4ed9ad5085 | ||
|
|
755d8311dc | ||
|
|
b6457cc6b9 | ||
|
|
2d51a7ce9a | ||
|
|
0ade0705e2 | ||
|
|
6bc25ccd9b | ||
|
|
ed7007fc8c | ||
|
|
71ad6a2ce5 | ||
|
|
e9a1af0f52 | ||
|
|
10c384ffa7 | ||
|
|
4e51977fb6 | ||
|
|
d632c268ae | ||
|
|
be371e5236 | ||
|
|
25d3068faf | ||
|
|
179c7c1855 | ||
|
|
8018000584 | ||
|
|
657af4fe04 | ||
|
|
219fcd3dd8 | ||
|
|
2e9726b84f | ||
|
|
64c6b0c8d0 | ||
|
|
fcc63b9f09 | ||
|
|
a283ffe2bc | ||
|
|
2811b181b7 | ||
|
|
730f314200 | ||
|
|
81da5c9a1a | ||
|
|
a59bf64677 | ||
|
|
e2c8f85a5b | ||
|
|
dd96c64182 | ||
|
|
9ba702eaa9 | ||
|
|
296b4c1f52 | ||
|
|
48e7effd0a | ||
|
|
b9f353bb5a | ||
|
|
c22e8447b3 | ||
|
|
f810a2d49b | ||
|
|
4f3f7b97fd | ||
|
|
718c466505 | ||
|
|
b8a558303b | ||
|
|
a892aa6dee | ||
|
|
5a6b3cbf09 | ||
|
|
5bdfbf5f6f | ||
|
|
1d7d2801e4 | ||
|
|
5201af70cd | ||
|
|
d0e95bc3c2 | ||
|
|
ffb9af1f1c | ||
|
|
6dcce45c59 | ||
|
|
6f36cae767 | ||
|
|
516039c91d | ||
|
|
8de57304bf | ||
|
|
869371b485 | ||
|
|
929711da98 | ||
|
|
b2816e1459 | ||
|
|
532bd8baa6 | ||
|
|
90ab7a2766 | ||
|
|
ee33a9350f | ||
|
|
f1e6f1ad31 | ||
|
|
11e3e37263 | ||
|
|
da694022ac | ||
|
|
29ade1e5b7 | ||
|
|
88565b70c5 | ||
|
|
e5dbcfc2a1 | ||
|
|
0cda8e4d70 | ||
|
|
7500b6d374 | ||
|
|
a4c7a9c4f7 | ||
|
|
8fc7e1039b | ||
|
|
79f52db929 | ||
|
|
13048cc2fd | ||
|
|
66395b9871 | ||
|
|
65f41beed8 | ||
|
|
f98b49608e | ||
|
|
3bd0ec4466 | ||
|
|
4befff8f42 | ||
|
|
89436b0a75 | ||
|
|
6de5a07e0d | ||
|
|
27b9d70333 | ||
|
|
9a94dc2548 | ||
|
|
b1a8308aaf | ||
|
|
ad7dddaac4 | ||
|
|
5d7f42d127 | ||
|
|
d9e8917418 | ||
|
|
09da7b8d68 |
390 changed files with 14767 additions and 12034 deletions
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
1
.idea/codeStyles/Project.xml
generated
1
.idea/codeStyles/Project.xml
generated
|
|
@ -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" />
|
||||
|
|
|
|||
36
CHANGELOG.md
36
CHANGELOG.md
|
|
@ -1,5 +1,41 @@
|
|||
# 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
|
||||
|
|
|
|||
11
README.md
11
README.md
|
|
@ -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/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/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/12448084?v=4" width="100px;"/><br /><sub><b>sivaraam</b></sub>](https://github.com/sivaraam) | [<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/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/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) | [<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/54016427?v=4" width="100px;"/><br /><sub><b>4D17Y4</b></sub>](https://github.com/4D17Y4) | [<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/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).
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ if (isRunningOnTravisAndIsNotPRBuild) {
|
|||
|
||||
android {
|
||||
namespace = "fr.free.nrw.commons"
|
||||
compileSdk = 34
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "fr.free.nrw.commons"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 1053
|
||||
versionName = "5.5.0"
|
||||
targetSdk = 35
|
||||
versionCode = 1059
|
||||
versionName = "6.1.0"
|
||||
|
||||
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
@ -226,6 +226,7 @@ dependencies {
|
|||
implementation(libs.rxbinding)
|
||||
implementation(libs.rxbinding.appcompat)
|
||||
implementation(libs.facebook.fresco)
|
||||
implementation(libs.facebook.fresco.middleware)
|
||||
implementation(libs.apache.commons.lang3)
|
||||
|
||||
// UI
|
||||
|
|
@ -347,6 +348,7 @@ dependencies {
|
|||
// Kotlin + coroutines
|
||||
implementation(libs.androidx.work.runtime.ktx)
|
||||
implementation(libs.androidx.work.runtime)
|
||||
implementation(libs.kotlinx.coroutines.rx2)
|
||||
testImplementation(libs.androidx.work.testing)
|
||||
|
||||
//Glide
|
||||
|
|
|
|||
|
|
@ -66,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
|
||||
|
|
|
|||
|
|
@ -57,8 +57,7 @@
|
|||
tools:replace="android:appComponentFactory">
|
||||
<activity
|
||||
android:name=".activity.SingleWebViewActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_single_web_view" />
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".nearby.WikidataFeedback"
|
||||
android:exported="false" />
|
||||
|
|
@ -85,6 +84,7 @@
|
|||
android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" />
|
||||
<activity
|
||||
android:name=".auth.LoginActivity"
|
||||
android:windowSoftInputMode="adjustPan"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
android:exported="true"
|
||||
android:hardwareAccelerated="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter android:label="@string/intent_share_upload_label">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
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
|
||||
|
|
@ -16,6 +18,10 @@ 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
|
||||
|
|
@ -42,6 +48,7 @@ class AboutActivity : BaseActivity() {
|
|||
*/
|
||||
binding = ActivityAboutBinding.inflate(layoutInflater)
|
||||
val view: View = binding!!.root
|
||||
applyEdgeToEdgeTopInsets(binding!!.toolbarLayout)
|
||||
setContentView(view)
|
||||
|
||||
setSupportActionBar(binding!!.toolbarBinding.toolbar)
|
||||
|
|
@ -59,30 +66,12 @@ class AboutActivity : BaseActivity() {
|
|||
binding!!.aboutImprove.setHtmlText(improveText)
|
||||
binding!!.aboutVersion.text = applicationContext.getVersionNameWithSha()
|
||||
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutFaq, R.string.about_faq,
|
||||
applicationContext
|
||||
)
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutRateUs, R.string.about_rate_us,
|
||||
applicationContext
|
||||
)
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutUserGuide, R.string.user_guide,
|
||||
applicationContext
|
||||
)
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutPrivacyPolicy, R.string.about_privacy_policy,
|
||||
applicationContext
|
||||
)
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutTranslate, R.string.about_translate,
|
||||
applicationContext
|
||||
)
|
||||
Utils.setUnderlinedText(
|
||||
binding!!.aboutCredits, R.string.about_credits,
|
||||
applicationContext
|
||||
)
|
||||
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.
|
||||
|
|
@ -106,47 +95,56 @@ class AboutActivity : BaseActivity() {
|
|||
fun launchFacebook(view: View?) {
|
||||
val intent: Intent
|
||||
try {
|
||||
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.FACEBOOK_APP_URL))
|
||||
intent = Intent(ACTION_VIEW, Urls.FACEBOOK_APP_URL.toUri())
|
||||
intent.setPackage(Urls.FACEBOOK_PACKAGE_NAME)
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.FACEBOOK_WEB_URL))
|
||||
handleWebUrl(this, Urls.FACEBOOK_WEB_URL.toUri())
|
||||
}
|
||||
}
|
||||
|
||||
fun launchGithub(view: View?) {
|
||||
val intent: Intent
|
||||
try {
|
||||
intent = Intent(Intent.ACTION_VIEW, Uri.parse(Urls.GITHUB_REPO_URL))
|
||||
intent = Intent(ACTION_VIEW, Urls.GITHUB_REPO_URL.toUri())
|
||||
intent.setPackage(Urls.GITHUB_PACKAGE_NAME)
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.GITHUB_REPO_URL))
|
||||
handleWebUrl(this, Urls.GITHUB_REPO_URL.toUri())
|
||||
}
|
||||
}
|
||||
|
||||
fun launchWebsite(view: View?) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.WEBSITE_URL))
|
||||
handleWebUrl(this, Urls.WEBSITE_URL.toUri())
|
||||
}
|
||||
|
||||
fun launchRatings(view: View?) {
|
||||
Utils.rateApp(this)
|
||||
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?) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.CREDITS_URL))
|
||||
handleWebUrl(this, Urls.CREDITS_URL.toUri())
|
||||
}
|
||||
|
||||
fun launchUserGuide(view: View?) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.USER_GUIDE_URL))
|
||||
handleWebUrl(this, Urls.USER_GUIDE_URL.toUri())
|
||||
}
|
||||
|
||||
fun launchPrivacyPolicy(view: View?) {
|
||||
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
|
||||
handleWebUrl(this, BuildConfig.PRIVACY_POLICY_URL.toUri())
|
||||
}
|
||||
|
||||
fun launchFrequentlyAskedQuesions(view: View?) {
|
||||
Utils.handleWebUrl(this, Uri.parse(Urls.FAQ_URL))
|
||||
handleWebUrl(this, Urls.FAQ_URL.toUri())
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
|
@ -193,7 +191,7 @@ class AboutActivity : BaseActivity() {
|
|||
|
||||
val positiveButtonRunnable = Runnable {
|
||||
val langCode = instance.languageLookUpTable!!.getCodes()[spinner.selectedItemPosition]
|
||||
Utils.handleWebUrl(this@AboutActivity, Uri.parse(Urls.TRANSLATE_WIKI_URL + langCode))
|
||||
handleWebUrl(this@AboutActivity, (Urls.TRANSLATE_WIKI_URL + langCode).toUri())
|
||||
}
|
||||
showAlertDialog(
|
||||
this,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
10
app/src/main/java/fr/free/nrw/commons/BasePresenter.kt
Normal file
10
app/src/main/java/fr/free/nrw/commons/BasePresenter.kt
Normal 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()
|
||||
}
|
||||
|
|
@ -15,9 +15,8 @@ import com.facebook.drawee.backends.pipeline.Fresco
|
|||
import com.facebook.imagepipeline.core.ImagePipelineConfig
|
||||
import fr.free.nrw.commons.auth.LoginActivity
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
|
||||
import fr.free.nrw.commons.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
|
||||
|
|
@ -257,8 +256,8 @@ class CommonsApplication : MultiDexApplication() {
|
|||
} catch (e: SQLiteException) {
|
||||
Timber.e(e)
|
||||
}
|
||||
BookmarkPicturesDao.Table.onDelete(db)
|
||||
BookmarkItemsDao.Table.onDelete(db)
|
||||
BookmarksTable.onDelete(db)
|
||||
BookmarkItemsTable.onDelete(db)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 currentLatLng; // 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 currentLatLng; // 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
|
||||
}
|
||||
}
|
||||
46
app/src/main/java/fr/free/nrw/commons/MapController.kt
Normal file
46
app/src/main/java/fr/free/nrw/commons/MapController.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
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 fr.free.nrw.commons.wikidata.model.WikiSite
|
||||
import fr.free.nrw.commons.wikidata.model.page.PageTitle
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
|
@ -173,7 +175,8 @@ class Media constructor(
|
|||
* 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
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
/**
|
||||
* Base interface for all the views
|
||||
*/
|
||||
public interface MvpView {
|
||||
void showMessage(String message);
|
||||
}
|
||||
|
|
@ -1,154 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
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 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;
|
||||
|
||||
public static OkHttpClient CLIENT;
|
||||
|
||||
@NonNull public static OkHttpClient getClient(final CommonsCookieJar cookieJar) {
|
||||
if (CLIENT == null) {
|
||||
CLIENT = createClient(cookieJar);
|
||||
}
|
||||
return CLIENT;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static OkHttpClient createClient(final CommonsCookieJar cookieJar) {
|
||||
return new OkHttpClient.Builder()
|
||||
.cookieJar(cookieJar)
|
||||
.cache((CommonsApplication.getInstance()!=null) ? new Cache(new File(CommonsApplication.getInstance().getCacheDir(), CACHE_DIR_NAME), NET_CACHE_SIZE) : null)
|
||||
.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 String SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log";
|
||||
public static final String SUPPRESS_ERROR_LOG_HEADER = SUPPRESS_ERROR_LOG+": true";
|
||||
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 Request 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.
|
||||
final boolean suppressErrors = rq.headers().names().contains(SUPPRESS_ERROR_LOG);
|
||||
final Request request = rq.newBuilder()
|
||||
.removeHeader(SUPPRESS_ERROR_LOG)
|
||||
.build();
|
||||
|
||||
final Response 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 (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) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
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() {
|
||||
}
|
||||
|
||||
public static class HttpStatusException extends IOException {
|
||||
private final int code;
|
||||
private final String url;
|
||||
public HttpStatusException(@NonNull Response rsp) {
|
||||
this.code = rsp.code();
|
||||
this.url = rsp.request().url().uri().toString();
|
||||
try {
|
||||
if (rsp.body() != null && rsp.body().contentType() != null
|
||||
&& rsp.body().contentType().toString().contains("json")) {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Log?
|
||||
}
|
||||
}
|
||||
|
||||
public int code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String str = "Code: " + code + ", URL: " + url;
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
135
app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
Normal file
135
app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt
Normal 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"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,264 +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 androidx.annotation.NonNull;
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import fr.free.nrw.commons.wikidata.model.WikiSite;
|
||||
import fr.free.nrw.commons.wikidata.model.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;
|
||||
|
||||
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());
|
||||
|
||||
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 The context for launching intent
|
||||
* @param latLng The latitude and longitude of the location
|
||||
*/
|
||||
public static void handleGeoCoordinates(final Context context, final LatLng latLng) {
|
||||
handleGeoCoordinates(context, latLng, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Util function to handle geo coordinates with specified zoom level. It no longer depends on
|
||||
* google maps and any app capable of handling the map intent can handle it
|
||||
*
|
||||
* @param context The context for launching intent
|
||||
* @param latLng The latitude and longitude of the location
|
||||
* @param zoomLevel The zoom level
|
||||
*/
|
||||
public static void handleGeoCoordinates(final Context context, final LatLng latLng,
|
||||
final double zoomLevel) {
|
||||
final Intent mapIntent = new Intent(Intent.ACTION_VIEW, latLng.getGmmIntentUri(zoomLevel));
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
public interface ViewHolder<T> {
|
||||
void bindModel(Context context, T model);
|
||||
}
|
||||
|
|
@ -1,68 +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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a ViewPagerAdapter with a specified Fragment Manager and Fragment resume behavior.
|
||||
*
|
||||
* @param manager The FragmentManager
|
||||
* @param behavior An integer which represents the behavior of non visible fragments. See
|
||||
* FragmentPagerAdapter.java for options.
|
||||
*/
|
||||
public ViewPagerAdapter(FragmentManager manager, int behavior) {
|
||||
super(manager, behavior);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
44
app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.kt
Normal file
44
app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.kt
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +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);
|
||||
dialogBuilder.setCancelable(false);
|
||||
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();
|
||||
}
|
||||
}
|
||||
80
app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt
Normal file
80
app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt
Normal 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))
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.net.Uri;
|
||||
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);
|
||||
}
|
||||
}
|
||||
70
app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.kt
Normal file
70
app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.kt
Normal 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,10 @@ 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.Utils
|
||||
import fr.free.nrw.commons.auth.login.LoginCallback
|
||||
import fr.free.nrw.commons.auth.login.LoginClient
|
||||
import fr.free.nrw.commons.auth.login.LoginResult
|
||||
|
|
@ -33,11 +33,14 @@ 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
|
||||
|
|
@ -79,7 +82,14 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
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)
|
||||
|
||||
|
|
@ -92,7 +102,19 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
aboutPrivacyPolicy.setOnClickListener { onPrivacyPolicyClicked() }
|
||||
signUpButton.setOnClickListener { signUp() }
|
||||
loginButton.setOnClickListener { performLogin() }
|
||||
loginPassword.setOnEditorActionListener(::onEditorAction)
|
||||
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)
|
||||
|
|
@ -113,6 +135,39 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
|
@ -236,7 +291,7 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
} else false
|
||||
|
||||
private fun isTriggerAction(actionId: Int, keyEvent: KeyEvent?) =
|
||||
actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
|
||||
actionId == EditorInfo.IME_ACTION_NEXT || actionId == EditorInfo.IME_ACTION_DONE || keyEvent?.keyCode == KeyEvent.KEYCODE_ENTER
|
||||
|
||||
private fun skipLogin() {
|
||||
AlertDialog.Builder(this)
|
||||
|
|
@ -254,10 +309,10 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
}
|
||||
|
||||
private fun forgotPassword() =
|
||||
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL))
|
||||
handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL))
|
||||
|
||||
private fun onPrivacyPolicyClicked() =
|
||||
Utils.handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
|
||||
handleWebUrl(this, Uri.parse(BuildConfig.PRIVACY_POLICY_URL))
|
||||
|
||||
private fun signUp() =
|
||||
startActivity(Intent(this, SignupActivity::class.java))
|
||||
|
|
@ -286,14 +341,14 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
Timber.d("Requesting 2FA prompt")
|
||||
progressDialog!!.dismiss()
|
||||
lastLoginResult = loginResult
|
||||
askUserForTwoFactorAuth()
|
||||
askUserForTwoFactorAuthWithKeyboard()
|
||||
}
|
||||
|
||||
override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) {
|
||||
override fun emailAuthPrompt(loginResult: LoginResult, caught: Throwable, token: String?) = runOnUiThread {
|
||||
Timber.d("Requesting email auth prompt")
|
||||
progressDialog!!.dismiss()
|
||||
lastLoginResult = loginResult
|
||||
askUserForTwoFactorAuth()
|
||||
askUserForTwoFactorAuthWithKeyboard()
|
||||
}
|
||||
|
||||
override fun passwordResetPrompt(token: String?) = runOnUiThread {
|
||||
|
|
@ -348,12 +403,31 @@ class LoginActivity : AccountAuthenticatorActivity() {
|
|||
|
||||
@VisibleForTesting
|
||||
fun askUserForTwoFactorAuth() {
|
||||
if (binding == null) {
|
||||
Timber.w("Binding is null, reinitializing in askUserForTwoFactorAuth")
|
||||
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||
setContentView(binding!!.root)
|
||||
}
|
||||
progressDialog!!.dismiss()
|
||||
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()
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ 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() {
|
||||
|
|
@ -21,6 +22,7 @@ class SignupActivity : BaseActivity() {
|
|||
Timber.d("Signup Activity started")
|
||||
|
||||
webView = WebView(this)
|
||||
applyEdgeToEdgeAllInsets(webView!!)
|
||||
with(webView!!) {
|
||||
setContentView(this)
|
||||
webViewClient = MyWebViewClient()
|
||||
|
|
|
|||
|
|
@ -1,105 +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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
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 fr.free.nrw.commons.contributions.ContributionController;
|
||||
import javax.inject.Named;
|
||||
|
||||
public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
private FragmentManager supportFragmentManager;
|
||||
private BookmarksPagerAdapter adapter;
|
||||
FragmentBookmarksBinding binding;
|
||||
|
||||
@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) {
|
||||
if (binding!=null) {
|
||||
binding.viewPagerBookmarks.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);
|
||||
binding = FragmentBookmarksBinding.inflate(inflater, container, false);
|
||||
|
||||
// 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"));
|
||||
binding.viewPagerBookmarks.setAdapter(adapter);
|
||||
binding.tabLayout.setupWithViewPager(binding.viewPagerBookmarks);
|
||||
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
setupTabLayout();
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
if (adapter.getCount() == 1) {
|
||||
binding.tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onBackPressed() {
|
||||
if (((BookmarkListRootFragment) (adapter.getItem(binding.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesFragment;
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsFragment;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
|
||||
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.navtab.NavTab;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import timber.log.Timber;
|
||||
|
||||
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;
|
||||
|
||||
FragmentFeaturedRootBinding binding;
|
||||
|
||||
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");
|
||||
|
||||
switch (order){
|
||||
case 0: listFragment = new BookmarkPicturesFragment();
|
||||
break;
|
||||
|
||||
case 1: listFragment = new BookmarkLocationsFragment();
|
||||
break;
|
||||
|
||||
case 3: listFragment = new BookmarkCategoriesFragment();
|
||||
break;
|
||||
}
|
||||
if(orderItem == 2) {
|
||||
listFragment = new BookmarkItemsFragment();
|
||||
}
|
||||
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
listFragment.setArguments(featuredArguments);
|
||||
this.bookmarksPagerAdapter = bookmarksPagerAdapter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@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) {
|
||||
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
|
||||
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) {
|
||||
Timber.d("on media clicked");
|
||||
binding.exploreContainer.setVisibility(View.VISIBLE);
|
||||
((BookmarkFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
|
||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, listFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -1,94 +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.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)));
|
||||
}
|
||||
final Bundle categoriesBundle = new Bundle();
|
||||
categoriesBundle.putString("categoryName",
|
||||
context.getString(R.string.title_page_bookmarks_categories));
|
||||
categoriesBundle.putInt("order", 3);
|
||||
pages.add(new BookmarkPages(
|
||||
new BookmarkListRootFragment(categoriesBundle, this),
|
||||
context.getString(R.string.title_page_bookmarks_categories)));
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.items;
|
||||
|
||||
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.COLUMN_ID;
|
||||
import static fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao.Table.TABLE_NAME;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Handles private storage for bookmarked items
|
||||
*/
|
||||
public class BookmarkItemsContentProvider extends CommonsDaggerContentProvider {
|
||||
|
||||
private static final String BASE_PATH = "bookmarksItems";
|
||||
public static final Uri BASE_URI =
|
||||
Uri.parse("content://" + BuildConfig.BOOKMARK_ITEMS_AUTHORITY + "/" + BASE_PATH);
|
||||
|
||||
|
||||
/**
|
||||
* Append bookmark items ID to the base uri
|
||||
*/
|
||||
public static Uri uriForName(final String id) {
|
||||
return Uri.parse(BASE_URI + "/" + id);
|
||||
}
|
||||
|
||||
@Inject
|
||||
DBOpenHelper dbOpenHelper;
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull final Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the SQLite database for the bookmark items
|
||||
* @param uri : contains the uri for bookmark items
|
||||
* @param projection : contains the all fields of the table
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
* @param sortOrder : ascending or descending
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection,
|
||||
final String[] selectionArgs, final String sortOrder) {
|
||||
final SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables(TABLE_NAME);
|
||||
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||
final Cursor cursor = queryBuilder.query(db, projection, selection,
|
||||
selectionArgs, null, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the update query of local SQLite Database
|
||||
* @param uri : contains the uri for bookmark items
|
||||
* @param contentValues : new values to be entered to db
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public int update(@NonNull final Uri uri, final ContentValues contentValues,
|
||||
final String selection, final String[] selectionArgs) {
|
||||
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
final int rowsUpdated;
|
||||
if (TextUtils.isEmpty(selection)) {
|
||||
final int id = Integer.parseInt(uri.getLastPathSegment());
|
||||
rowsUpdated = sqlDB.update(TABLE_NAME,
|
||||
contentValues,
|
||||
COLUMN_ID + " = ?",
|
||||
new String[]{String.valueOf(id)});
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Parameter `selection` should be empty when updating an ID");
|
||||
}
|
||||
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return rowsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the insertion of new bookmark items record to local SQLite Database
|
||||
* @param uri
|
||||
* @param contentValues
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public Uri insert(@NonNull final Uri uri, final ContentValues contentValues) {
|
||||
final SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
final long id = sqlDB.insert(TABLE_NAME, null, contentValues);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return Uri.parse(BASE_URI + "/" + id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the deletion of new bookmark items record to local SQLite Database
|
||||
* @param uri
|
||||
* @param s
|
||||
* @param strings
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public int delete(@NonNull final Uri uri, final String s, final String[] strings) {
|
||||
final int rows;
|
||||
final SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
|
||||
rows = db.delete(
|
||||
TABLE_NAME,
|
||||
"item_id = ?",
|
||||
new String[]{uri.getLastPathSegment()}
|
||||
);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package fr.free.nrw.commons.bookmarks.items
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteQueryBuilder
|
||||
import android.net.Uri
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.TABLE_NAME
|
||||
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
|
||||
import androidx.core.net.toUri
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID
|
||||
|
||||
/**
|
||||
* Handles private storage for bookmarked items
|
||||
*/
|
||||
class BookmarkItemsContentProvider : CommonsDaggerContentProvider() {
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
/**
|
||||
* Queries the SQLite database for the bookmark items
|
||||
* @param uri : contains the uri for bookmark items
|
||||
* @param projection : contains the all fields of the table
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
* @param sortOrder : ascending or descending
|
||||
*/
|
||||
override fun query(
|
||||
uri: Uri, projection: Array<String>?, selection: String?,
|
||||
selectionArgs: Array<String>?, sortOrder: String?
|
||||
): Cursor {
|
||||
val queryBuilder = SQLiteQueryBuilder().apply {
|
||||
tables = TABLE_NAME
|
||||
}
|
||||
|
||||
return queryBuilder.query(
|
||||
requireDb(), projection, selection,
|
||||
selectionArgs, null, null, sortOrder
|
||||
).apply {
|
||||
setNotificationUri(context?.contentResolver, uri)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the update query of local SQLite Database
|
||||
* @param uri : contains the uri for bookmark items
|
||||
* @param contentValues : new values to be entered to db
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
*/
|
||||
override fun update(
|
||||
uri: Uri, contentValues: ContentValues?,
|
||||
selection: String?, selectionArgs: Array<String>?
|
||||
): Int {
|
||||
val rowsUpdated: Int
|
||||
if (selection.isNullOrEmpty()) {
|
||||
val id = uri.lastPathSegment!!.toInt()
|
||||
rowsUpdated = requireDb().update(
|
||||
TABLE_NAME,
|
||||
contentValues,
|
||||
"$COLUMN_ID = ?",
|
||||
arrayOf(id.toString())
|
||||
)
|
||||
} else {
|
||||
throw IllegalArgumentException(
|
||||
"Parameter `selection` should be empty when updating an ID"
|
||||
)
|
||||
}
|
||||
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return rowsUpdated
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the insertion of new bookmark items record to local SQLite Database
|
||||
*/
|
||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
|
||||
val id = requireDb().insert(TABLE_NAME, null, contentValues)
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return "$BASE_URI/$id".toUri()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles the deletion of new bookmark items record to local SQLite Database
|
||||
*/
|
||||
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
|
||||
val rows: Int = requireDb().delete(
|
||||
TABLE_NAME,
|
||||
"$COLUMN_ID = ?",
|
||||
arrayOf(uri.lastPathSegment)
|
||||
)
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return rows
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BASE_PATH = "bookmarksItems"
|
||||
val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_ITEMS_AUTHORITY}/$BASE_PATH".toUri()
|
||||
fun uriForName(id: String) = "$BASE_URI/$id".toUri()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.items;
|
||||
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Handles loading bookmarked items from Database
|
||||
*/
|
||||
@Singleton
|
||||
public class BookmarkItemsController {
|
||||
|
||||
@Inject
|
||||
BookmarkItemsDao bookmarkItemsDao;
|
||||
|
||||
@Inject
|
||||
public BookmarkItemsController() {}
|
||||
|
||||
/**
|
||||
* Load from DB the bookmarked items
|
||||
* @return a list of DepictedItem objects.
|
||||
*/
|
||||
public List<DepictedItem> loadFavoritesItems() {
|
||||
return bookmarkItemsDao.getAllBookmarksItems();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package fr.free.nrw.commons.bookmarks.items
|
||||
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Handles loading bookmarked items from Database
|
||||
*/
|
||||
@Singleton
|
||||
class BookmarkItemsController @Inject constructor() {
|
||||
@JvmField
|
||||
@Inject
|
||||
var bookmarkItemsDao: BookmarkItemsDao? = null
|
||||
|
||||
/**
|
||||
* Load from DB the bookmarked items
|
||||
* @return a list of DepictedItem objects.
|
||||
*/
|
||||
fun loadFavoritesItems(): List<DepictedItem> {
|
||||
return bookmarkItemsDao?.getAllBookmarksItems() ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,329 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.items;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.RemoteException;
|
||||
import fr.free.nrw.commons.category.CategoryItem;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Handles database operations for bookmarked items
|
||||
*/
|
||||
@Singleton
|
||||
public class BookmarkItemsDao {
|
||||
|
||||
private final Provider<ContentProviderClient> clientProvider;
|
||||
|
||||
@Inject
|
||||
public BookmarkItemsDao(
|
||||
@Named("bookmarksItem") final Provider<ContentProviderClient> clientProvider) {
|
||||
this.clientProvider = clientProvider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find all persisted items bookmarks on database
|
||||
* @return list of bookmarks
|
||||
*/
|
||||
public List<DepictedItem> getAllBookmarksItems() {
|
||||
final List<DepictedItem> items = new ArrayList<>();
|
||||
final ContentProviderClient db = clientProvider.get();
|
||||
try (final Cursor cursor = db.query(
|
||||
BookmarkItemsContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
null,
|
||||
new String[]{},
|
||||
null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
items.add(fromCursor(cursor));
|
||||
}
|
||||
} catch (final RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look for a bookmark in database and in order to insert or delete it
|
||||
* @param depictedItem : Bookmark object
|
||||
* @return boolean : is bookmark now favorite ?
|
||||
*/
|
||||
public boolean updateBookmarkItem(final DepictedItem depictedItem) {
|
||||
final boolean bookmarkExists = findBookmarkItem(depictedItem.getId());
|
||||
if (bookmarkExists) {
|
||||
deleteBookmarkItem(depictedItem);
|
||||
} else {
|
||||
addBookmarkItem(depictedItem);
|
||||
}
|
||||
return !bookmarkExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Bookmark to database
|
||||
* @param depictedItem : Bookmark to add
|
||||
*/
|
||||
private void addBookmarkItem(final DepictedItem depictedItem) {
|
||||
final ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
db.insert(BookmarkItemsContentProvider.BASE_URI, toContentValues(depictedItem));
|
||||
} catch (final RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bookmark from database
|
||||
* @param depictedItem : Bookmark to delete
|
||||
*/
|
||||
private void deleteBookmarkItem(final DepictedItem depictedItem) {
|
||||
final ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
db.delete(BookmarkItemsContentProvider.uriForName(depictedItem.getId()), null, null);
|
||||
} catch (final RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a bookmark from database based on its name
|
||||
* @param depictedItemID : Bookmark to find
|
||||
* @return boolean : is bookmark in database ?
|
||||
*/
|
||||
public boolean findBookmarkItem(final String depictedItemID) {
|
||||
if (depictedItemID == null) { //Avoiding NPE's
|
||||
return false;
|
||||
}
|
||||
final ContentProviderClient db = clientProvider.get();
|
||||
try (final Cursor cursor = db.query(
|
||||
BookmarkItemsContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
Table.COLUMN_ID + "=?",
|
||||
new String[]{depictedItemID},
|
||||
null
|
||||
)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return true;
|
||||
}
|
||||
} catch (final RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recives real data from cursor
|
||||
* @param cursor : Object for storing database data
|
||||
* @return DepictedItem
|
||||
*/
|
||||
@SuppressLint("Range")
|
||||
DepictedItem fromCursor(final Cursor cursor) {
|
||||
final String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME));
|
||||
final String description
|
||||
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION));
|
||||
final String imageUrl = cursor.getString(cursor.getColumnIndex(Table.COLUMN_IMAGE));
|
||||
final String instanceListString
|
||||
= cursor.getString(cursor.getColumnIndex(Table.COLUMN_INSTANCE_LIST));
|
||||
final List<String> instanceList = StringToArray(instanceListString);
|
||||
final String categoryNameListString = cursor.getString(cursor
|
||||
.getColumnIndex(Table.COLUMN_CATEGORIES_NAME_LIST));
|
||||
final List<String> categoryNameList = StringToArray(categoryNameListString);
|
||||
final String categoryDescriptionListString = cursor.getString(cursor
|
||||
.getColumnIndex(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST));
|
||||
final List<String> categoryDescriptionList = StringToArray(categoryDescriptionListString);
|
||||
final String categoryThumbnailListString = cursor.getString(cursor
|
||||
.getColumnIndex(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST));
|
||||
final List<String> categoryThumbnailList = StringToArray(categoryThumbnailListString);
|
||||
final List<CategoryItem> categoryList = convertToCategoryItems(categoryNameList,
|
||||
categoryDescriptionList, categoryThumbnailList);
|
||||
final boolean isSelected
|
||||
= Boolean.parseBoolean(cursor.getString(cursor
|
||||
.getColumnIndex(Table.COLUMN_IS_SELECTED)));
|
||||
final String id = cursor.getString(cursor.getColumnIndex(Table.COLUMN_ID));
|
||||
|
||||
return new DepictedItem(
|
||||
fileName,
|
||||
description,
|
||||
imageUrl,
|
||||
instanceList,
|
||||
categoryList,
|
||||
isSelected,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
private List<CategoryItem> convertToCategoryItems(List<String> categoryNameList,
|
||||
List<String> categoryDescriptionList, List<String> categoryThumbnailList) {
|
||||
List<CategoryItem> categoryItems = new ArrayList<>();
|
||||
for(int i=0; i<categoryNameList.size(); i++){
|
||||
categoryItems.add(new CategoryItem(categoryNameList.get(i),
|
||||
categoryDescriptionList.get(i),
|
||||
categoryThumbnailList.get(i), false));
|
||||
}
|
||||
return categoryItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string to List
|
||||
* @param listString comma separated single string from of list items
|
||||
* @return List of string
|
||||
*/
|
||||
private List<String> StringToArray(final String listString) {
|
||||
final String[] elements = listString.split(",");
|
||||
return Arrays.asList(elements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string to List
|
||||
* @param list list of items
|
||||
* @return string comma separated single string of items
|
||||
*/
|
||||
private String ArrayToString(final List<String> list) {
|
||||
if (list != null) {
|
||||
return StringUtils.join(list, ',');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes data from DepictedItem and create a content value object
|
||||
* @param depictedItem depicted item
|
||||
* @return ContentValues
|
||||
*/
|
||||
private ContentValues toContentValues(final DepictedItem depictedItem) {
|
||||
|
||||
final List<String> namesOfCommonsCategories = new ArrayList<>();
|
||||
for (final CategoryItem category :
|
||||
depictedItem.getCommonsCategories()) {
|
||||
namesOfCommonsCategories.add(category.getName());
|
||||
}
|
||||
|
||||
final List<String> descriptionsOfCommonsCategories = new ArrayList<>();
|
||||
for (final CategoryItem category :
|
||||
depictedItem.getCommonsCategories()) {
|
||||
descriptionsOfCommonsCategories.add(category.getDescription());
|
||||
}
|
||||
|
||||
final List<String> thumbnailsOfCommonsCategories = new ArrayList<>();
|
||||
for (final CategoryItem category :
|
||||
depictedItem.getCommonsCategories()) {
|
||||
thumbnailsOfCommonsCategories.add(category.getThumbnail());
|
||||
}
|
||||
|
||||
final ContentValues cv = new ContentValues();
|
||||
cv.put(Table.COLUMN_NAME, depictedItem.getName());
|
||||
cv.put(Table.COLUMN_DESCRIPTION, depictedItem.getDescription());
|
||||
cv.put(Table.COLUMN_IMAGE, depictedItem.getImageUrl());
|
||||
cv.put(Table.COLUMN_INSTANCE_LIST, ArrayToString(depictedItem.getInstanceOfs()));
|
||||
cv.put(Table.COLUMN_CATEGORIES_NAME_LIST, ArrayToString(namesOfCommonsCategories));
|
||||
cv.put(Table.COLUMN_CATEGORIES_DESCRIPTION_LIST,
|
||||
ArrayToString(descriptionsOfCommonsCategories));
|
||||
cv.put(Table.COLUMN_CATEGORIES_THUMBNAIL_LIST,
|
||||
ArrayToString(thumbnailsOfCommonsCategories));
|
||||
cv.put(Table.COLUMN_IS_SELECTED, depictedItem.isSelected());
|
||||
cv.put(Table.COLUMN_ID, depictedItem.getId());
|
||||
return cv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table of bookmarksItems data
|
||||
*/
|
||||
public static final class Table {
|
||||
public static final String TABLE_NAME = "bookmarksItems";
|
||||
public static final String COLUMN_NAME = "item_name";
|
||||
public static final String COLUMN_DESCRIPTION = "item_description";
|
||||
public static final String COLUMN_IMAGE = "item_image_url";
|
||||
public static final String COLUMN_INSTANCE_LIST = "item_instance_of";
|
||||
public static final String COLUMN_CATEGORIES_NAME_LIST = "item_name_categories";
|
||||
public static final String COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories";
|
||||
public static final String COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories";
|
||||
public static final String COLUMN_IS_SELECTED = "item_is_selected";
|
||||
public static final String COLUMN_ID = "item_id";
|
||||
|
||||
public static final String[] ALL_FIELDS = {
|
||||
COLUMN_NAME,
|
||||
COLUMN_DESCRIPTION,
|
||||
COLUMN_IMAGE,
|
||||
COLUMN_INSTANCE_LIST,
|
||||
COLUMN_CATEGORIES_NAME_LIST,
|
||||
COLUMN_CATEGORIES_DESCRIPTION_LIST,
|
||||
COLUMN_CATEGORIES_THUMBNAIL_LIST,
|
||||
COLUMN_IS_SELECTED,
|
||||
COLUMN_ID
|
||||
};
|
||||
|
||||
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_NAME + " STRING,"
|
||||
+ COLUMN_DESCRIPTION + " STRING,"
|
||||
+ COLUMN_IMAGE + " STRING,"
|
||||
+ COLUMN_INSTANCE_LIST + " STRING,"
|
||||
+ COLUMN_CATEGORIES_NAME_LIST + " STRING,"
|
||||
+ COLUMN_CATEGORIES_DESCRIPTION_LIST + " STRING,"
|
||||
+ COLUMN_CATEGORIES_THUMBNAIL_LIST + " STRING,"
|
||||
+ COLUMN_IS_SELECTED + " STRING,"
|
||||
+ COLUMN_ID + " STRING PRIMARY KEY"
|
||||
+ ");";
|
||||
|
||||
/**
|
||||
* Creates table
|
||||
* @param db SQLiteDatabase
|
||||
*/
|
||||
public static void onCreate(final SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes database
|
||||
* @param db SQLiteDatabase
|
||||
*/
|
||||
public static void onDelete(final SQLiteDatabase db) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates database
|
||||
* @param db SQLiteDatabase
|
||||
* @param from starting
|
||||
* @param to end
|
||||
*/
|
||||
public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if (from < 18) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
|
||||
if (from == 18) {
|
||||
// table added in version 19
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
package fr.free.nrw.commons.bookmarks.items
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentProviderClient
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.os.RemoteException
|
||||
import androidx.core.content.contentValuesOf
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.BASE_URI
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider.Companion.uriForName
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_DESCRIPTION_LIST
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_NAME_LIST
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_CATEGORIES_THUMBNAIL_LIST
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_DESCRIPTION
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_ID
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IMAGE
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_INSTANCE_LIST
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_IS_SELECTED
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable.COLUMN_NAME
|
||||
import fr.free.nrw.commons.category.CategoryItem
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.utils.arrayToString
|
||||
import fr.free.nrw.commons.utils.getString
|
||||
import fr.free.nrw.commons.utils.getStringArray
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Handles database operations for bookmarked items
|
||||
*/
|
||||
@Singleton
|
||||
class BookmarkItemsDao @Inject constructor(
|
||||
@param:Named("bookmarksItem") private val clientProvider: Provider<ContentProviderClient>
|
||||
) {
|
||||
/**
|
||||
* Find all persisted items bookmarks on database
|
||||
* @return list of bookmarks
|
||||
*/
|
||||
fun getAllBookmarksItems(): List<DepictedItem> {
|
||||
val items: MutableList<DepictedItem> = mutableListOf()
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
db.query(
|
||||
BASE_URI,
|
||||
BookmarkItemsTable.ALL_FIELDS,
|
||||
null,
|
||||
arrayOf(),
|
||||
null
|
||||
).use { cursor ->
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
items.add(fromCursor(cursor))
|
||||
}
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look for a bookmark in database and in order to insert or delete it
|
||||
* @param depictedItem : Bookmark object
|
||||
* @return boolean : is bookmark now favorite ?
|
||||
*/
|
||||
fun updateBookmarkItem(depictedItem: DepictedItem): Boolean {
|
||||
val bookmarkExists = findBookmarkItem(depictedItem.id)
|
||||
if (bookmarkExists) {
|
||||
deleteBookmarkItem(depictedItem)
|
||||
} else {
|
||||
addBookmarkItem(depictedItem)
|
||||
}
|
||||
return !bookmarkExists
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Bookmark to database
|
||||
* @param depictedItem : Bookmark to add
|
||||
*/
|
||||
private fun addBookmarkItem(depictedItem: DepictedItem) {
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
db.insert(BASE_URI, toContentValues(depictedItem))
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bookmark from database
|
||||
* @param depictedItem : Bookmark to delete
|
||||
*/
|
||||
private fun deleteBookmarkItem(depictedItem: DepictedItem) {
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
db.delete(uriForName(depictedItem.id), null, null)
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a bookmark from database based on its name
|
||||
* @param depictedItemID : Bookmark to find
|
||||
* @return boolean : is bookmark in database ?
|
||||
*/
|
||||
fun findBookmarkItem(depictedItemID: String?): Boolean {
|
||||
if (depictedItemID == null) { //Avoiding NPE's
|
||||
return false
|
||||
}
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
db.query(
|
||||
BASE_URI,
|
||||
BookmarkItemsTable.ALL_FIELDS,
|
||||
COLUMN_ID + "=?",
|
||||
arrayOf(depictedItemID),
|
||||
null
|
||||
).use { cursor ->
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Recives real data from cursor
|
||||
* @param cursor : Object for storing database data
|
||||
* @return DepictedItem
|
||||
*/
|
||||
@SuppressLint("Range")
|
||||
fun fromCursor(cursor: Cursor) = with(cursor) {
|
||||
var name = getString(COLUMN_NAME)
|
||||
if (name == null) {
|
||||
name = ""
|
||||
}
|
||||
|
||||
var id = getString(COLUMN_ID)
|
||||
if (id == null) {
|
||||
id = ""
|
||||
}
|
||||
|
||||
DepictedItem(
|
||||
name,
|
||||
getString(COLUMN_DESCRIPTION),
|
||||
getString(COLUMN_IMAGE),
|
||||
getStringArray(COLUMN_INSTANCE_LIST),
|
||||
convertToCategoryItems(
|
||||
getStringArray(COLUMN_CATEGORIES_NAME_LIST),
|
||||
getStringArray(COLUMN_CATEGORIES_DESCRIPTION_LIST),
|
||||
getStringArray(COLUMN_CATEGORIES_THUMBNAIL_LIST)
|
||||
),
|
||||
getString(COLUMN_IS_SELECTED).toBoolean(),
|
||||
id
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertToCategoryItems(
|
||||
categoryNameList: List<String>,
|
||||
categoryDescriptionList: List<String>,
|
||||
categoryThumbnailList: List<String>
|
||||
): List<CategoryItem> = categoryNameList.mapIndexed { index, name ->
|
||||
CategoryItem(
|
||||
name = name,
|
||||
description = categoryDescriptionList.getOrNull(index),
|
||||
thumbnail = categoryThumbnailList.getOrNull(index),
|
||||
isSelected = false
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes data from DepictedItem and create a content value object
|
||||
* @param depictedItem depicted item
|
||||
* @return ContentValues
|
||||
*/
|
||||
private fun toContentValues(depictedItem: DepictedItem): ContentValues {
|
||||
return contentValuesOf(
|
||||
COLUMN_NAME to depictedItem.name,
|
||||
COLUMN_DESCRIPTION to depictedItem.description,
|
||||
COLUMN_IMAGE to depictedItem.imageUrl,
|
||||
COLUMN_INSTANCE_LIST to arrayToString(depictedItem.instanceOfs),
|
||||
COLUMN_CATEGORIES_NAME_LIST to arrayToString(depictedItem.commonsCategories.map { it.name }),
|
||||
COLUMN_CATEGORIES_DESCRIPTION_LIST to arrayToString(depictedItem.commonsCategories.map { it.description }),
|
||||
COLUMN_CATEGORIES_THUMBNAIL_LIST to arrayToString(depictedItem.commonsCategories.map { it.thumbnail }),
|
||||
COLUMN_IS_SELECTED to depictedItem.isSelected,
|
||||
COLUMN_ID to depictedItem.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.items;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Tab fragment to show list of bookmarked Wikidata Items
|
||||
*/
|
||||
public class BookmarkItemsFragment extends DaggerFragment {
|
||||
|
||||
private FragmentBookmarksItemsBinding binding;
|
||||
|
||||
@Inject
|
||||
BookmarkItemsController controller;
|
||||
|
||||
public static BookmarkItemsFragment newInstance() {
|
||||
return new BookmarkItemsFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull final LayoutInflater inflater,
|
||||
final ViewGroup container,
|
||||
final Bundle savedInstanceState
|
||||
) {
|
||||
binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(final @NotNull View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initList(requireContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
initList(requireContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of DepictedItem and sets to the adapter
|
||||
* @param context context
|
||||
*/
|
||||
private void initList(final Context context) {
|
||||
final List<DepictedItem> depictItems = controller.loadFavoritesItems();
|
||||
final BookmarkItemsAdapter adapter = new BookmarkItemsAdapter(depictItems, context);
|
||||
binding.listView.setAdapter(adapter);
|
||||
binding.loadingImagesProgressBar.setVisibility(View.GONE);
|
||||
if (depictItems.isEmpty()) {
|
||||
binding.statusMessage.setText(R.string.bookmark_empty);
|
||||
binding.statusMessage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.statusMessage.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package fr.free.nrw.commons.bookmarks.items
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import dagger.android.support.DaggerFragment
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.databinding.FragmentBookmarksItemsBinding
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Tab fragment to show list of bookmarked Wikidata Items
|
||||
*/
|
||||
class BookmarkItemsFragment : DaggerFragment() {
|
||||
private var binding: FragmentBookmarksItemsBinding? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var controller: BookmarkItemsController? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentBookmarksItemsBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initList(requireContext())
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
initList(requireContext())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of DepictedItem and sets to the adapter
|
||||
* @param context context
|
||||
*/
|
||||
private fun initList(context: Context) {
|
||||
val depictItems = controller!!.loadFavoritesItems()
|
||||
binding!!.listView.adapter = BookmarkItemsAdapter(depictItems, context)
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
if (depictItems.isEmpty()) {
|
||||
binding!!.statusMessage.setText(R.string.bookmark_empty)
|
||||
binding!!.statusMessage.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
package fr.free.nrw.commons.bookmarks.items
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
|
||||
/**
|
||||
* Table of bookmarksItems data
|
||||
*/
|
||||
object BookmarkItemsTable {
|
||||
const val TABLE_NAME = "bookmarksItems"
|
||||
const val COLUMN_NAME = "item_name"
|
||||
const val COLUMN_DESCRIPTION = "item_description"
|
||||
const val COLUMN_IMAGE = "item_image_url"
|
||||
const val COLUMN_INSTANCE_LIST = "item_instance_of"
|
||||
const val COLUMN_CATEGORIES_NAME_LIST = "item_name_categories"
|
||||
const val COLUMN_CATEGORIES_DESCRIPTION_LIST = "item_description_categories"
|
||||
const val COLUMN_CATEGORIES_THUMBNAIL_LIST = "item_thumbnail_categories"
|
||||
const val COLUMN_IS_SELECTED = "item_is_selected"
|
||||
const val COLUMN_ID = "item_id"
|
||||
|
||||
val ALL_FIELDS = arrayOf(
|
||||
COLUMN_NAME,
|
||||
COLUMN_DESCRIPTION,
|
||||
COLUMN_IMAGE,
|
||||
COLUMN_INSTANCE_LIST,
|
||||
COLUMN_CATEGORIES_NAME_LIST,
|
||||
COLUMN_CATEGORIES_DESCRIPTION_LIST,
|
||||
COLUMN_CATEGORIES_THUMBNAIL_LIST,
|
||||
COLUMN_IS_SELECTED,
|
||||
COLUMN_ID
|
||||
)
|
||||
|
||||
const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
|
||||
val CREATE_TABLE_STATEMENT =
|
||||
"""CREATE TABLE $TABLE_NAME (
|
||||
$COLUMN_NAME STRING,
|
||||
$COLUMN_DESCRIPTION STRING,
|
||||
$COLUMN_IMAGE STRING,
|
||||
$COLUMN_INSTANCE_LIST STRING,
|
||||
$COLUMN_CATEGORIES_NAME_LIST STRING,
|
||||
$COLUMN_CATEGORIES_DESCRIPTION_LIST STRING,
|
||||
$COLUMN_CATEGORIES_THUMBNAIL_LIST STRING,
|
||||
$COLUMN_IS_SELECTED STRING,
|
||||
$COLUMN_ID STRING PRIMARY KEY
|
||||
);""".trimIndent()
|
||||
|
||||
/**
|
||||
* Creates table
|
||||
*
|
||||
* @param db SQLiteDatabase
|
||||
*/
|
||||
fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes database
|
||||
*
|
||||
* @param db SQLiteDatabase
|
||||
*/
|
||||
fun onDelete(db: SQLiteDatabase) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT)
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates database
|
||||
*
|
||||
* @param db SQLiteDatabase
|
||||
* @param from starting
|
||||
* @param to end
|
||||
*/
|
||||
fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
|
||||
if (from == to) {
|
||||
return
|
||||
}
|
||||
|
||||
if (from < 18) {
|
||||
// doesn't exist yet
|
||||
onUpdate(db, from + 1, to)
|
||||
return
|
||||
}
|
||||
|
||||
if (from == 18) {
|
||||
// table added in version 19
|
||||
onCreate(db)
|
||||
onUpdate(db, from + 1, to)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME;
|
||||
import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao.Table.TABLE_NAME;
|
||||
|
||||
/**
|
||||
* Handles private storage for Bookmark pictures
|
||||
*/
|
||||
public class BookmarkPicturesContentProvider extends CommonsDaggerContentProvider {
|
||||
|
||||
private static final String BASE_PATH = "bookmarks";
|
||||
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_AUTHORITY + "/" + BASE_PATH);
|
||||
|
||||
/**
|
||||
* Append bookmark pictures name to the base uri
|
||||
*/
|
||||
public static Uri uriForName(String name) {
|
||||
return Uri.parse(BASE_URI.toString() + "/" + name);
|
||||
}
|
||||
|
||||
@Inject
|
||||
DBOpenHelper dbOpenHelper;
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the SQLite database for the bookmark pictures
|
||||
* @param uri : contains the uri for bookmark pictures
|
||||
* @param projection
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
* @param sortOrder : ascending or descending
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||
queryBuilder.setTables(TABLE_NAME);
|
||||
|
||||
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the update query of local SQLite Database
|
||||
* @param uri : contains the uri for bookmark pictures
|
||||
* @param contentValues : new values to be entered to db
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
|
||||
String[] selectionArgs) {
|
||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
int rowsUpdated;
|
||||
if (TextUtils.isEmpty(selection)) {
|
||||
int id = Integer.valueOf(uri.getLastPathSegment());
|
||||
rowsUpdated = sqlDB.update(TABLE_NAME,
|
||||
contentValues,
|
||||
COLUMN_MEDIA_NAME + " = ?",
|
||||
new String[]{String.valueOf(id)});
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Parameter `selection` should be empty when updating an ID");
|
||||
}
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return rowsUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the insertion of new bookmark pictures record to local SQLite Database
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
|
||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
long id = sqlDB.insert(BookmarkPicturesDao.Table.TABLE_NAME, null, contentValues);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return Uri.parse(BASE_URI + "/" + id);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, String s, String[] strings) {
|
||||
int rows;
|
||||
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
|
||||
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
|
||||
rows = db.delete(TABLE_NAME,
|
||||
"media_name = ?",
|
||||
new String[]{uri.getLastPathSegment()}
|
||||
);
|
||||
getContext().getContentResolver().notifyChange(uri, null);
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.sqlite.SQLiteQueryBuilder
|
||||
import android.net.Uri
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
|
||||
import androidx.core.net.toUri
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.TABLE_NAME
|
||||
|
||||
/**
|
||||
* Handles private storage for Bookmark pictures
|
||||
*/
|
||||
class BookmarkPicturesContentProvider : CommonsDaggerContentProvider() {
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
/**
|
||||
* Queries the SQLite database for the bookmark pictures
|
||||
* @param uri : contains the uri for bookmark pictures
|
||||
* @param projection
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
* @param sortOrder : ascending or descending
|
||||
*/
|
||||
override fun query(
|
||||
uri: Uri, projection: Array<String>?, selection: String?,
|
||||
selectionArgs: Array<String>?, sortOrder: String?
|
||||
): Cursor {
|
||||
val queryBuilder = SQLiteQueryBuilder().apply {
|
||||
tables = TABLE_NAME
|
||||
}
|
||||
|
||||
val cursor = queryBuilder.query(
|
||||
requireDb(), projection, selection,
|
||||
selectionArgs, null, null, sortOrder
|
||||
)
|
||||
cursor.setNotificationUri(context?.contentResolver, uri)
|
||||
|
||||
return cursor
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the update query of local SQLite Database
|
||||
* @param uri : contains the uri for bookmark pictures
|
||||
* @param contentValues : new values to be entered to db
|
||||
* @param selection : handles Where
|
||||
* @param selectionArgs : the condition of Where clause
|
||||
*/
|
||||
override fun update(
|
||||
uri: Uri, contentValues: ContentValues?, selection: String?,
|
||||
selectionArgs: Array<String>?
|
||||
): Int {
|
||||
val rowsUpdated: Int
|
||||
if (selection.isNullOrEmpty()) {
|
||||
val id = uri.lastPathSegment!!.toInt()
|
||||
rowsUpdated = requireDb().update(
|
||||
TABLE_NAME,
|
||||
contentValues,
|
||||
"$COLUMN_MEDIA_NAME = ?",
|
||||
arrayOf(id.toString())
|
||||
)
|
||||
} else {
|
||||
throw IllegalArgumentException(
|
||||
"Parameter `selection` should be empty when updating an ID"
|
||||
)
|
||||
}
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return rowsUpdated
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the insertion of new bookmark pictures record to local SQLite Database
|
||||
*/
|
||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
|
||||
val id = requireDb().insert(TABLE_NAME, null, contentValues)
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return "$BASE_URI/$id".toUri()
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, s: String?, strings: Array<String>?): Int {
|
||||
val rows: Int = requireDb().delete(
|
||||
TABLE_NAME,
|
||||
"media_name = ?",
|
||||
arrayOf(uri.lastPathSegment)
|
||||
)
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return rows
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BASE_PATH = "bookmarks"
|
||||
@JvmField
|
||||
val BASE_URI: Uri = "content://${BuildConfig.BOOKMARK_AUTHORITY}/$BASE_PATH".toUri()
|
||||
|
||||
@JvmStatic
|
||||
fun uriForName(name: String): Uri = "$BASE_URI/$name".toUri()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.bookmarks.models.Bookmark;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.functions.Function;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class BookmarkPicturesController {
|
||||
|
||||
private final MediaClient mediaClient;
|
||||
private final BookmarkPicturesDao bookmarkDao;
|
||||
|
||||
private List<Bookmark> currentBookmarks;
|
||||
|
||||
@Inject
|
||||
public BookmarkPicturesController(MediaClient mediaClient, BookmarkPicturesDao bookmarkDao) {
|
||||
this.mediaClient = mediaClient;
|
||||
this.bookmarkDao = bookmarkDao;
|
||||
currentBookmarks = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Media objects from the raw data stored in DB and the API.
|
||||
* @return a list of bookmarked Media object
|
||||
*/
|
||||
Single<List<Media>> loadBookmarkedPictures() {
|
||||
List<Bookmark> bookmarks = bookmarkDao.getAllBookmarks();
|
||||
currentBookmarks = bookmarks;
|
||||
return Observable.fromIterable(bookmarks)
|
||||
.flatMap((Function<Bookmark, ObservableSource<Media>>) this::getMediaFromBookmark)
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Observable<Media> getMediaFromBookmark(Bookmark bookmark) {
|
||||
return mediaClient.getMedia(bookmark.getMediaName())
|
||||
.toObservable()
|
||||
.onErrorResumeNext(Observable.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the Media objects from the raw data stored in DB and the API.
|
||||
* @return a list of bookmarked Media object
|
||||
*/
|
||||
boolean needRefreshBookmarkedPictures() {
|
||||
List<Bookmark> bookmarks = bookmarkDao.getAllBookmarks();
|
||||
return bookmarks.size() != currentBookmarks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the requests to the API and the DB
|
||||
*/
|
||||
void stop() {
|
||||
//noop
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.bookmarks.models.Bookmark
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BookmarkPicturesController @Inject constructor(
|
||||
private val mediaClient: MediaClient,
|
||||
private val bookmarkDao: BookmarkPicturesDao
|
||||
) {
|
||||
private var currentBookmarks: List<Bookmark> = listOf()
|
||||
|
||||
/**
|
||||
* Loads the Media objects from the raw data stored in DB and the API.
|
||||
* @return a list of bookmarked Media object
|
||||
*/
|
||||
fun loadBookmarkedPictures(): Single<List<Media>> {
|
||||
val bookmarks = bookmarkDao.getAllBookmarks()
|
||||
currentBookmarks = bookmarks
|
||||
return Observable.fromIterable(bookmarks).flatMap {
|
||||
mediaClient.getMedia(it.mediaName)
|
||||
.toObservable()
|
||||
.onErrorResumeNext(Observable.empty())
|
||||
}.toList()
|
||||
}
|
||||
|
||||
fun needRefreshBookmarkedPictures(): Boolean {
|
||||
val bookmarks = bookmarkDao.getAllBookmarks()
|
||||
return bookmarks.size != currentBookmarks.size
|
||||
}
|
||||
|
||||
fun stop() = Unit
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Provider;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.bookmarks.models.Bookmark;
|
||||
|
||||
import static fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.BASE_URI;
|
||||
|
||||
@Singleton
|
||||
public class BookmarkPicturesDao {
|
||||
|
||||
private final Provider<ContentProviderClient> clientProvider;
|
||||
|
||||
@Inject
|
||||
public BookmarkPicturesDao(@Named("bookmarks") Provider<ContentProviderClient> clientProvider) {
|
||||
this.clientProvider = clientProvider;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find all persisted pictures bookmarks on database
|
||||
*
|
||||
* @return list of bookmarks
|
||||
*/
|
||||
@NonNull
|
||||
public List<Bookmark> getAllBookmarks() {
|
||||
List<Bookmark> items = new ArrayList<>();
|
||||
Cursor cursor = null;
|
||||
ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
cursor = db.query(
|
||||
BookmarkPicturesContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
null,
|
||||
new String[]{},
|
||||
null);
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
items.add(fromCursor(cursor));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
db.release();
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Look for a bookmark in database and in order to insert or delete it
|
||||
*
|
||||
* @param bookmark : Bookmark object
|
||||
* @return boolean : is bookmark now fav ?
|
||||
*/
|
||||
public boolean updateBookmark(Bookmark bookmark) {
|
||||
boolean bookmarkExists = findBookmark(bookmark);
|
||||
if (bookmarkExists) {
|
||||
deleteBookmark(bookmark);
|
||||
} else {
|
||||
addBookmark(bookmark);
|
||||
}
|
||||
return !bookmarkExists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Bookmark to database
|
||||
*
|
||||
* @param bookmark : Bookmark to add
|
||||
*/
|
||||
private void addBookmark(Bookmark bookmark) {
|
||||
ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
db.insert(BASE_URI, toContentValues(bookmark));
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bookmark from database
|
||||
*
|
||||
* @param bookmark : Bookmark to delete
|
||||
*/
|
||||
private void deleteBookmark(Bookmark bookmark) {
|
||||
ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
if (bookmark.getContentUri() == null) {
|
||||
throw new RuntimeException("tried to delete item with no content URI");
|
||||
} else {
|
||||
db.delete(bookmark.getContentUri(), null, null);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a bookmark from database based on its name
|
||||
*
|
||||
* @param bookmark : Bookmark to find
|
||||
* @return boolean : is bookmark in database ?
|
||||
*/
|
||||
public boolean findBookmark(Bookmark bookmark) {
|
||||
if (bookmark == null) {//Avoiding NPE's
|
||||
return false;
|
||||
}
|
||||
|
||||
Cursor cursor = null;
|
||||
ContentProviderClient db = clientProvider.get();
|
||||
try {
|
||||
cursor = db.query(
|
||||
BookmarkPicturesContentProvider.BASE_URI,
|
||||
Table.ALL_FIELDS,
|
||||
Table.COLUMN_MEDIA_NAME + "=?",
|
||||
new String[]{bookmark.getMediaName()},
|
||||
null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return true;
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// This feels lazy, but to hell with checked exceptions. :)
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close();
|
||||
}
|
||||
db.release();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("Range")
|
||||
@NonNull
|
||||
Bookmark fromCursor(Cursor cursor) {
|
||||
String fileName = cursor.getString(cursor.getColumnIndex(Table.COLUMN_MEDIA_NAME));
|
||||
return new Bookmark(
|
||||
fileName,
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CREATOR)),
|
||||
BookmarkPicturesContentProvider.uriForName(fileName)
|
||||
);
|
||||
}
|
||||
|
||||
private ContentValues toContentValues(Bookmark bookmark) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(BookmarkPicturesDao.Table.COLUMN_MEDIA_NAME, bookmark.getMediaName());
|
||||
cv.put(BookmarkPicturesDao.Table.COLUMN_CREATOR, bookmark.getMediaCreator());
|
||||
return cv;
|
||||
}
|
||||
|
||||
|
||||
public static class Table {
|
||||
public static final String TABLE_NAME = "bookmarks";
|
||||
|
||||
public static final String COLUMN_MEDIA_NAME = "media_name";
|
||||
public static final String COLUMN_CREATOR = "media_creator";
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
public static final String[] ALL_FIELDS = {
|
||||
COLUMN_MEDIA_NAME,
|
||||
COLUMN_CREATOR
|
||||
};
|
||||
|
||||
public static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||
|
||||
public static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_MEDIA_NAME + " STRING PRIMARY KEY,"
|
||||
+ COLUMN_CREATOR + " STRING"
|
||||
+ ");";
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||
}
|
||||
|
||||
public static void onDelete(SQLiteDatabase db) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT);
|
||||
onCreate(db);
|
||||
}
|
||||
|
||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if (from < 7) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
|
||||
if (from == 7) {
|
||||
// table added in version 8
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
|
||||
if (from == 8) {
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import android.content.ContentProviderClient
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.os.RemoteException
|
||||
import androidx.core.content.contentValuesOf
|
||||
import fr.free.nrw.commons.bookmarks.models.Bookmark
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.BASE_URI
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider.Companion.uriForName
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.ALL_FIELDS
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_CREATOR
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable.COLUMN_MEDIA_NAME
|
||||
import fr.free.nrw.commons.utils.getString
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class BookmarkPicturesDao @Inject constructor(
|
||||
@param:Named("bookmarks") private val clientProvider: Provider<ContentProviderClient>
|
||||
) {
|
||||
/**
|
||||
* Find all persisted pictures bookmarks on database
|
||||
*
|
||||
* @return list of bookmarks
|
||||
*/
|
||||
fun getAllBookmarks(): List<Bookmark> {
|
||||
val items: MutableList<Bookmark> = mutableListOf()
|
||||
var cursor: Cursor? = null
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
cursor = db.query(
|
||||
BASE_URI, ALL_FIELDS, null, arrayOf(), null
|
||||
)
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
items.add(fromCursor(cursor))
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
cursor?.close()
|
||||
db.release()
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a bookmark in database and in order to insert or delete it
|
||||
*
|
||||
* @param bookmark : Bookmark object
|
||||
* @return boolean : is bookmark now fav ?
|
||||
*/
|
||||
fun updateBookmark(bookmark: Bookmark): Boolean {
|
||||
val bookmarkExists = findBookmark(bookmark)
|
||||
if (bookmarkExists) {
|
||||
deleteBookmark(bookmark)
|
||||
} else {
|
||||
addBookmark(bookmark)
|
||||
}
|
||||
return !bookmarkExists
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a Bookmark to database
|
||||
*
|
||||
* @param bookmark : Bookmark to add
|
||||
*/
|
||||
private fun addBookmark(bookmark: Bookmark) {
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
db.insert(BASE_URI, toContentValues(bookmark))
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a bookmark from database
|
||||
*
|
||||
* @param bookmark : Bookmark to delete
|
||||
*/
|
||||
private fun deleteBookmark(bookmark: Bookmark) {
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
if (bookmark.contentUri == null) {
|
||||
throw RuntimeException("tried to delete item with no content URI")
|
||||
} else {
|
||||
db.delete(bookmark.contentUri!!, null, null)
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.release()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a bookmark from database based on its name
|
||||
*
|
||||
* @param bookmark : Bookmark to find
|
||||
* @return boolean : is bookmark in database ?
|
||||
*/
|
||||
fun findBookmark(bookmark: Bookmark?): Boolean {
|
||||
if (bookmark == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
var cursor: Cursor? = null
|
||||
val db = clientProvider.get()
|
||||
try {
|
||||
cursor = db.query(
|
||||
BASE_URI, ALL_FIELDS, "$COLUMN_MEDIA_NAME=?", arrayOf(bookmark.mediaName), null
|
||||
)
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return true
|
||||
}
|
||||
} catch (e: RemoteException) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
cursor?.close()
|
||||
db.release()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun fromCursor(cursor: Cursor): Bookmark {
|
||||
var fileName = cursor.getString(COLUMN_MEDIA_NAME)
|
||||
if (fileName == null) {
|
||||
fileName = ""
|
||||
}
|
||||
return Bookmark(
|
||||
fileName, cursor.getString(COLUMN_CREATOR), uriForName(fileName)
|
||||
)
|
||||
}
|
||||
|
||||
private fun toContentValues(bookmark: Bookmark): ContentValues = contentValuesOf(
|
||||
COLUMN_MEDIA_NAME to bookmark.mediaName,
|
||||
COLUMN_CREATOR to bookmark.mediaCreator
|
||||
)
|
||||
}
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ListAdapter;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import dagger.android.support.DaggerFragment;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment;
|
||||
import fr.free.nrw.commons.category.GridViewAdapter;
|
||||
import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class BookmarkPicturesFragment extends DaggerFragment {
|
||||
|
||||
private GridViewAdapter gridAdapter;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
private FragmentBookmarksPicturesBinding binding;
|
||||
@Inject
|
||||
BookmarkPicturesController controller;
|
||||
|
||||
/**
|
||||
* Create an instance of the fragment with the right bundle parameters
|
||||
* @return an instance of the fragment
|
||||
*/
|
||||
public static BookmarkPicturesFragment newInstance() {
|
||||
return new BookmarkPicturesFragment();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
@NonNull LayoutInflater inflater,
|
||||
ViewGroup container,
|
||||
Bundle savedInstanceState
|
||||
) {
|
||||
binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
binding.bookmarkedPicturesList.setOnItemClickListener((AdapterView.OnItemClickListener) getParentFragment());
|
||||
initList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
controller.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
compositeDisposable.clear();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (controller.needRefreshBookmarkedPictures()) {
|
||||
binding.bookmarkedPicturesList.setVisibility(GONE);
|
||||
if (gridAdapter != null) {
|
||||
gridAdapter.clear();
|
||||
((BookmarkListRootFragment)getParentFragment()).viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
initList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for internet connection and then initializes
|
||||
* the recycler view with bookmarked pictures
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void initList() {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
||||
binding.loadingImagesProgressBar.setVisibility(VISIBLE);
|
||||
binding.statusMessage.setVisibility(GONE);
|
||||
|
||||
compositeDisposable.add(controller.loadBookmarkedPictures()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::handleSuccess, this::handleError));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates for no internet scenario
|
||||
*/
|
||||
private void handleNoInternet() {
|
||||
binding.loadingImagesProgressBar.setVisibility(GONE);
|
||||
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||
binding.statusMessage.setVisibility(VISIBLE);
|
||||
binding.statusMessage.setText(getString(R.string.no_internet));
|
||||
} else {
|
||||
ViewUtil.showShortSnackbar(binding.parentLayout, R.string.no_internet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs and handles API error scenario
|
||||
* @param throwable
|
||||
*/
|
||||
private void handleError(Throwable throwable) {
|
||||
Timber.e(throwable, "Error occurred while loading images inside a category");
|
||||
try{
|
||||
ViewUtil.showShortSnackbar(binding.getRoot(), R.string.error_loading_images);
|
||||
initErrorView();
|
||||
}catch (Exception e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates for a error scenario
|
||||
*/
|
||||
private void initErrorView() {
|
||||
binding.loadingImagesProgressBar.setVisibility(GONE);
|
||||
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||
binding.statusMessage.setVisibility(VISIBLE);
|
||||
binding.statusMessage.setText(getString(R.string.no_images_found));
|
||||
} else {
|
||||
binding.statusMessage.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates when there is no bookmarks
|
||||
*/
|
||||
private void initEmptyBookmarkListView() {
|
||||
binding.loadingImagesProgressBar.setVisibility(GONE);
|
||||
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||
binding.statusMessage.setVisibility(VISIBLE);
|
||||
binding.statusMessage.setText(getString(R.string.bookmark_empty));
|
||||
} else {
|
||||
binding.statusMessage.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the success scenario
|
||||
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||
* @param collection List of new Media to be displayed
|
||||
*/
|
||||
private void handleSuccess(List<Media> collection) {
|
||||
if (collection == null) {
|
||||
initErrorView();
|
||||
return;
|
||||
}
|
||||
if (collection.isEmpty()) {
|
||||
initEmptyBookmarkListView();
|
||||
return;
|
||||
}
|
||||
|
||||
if (gridAdapter == null) {
|
||||
setAdapter(collection);
|
||||
} else {
|
||||
if (gridAdapter.containsAll(collection)) {
|
||||
binding.loadingImagesProgressBar.setVisibility(GONE);
|
||||
binding.statusMessage.setVisibility(GONE);
|
||||
binding.bookmarkedPicturesList.setVisibility(VISIBLE);
|
||||
binding.bookmarkedPicturesList.setAdapter(gridAdapter);
|
||||
return;
|
||||
}
|
||||
gridAdapter.addItems(collection);
|
||||
((BookmarkListRootFragment) getParentFragment()).viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
binding.loadingImagesProgressBar.setVisibility(GONE);
|
||||
binding.statusMessage.setVisibility(GONE);
|
||||
binding.bookmarkedPicturesList.setVisibility(VISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the adapter with a list of Media objects
|
||||
* @param mediaList List of new Media to be displayed
|
||||
*/
|
||||
private void setAdapter(List<Media> mediaList) {
|
||||
gridAdapter = new GridViewAdapter(
|
||||
this.getContext(),
|
||||
R.layout.layout_category_images,
|
||||
mediaList
|
||||
);
|
||||
binding.bookmarkedPicturesList.setAdapter(gridAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* It return an instance of gridView adapter which helps in extracting media details
|
||||
* used by the gridView
|
||||
* @return GridView Adapter
|
||||
*/
|
||||
public ListAdapter getAdapter() {
|
||||
return binding.bookmarkedPicturesList.getAdapter();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,201 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.ListAdapter
|
||||
import dagger.android.support.DaggerFragment
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.bookmarks.BookmarkListRootFragment
|
||||
import fr.free.nrw.commons.category.GridViewAdapter
|
||||
import fr.free.nrw.commons.databinding.FragmentBookmarksPicturesBinding
|
||||
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showShortSnackbar
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.functions.Consumer
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookmarkPicturesFragment : DaggerFragment() {
|
||||
private var gridAdapter: GridViewAdapter? = null
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
private var binding: FragmentBookmarksPicturesBinding? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var controller: BookmarkPicturesController? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentBookmarksPicturesBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding!!.bookmarkedPicturesList.onItemClickListener =
|
||||
parentFragment as OnItemClickListener?
|
||||
initList()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
controller!!.stop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
compositeDisposable.clear()
|
||||
binding = null
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if (controller!!.needRefreshBookmarkedPictures()) {
|
||||
binding!!.bookmarkedPicturesList.visibility = View.GONE
|
||||
gridAdapter?.let {
|
||||
it.clear()
|
||||
(parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged()
|
||||
}
|
||||
initList()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for internet connection and then initializes
|
||||
* the recycler view with bookmarked pictures
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private fun initList() {
|
||||
if (!isInternetConnectionEstablished(context)) {
|
||||
handleNoInternet()
|
||||
return
|
||||
}
|
||||
|
||||
binding!!.loadingImagesProgressBar.visibility = View.VISIBLE
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
|
||||
compositeDisposable.add(
|
||||
controller!!.loadBookmarkedPictures()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(::handleSuccess, ::handleError)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates for no internet scenario
|
||||
*/
|
||||
private fun handleNoInternet() {
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
if (gridAdapter == null || gridAdapter!!.isEmpty) {
|
||||
binding!!.statusMessage.visibility = View.VISIBLE
|
||||
binding!!.statusMessage.text = getString(R.string.no_internet)
|
||||
} else {
|
||||
showShortSnackbar(binding!!.parentLayout, R.string.no_internet)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs and handles API error scenario
|
||||
* @param throwable
|
||||
*/
|
||||
private fun handleError(throwable: Throwable) {
|
||||
Timber.e(throwable, "Error occurred while loading images inside a category")
|
||||
try {
|
||||
showShortSnackbar(binding!!.root, R.string.error_loading_images)
|
||||
initErrorView()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates for a error scenario
|
||||
*/
|
||||
private fun initErrorView() {
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
if (gridAdapter == null || gridAdapter!!.isEmpty) {
|
||||
binding!!.statusMessage.visibility = View.VISIBLE
|
||||
binding!!.statusMessage.text = getString(R.string.no_images_found)
|
||||
} else {
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the UI updates when there is no bookmarks
|
||||
*/
|
||||
private fun initEmptyBookmarkListView() {
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
if (gridAdapter == null || gridAdapter!!.isEmpty) {
|
||||
binding!!.statusMessage.visibility = View.VISIBLE
|
||||
binding!!.statusMessage.text = getString(R.string.bookmark_empty)
|
||||
} else {
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the success scenario
|
||||
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||
* @param collection List of new Media to be displayed
|
||||
*/
|
||||
private fun handleSuccess(collection: List<Media>?) {
|
||||
if (collection == null) {
|
||||
initErrorView()
|
||||
return
|
||||
}
|
||||
if (collection.isEmpty()) {
|
||||
initEmptyBookmarkListView()
|
||||
return
|
||||
}
|
||||
|
||||
if (gridAdapter == null) {
|
||||
setAdapter(collection)
|
||||
} else {
|
||||
if (gridAdapter!!.containsAll(collection)) {
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
binding!!.bookmarkedPicturesList.visibility = View.VISIBLE
|
||||
binding!!.bookmarkedPicturesList.adapter = gridAdapter
|
||||
return
|
||||
}
|
||||
gridAdapter!!.addItems(collection)
|
||||
(parentFragment as BookmarkListRootFragment).viewPagerNotifyDataSetChanged()
|
||||
}
|
||||
binding!!.loadingImagesProgressBar.visibility = View.GONE
|
||||
binding!!.statusMessage.visibility = View.GONE
|
||||
binding!!.bookmarkedPicturesList.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the adapter with a list of Media objects
|
||||
* @param mediaList List of new Media to be displayed
|
||||
*/
|
||||
private fun setAdapter(mediaList: List<Media>) {
|
||||
gridAdapter = GridViewAdapter(
|
||||
requireContext(),
|
||||
R.layout.layout_category_images,
|
||||
mediaList.toMutableList()
|
||||
)
|
||||
binding?.let { it.bookmarkedPicturesList.adapter = gridAdapter }
|
||||
}
|
||||
|
||||
/**
|
||||
* It return an instance of gridView adapter which helps in extracting media details
|
||||
* used by the gridView
|
||||
* @return GridView Adapter
|
||||
*/
|
||||
fun getAdapter(): ListAdapter? = binding?.bookmarkedPicturesList?.adapter
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package fr.free.nrw.commons.bookmarks.pictures
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
|
||||
object BookmarksTable {
|
||||
const val TABLE_NAME: String = "bookmarks"
|
||||
const val COLUMN_MEDIA_NAME: String = "media_name"
|
||||
const val COLUMN_CREATOR: String = "media_creator"
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
val ALL_FIELDS = arrayOf(
|
||||
COLUMN_MEDIA_NAME,
|
||||
COLUMN_CREATOR
|
||||
)
|
||||
|
||||
const val DROP_TABLE_STATEMENT: String = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
|
||||
const val CREATE_TABLE_STATEMENT: String = ("CREATE TABLE $TABLE_NAME (" +
|
||||
"$COLUMN_MEDIA_NAME STRING PRIMARY KEY, " +
|
||||
"$COLUMN_CREATOR STRING" +
|
||||
");")
|
||||
|
||||
fun onCreate(db: SQLiteDatabase) =
|
||||
db.execSQL(CREATE_TABLE_STATEMENT)
|
||||
|
||||
fun onDelete(db: SQLiteDatabase) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT)
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) {
|
||||
if (from == to) {
|
||||
return
|
||||
}
|
||||
|
||||
if (from < 7) {
|
||||
// doesn't exist yet
|
||||
onUpdate(db, from+1, to)
|
||||
return
|
||||
}
|
||||
|
||||
if (from == 7) {
|
||||
// table added in version 8
|
||||
onCreate(db)
|
||||
onUpdate(db, from+1, to)
|
||||
return
|
||||
}
|
||||
|
||||
if (from == 8) {
|
||||
onUpdate(db, from+1, to)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,8 @@ import com.google.gson.annotations.SerializedName
|
|||
*/
|
||||
class CampaignConfig {
|
||||
@SerializedName("showOnlyLiveCampaigns")
|
||||
private val showOnlyLiveCampaigns = false
|
||||
var showOnlyLiveCampaigns = false
|
||||
|
||||
@SerializedName("sortBy")
|
||||
private val sortBy: String? = null
|
||||
var sortBy: String? = null
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ import fr.free.nrw.commons.campaigns.models.Campaign
|
|||
*/
|
||||
class CampaignResponseDTO {
|
||||
@SerializedName("config")
|
||||
val campaignConfig: CampaignConfig? = null
|
||||
var campaignConfig: CampaignConfig? = null
|
||||
|
||||
@SerializedName("campaigns")
|
||||
val campaigns: List<Campaign>? = null
|
||||
var campaigns: List<Campaign>? = null
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.Utils
|
||||
import fr.free.nrw.commons.campaigns.models.Campaign
|
||||
import fr.free.nrw.commons.contributions.MainActivity
|
||||
import fr.free.nrw.commons.databinding.LayoutCampaginBinding
|
||||
|
|
@ -16,6 +15,7 @@ import fr.free.nrw.commons.utils.CommonsDateUtil.getIso8601DateFormatShort
|
|||
import fr.free.nrw.commons.utils.DateUtil.getExtraShortDateString
|
||||
import fr.free.nrw.commons.utils.SwipableCardView
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||
import fr.free.nrw.commons.utils.handleWebUrl
|
||||
import timber.log.Timber
|
||||
import java.text.ParseException
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ class CampaignView : SwipableCardView {
|
|||
if (it.isWLMCampaign) {
|
||||
((context) as MainActivity).showNearby()
|
||||
} else {
|
||||
Utils.handleWebUrl(context, Uri.parse(it.link))
|
||||
handleWebUrl(context, Uri.parse(it.link))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class CampaignsPresenter @Inject constructor(
|
|||
private val okHttpJsonApiClient: OkHttpJsonApiClient?,
|
||||
@param:Named(IO_THREAD) private val ioScheduler: Scheduler,
|
||||
@param:Named(MAIN_THREAD) private val mainThreadScheduler: Scheduler
|
||||
) : BasePresenter<ICampaignsView?> {
|
||||
) : BasePresenter<ICampaignsView> {
|
||||
private var view: ICampaignsView? = null
|
||||
private var disposable: Disposable? = null
|
||||
private var campaign: Campaign? = null
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package fr.free.nrw.commons.campaigns
|
||||
|
||||
import fr.free.nrw.commons.MvpView
|
||||
import fr.free.nrw.commons.campaigns.models.Campaign
|
||||
|
||||
/**
|
||||
* Interface which defines the view contracts of the campaign view
|
||||
*/
|
||||
interface ICampaignsView : MvpView {
|
||||
interface ICampaignsView {
|
||||
fun showCampaigns(campaign: Campaign?)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,12 +9,9 @@ import android.database.sqlite.SQLiteDatabase
|
|||
import android.database.sqlite.SQLiteQueryBuilder
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import androidx.annotation.NonNull
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.data.DBOpenHelper
|
||||
import fr.free.nrw.commons.di.CommonsDaggerContentProvider
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import androidx.core.net.toUri
|
||||
|
||||
class CategoryContentProvider : CommonsDaggerContentProvider() {
|
||||
|
||||
|
|
@ -23,9 +20,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var dbOpenHelper: DBOpenHelper
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?,
|
||||
selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
|
||||
|
|
@ -34,7 +28,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
}
|
||||
|
||||
val uriType = uriMatcher.match(uri)
|
||||
val db = dbOpenHelper.readableDatabase
|
||||
val db = requireDb()
|
||||
|
||||
val cursor: Cursor? = when (uriType) {
|
||||
CATEGORIES -> queryBuilder.query(
|
||||
|
|
@ -58,45 +52,37 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
else -> throw IllegalArgumentException("Unknown URI $uri")
|
||||
}
|
||||
|
||||
cursor?.setNotificationUri(context?.contentResolver, uri)
|
||||
cursor?.setNotificationUri(requireContext().contentResolver, uri)
|
||||
return cursor
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
return null
|
||||
}
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? {
|
||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri {
|
||||
val uriType = uriMatcher.match(uri)
|
||||
val sqlDB = dbOpenHelper.writableDatabase
|
||||
val id: Long
|
||||
when (uriType) {
|
||||
CATEGORIES -> {
|
||||
id = sqlDB.insert(TABLE_NAME, null, contentValues)
|
||||
id = requireDb().insert(TABLE_NAME, null, contentValues)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
||||
}
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
return Uri.parse("${Companion.BASE_URI}/$id")
|
||||
requireContext().contentResolver?.notifyChange(uri, null)
|
||||
return "${BASE_URI}/$id".toUri()
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
// Not implemented
|
||||
return 0
|
||||
}
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int = 0
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int {
|
||||
Timber.d("Hello, bulk insert! (CategoryContentProvider)")
|
||||
val uriType = uriMatcher.match(uri)
|
||||
val sqlDB = dbOpenHelper.writableDatabase
|
||||
val sqlDB = requireDb()
|
||||
sqlDB.beginTransaction()
|
||||
when (uriType) {
|
||||
CATEGORIES -> {
|
||||
for (value in values) {
|
||||
Timber.d("Inserting! %s", value)
|
||||
sqlDB.insert(TABLE_NAME, null, value)
|
||||
}
|
||||
sqlDB.setTransactionSuccessful()
|
||||
|
|
@ -104,7 +90,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
else -> throw IllegalArgumentException("Unknown URI: $uri")
|
||||
}
|
||||
sqlDB.endTransaction()
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
requireContext().contentResolver?.notifyChange(uri, null)
|
||||
return values.size
|
||||
}
|
||||
|
||||
|
|
@ -112,17 +98,18 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
override fun update(uri: Uri, contentValues: ContentValues?, selection: String?,
|
||||
selectionArgs: Array<String>?): Int {
|
||||
val uriType = uriMatcher.match(uri)
|
||||
val sqlDB = dbOpenHelper.writableDatabase
|
||||
val rowsUpdated: Int
|
||||
when (uriType) {
|
||||
CATEGORIES_ID -> {
|
||||
if (TextUtils.isEmpty(selection)) {
|
||||
val id = uri.lastPathSegment?.toInt()
|
||||
?: throw IllegalArgumentException("Invalid ID")
|
||||
rowsUpdated = sqlDB.update(TABLE_NAME,
|
||||
rowsUpdated = requireDb().update(
|
||||
TABLE_NAME,
|
||||
contentValues,
|
||||
"$COLUMN_ID = ?",
|
||||
arrayOf(id.toString()))
|
||||
arrayOf(id.toString())
|
||||
)
|
||||
} else {
|
||||
throw IllegalArgumentException(
|
||||
"Parameter `selection` should be empty when updating an ID")
|
||||
|
|
@ -130,7 +117,7 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
}
|
||||
else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType")
|
||||
}
|
||||
context?.contentResolver?.notifyChange(uri, null)
|
||||
requireContext().contentResolver?.notifyChange(uri, null)
|
||||
return rowsUpdated
|
||||
}
|
||||
|
||||
|
|
@ -165,13 +152,9 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
"$COLUMN_TIMES_USED INTEGER" +
|
||||
");"
|
||||
|
||||
fun uriForId(id: Int): Uri {
|
||||
return Uri.parse("${BASE_URI}/$id")
|
||||
}
|
||||
fun uriForId(id: Int): Uri = Uri.parse("${BASE_URI}/$id")
|
||||
|
||||
fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(CREATE_TABLE_STATEMENT)
|
||||
}
|
||||
fun onCreate(db: SQLiteDatabase) = db.execSQL(CREATE_TABLE_STATEMENT)
|
||||
|
||||
fun onDelete(db: SQLiteDatabase) {
|
||||
db.execSQL(DROP_TABLE_STATEMENT)
|
||||
|
|
@ -200,6 +183,6 @@ class CategoryContentProvider : CommonsDaggerContentProvider() {
|
|||
private const val CATEGORIES = 1
|
||||
private const val CATEGORIES_ID = 2
|
||||
private const val BASE_PATH = "categories"
|
||||
val BASE_URI: Uri = Uri.parse("content://${BuildConfig.CATEGORY_AUTHORITY}/${Companion.BASE_PATH}")
|
||||
val BASE_URI: Uri = "content://${BuildConfig.CATEGORY_AUTHORITY}/${BASE_PATH}".toUri()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,21 +8,25 @@ import android.view.Menu
|
|||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import fr.free.nrw.commons.BuildConfig.COMMONS_URL
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.Utils
|
||||
import fr.free.nrw.commons.ViewPagerAdapter
|
||||
import fr.free.nrw.commons.databinding.ActivityCategoryDetailsBinding
|
||||
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment
|
||||
import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment
|
||||
import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
|
||||
import fr.free.nrw.commons.utils.handleWebUrl
|
||||
import fr.free.nrw.commons.wikidata.model.WikiSite
|
||||
import fr.free.nrw.commons.wikidata.model.page.PageTitle
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -33,7 +37,7 @@ import javax.inject.Inject
|
|||
* a particular category on wikimedia commons.
|
||||
*/
|
||||
class CategoryDetailsActivity : BaseActivity(),
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
MediaDetailProvider,
|
||||
CategoryImagesCallback {
|
||||
|
||||
private lateinit var supportFragmentManager: FragmentManager
|
||||
|
|
@ -54,9 +58,10 @@ class CategoryDetailsActivity : BaseActivity(),
|
|||
|
||||
binding = ActivityCategoryDetailsBinding.inflate(layoutInflater)
|
||||
val view = binding.root
|
||||
applyEdgeToEdgeAllInsets(view)
|
||||
setContentView(view)
|
||||
supportFragmentManager = getSupportFragmentManager()
|
||||
viewPagerAdapter = ViewPagerAdapter(supportFragmentManager)
|
||||
viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
|
||||
binding.viewPager.adapter = viewPagerAdapter
|
||||
binding.viewPager.offscreenPageLimit = 2
|
||||
binding.tabLayout.setupWithViewPager(binding.viewPager)
|
||||
|
|
@ -80,8 +85,6 @@ class CategoryDetailsActivity : BaseActivity(),
|
|||
* Set the fragments according to the tab selected in the viewPager.
|
||||
*/
|
||||
private fun setTabs() {
|
||||
val fragmentList = mutableListOf<Fragment>()
|
||||
val titleList = mutableListOf<String>()
|
||||
categoriesMediaFragment = CategoriesMediaFragment()
|
||||
val subCategoryListFragment = SubCategoriesFragment()
|
||||
val parentCategoriesFragment = ParentCategoriesFragment()
|
||||
|
|
@ -96,13 +99,12 @@ class CategoryDetailsActivity : BaseActivity(),
|
|||
|
||||
viewModel.onCheckIfBookmarked(categoryName!!)
|
||||
}
|
||||
fragmentList.add(categoriesMediaFragment)
|
||||
titleList.add("MEDIA")
|
||||
fragmentList.add(subCategoryListFragment)
|
||||
titleList.add("SUBCATEGORIES")
|
||||
fragmentList.add(parentCategoriesFragment)
|
||||
titleList.add("PARENT CATEGORIES")
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList)
|
||||
|
||||
viewPagerAdapter.setTabs(
|
||||
R.string.title_for_media to categoriesMediaFragment,
|
||||
R.string.title_for_subcategories to subCategoryListFragment,
|
||||
R.string.title_for_parent_categories to parentCategoriesFragment
|
||||
)
|
||||
viewPagerAdapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
|
@ -199,8 +201,9 @@ class CategoryDetailsActivity : BaseActivity(),
|
|||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_browser_current_category -> {
|
||||
val title = Utils.getPageTitle(CATEGORY_PREFIX + categoryName)
|
||||
Utils.handleWebUrl(this, Uri.parse(title.canonicalUri))
|
||||
val title = PageTitle(CATEGORY_PREFIX + categoryName, WikiSite(COMMONS_URL))
|
||||
|
||||
handleWebUrl(this, Uri.parse(title.canonicalUri))
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -180,8 +180,8 @@ class ContributionController @Inject constructor(@param:Named("default_preferenc
|
|||
showAlertDialog(
|
||||
activity, activity.getString(R.string.location_permission_title),
|
||||
activity.getString(R.string.in_app_camera_location_permission_rationale),
|
||||
activity.getString(android.R.string.ok),
|
||||
activity.getString(android.R.string.cancel),
|
||||
activity.getString(R.string.ok),
|
||||
activity.getString(R.string.cancel),
|
||||
{
|
||||
createDialogsAndHandleLocationPermissions(
|
||||
activity,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import fr.free.nrw.commons.BasePresenter
|
|||
interface ContributionsContract {
|
||||
|
||||
interface View {
|
||||
fun showMessage(localizedMessage: String)
|
||||
fun getContext(): Context?
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import androidx.work.WorkManager
|
|||
import fr.free.nrw.commons.MapController.NearbyPlacesInfo
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.Utils
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.campaigns.CampaignView
|
||||
import fr.free.nrw.commons.campaigns.CampaignsPresenter
|
||||
|
|
@ -44,7 +43,7 @@ import fr.free.nrw.commons.location.LatLng
|
|||
import fr.free.nrw.commons.location.LocationServiceManager
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.nearby.NearbyController
|
||||
import fr.free.nrw.commons.nearby.NearbyNotificationCardView
|
||||
|
|
@ -64,13 +63,15 @@ import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween
|
|||
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
|
||||
import fr.free.nrw.commons.utils.PermissionUtils.hasPermission
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||
import fr.free.nrw.commons.utils.isMonumentsEnabled
|
||||
import fr.free.nrw.commons.utils.wLMEndDate
|
||||
import fr.free.nrw.commons.utils.wLMStartDate
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
|
|
@ -242,8 +243,8 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
|
|||
private fun initWLMCampaign() {
|
||||
wlmCampaign = Campaign(
|
||||
getString(R.string.wlm_campaign_title),
|
||||
getString(R.string.wlm_campaign_description), Utils.getWLMStartDate().toString(),
|
||||
Utils.getWLMEndDate().toString(), NearbyParentFragment.WLM_URL, true
|
||||
getString(R.string.wlm_campaign_description), wLMStartDate,
|
||||
wLMEndDate, NearbyParentFragment.WLM_URL, true
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -729,7 +730,7 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
|
|||
* of campaigns on the campaigns card
|
||||
*/
|
||||
private fun fetchCampaigns() {
|
||||
if (Utils.isMonumentsEnabled(Date())) {
|
||||
if (isMonumentsEnabled) {
|
||||
if (binding != null) {
|
||||
binding!!.campaignsView.setCampaign(wlmCampaign)
|
||||
binding!!.campaignsView.visibility = View.VISIBLE
|
||||
|
|
@ -743,10 +744,6 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
|
|||
}
|
||||
}
|
||||
|
||||
override fun showMessage(message: String) {
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun showCampaigns(campaign: Campaign?) {
|
||||
if (campaign != null && !isUserProfile) {
|
||||
if (binding != null) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ContributionsListContract {
|
|||
fun showNoContributionsUI(shouldShow: Boolean)
|
||||
}
|
||||
|
||||
interface UserActionListener : BasePresenter<View?> {
|
||||
interface UserActionListener : BasePresenter<View> {
|
||||
fun refreshList(swipeRefreshLayout: SwipeRefreshLayout?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import android.annotation.SuppressLint
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
|
|
@ -20,6 +19,8 @@ import androidx.activity.result.ActivityResultLauncher
|
|||
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.os.BundleCompat
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
|
@ -29,7 +30,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
|||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.MediaDataExtractor
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.Utils
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.contributions.WikipediaInstructionsDialogFragment.Companion.newInstance
|
||||
import fr.free.nrw.commons.databinding.FragmentContributionsListBinding
|
||||
|
|
@ -39,10 +39,10 @@ import fr.free.nrw.commons.filepicker.FilePicker
|
|||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.profile.ProfileActivity
|
||||
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||
import fr.free.nrw.commons.utils.SystemThemeUtils
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showShortToast
|
||||
import fr.free.nrw.commons.utils.copyToClipboard
|
||||
import fr.free.nrw.commons.utils.handleWebUrl
|
||||
import fr.free.nrw.commons.wikidata.model.WikiSite
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
|
|
@ -52,10 +52,6 @@ import javax.inject.Named
|
|||
*/
|
||||
class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsListContract.View,
|
||||
ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback {
|
||||
@JvmField
|
||||
@Inject
|
||||
var systemThemeUtils: SystemThemeUtils? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var controller: ContributionController? = null
|
||||
|
|
@ -82,13 +78,14 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
var sessionManager: SessionManager? = null
|
||||
|
||||
private var binding: FragmentContributionsListBinding? = null
|
||||
private var fab_close: Animation? = null
|
||||
private var fab_open: Animation? = null
|
||||
private var rotate_forward: Animation? = null
|
||||
private var rotate_backward: Animation? = null
|
||||
private var fabClose: Animation? = null
|
||||
private var fabOpen: Animation? = null
|
||||
private var rotateForward: Animation? = null
|
||||
private var rotateBackward: Animation? = null
|
||||
private var isFabOpen = false
|
||||
|
||||
private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>
|
||||
private lateinit var inAppCameraLocationPermissionLauncher:
|
||||
ActivityResultLauncher<Array<String>>
|
||||
|
||||
@VisibleForTesting
|
||||
var rvContributionsList: RecyclerView? = null
|
||||
|
|
@ -99,8 +96,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
@VisibleForTesting
|
||||
var callback: Callback? = null
|
||||
|
||||
private val SPAN_COUNT_LANDSCAPE = 3
|
||||
private val SPAN_COUNT_PORTRAIT = 1
|
||||
private val spanCountLandscape = 3
|
||||
private val spanCountPortrait = 1
|
||||
|
||||
private var contributionsSize = 0
|
||||
private var userName: String? = null
|
||||
|
|
@ -149,7 +146,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
userName = requireArguments().getString(ProfileActivity.KEY_USERNAME)
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(userName)) {
|
||||
if (userName.isNullOrEmpty()) {
|
||||
userName = sessionManager!!.userName
|
||||
}
|
||||
inAppCameraLocationPermissionLauncher =
|
||||
|
|
@ -160,7 +157,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
controller?.locationPermissionCallback?.onLocationPermissionGranted()
|
||||
} else {
|
||||
activity?.let { currentActivity ->
|
||||
if (currentActivity.shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
|
||||
if (currentActivity.shouldShowRequestPermissionRationale(
|
||||
permission.ACCESS_FINE_LOCATION)) {
|
||||
controller?.handleShowRationaleFlowCameraLocation(
|
||||
currentActivity,
|
||||
inAppCameraLocationPermissionLauncher, // Pass launcher
|
||||
|
|
@ -168,7 +166,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
)
|
||||
} else {
|
||||
controller?.locationPermissionCallback?.onLocationPermissionDenied(
|
||||
currentActivity.getString(R.string.in_app_camera_location_permission_denied)
|
||||
currentActivity.getString(
|
||||
R.string.in_app_camera_location_permission_denied)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -188,7 +187,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
contributionsListPresenter!!.onAttachView(this)
|
||||
binding!!.fabCustomGallery.setOnClickListener { v: View? -> launchCustomSelector() }
|
||||
binding!!.fabCustomGallery.setOnLongClickListener { view: View? ->
|
||||
showShortToast(context, fr.free.nrw.commons.R.string.custom_selector_title)
|
||||
showShortToast(context, R.string.custom_selector_title)
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +197,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
} else {
|
||||
binding!!.tvContributionsOfUser.visibility = View.VISIBLE
|
||||
binding!!.tvContributionsOfUser.text =
|
||||
getString(fr.free.nrw.commons.R.string.contributions_of_user, userName)
|
||||
getString(R.string.contributions_of_user, userName)
|
||||
binding!!.fabLayout.visibility = View.GONE
|
||||
}
|
||||
|
||||
|
|
@ -236,7 +235,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
adapter = ContributionsListAdapter(this, mediaClient!!, mediaDataExtractor!!, compositeDisposable)
|
||||
adapter = ContributionsListAdapter(this,
|
||||
mediaClient!!,
|
||||
mediaDataExtractor!!,
|
||||
compositeDisposable)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
|
@ -311,7 +313,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
|
||||
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||
if (isFabOpen) {
|
||||
animateFAB(isFabOpen)
|
||||
animateFAB(true)
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
|
@ -343,14 +345,20 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
}
|
||||
|
||||
private fun getSpanCount(orientation: Int): Int {
|
||||
return if (orientation == Configuration.ORIENTATION_LANDSCAPE) SPAN_COUNT_LANDSCAPE else SPAN_COUNT_PORTRAIT
|
||||
return if (orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
spanCountLandscape
|
||||
else
|
||||
spanCountPortrait
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
// check orientation
|
||||
binding!!.fabLayout.orientation =
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
LinearLayout.HORIZONTAL
|
||||
else
|
||||
LinearLayout.VERTICAL
|
||||
rvContributionsList
|
||||
?.setLayoutManager(
|
||||
GridLayoutManager(context, getSpanCount(newConfig.orientation))
|
||||
|
|
@ -358,10 +366,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
}
|
||||
|
||||
private fun initializeAnimations() {
|
||||
fab_open = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_open)
|
||||
fab_close = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_close)
|
||||
rotate_forward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_forward)
|
||||
rotate_backward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_backward)
|
||||
fabOpen = AnimationUtils.loadAnimation(activity, R.anim.fab_open)
|
||||
fabClose = AnimationUtils.loadAnimation(activity, R.anim.fab_close)
|
||||
rotateForward = AnimationUtils.loadAnimation(activity, R.anim.rotate_forward)
|
||||
rotateBackward = AnimationUtils.loadAnimation(activity, R.anim.rotate_backward)
|
||||
}
|
||||
|
||||
private fun setListeners() {
|
||||
|
|
@ -377,7 +385,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
binding!!.fabCamera.setOnLongClickListener { view: View? ->
|
||||
showShortToast(
|
||||
context,
|
||||
fr.free.nrw.commons.R.string.add_contribution_from_camera
|
||||
R.string.add_contribution_from_camera
|
||||
)
|
||||
true
|
||||
}
|
||||
|
|
@ -386,7 +394,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
animateFAB(isFabOpen)
|
||||
}
|
||||
binding!!.fabGallery.setOnLongClickListener { view: View? ->
|
||||
showShortToast(context, fr.free.nrw.commons.R.string.menu_from_gallery)
|
||||
showShortToast(context, R.string.menu_from_gallery)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -394,7 +402,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
/**
|
||||
* Launch Custom Selector.
|
||||
*/
|
||||
protected fun launchCustomSelector() {
|
||||
private fun launchCustomSelector() {
|
||||
controller!!.initiateCustomGalleryPickWithPermission(
|
||||
requireActivity(),
|
||||
customSelectorLauncherForResult
|
||||
|
|
@ -410,18 +418,18 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
this.isFabOpen = !isFabOpen
|
||||
if (binding!!.fabPlus.isShown) {
|
||||
if (isFabOpen) {
|
||||
binding!!.fabPlus.startAnimation(rotate_backward)
|
||||
binding!!.fabCamera.startAnimation(fab_close)
|
||||
binding!!.fabGallery.startAnimation(fab_close)
|
||||
binding!!.fabCustomGallery.startAnimation(fab_close)
|
||||
binding!!.fabPlus.startAnimation(rotateBackward)
|
||||
binding!!.fabCamera.startAnimation(fabClose)
|
||||
binding!!.fabGallery.startAnimation(fabClose)
|
||||
binding!!.fabCustomGallery.startAnimation(fabClose)
|
||||
binding!!.fabCamera.hide()
|
||||
binding!!.fabGallery.hide()
|
||||
binding!!.fabCustomGallery.hide()
|
||||
} else {
|
||||
binding!!.fabPlus.startAnimation(rotate_forward)
|
||||
binding!!.fabCamera.startAnimation(fab_open)
|
||||
binding!!.fabGallery.startAnimation(fab_open)
|
||||
binding!!.fabCustomGallery.startAnimation(fab_open)
|
||||
binding!!.fabPlus.startAnimation(rotateForward)
|
||||
binding!!.fabCamera.startAnimation(fabOpen)
|
||||
binding!!.fabGallery.startAnimation(fabOpen)
|
||||
binding!!.fabCustomGallery.startAnimation(fabOpen)
|
||||
binding!!.fabCamera.show()
|
||||
binding!!.fabGallery.show()
|
||||
binding!!.fabCustomGallery.show()
|
||||
|
|
@ -433,9 +441,9 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
/**
|
||||
* Shows welcome message if user has no contributions yet i.e. new user.
|
||||
*/
|
||||
override fun showWelcomeTip(shouldShow: Boolean) {
|
||||
override fun showWelcomeTip(numberOfUploads: Boolean) {
|
||||
binding!!.noContributionsYet.visibility =
|
||||
if (shouldShow) View.VISIBLE else View.GONE
|
||||
if (numberOfUploads) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -455,22 +463,22 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val layoutManager = rvContributionsList
|
||||
?.getLayoutManager() as GridLayoutManager?
|
||||
val layoutManager = rvContributionsList?.layoutManager as GridLayoutManager?
|
||||
outState.putParcelable(RV_STATE, layoutManager!!.onSaveInstanceState())
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
super.onViewStateRestored(savedInstanceState)
|
||||
if (null != savedInstanceState) {
|
||||
val savedRecyclerLayoutState = savedInstanceState.getParcelable<Parcelable>(RV_STATE)
|
||||
val savedRecyclerLayoutState =
|
||||
BundleCompat.getParcelable(savedInstanceState, RV_STATE, Parcelable::class.java)
|
||||
rvContributionsList!!.layoutManager!!.onRestoreInstanceState(savedRecyclerLayoutState)
|
||||
}
|
||||
}
|
||||
|
||||
override fun openMediaDetail(position: Int, isWikipediaButtonDisplayed: Boolean) {
|
||||
override fun openMediaDetail(contribution: Int, isWikipediaPageExists: Boolean) {
|
||||
if (null != callback) { //Just being safe, ideally they won't be called when detached
|
||||
callback!!.showDetail(position, isWikipediaButtonDisplayed)
|
||||
callback!!.showDetail(contribution, isWikipediaPageExists)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -482,8 +490,8 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
override fun addImageToWikipedia(contribution: Contribution?) {
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_title),
|
||||
getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_desc),
|
||||
getString(R.string.add_picture_to_wikipedia_article_title),
|
||||
getString(R.string.add_picture_to_wikipedia_article_desc),
|
||||
{
|
||||
if (contribution != null) {
|
||||
showAddImageToWikipediaInstructions(contribution)
|
||||
|
|
@ -497,16 +505,18 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
* @param contribution
|
||||
*/
|
||||
private fun showAddImageToWikipediaInstructions(contribution: Contribution) {
|
||||
val fragmentManager = fragmentManager
|
||||
val fragmentManager = this.parentFragmentManager
|
||||
val fragment = newInstance(contribution)
|
||||
fragment.callback =
|
||||
WikipediaInstructionsDialogFragment.Callback { contribution: Contribution?, copyWikicode: Boolean ->
|
||||
this.onConfirmClicked(
|
||||
WikipediaInstructionsDialogFragment.Callback {
|
||||
contribution: Contribution?,
|
||||
copyWikicode: Boolean ->
|
||||
onConfirmClicked(
|
||||
contribution,
|
||||
copyWikicode
|
||||
)
|
||||
}
|
||||
fragment.show(fragmentManager!!, "WikimediaFragment")
|
||||
fragment.show(fragmentManager, "WikimediaFragment")
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -527,14 +537,13 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
|||
*/
|
||||
override fun onConfirmClicked(contribution: Contribution?, copyWikicode: Boolean) {
|
||||
if (copyWikicode) {
|
||||
val wikicode = contribution!!.media.wikiCode
|
||||
Utils.copy("wikicode", wikicode, context)
|
||||
requireContext().copyToClipboard("wikicode", contribution!!.media.wikiCode)
|
||||
}
|
||||
|
||||
val url =
|
||||
languageWikipediaSite!!.mobileUrl() + "/wiki/" + (contribution!!.wikidataPlace
|
||||
?.getWikipediaPageTitle())
|
||||
Utils.handleWebUrl(context, Uri.parse(url))
|
||||
handleWebUrl(requireContext(), url.toUri())
|
||||
}
|
||||
|
||||
fun getContributionStateAt(position: Int): Int {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.WelcomeActivity
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.bookmarks.BookmarkFragment
|
||||
import fr.free.nrw.commons.contributions.ContributionsFragment.Companion.newInstance
|
||||
|
|
@ -33,7 +32,9 @@ import fr.free.nrw.commons.notification.NotificationActivity.Companion.startYour
|
|||
import fr.free.nrw.commons.notification.NotificationController
|
||||
import fr.free.nrw.commons.quiz.QuizChecker
|
||||
import fr.free.nrw.commons.settings.SettingsFragment
|
||||
import fr.free.nrw.commons.startWelcome
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
|
||||
import fr.free.nrw.commons.upload.UploadProgressActivity
|
||||
import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper
|
||||
|
|
@ -112,6 +113,7 @@ class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener
|
|||
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = MainBinding.inflate(layoutInflater)
|
||||
applyEdgeToEdgeAllInsets(binding!!.root)
|
||||
setContentView(binding!!.root)
|
||||
setSupportActionBar(binding!!.toolbarBinding.toolbar)
|
||||
tabLayout = binding!!.fragmentMainNavTabLayout
|
||||
|
|
@ -151,21 +153,7 @@ after opening the app.
|
|||
}
|
||||
}
|
||||
setUpPager()
|
||||
/**
|
||||
* Ask the user for media location access just after login
|
||||
* so that location in the EXIF metadata of the images shared by the user
|
||||
* is retained on devices running Android 10 or above
|
||||
*/
|
||||
// if (VERSION.SDK_INT >= VERSION_CODES.Q) {
|
||||
// ActivityCompat.requestPermissions(this,
|
||||
// new String[]{Manifest.permission.ACCESS_MEDIA_LOCATION}, 0);
|
||||
// PermissionUtils.checkPermissionsAndPerformAction(
|
||||
// this,
|
||||
// () -> {},
|
||||
// R.string.media_location_permission_denied,
|
||||
// R.string.add_location_manually,
|
||||
// permission.ACCESS_MEDIA_LOCATION);
|
||||
// }
|
||||
|
||||
checkAndResumeStuckUploads()
|
||||
}
|
||||
}
|
||||
|
|
@ -336,7 +324,7 @@ after opening the app.
|
|||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.blockingGet()
|
||||
Timber.d("Resuming " + stuckUploads.size + " uploads...")
|
||||
Timber.d("Resuming %d uploads...", stuckUploads.size)
|
||||
if (!stuckUploads.isEmpty()) {
|
||||
for (contribution in stuckUploads) {
|
||||
contribution.state = Contribution.STATE_QUEUED
|
||||
|
|
@ -517,7 +505,7 @@ after opening the app.
|
|||
(!applicationKvStore!!.getBoolean("login_skipped"))
|
||||
) {
|
||||
defaultKvStore.putBoolean("inAppCameraFirstRun", true)
|
||||
WelcomeActivity.startYourself(this)
|
||||
startWelcome()
|
||||
}
|
||||
|
||||
retryAllFailedUploads()
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ class SetWallpaperWorker(context: Context, params: WorkerParameters) :
|
|||
}
|
||||
}
|
||||
|
||||
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>?) {
|
||||
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage?>?>) {
|
||||
Timber.d("Error getting bitmap from image url %s", imageUrl.toString())
|
||||
showNotification(context, "Setting Wallpaper Failed", "Failed to download image.")
|
||||
dataSource?.close()
|
||||
dataSource.close()
|
||||
}
|
||||
}, CallerThreadExecutor.getInstance())
|
||||
|
||||
|
|
|
|||
|
|
@ -39,4 +39,11 @@ data class Folder(
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = bucketId.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + images.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.customselector.model
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
|
||||
|
|
@ -48,7 +49,12 @@ data class Image(
|
|||
this(
|
||||
parcel.readLong(),
|
||||
parcel.readString()!!,
|
||||
parcel.readParcelable(Uri::class.java.classLoader)!!,
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
parcel.readParcelable(Uri::class.java.classLoader, Uri::class.java)!!
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
parcel.readParcelable(Uri::class.java.classLoader)!!
|
||||
},
|
||||
parcel.readString()!!,
|
||||
parcel.readLong(),
|
||||
parcel.readString()!!,
|
||||
|
|
@ -121,4 +127,16 @@ data class Image(
|
|||
|
||||
override fun newArray(size: Int): Array<Image?> = arrayOfNulls(size)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + bucketId.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + uri.hashCode()
|
||||
result = 31 * result + path.hashCode()
|
||||
result = 31 * result + bucketName.hashCode()
|
||||
result = 31 * result + sha1.hashCode()
|
||||
result = 31 * result + date.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,8 +168,7 @@ class ImageAdapter(
|
|||
|
||||
// Getting selected index when switch is off
|
||||
} else if (actionableImagesMap.size > position) {
|
||||
ImageHelper
|
||||
.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
|
||||
ImageHelper.getIndex(selectedImages, ArrayList(actionableImagesMap.values)[position])
|
||||
|
||||
// For any other case return -1
|
||||
} else {
|
||||
|
|
@ -348,8 +347,14 @@ class ImageAdapter(
|
|||
numberOfSelectedImagesMarkedAsNotForUpload--
|
||||
}
|
||||
notifyItemChanged(position, ImageUnselected())
|
||||
// Notify listener of deselection to update UI
|
||||
imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
|
||||
} else {
|
||||
val image = images[position]
|
||||
// Prevent adding the same image multiple times
|
||||
val image = if (showAlreadyActionedImages) images[position] else ArrayList(actionableImagesMap.values)[position]
|
||||
if (selectedImages.contains(image)) {
|
||||
return // Image already selected, ignore additional clicks
|
||||
}
|
||||
scope.launch(ioDispatcher) {
|
||||
val imageSHA1 = imageLoader.getSHA1(image, defaultDispatcher)
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
@ -373,7 +378,6 @@ class ImageAdapter(
|
|||
}
|
||||
selectedImages.add(image)
|
||||
notifyItemChanged(position, ImageSelectedOrUpdated())
|
||||
|
||||
imageSelectListener.onSelectedImagesChanged(selectedImages, numberOfSelectedImagesMarkedAsNotForUpload)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewGroupCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.customselector.database.NotForUploadStatus
|
||||
|
|
@ -56,6 +57,8 @@ import fr.free.nrw.commons.media.ZoomableActivity
|
|||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||
import fr.free.nrw.commons.utils.CustomSelectorUtils
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopInsets
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -198,6 +201,9 @@ class CustomSelectorActivity :
|
|||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
ViewGroupCompat.installCompatInsetsDispatch(binding.root)
|
||||
applyEdgeToEdgeTopInsets(toolbarBinding.toolbarLayout)
|
||||
bottomSheetBinding.bottomLayout.applyEdgeToEdgeBottomPaddingInsets()
|
||||
val view = binding.root
|
||||
setContentView(view)
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import fr.free.nrw.commons.databinding.FragmentCustomSelectorBinding
|
|||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import fr.free.nrw.commons.upload.FileProcessor
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
|
|
@ -99,6 +100,7 @@ class FolderFragment : CommonsDaggerSupportFragment() {
|
|||
selectorRV = binding?.selectorRv
|
||||
loader = binding?.loader
|
||||
with(binding?.selectorRv) {
|
||||
this?.applyEdgeToEdgeBottomPaddingInsets()
|
||||
this?.layoutManager = gridLayoutManager
|
||||
this?.setHasFixedSize(true)
|
||||
this?.adapter = folderAdapter
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Switch
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
|
|
@ -20,6 +19,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import fr.free.nrw.commons.contributions.ContributionDao
|
||||
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
|
||||
|
|
@ -41,11 +41,13 @@ import fr.free.nrw.commons.media.MediaClient
|
|||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.upload.FileProcessor
|
||||
import fr.free.nrw.commons.upload.FileUtilsWrapper
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomPaddingInsets
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.TreeMap
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.ArrayList
|
||||
|
|
@ -80,7 +82,7 @@ class ImageFragment :
|
|||
*/
|
||||
private var selectorRV: RecyclerView? = null
|
||||
private var loader: ProgressBar? = null
|
||||
private var switch: Switch? = null
|
||||
private var switch: SwitchMaterial? = null
|
||||
lateinit var filteredImages: ArrayList<Image>
|
||||
|
||||
/**
|
||||
|
|
@ -210,13 +212,18 @@ class ImageFragment :
|
|||
savedInstanceState: Bundle?,
|
||||
): View? {
|
||||
_binding = FragmentCustomSelectorBinding.inflate(inflater, container, false)
|
||||
imageAdapter =
|
||||
ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
|
||||
|
||||
// ensures imageAdapter is initialized
|
||||
if (!::imageAdapter.isInitialized) {
|
||||
imageAdapter = ImageAdapter(requireActivity(), activity as ImageSelectListener, imageLoader!!)
|
||||
Timber.d("Initialized imageAdapter in onCreateView")
|
||||
}
|
||||
// Set single selection mode if needed
|
||||
val singleSelection = (activity as? CustomSelectorActivity)?.intent?.getBooleanExtra(CustomSelectorActivity.EXTRA_SINGLE_SELECTION, false) == true
|
||||
imageAdapter.setSingleSelection(singleSelection)
|
||||
gridLayoutManager = GridLayoutManager(context, getSpanCount())
|
||||
with(binding?.selectorRv) {
|
||||
this?.applyEdgeToEdgeBottomPaddingInsets()
|
||||
this?.layoutManager = gridLayoutManager
|
||||
this?.setHasFixedSize(true)
|
||||
this?.adapter = imageAdapter
|
||||
|
|
@ -368,7 +375,12 @@ class ImageFragment :
|
|||
* notifyDataSetChanged, rebuild the holder views to account for deleted images.
|
||||
*/
|
||||
override fun onResume() {
|
||||
imageAdapter.notifyDataSetChanged()
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.notifyDataSetChanged()
|
||||
Timber.d("Notified imageAdapter in onResume")
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in onResume")
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
|
|
@ -378,14 +390,19 @@ class ImageFragment :
|
|||
* Save the Image Fragment state.
|
||||
*/
|
||||
override fun onDestroy() {
|
||||
imageAdapter.cleanUp()
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.cleanUp()
|
||||
Timber.d("Cleaned up imageAdapter in onDestroy")
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in onDestroy, skipping cleanup")
|
||||
}
|
||||
|
||||
val position =
|
||||
(selectorRV?.layoutManager as GridLayoutManager)
|
||||
.findFirstVisibleItemPosition()
|
||||
(selectorRV?.layoutManager as? GridLayoutManager)
|
||||
?.findFirstVisibleItemPosition() ?: -1
|
||||
|
||||
// Check for empty RecyclerView.
|
||||
if (position != -1 && filteredImages.size > 0) {
|
||||
// check for valid position and non-empty image list
|
||||
if (position != -1 && filteredImages.isNotEmpty() && ::imageAdapter.isInitialized) {
|
||||
context?.let { context ->
|
||||
context
|
||||
.getSharedPreferences(
|
||||
|
|
@ -394,34 +411,57 @@ class ImageFragment :
|
|||
)?.let { prefs ->
|
||||
prefs.edit()?.let { editor ->
|
||||
editor.putLong("ItemId", imageAdapter.getImageIdAt(position))?.apply()
|
||||
Timber.d("Saved last visible item ID: %d", imageAdapter.getImageIdAt(position))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Timber.d("Skipped saving item ID: position=%d, filteredImages.size=%d, imageAdapter initialized=%b",
|
||||
position, filteredImages.size, ::imageAdapter.isInitialized)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
_binding = null
|
||||
selectorRV = null
|
||||
loader = null
|
||||
switch = null
|
||||
progressLayout = null
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
override fun refresh() {
|
||||
imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
|
||||
Timber.d("Refreshed imageAdapter")
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in refresh")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the image from the actionable image map
|
||||
*/
|
||||
fun removeImage(image: Image) {
|
||||
imageAdapter.removeImageFromActionableImageMap(image)
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.removeImageFromActionableImageMap(image)
|
||||
Timber.d("Removed image from actionable image map")
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in removeImage")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the selected images
|
||||
*/
|
||||
fun clearSelectedImages() {
|
||||
imageAdapter.clearSelectedImages()
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.clearSelectedImages()
|
||||
Timber.d("Cleared selected images")
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in clearSelectedImages")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -432,6 +472,15 @@ class ImageFragment :
|
|||
selectedImages: ArrayList<Image>,
|
||||
shouldRefresh: Boolean,
|
||||
) {
|
||||
if (::imageAdapter.isInitialized) {
|
||||
imageAdapter.setSelectedImages(selectedImages)
|
||||
if (shouldRefresh) {
|
||||
imageAdapter.refresh(filteredImages, allImages, getUploadingContributions())
|
||||
}
|
||||
Timber.d("Passed %d selected images to imageAdapter, shouldRefresh=%b", selectedImages.size, shouldRefresh)
|
||||
} else {
|
||||
Timber.w("imageAdapter not initialized in passSelectedImages")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -441,6 +490,7 @@ class ImageFragment :
|
|||
if (!progressDialog.isShowing) {
|
||||
progressDialogLayout.progressDialogText.text = text
|
||||
progressDialog.show()
|
||||
Timber.d("Showing mark/unmark progress dialog: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -450,6 +500,7 @@ class ImageFragment :
|
|||
fun dismissMarkUnmarkProgressDialog() {
|
||||
if (progressDialog.isShowing) {
|
||||
progressDialog.dismiss()
|
||||
Timber.d("Dismissed mark/unmark progress dialog")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,10 @@ import android.content.Context
|
|||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsTable
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarksTable
|
||||
import fr.free.nrw.commons.category.CategoryDao
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesTable
|
||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
|
||||
|
||||
|
||||
|
|
@ -30,17 +29,17 @@ class DBOpenHelper(
|
|||
*/
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
CategoryDao.Table.onCreate(db)
|
||||
BookmarkPicturesDao.Table.onCreate(db)
|
||||
BookmarkItemsDao.Table.onCreate(db)
|
||||
RecentSearchesDao.Table.onCreate(db)
|
||||
BookmarksTable.onCreate(db)
|
||||
BookmarkItemsTable.onCreate(db)
|
||||
RecentSearchesTable.onCreate(db)
|
||||
RecentLanguagesDao.Table.onCreate(db)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
|
||||
CategoryDao.Table.onUpdate(db, from, to)
|
||||
BookmarkPicturesDao.Table.onUpdate(db, from, to)
|
||||
BookmarkItemsDao.Table.onUpdate(db, from, to)
|
||||
RecentSearchesDao.Table.onUpdate(db, from, to)
|
||||
BookmarksTable.onUpdate(db, from, to)
|
||||
BookmarkItemsTable.onUpdate(db, from, to)
|
||||
RecentSearchesTable.onUpdate(db, from, to)
|
||||
RecentLanguagesDao.Table.onUpdate(db, from, to)
|
||||
deleteTable(db, CONTRIBUTIONS_TABLE)
|
||||
deleteTable(db, BOOKMARKS_LOCATIONS)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ class DeleteHelper @Inject constructor(
|
|||
media: Media?,
|
||||
reason: String?
|
||||
): Single<Boolean>? {
|
||||
|
||||
if(context == null && media == null) {
|
||||
return null
|
||||
}
|
||||
|
|
@ -86,7 +85,6 @@ class DeleteHelper @Inject constructor(
|
|||
* @return
|
||||
*/
|
||||
private fun delete(media: Media, reason: String): Observable<Boolean> {
|
||||
Timber.d("thread is delete %s", Thread.currentThread().name)
|
||||
val summary = "Nominating ${media.filename} for deletion."
|
||||
val calendar = Calendar.getInstance()
|
||||
val fileDeleteString = """
|
||||
|
|
|
|||
|
|
@ -2,21 +2,19 @@ package fr.free.nrw.commons.delete
|
|||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
|
||||
import fr.free.nrw.commons.utils.DateUtil
|
||||
import java.util.Locale
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.utils.DateUtil
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper
|
||||
import io.reactivex.Single
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.rx2.rxSingle
|
||||
import timber.log.Timber
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class handles the reason for deleting a Media object
|
||||
|
|
@ -29,6 +27,8 @@ class ReasonBuilder @Inject constructor(
|
|||
private val viewUtilWrapper: ViewUtilWrapper
|
||||
) {
|
||||
|
||||
private val defaultFileUsagePageSize = 10
|
||||
|
||||
/**
|
||||
* To process the reason and append the media's upload date and uploaded_by_me string
|
||||
* @param media
|
||||
|
|
@ -39,7 +39,7 @@ class ReasonBuilder @Inject constructor(
|
|||
if (media == null || reason == null) {
|
||||
return Single.just("Not known")
|
||||
}
|
||||
return fetchArticleNumber(media, reason)
|
||||
return getAndAppendFileUsage(media, reason)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,27 +54,36 @@ class ReasonBuilder @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun fetchArticleNumber(media: Media, reason: String): Single<String> {
|
||||
return if (checkAccount()) {
|
||||
okHttpJsonApiClient
|
||||
.getAchievements(sessionManager.userName)
|
||||
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }
|
||||
} else {
|
||||
Single.just("")
|
||||
private fun getAndAppendFileUsage(media: Media, reason: String): Single<String> {
|
||||
return rxSingle(context = Dispatchers.IO) {
|
||||
if (!checkAccount()) return@rxSingle ""
|
||||
|
||||
try {
|
||||
val globalFileUsage = okHttpJsonApiClient.getGlobalFileUsages(
|
||||
fileName = media.filename,
|
||||
pageSize = defaultFileUsagePageSize
|
||||
)
|
||||
val globalUsages = globalFileUsage?.query?.pages?.sumOf { it.fileUsage.size } ?: 0
|
||||
|
||||
appendArticlesUsed(globalUsages, media, reason)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error fetching file usage")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the uploaded_by_me string, the upload date, name of articles using images
|
||||
* Takes the uploaded_by_me string, the upload date, no. of articles using images
|
||||
* and appends it to the received reason
|
||||
* @param feedBack object
|
||||
* @param fileUsages No. of files/articles using this image
|
||||
* @param media whose upload data is to be fetched
|
||||
* @param reason
|
||||
* @param reason string to be appended
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
|
||||
private fun appendArticlesUsed(fileUsages: Int, media: Media, reason: String): String {
|
||||
val reason1Template = context.getString(R.string.uploaded_by_myself)
|
||||
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
|
||||
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), fileUsages)
|
||||
.also { Timber.i("New Reason %s", it) }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.speech.RecognizerIntent
|
|||
import android.view.View
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.CommonsApplication
|
||||
|
|
@ -20,9 +21,11 @@ import fr.free.nrw.commons.description.EditDescriptionConstants.WIKITEXT
|
|||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
|
||||
import fr.free.nrw.commons.settings.Prefs
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeBottomInsets
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
|
||||
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeTopPaddingInsets
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Consumer
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
|
@ -87,6 +90,10 @@ class DescriptionEditActivity :
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityDescriptionEditBinding.inflate(layoutInflater)
|
||||
applyEdgeToEdgeBottomInsets(binding.btnEditSubmit)
|
||||
WindowCompat.getInsetsController(window, window.decorView)
|
||||
.isAppearanceLightStatusBars = false
|
||||
binding.toolbar.applyEdgeToEdgeTopPaddingInsets()
|
||||
setContentView(binding.root)
|
||||
|
||||
val bundle = intent.extras
|
||||
|
|
@ -143,7 +150,7 @@ class DescriptionEditActivity :
|
|||
this,
|
||||
getString(titleStringID),
|
||||
getString(messageStringId),
|
||||
getString(android.R.string.ok),
|
||||
getString(R.string.ok),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
package fr.free.nrw.commons.di
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import fr.free.nrw.commons.data.DBOpenHelper
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection.Companion.getInstance
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class CommonsDaggerContentProvider : ContentProvider() {
|
||||
@JvmField
|
||||
@Inject
|
||||
var dbOpenHelper: DBOpenHelper? = null
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
inject()
|
||||
return true
|
||||
}
|
||||
|
||||
fun requireDbOpenHelper(): DBOpenHelper = dbOpenHelper!!
|
||||
|
||||
fun requireDb(): SQLiteDatabase = requireDbOpenHelper().writableDatabase!!
|
||||
|
||||
private fun inject() {
|
||||
val injection = getInstance(context!!)
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import dagger.Provides
|
|||
import fr.free.nrw.commons.BetaConstants
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.OkHttpConnectionFactory
|
||||
import fr.free.nrw.commons.CommonHeaderRequestInterceptor
|
||||
import fr.free.nrw.commons.actions.PageEditClient
|
||||
import fr.free.nrw.commons.actions.PageEditInterface
|
||||
import fr.free.nrw.commons.actions.ThanksInterface
|
||||
|
|
@ -60,6 +61,7 @@ class NetworkingModule {
|
|||
.connectTimeout(120, TimeUnit.SECONDS)
|
||||
.writeTimeout(120, TimeUnit.SECONDS)
|
||||
.addInterceptor(httpLoggingInterceptor)
|
||||
.addInterceptor(CommonHeaderRequestInterceptor())
|
||||
.readTimeout(120, TimeUnit.SECONDS)
|
||||
.cache(Cache(File(context.cacheDir, "okHttpCache"), OK_HTTP_CACHE_SIZE))
|
||||
.build()
|
||||
|
|
|
|||
|
|
@ -1,264 +0,0 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.databinding.FragmentExploreBinding;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
public class ExploreFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
private static final String FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons";
|
||||
private static final String MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android";
|
||||
private static final String EXPLORE_MAP = "Map";
|
||||
private static final String MEDIA_DETAILS_FRAGMENT_TAG = "MediaDetailsFragment";
|
||||
|
||||
|
||||
public FragmentExploreBinding binding;
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
private ExploreListRootFragment featuredRootFragment;
|
||||
private ExploreListRootFragment mobileRootFragment;
|
||||
private ExploreMapRootFragment mapRootFragment;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public JsonKvStore applicationKvStore;
|
||||
|
||||
// Nearby map state (for if we came from Nearby fragment)
|
||||
private double prevZoom;
|
||||
private double prevLatitude;
|
||||
private double prevLongitude;
|
||||
|
||||
public void setScroll(boolean canScroll) {
|
||||
if (binding != null) {
|
||||
binding.viewPager.setCanScroll(canScroll);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ExploreFragment newInstance() {
|
||||
ExploreFragment fragment = new ExploreFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
loadNearbyMapData();
|
||||
binding = FragmentExploreBinding.inflate(inflater, container, false);
|
||||
|
||||
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager(),
|
||||
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
|
||||
binding.viewPager.setAdapter(viewPagerAdapter);
|
||||
binding.viewPager.setId(R.id.viewPager);
|
||||
binding.tabLayout.setupWithViewPager(binding.viewPager);
|
||||
binding.viewPager.addOnPageChangeListener(new OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset,
|
||||
int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
if (position == 2) {
|
||||
binding.viewPager.setCanScroll(false);
|
||||
} else {
|
||||
binding.viewPager.setCanScroll(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
});
|
||||
setTabs();
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, jump to Map tab
|
||||
if (isCameFromNearbyMap()) {
|
||||
binding.viewPager.setCurrentItem(2);
|
||||
}
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the titles in the tabLayout and fragments in the viewPager
|
||||
*/
|
||||
public void setTabs() {
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> titleList = new ArrayList<>();
|
||||
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY);
|
||||
|
||||
Bundle mobileArguments = new Bundle();
|
||||
mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY);
|
||||
|
||||
Bundle mapArguments = new Bundle();
|
||||
mapArguments.putString("categoryName", EXPLORE_MAP);
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, pass on zoom and center to Explore map root
|
||||
if (isCameFromNearbyMap()) {
|
||||
mapArguments.putDouble("prev_zoom", prevZoom);
|
||||
mapArguments.putDouble("prev_latitude", prevLatitude);
|
||||
mapArguments.putDouble("prev_longitude", prevLongitude);
|
||||
}
|
||||
|
||||
featuredRootFragment = new ExploreListRootFragment(featuredArguments);
|
||||
mobileRootFragment = new ExploreListRootFragment(mobileArguments);
|
||||
mapRootFragment = new ExploreMapRootFragment(mapArguments);
|
||||
fragmentList.add(featuredRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT));
|
||||
|
||||
fragmentList.add(mobileRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT));
|
||||
|
||||
fragmentList.add(mapRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT));
|
||||
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||
viewPagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Nearby map camera data from fragment arguments if any.
|
||||
*/
|
||||
public void loadNearbyMapData() {
|
||||
// get fragment arguments
|
||||
if (getArguments() != null) {
|
||||
prevZoom = getArguments().getDouble("prev_zoom");
|
||||
prevLatitude = getArguments().getDouble("prev_latitude");
|
||||
prevLongitude = getArguments().getDouble("prev_longitude");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if fragment arguments contain data from Nearby map. if present, then the user
|
||||
* navigated from Nearby using 'Show in Explore'.
|
||||
*
|
||||
* @return true if user navigated from Nearby map
|
||||
**/
|
||||
public boolean isCameFromNearbyMap() {
|
||||
return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0;
|
||||
}
|
||||
|
||||
public boolean onBackPressed() {
|
||||
if (binding.tabLayout.getSelectedTabPosition() == 0) {
|
||||
if (featuredRootFragment.backPressed()) {
|
||||
((BaseActivity) getActivity()).getSupportActionBar()
|
||||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
} else if (binding.tabLayout.getSelectedTabPosition() == 1) { //Mobile root fragment
|
||||
if (mobileRootFragment.backPressed()) {
|
||||
((BaseActivity) getActivity()).getSupportActionBar()
|
||||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
} else { //explore map fragment
|
||||
if (mapRootFragment.backPressed()) {
|
||||
((BaseActivity) getActivity()).getSupportActionBar()
|
||||
.setDisplayHomeAsUpEnabled(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method inflates the menu in the toolbar
|
||||
*/
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
// if logged in 'Show in Nearby' menu item is visible
|
||||
if (applicationKvStore.getBoolean("login_skipped") == false) {
|
||||
inflater.inflate(R.menu.explore_fragment_menu, menu);
|
||||
|
||||
MenuItem others = menu.findItem(R.id.list_item_show_in_nearby);
|
||||
|
||||
if (binding.viewPager.getCurrentItem() == 2) {
|
||||
others.setVisible(true);
|
||||
}
|
||||
|
||||
// if on Map tab, show all menu options, else only show search
|
||||
binding.viewPager.addOnPageChangeListener(new OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset,
|
||||
int positionOffsetPixels) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
others.setVisible((position == 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
if (state == SCROLL_STATE_IDLE && binding.viewPager.getCurrentItem() == 2) {
|
||||
onPageSelected(2);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
inflater.inflate(R.menu.menu_search, menu);
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the logic on ItemSelect in toolbar menu Currently only 1 choice is
|
||||
* available to open search page of the app
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
// Handle item selection
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_search:
|
||||
ActivityUtils.startActivityWithFlags(getActivity(), SearchActivity.class);
|
||||
return true;
|
||||
case R.id.list_item_show_in_nearby:
|
||||
mapRootFragment.loadNearbyMapFromExplore();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
227
app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
Normal file
227
app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.kt
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentPagerAdapter
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.ViewPagerAdapter
|
||||
import fr.free.nrw.commons.contributions.MainActivity
|
||||
import fr.free.nrw.commons.databinding.FragmentExploreBinding
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class ExploreFragment : CommonsDaggerSupportFragment() {
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
var applicationKvStore: JsonKvStore? = null
|
||||
|
||||
private var featuredRootFragment: ExploreListRootFragment? = null
|
||||
private var mobileRootFragment: ExploreListRootFragment? = null
|
||||
private var mapRootFragment: ExploreMapRootFragment? = null
|
||||
private var prevZoom = 0.0
|
||||
private var prevLatitude = 0.0
|
||||
private var prevLongitude = 0.0
|
||||
private var viewPagerAdapter: ViewPagerAdapter? = null
|
||||
var binding: FragmentExploreBinding? = null
|
||||
|
||||
fun setScroll(canScroll: Boolean) {
|
||||
if (binding != null) {
|
||||
binding!!.viewPager.canScroll = canScroll
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
super.onCreate(savedInstanceState)
|
||||
loadNearbyMapData()
|
||||
binding = FragmentExploreBinding.inflate(inflater, container, false)
|
||||
|
||||
viewPagerAdapter = ViewPagerAdapter(
|
||||
requireContext(), childFragmentManager,
|
||||
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
|
||||
)
|
||||
|
||||
binding!!.viewPager.adapter = viewPagerAdapter
|
||||
binding!!.viewPager.id = R.id.viewPager
|
||||
binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
|
||||
binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
|
||||
override fun onPageScrollStateChanged(state: Int) = Unit
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding!!.viewPager.canScroll = position != 2
|
||||
if (position == 2) {
|
||||
mapRootFragment?.requestLocationIfNeeded()
|
||||
}
|
||||
}
|
||||
})
|
||||
setTabs()
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, jump to Map tab
|
||||
if (isCameFromNearbyMap) {
|
||||
binding!!.viewPager.currentItem = 2
|
||||
}
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the titles in the tabLayout and fragments in the viewPager
|
||||
*/
|
||||
fun setTabs() {
|
||||
val featuredArguments = Bundle()
|
||||
featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY)
|
||||
|
||||
val mobileArguments = Bundle()
|
||||
mobileArguments.putString("categoryName", MOBILE_UPLOADS_CATEGORY)
|
||||
|
||||
val mapArguments = Bundle()
|
||||
mapArguments.putString("categoryName", EXPLORE_MAP)
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, pass on zoom and center to Explore map root
|
||||
if (isCameFromNearbyMap) {
|
||||
mapArguments.putDouble("prev_zoom", prevZoom)
|
||||
mapArguments.putDouble("prev_latitude", prevLatitude)
|
||||
mapArguments.putDouble("prev_longitude", prevLongitude)
|
||||
}
|
||||
|
||||
featuredRootFragment = ExploreListRootFragment(featuredArguments)
|
||||
mobileRootFragment = ExploreListRootFragment(mobileArguments)
|
||||
mapRootFragment = ExploreMapRootFragment(mapArguments)
|
||||
|
||||
(activity as MainActivity).showTabs()
|
||||
(activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
|
||||
viewPagerAdapter!!.setTabs(
|
||||
R.string.explore_tab_title_featured to featuredRootFragment!!,
|
||||
R.string.explore_tab_title_mobile to mobileRootFragment!!,
|
||||
R.string.explore_tab_title_map to mapRootFragment!!
|
||||
)
|
||||
viewPagerAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Nearby map camera data from fragment arguments if any.
|
||||
*/
|
||||
private fun loadNearbyMapData() {
|
||||
// get fragment arguments
|
||||
if (arguments != null) {
|
||||
with (requireArguments()) {
|
||||
prevZoom = getDouble("prev_zoom")
|
||||
prevLatitude = getDouble("prev_latitude")
|
||||
prevLongitude = getDouble("prev_longitude")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if fragment arguments contain data from Nearby map. if present, then the user
|
||||
* navigated from Nearby using 'Show in Explore'.
|
||||
*
|
||||
* @return true if user navigated from Nearby map
|
||||
*/
|
||||
private val isCameFromNearbyMap: Boolean
|
||||
get() = prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0
|
||||
|
||||
fun onBackPressed(): Boolean {
|
||||
if (binding!!.tabLayout.selectedTabPosition == 0) {
|
||||
if (featuredRootFragment!!.backPressed()) {
|
||||
(activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
return true
|
||||
}
|
||||
} else if (binding!!.tabLayout.selectedTabPosition == 1) { //Mobile root fragment
|
||||
if (mobileRootFragment!!.backPressed()) {
|
||||
(activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
return true
|
||||
}
|
||||
} else { //explore map fragment
|
||||
if (mapRootFragment!!.backPressed()) {
|
||||
(activity as BaseActivity).supportActionBar!!.setDisplayHomeAsUpEnabled(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* This method inflates the menu in the toolbar
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
// if logged in 'Show in Nearby' menu item is visible
|
||||
if (applicationKvStore!!.getBoolean("login_skipped") == false) {
|
||||
inflater.inflate(R.menu.explore_fragment_menu, menu)
|
||||
|
||||
val others = menu.findItem(R.id.list_item_show_in_nearby)
|
||||
|
||||
if (binding!!.viewPager.currentItem == 2) {
|
||||
others.setVisible(true)
|
||||
}
|
||||
|
||||
// if on Map tab, show all menu options, else only show search
|
||||
binding!!.viewPager.addOnPageChangeListener(object : OnPageChangeListener {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
|
||||
override fun onPageScrollStateChanged(state: Int) = Unit
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding!!.viewPager.canScroll = position != 2
|
||||
others.setVisible(position == 2)
|
||||
if (position == 2) {
|
||||
mapRootFragment?.requestLocationIfNeeded()
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
inflater.inflate(R.menu.menu_search, menu)
|
||||
}
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the logic on ItemSelect in toolbar menu Currently only 1 choice is
|
||||
* available to open search page of the app
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
// Handle item selection
|
||||
when (item.itemId) {
|
||||
R.id.action_search -> {
|
||||
startActivityWithFlags(requireActivity(), SearchActivity::class.java)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.list_item_show_in_nearby -> {
|
||||
mapRootFragment!!.loadNearbyMapFromExplore()
|
||||
return true
|
||||
}
|
||||
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
binding = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val FEATURED_IMAGES_CATEGORY = "Featured_pictures_on_Wikimedia_Commons"
|
||||
private const val MOBILE_UPLOADS_CATEGORY = "Uploaded_with_Mobile/Android"
|
||||
private const val EXPLORE_MAP = "Map"
|
||||
|
||||
fun newInstance(): ExploreFragment = ExploreFragment().apply {
|
||||
retainInstance = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,215 +0,0 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.content.Context;
|
||||
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.Fragment;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
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.explore.categories.media.CategoriesMediaFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.navtab.NavTab;
|
||||
|
||||
public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
|
||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategoriesMediaFragment listFragment;
|
||||
|
||||
private FragmentFeaturedRootBinding binding;
|
||||
|
||||
public ExploreListRootFragment() {
|
||||
//empty constructor necessary otherwise crashes on recreate
|
||||
}
|
||||
|
||||
public ExploreListRootFragment(Bundle bundle) {
|
||||
String title = bundle.getString("categoryName");
|
||||
listFragment = new CategoriesMediaFragment();
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
listFragment.setArguments(featuredArguments);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@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) {
|
||||
if (binding!=null) {
|
||||
binding.exploreContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (((ExploreFragment) getParentFragment()).binding!=null) {
|
||||
((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
|
||||
((ExploreFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, listFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (listFragment != null) {
|
||||
return listFragment.getMediaAtPosition(i);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (listFragment != null) {
|
||||
return listFragment.getTotalMediaCount();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
onMediaClicked(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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||
* mediaDetails otherwise returns false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean backPressed() {
|
||||
if (null != mediaDetails && mediaDetails.isVisible()) {
|
||||
if (((ExploreFragment) getParentFragment()).binding != null) {
|
||||
((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
removeFragment(mediaDetails);
|
||||
((ExploreFragment) getParentFragment()).setScroll(true);
|
||||
setFragment(listFragment, mediaDetails);
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return true;
|
||||
} else {
|
||||
if (((MainActivity) getActivity()) != null) {
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
}
|
||||
if (((MainActivity) getActivity()) != null) {
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback
|
||||
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.explore.categories.media.CategoriesMediaFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.navtab.NavTab
|
||||
|
||||
class ExploreListRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider,
|
||||
CategoryImagesCallback {
|
||||
private var mediaDetails: MediaDetailPagerFragment? = null
|
||||
private var listFragment: CategoriesMediaFragment? = null
|
||||
private var binding: FragmentFeaturedRootBinding? = null
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(bundle: Bundle) {
|
||||
listFragment = CategoriesMediaFragment().apply {
|
||||
arguments = bundleOf(
|
||||
"categoryName" to bundle.getString("categoryName")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
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) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (fragment.isAdded && otherFragment == null) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (!fragment.isAdded && otherFragment != null) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.add(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (!fragment.isAdded) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFragment(fragment: Fragment) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
}
|
||||
|
||||
override fun onMediaClicked(position: Int) {
|
||||
if (binding != null) {
|
||||
binding!!.exploreContainer.visibility = View.VISIBLE
|
||||
}
|
||||
if ((parentFragment as ExploreFragment).binding != null) {
|
||||
(parentFragment as ExploreFragment).binding!!.tabLayout.visibility =
|
||||
View.GONE
|
||||
}
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
|
||||
(parentFragment as ExploreFragment).setScroll(false)
|
||||
setFragment(mediaDetails!!, listFragment)
|
||||
mediaDetails!!.showImage(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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? = listFragment?.getMediaAtPosition(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 fun getTotalMediaCount(): Int = listFragment?.getTotalMediaCount() ?: 0
|
||||
|
||||
override fun getContributionStateAt(position: Int): Int? = 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!!)
|
||||
onMediaClicked(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() {
|
||||
mediaDetails?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||
* mediaDetails otherwise returns false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
fun backPressed(): Boolean {
|
||||
if (null != mediaDetails && mediaDetails!!.isVisible) {
|
||||
if ((parentFragment as ExploreFragment).binding != null) {
|
||||
(parentFragment as ExploreFragment).binding!!.tabLayout.visibility =
|
||||
View.VISIBLE
|
||||
}
|
||||
removeFragment(mediaDetails!!)
|
||||
(parentFragment as ExploreFragment).setScroll(true)
|
||||
setFragment(listFragment!!, mediaDetails)
|
||||
(activity as MainActivity).showTabs()
|
||||
return true
|
||||
} else {
|
||||
if ((activity as MainActivity?) != null) {
|
||||
(activity as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
|
||||
}
|
||||
}
|
||||
if ((activity as MainActivity?) != null) {
|
||||
(activity as MainActivity).showTabs()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,239 +0,0 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.content.Context;
|
||||
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.Fragment;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
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.explore.map.ExploreMapFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.navtab.NavTab;
|
||||
|
||||
public class ExploreMapRootFragment extends CommonsDaggerSupportFragment implements
|
||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private ExploreMapFragment mapFragment;
|
||||
|
||||
private FragmentFeaturedRootBinding binding;
|
||||
|
||||
public ExploreMapRootFragment() {
|
||||
//empty constructor necessary otherwise crashes on recreate
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ExploreMapRootFragment newInstance() {
|
||||
ExploreMapRootFragment fragment = new ExploreMapRootFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public ExploreMapRootFragment(Bundle bundle) {
|
||||
// get fragment arguments
|
||||
String title = bundle.getString("categoryName");
|
||||
double zoom = bundle.getDouble("prev_zoom");
|
||||
double latitude = bundle.getDouble("prev_latitude");
|
||||
double longitude = bundle.getDouble("prev_longitude");
|
||||
|
||||
mapFragment = new ExploreMapFragment();
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, pass on zoom and center
|
||||
if (zoom != 0.0 || latitude != 0.0 || longitude != 0.0) {
|
||||
featuredArguments.putDouble("prev_zoom", zoom);
|
||||
featuredArguments.putDouble("prev_latitude", latitude);
|
||||
featuredArguments.putDouble("prev_longitude", longitude);
|
||||
}
|
||||
mapFragment.setArguments(featuredArguments);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
@Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
setFragment(mapFragment, 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) {
|
||||
binding.exploreContainer.setVisibility(View.VISIBLE);
|
||||
((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
|
||||
((ExploreFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, mapFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (mapFragment != null && mapFragment.mediaList != null) {
|
||||
return mapFragment.mediaList.get(i);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (mapFragment != null && mapFragment.mediaList != null) {
|
||||
return mapFragment.mediaList.size();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@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 && !mapFragment.isVisible()) {
|
||||
removeFragment(mediaDetails);
|
||||
onMediaClicked(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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||
* mediaDetails otherwise returns false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean backPressed() {
|
||||
if (null != mediaDetails && mediaDetails.isVisible()) {
|
||||
((ExploreFragment) getParentFragment()).binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
removeFragment(mediaDetails);
|
||||
((ExploreFragment) getParentFragment()).setScroll(true);
|
||||
setFragment(mapFragment, mediaDetails);
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return true;
|
||||
|
||||
}
|
||||
if (mapFragment != null && mapFragment.isVisible()) {
|
||||
if (mapFragment.backButtonClicked()) {
|
||||
// Explore map fragment handled the event no further action required.
|
||||
return true;
|
||||
} else {
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void loadNearbyMapFromExplore() {
|
||||
mapFragment.loadNearbyMapFromExplore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback
|
||||
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.explore.map.ExploreMapFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.navtab.NavTab
|
||||
|
||||
class ExploreMapRootFragment : CommonsDaggerSupportFragment, MediaDetailProvider,
|
||||
CategoryImagesCallback {
|
||||
private var mediaDetails: MediaDetailPagerFragment? = null
|
||||
private var mapFragment: ExploreMapFragment? = null
|
||||
private var binding: FragmentFeaturedRootBinding? = null
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(bundle: Bundle) {
|
||||
// get fragment arguments
|
||||
val title = bundle.getString("categoryName")
|
||||
val zoom = bundle.getDouble("prev_zoom")
|
||||
val latitude = bundle.getDouble("prev_latitude")
|
||||
val longitude = bundle.getDouble("prev_longitude")
|
||||
|
||||
mapFragment = ExploreMapFragment()
|
||||
val featuredArguments = bundleOf(
|
||||
"categoryName" to title
|
||||
)
|
||||
|
||||
// if we came from 'Show in Explore' in Nearby, pass on zoom and center
|
||||
if (zoom != 0.0 || latitude != 0.0 || longitude != 0.0) {
|
||||
featuredArguments.putDouble("prev_zoom", zoom)
|
||||
featuredArguments.putDouble("prev_latitude", latitude)
|
||||
featuredArguments.putDouble("prev_longitude", longitude)
|
||||
}
|
||||
mapFragment!!.arguments = featuredArguments
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = FragmentFeaturedRootBinding.inflate(inflater, container, false)
|
||||
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
setFragment(mapFragment!!, mediaDetails)
|
||||
}
|
||||
}
|
||||
|
||||
fun setFragment(fragment: Fragment, otherFragment: Fragment?) {
|
||||
if (fragment.isAdded && otherFragment != null) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (fragment.isAdded && otherFragment == null) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.show(fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (!fragment.isAdded && otherFragment != null) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
.add(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
} else if (!fragment.isAdded) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.explore_container, fragment)
|
||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFragment(fragment: Fragment) {
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit()
|
||||
childFragmentManager.executePendingTransactions()
|
||||
}
|
||||
|
||||
override fun onMediaClicked(position: Int) {
|
||||
binding!!.exploreContainer.visibility = View.VISIBLE
|
||||
(parentFragment as ExploreFragment).binding!!.tabLayout.visibility = View.GONE
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
|
||||
(parentFragment as ExploreFragment).setScroll(false)
|
||||
setFragment(mediaDetails!!, mapFragment)
|
||||
mediaDetails!!.showImage(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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? = mapFragment?.mediaList?.get(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 fun getTotalMediaCount(): Int = mapFragment?.mediaList?.size ?: 0
|
||||
|
||||
override fun getContributionStateAt(position: Int): Int? = 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 && !mapFragment!!.isVisible) {
|
||||
removeFragment(mediaDetails!!)
|
||||
onMediaClicked(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() {
|
||||
mediaDetails?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||
* mediaDetails otherwise returns false.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
fun backPressed(): Boolean {
|
||||
if (null != mediaDetails && mediaDetails!!.isVisible) {
|
||||
(parentFragment as ExploreFragment).binding!!.tabLayout.visibility = View.VISIBLE
|
||||
removeFragment(mediaDetails!!)
|
||||
(parentFragment as ExploreFragment).setScroll(true)
|
||||
setFragment(mapFragment!!, mediaDetails)
|
||||
(activity as MainActivity).showTabs()
|
||||
return true
|
||||
}
|
||||
if (mapFragment != null && mapFragment!!.isVisible) {
|
||||
if (mapFragment!!.backButtonClicked()) {
|
||||
// Explore map fragment handled the event no further action required.
|
||||
return true
|
||||
} else {
|
||||
(activity as MainActivity).showTabs()
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
(activity as MainActivity).setSelectedItemId(NavTab.CONTRIBUTIONS.code())
|
||||
}
|
||||
(activity as MainActivity).showTabs()
|
||||
return false
|
||||
}
|
||||
|
||||
fun loadNearbyMapFromExplore() = mapFragment?.loadNearbyMapFromExplore()
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
binding = null
|
||||
}
|
||||
|
||||
fun requestLocationIfNeeded() {
|
||||
mapFragment?.requestLocationIfNeeded()
|
||||
}
|
||||
|
||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||
super.setUserVisibleHint(isVisibleToUser)
|
||||
if (isVisibleToUser) {
|
||||
requestLocationIfNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun newInstance(): ExploreMapRootFragment = ExploreMapRootFragment().apply {
|
||||
retainInstance = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
/**
|
||||
* ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
|
||||
*/
|
||||
public class ParentViewPager extends ViewPager {
|
||||
|
||||
/**
|
||||
* Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
|
||||
*/
|
||||
private boolean canScroll = true;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructors
|
||||
*/
|
||||
public ParentViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ParentViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter method for canScroll.
|
||||
*/
|
||||
public void setCanScroll(boolean canScroll) {
|
||||
this.canScroll = canScroll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter method for canScroll.
|
||||
*/
|
||||
public boolean isCanScroll() {
|
||||
return canScroll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method that prevents scrolling if canScroll is set to false.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A facilitator method that allows parent to intercept touch events before its children. thus
|
||||
* making it possible to prevent swiping parent on child end.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
|
||||
/**
|
||||
* ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
|
||||
*/
|
||||
class ParentViewPager : ViewPager {
|
||||
var canScroll: Boolean = true
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
override fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
return canScroll && super.onTouchEvent(ev)
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
return canScroll && super.onInterceptTouchEvent(ev)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.databinding.ActivitySearchBinding;
|
||||
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment;
|
||||
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
|
||||
import fr.free.nrw.commons.explore.media.SearchMediaFragment;
|
||||
import fr.free.nrw.commons.explore.models.RecentSearch;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.FragmentUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Represents search screen of this app
|
||||
*/
|
||||
|
||||
public class SearchActivity extends BaseActivity
|
||||
implements MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
||||
@Inject
|
||||
RecentSearchesDao recentSearchesDao;
|
||||
|
||||
private SearchMediaFragment searchMediaFragment;
|
||||
private SearchCategoryFragment searchCategoryFragment;
|
||||
private SearchDepictionsFragment searchDepictionsFragment;
|
||||
private RecentSearchesFragment recentSearchesFragment;
|
||||
private FragmentManager supportFragmentManager;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
|
||||
private ActivitySearchBinding binding;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivitySearchBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
setTitle(getString(R.string.title_activity_search));
|
||||
setSupportActionBar(binding.toolbarSearch);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed());
|
||||
supportFragmentManager = getSupportFragmentManager();
|
||||
setSearchHistoryFragment();
|
||||
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||
binding.viewPager.setAdapter(viewPagerAdapter);
|
||||
binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive
|
||||
binding.tabLayout.setupWithViewPager(binding.viewPager);
|
||||
setTabs();
|
||||
binding.searchBox.setQueryHint(getString(R.string.search_commons));
|
||||
binding.searchBox.onActionViewExpanded();
|
||||
binding.searchBox.clearFocus();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the search history fragment.
|
||||
* Search history fragment is displayed when query is empty.
|
||||
*/
|
||||
private void setSearchHistoryFragment() {
|
||||
recentSearchesFragment = new RecentSearchesFragment();
|
||||
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
|
||||
transaction.add(R.id.searchHistoryContainer, recentSearchesFragment).commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the titles in the tabLayout and fragments in the viewPager
|
||||
*/
|
||||
public void setTabs() {
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> titleList = new ArrayList<>();
|
||||
searchMediaFragment = new SearchMediaFragment();
|
||||
searchDepictionsFragment = new SearchDepictionsFragment();
|
||||
searchCategoryFragment= new SearchCategoryFragment();
|
||||
fragmentList.add(searchMediaFragment);
|
||||
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT));
|
||||
fragmentList.add(searchCategoryFragment);
|
||||
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT));
|
||||
fragmentList.add(searchDepictionsFragment);
|
||||
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT));
|
||||
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||
viewPagerAdapter.notifyDataSetChanged();
|
||||
getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox)
|
||||
.takeUntil(RxView.detaches(binding.searchBox))
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::handleSearch, Timber::e
|
||||
));
|
||||
}
|
||||
|
||||
private void handleSearch(final CharSequence query) {
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
saveRecentSearch(query.toString());
|
||||
binding.viewPager.setVisibility(View.VISIBLE);
|
||||
binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
binding.searchHistoryContainer.setVisibility(View.GONE);
|
||||
|
||||
if (FragmentUtils.isFragmentUIActive(searchDepictionsFragment)) {
|
||||
searchDepictionsFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
if (FragmentUtils.isFragmentUIActive(searchMediaFragment)) {
|
||||
searchMediaFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
if (FragmentUtils.isFragmentUIActive(searchCategoryFragment)) {
|
||||
searchCategoryFragment.onQueryUpdated(query.toString());
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
//Open RecentSearchesFragment
|
||||
recentSearchesFragment.updateRecentSearches();
|
||||
binding.viewPager.setVisibility(View.GONE);
|
||||
binding.tabLayout.setVisibility(View.GONE);
|
||||
setSearchHistoryFragment();
|
||||
binding.searchHistoryContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveRecentSearch(@NonNull final String query) {
|
||||
final RecentSearch recentSearch = recentSearchesDao.find(query);
|
||||
// Newly searched query...
|
||||
if (recentSearch == null) {
|
||||
recentSearchesDao.save(new RecentSearch(null, query, new Date()));
|
||||
} else {
|
||||
recentSearch.setLastSearched(new Date());
|
||||
recentSearchesDao.save(recentSearch);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns Media Object at position
|
||||
* @param i position of Media in the imagesRecyclerView adapter.
|
||||
*/
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return searchMediaFragment.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns total number of images present in the imagesRecyclerView adapter.
|
||||
*/
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
return searchMediaFragment.getTotalMediaCount();
|
||||
}
|
||||
|
||||
@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 (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||
onBackPressed();
|
||||
onMediaClicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for image Search.
|
||||
* The viewpager will notified that number of items have changed.
|
||||
*/
|
||||
@Override
|
||||
public void viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetails!=null){
|
||||
mediaDetails.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open media detail pager fragment on click of image in search results
|
||||
* @param index item index that should be opened
|
||||
*/
|
||||
@Override
|
||||
public void onMediaClicked(int index) {
|
||||
ViewUtil.hideKeyboard(this.findViewById(R.id.searchBox));
|
||||
binding.tabLayout.setVisibility(View.GONE);
|
||||
binding.viewPager.setVisibility(View.GONE);
|
||||
binding.mediaContainer.setVisibility(View.VISIBLE);
|
||||
binding.searchBox.setVisibility(View.GONE);// to remove searchview when mediaDetails fragment open
|
||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true);
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount()))
|
||||
.add(R.id.mediaContainer, mediaDetails)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
// Reason for using hide, add instead of replace is to maintain scroll position after
|
||||
// coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
|
||||
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550
|
||||
supportFragmentManager.executePendingTransactions();
|
||||
}
|
||||
mediaDetails.showImage(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on Screen Rotation
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
if (supportFragmentManager.getBackStackEntryCount()==1){
|
||||
//FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
|
||||
//FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
|
||||
// This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
|
||||
//
|
||||
onBackPressed();
|
||||
}
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on backPressed of anyFragment in the activity.
|
||||
* If condition is called when mediaDetailFragment is opened.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
//Remove the backstack entry that gets added when share button is clicked
|
||||
//fixing:https://github.com/commons-app/apps-android-commons/issues/2296
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 2) {
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.remove(mediaDetails)
|
||||
.commit();
|
||||
supportFragmentManager.popBackStack();
|
||||
supportFragmentManager.executePendingTransactions();
|
||||
}
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||
// back to search so show search toolbar and hide navigation toolbar
|
||||
binding.searchBox.setVisibility(View.VISIBLE);//set the searchview
|
||||
binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
binding.viewPager.setVisibility(View.VISIBLE);
|
||||
binding.mediaContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.toolbarSearch.setVisibility(View.GONE);
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on click of a recent search to update query in SearchView.
|
||||
* @param query Recent Search Query
|
||||
*/
|
||||
public void updateText(String query) {
|
||||
binding.searchBox.setQuery(query, true);
|
||||
// Clear focus of searchView now. searchView.clearFocus(); does not seem to work Check the below link for more details.
|
||||
// https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
|
||||
binding.viewPager.requestFocus();
|
||||
}
|
||||
|
||||
@Override protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
//Dispose the disposables when the activity is destroyed
|
||||
getCompositeDisposable().dispose();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
254
app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt
Normal file
254
app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.kt
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
package fr.free.nrw.commons.explore
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.jakewharton.rxbinding2.view.RxView
|
||||
import com.jakewharton.rxbinding2.widget.RxSearchView
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.ViewPagerAdapter
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback
|
||||
import fr.free.nrw.commons.databinding.ActivitySearchBinding
|
||||
import fr.free.nrw.commons.explore.categories.search.SearchCategoryFragment
|
||||
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment
|
||||
import fr.free.nrw.commons.explore.media.SearchMediaFragment
|
||||
import fr.free.nrw.commons.explore.models.RecentSearch
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.utils.FragmentUtils.isFragmentUIActive
|
||||
import fr.free.nrw.commons.utils.ViewUtil.hideKeyboard
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import timber.log.Timber
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Represents search screen of this app
|
||||
*/
|
||||
class SearchActivity : BaseActivity(), MediaDetailProvider, CategoryImagesCallback {
|
||||
@JvmField
|
||||
@Inject
|
||||
var recentSearchesDao: RecentSearchesDao? = null
|
||||
|
||||
private var searchMediaFragment: SearchMediaFragment? = null
|
||||
private var searchCategoryFragment: SearchCategoryFragment? = null
|
||||
private var searchDepictionsFragment: SearchDepictionsFragment? = null
|
||||
private var recentSearchesFragment: RecentSearchesFragment? = null
|
||||
private var supportFragmentManager: FragmentManager? = null
|
||||
private var mediaDetails: MediaDetailPagerFragment? = null
|
||||
private var viewPagerAdapter: ViewPagerAdapter? = null
|
||||
private var binding: ActivitySearchBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivitySearchBinding.inflate(layoutInflater)
|
||||
applyEdgeToEdgeAllInsets(binding!!.root)
|
||||
setContentView(binding!!.root)
|
||||
|
||||
title = getString(R.string.title_activity_search)
|
||||
setSupportActionBar(binding!!.toolbarSearch)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
binding!!.toolbarSearch.setNavigationOnClickListener { onBackPressed() }
|
||||
supportFragmentManager = getSupportFragmentManager()
|
||||
setSearchHistoryFragment()
|
||||
viewPagerAdapter = ViewPagerAdapter(this, getSupportFragmentManager())
|
||||
binding!!.viewPager.adapter = viewPagerAdapter
|
||||
binding!!.viewPager.offscreenPageLimit = 2 // Because we want all the fragments to be alive
|
||||
binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
|
||||
setTabs()
|
||||
binding!!.searchBox.queryHint = getString(R.string.search_commons)
|
||||
binding!!.searchBox.onActionViewExpanded()
|
||||
binding!!.searchBox.clearFocus()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the search history fragment.
|
||||
* Search history fragment is displayed when query is empty.
|
||||
*/
|
||||
private fun setSearchHistoryFragment() {
|
||||
recentSearchesFragment = RecentSearchesFragment()
|
||||
val transaction = supportFragmentManager!!.beginTransaction()
|
||||
transaction.add(R.id.searchHistoryContainer, recentSearchesFragment!!).commit()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the titles in the tabLayout and fragments in the viewPager
|
||||
*/
|
||||
fun setTabs() {
|
||||
searchMediaFragment = SearchMediaFragment()
|
||||
searchDepictionsFragment = SearchDepictionsFragment()
|
||||
searchCategoryFragment = SearchCategoryFragment()
|
||||
|
||||
viewPagerAdapter!!.setTabs(
|
||||
R.string.search_tab_title_media to searchMediaFragment!!,
|
||||
R.string.search_tab_title_categories to searchCategoryFragment!!,
|
||||
R.string.search_tab_title_depictions to searchDepictionsFragment!!
|
||||
)
|
||||
viewPagerAdapter!!.notifyDataSetChanged()
|
||||
compositeDisposable.add(
|
||||
RxSearchView.queryTextChanges(binding!!.searchBox)
|
||||
.takeUntil(RxView.detaches(binding!!.searchBox))
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(::handleSearch, Timber::e)
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleSearch(query: CharSequence) {
|
||||
if (!TextUtils.isEmpty(query)) {
|
||||
saveRecentSearch(query.toString())
|
||||
binding!!.viewPager.visibility = View.VISIBLE
|
||||
binding!!.tabLayout.visibility = View.VISIBLE
|
||||
binding!!.searchHistoryContainer.visibility = View.GONE
|
||||
|
||||
if (isFragmentUIActive(searchDepictionsFragment)) {
|
||||
searchDepictionsFragment!!.onQueryUpdated(query.toString())
|
||||
}
|
||||
|
||||
if (isFragmentUIActive(searchMediaFragment)) {
|
||||
searchMediaFragment!!.onQueryUpdated(query.toString())
|
||||
}
|
||||
|
||||
if (isFragmentUIActive(searchCategoryFragment)) {
|
||||
searchCategoryFragment!!.onQueryUpdated(query.toString())
|
||||
}
|
||||
} else {
|
||||
//Open RecentSearchesFragment
|
||||
recentSearchesFragment!!.updateRecentSearches()
|
||||
binding!!.viewPager.visibility = View.GONE
|
||||
binding!!.tabLayout.visibility = View.GONE
|
||||
setSearchHistoryFragment()
|
||||
binding!!.searchHistoryContainer.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveRecentSearch(query: String) {
|
||||
val recentSearch = recentSearchesDao!!.find(query)
|
||||
// Newly searched query...
|
||||
if (recentSearch == null) {
|
||||
recentSearchesDao!!.save(RecentSearch(null, query, Date()))
|
||||
} else {
|
||||
recentSearch.lastSearched = Date()
|
||||
recentSearchesDao!!.save(recentSearch)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMediaAtPosition(i: Int): Media? = searchMediaFragment!!.getMediaAtPosition(i)
|
||||
|
||||
override fun getTotalMediaCount(): Int = searchMediaFragment!!.getTotalMediaCount()
|
||||
|
||||
override fun getContributionStateAt(position: Int): Int? = null
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
override fun refreshNominatedMedia(index: Int) {
|
||||
if (getSupportFragmentManager().backStackEntryCount == 1) {
|
||||
onBackPressed()
|
||||
onMediaClicked(index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for image Search.
|
||||
* The viewpager will notified that number of items have changed.
|
||||
*/
|
||||
override fun viewPagerNotifyDataSetChanged() {
|
||||
mediaDetails?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Open media detail pager fragment on click of image in search results
|
||||
* @param position item index that should be opened
|
||||
*/
|
||||
override fun onMediaClicked(position: Int) {
|
||||
hideKeyboard(findViewById(R.id.searchBox))
|
||||
binding!!.tabLayout.visibility = View.GONE
|
||||
binding!!.viewPager.visibility = View.GONE
|
||||
binding!!.mediaContainer.visibility = View.VISIBLE
|
||||
binding!!.searchBox.visibility =
|
||||
View.GONE // to remove searchview when mediaDetails fragment open
|
||||
if (mediaDetails == null || !mediaDetails!!.isVisible) {
|
||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||
mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
|
||||
supportFragmentManager!!
|
||||
.beginTransaction()
|
||||
.hide(supportFragmentManager!!.fragments[supportFragmentManager!!.backStackEntryCount])
|
||||
.add(R.id.mediaContainer, mediaDetails!!)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
// Reason for using hide, add instead of replace is to maintain scroll position after
|
||||
// coming back to the search activity. See https://github.com/commons-app/apps-android-commons/issues/1631
|
||||
// https://stackoverflow.com/questions/11353075/how-can-i-maintain-fragment-state-when-added-to-the-back-stack/19022550#19022550
|
||||
supportFragmentManager!!.executePendingTransactions()
|
||||
}
|
||||
mediaDetails!!.showImage(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on Screen Rotation
|
||||
*/
|
||||
override fun onResume() {
|
||||
if (supportFragmentManager!!.backStackEntryCount == 1) {
|
||||
//FIXME: Temporary fix for screen rotation inside media details. If we don't call onBackPressed then fragment stack is increasing every time.
|
||||
//FIXME: Similar issue like this https://github.com/commons-app/apps-android-commons/issues/894
|
||||
// This is called on screen rotation when user is inside media details. Ideally it should show Media Details but since we are not saving the state now. We are throwing the user to search screen otherwise the app was crashing.
|
||||
onBackPressed()
|
||||
}
|
||||
super.onResume()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on backPressed of anyFragment in the activity.
|
||||
* If condition is called when mediaDetailFragment is opened.
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
//Remove the backstack entry that gets added when share button is clicked
|
||||
//fixing:https://github.com/commons-app/apps-android-commons/issues/2296
|
||||
if (getSupportFragmentManager().backStackEntryCount == 2) {
|
||||
supportFragmentManager!!
|
||||
.beginTransaction()
|
||||
.remove(mediaDetails!!)
|
||||
.commit()
|
||||
supportFragmentManager!!.popBackStack()
|
||||
supportFragmentManager!!.executePendingTransactions()
|
||||
}
|
||||
if (getSupportFragmentManager().backStackEntryCount == 1) {
|
||||
// back to search so show search toolbar and hide navigation toolbar
|
||||
binding!!.searchBox.visibility = View.VISIBLE //set the searchview
|
||||
binding!!.tabLayout.visibility = View.VISIBLE
|
||||
binding!!.viewPager.visibility = View.VISIBLE
|
||||
binding!!.mediaContainer.visibility = View.GONE
|
||||
} else {
|
||||
binding!!.toolbarSearch.visibility = View.GONE
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on click of a recent search to update query in SearchView.
|
||||
* @param query Recent Search Query
|
||||
*/
|
||||
fun updateText(query: String?) {
|
||||
binding!!.searchBox.setQuery(query, true)
|
||||
// Clear focus of searchView now. searchView.clearFocus(); does not seem to work Check the below link for more details.
|
||||
// https://stackoverflow.com/questions/6117967/how-to-remove-focus-without-setting-focus-to-another-control/15481511
|
||||
binding!!.viewPager.requestFocus()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
//Dispose the disposables when the activity is destroyed
|
||||
compositeDisposable.dispose()
|
||||
binding = null
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,6 @@ import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
|||
abstract class PageableDepictionsFragment : BasePagingFragment<DepictedItem>() {
|
||||
override val errorTextId: Int = R.string.error_loading_depictions
|
||||
override val pagedListAdapter by lazy {
|
||||
DepictionAdapter { WikidataItemDetailsActivity.startYourself(context, it) }
|
||||
DepictionAdapter { WikidataItemDetailsActivity.startYourself(requireContext(), it) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,309 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.depictions;
|
||||
|
||||
import android.content.Context;
|
||||
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.FrameLayout;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding;
|
||||
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment;
|
||||
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment;
|
||||
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.wikidata.WikidataConstants;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Activity to show depiction media, parent classes and child classes of depicted items in Explore
|
||||
*/
|
||||
public class WikidataItemDetailsActivity extends BaseActivity implements MediaDetailPagerFragment.MediaDetailProvider,
|
||||
CategoryImagesCallback {
|
||||
private FragmentManager supportFragmentManager;
|
||||
private DepictedImagesFragment depictionImagesListFragment;
|
||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||
|
||||
/**
|
||||
* Name of the depicted item
|
||||
* Ex: Rabbit
|
||||
*/
|
||||
|
||||
@Inject BookmarkItemsDao bookmarkItemsDao;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
@Inject
|
||||
DepictModel depictModel;
|
||||
private String wikidataItemName;
|
||||
private ActivityWikidataItemDetailsBinding binding;
|
||||
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
private DepictedItem wikidataItem;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
binding = ActivityWikidataItemDetailsBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
supportFragmentManager = getSupportFragmentManager();
|
||||
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
|
||||
binding.viewPager.setAdapter(viewPagerAdapter);
|
||||
binding.viewPager.setOffscreenPageLimit(2);
|
||||
binding.tabLayout.setupWithViewPager(binding.viewPager);
|
||||
|
||||
final DepictedItem depictedItem = getIntent().getParcelableExtra(
|
||||
WikidataConstants.BOOKMARKS_ITEMS);
|
||||
wikidataItem = depictedItem;
|
||||
setSupportActionBar(binding.toolbarBinding.toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setTabs();
|
||||
setPageTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passed wikidataItemName from the intents and displays it as the page title
|
||||
*/
|
||||
private void setPageTitle() {
|
||||
if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) {
|
||||
setTitle(getIntent().getStringExtra("wikidataItemName"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for featured Images.
|
||||
* The viewpager will notified that number of items have changed.
|
||||
*/
|
||||
@Override
|
||||
public void viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetailPagerFragment !=null){
|
||||
mediaDetailPagerFragment.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
|
||||
* Set the fragments according to the tab selected in the viewPager.
|
||||
*/
|
||||
private void setTabs() {
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> titleList = new ArrayList<>();
|
||||
depictionImagesListFragment = new DepictedImagesFragment();
|
||||
ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment();
|
||||
ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment();
|
||||
wikidataItemName = getIntent().getStringExtra("wikidataItemName");
|
||||
String entityId = getIntent().getStringExtra("entityId");
|
||||
if (getIntent() != null && wikidataItemName != null) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString("wikidataItemName", wikidataItemName);
|
||||
arguments.putString("entityId", entityId);
|
||||
depictionImagesListFragment.setArguments(arguments);
|
||||
parentDepictionsFragment.setArguments(arguments);
|
||||
childDepictionsFragment.setArguments(arguments);
|
||||
}
|
||||
fragmentList.add(depictionImagesListFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_media));
|
||||
fragmentList.add(childDepictionsFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_child_classes));
|
||||
fragmentList.add(parentDepictionsFragment);
|
||||
titleList.add(getResources().getString(R.string.title_for_parent_classes));
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||
binding.viewPager.setOffscreenPageLimit(2);
|
||||
viewPagerAdapter.notifyDataSetChanged();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows media detail fragment when user clicks on any image in the list
|
||||
*/
|
||||
@Override
|
||||
public void onMediaClicked(int position) {
|
||||
binding.tabLayout.setVisibility(View.GONE);
|
||||
binding.viewPager.setVisibility(View.GONE);
|
||||
binding.mediaContainer.setVisibility(View.VISIBLE);
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||
mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true);
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.mediaContainer, mediaDetailPagerFragment)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
supportFragmentManager.executePendingTransactions();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return depictionImagesListFragment.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on backPressed of anyFragment in the activity.
|
||||
* If condition is called when mediaDetailFragment is opened.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (supportFragmentManager.getBackStackEntryCount() == 1){
|
||||
binding.tabLayout.setVisibility(View.VISIBLE);
|
||||
binding.viewPager.setVisibility(View.VISIBLE);
|
||||
binding.mediaContainer.setVisibility(View.GONE);
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return depictionImagesListFragment.getTotalMediaCount();
|
||||
}
|
||||
|
||||
@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 (getSupportFragmentManager().getBackStackEntryCount() == 1) {
|
||||
onBackPressed();
|
||||
onMediaClicked(index);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumers should be simply using this method to use this activity.
|
||||
*
|
||||
* @param context A Context of the application package implementing this class.
|
||||
* @param depictedItem Name of the depicts for displaying its details
|
||||
*/
|
||||
public static void startYourself(Context context, DepictedItem depictedItem) {
|
||||
Intent intent = new Intent(context, WikidataItemDetailsActivity.class);
|
||||
intent.putExtra("wikidataItemName", depictedItem.getName());
|
||||
intent.putExtra("entityId", depictedItem.getId());
|
||||
intent.putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function inflates the menu
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater menuInflater=getMenuInflater();
|
||||
menuInflater.inflate(R.menu.menu_wikidata_item,menu);
|
||||
|
||||
updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item));
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the logic on item select in toolbar menu
|
||||
* Currently only 1 choice is available to open Wikidata item details page in browser
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
switch (item.getItemId()){
|
||||
case R.id.browser_actions_menu_items:
|
||||
String entityId=getIntent().getStringExtra("entityId");
|
||||
Uri uri = Uri.parse("https://www.wikidata.org/wiki/" + entityId);
|
||||
Utils.handleWebUrl(this, uri);
|
||||
return true;
|
||||
case R.id.menu_bookmark_current_item:
|
||||
|
||||
if(getIntent().getStringExtra("fragment") != null) {
|
||||
compositeDisposable.add(depictModel.getDepictions(
|
||||
getIntent().getStringExtra("entityId")
|
||||
).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(depictedItems -> {
|
||||
final boolean bookmarkExists = bookmarkItemsDao.updateBookmarkItem(
|
||||
depictedItems.get(0));
|
||||
final Snackbar snackbar
|
||||
= bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout),
|
||||
R.string.add_bookmark, Snackbar.LENGTH_LONG)
|
||||
: Snackbar.make(findViewById(R.id.toolbar_layout),
|
||||
R.string.remove_bookmark,
|
||||
Snackbar.LENGTH_LONG);
|
||||
|
||||
snackbar.show();
|
||||
updateBookmarkState(item);
|
||||
}));
|
||||
|
||||
} else {
|
||||
final boolean bookmarkExists
|
||||
= bookmarkItemsDao.updateBookmarkItem(wikidataItem);
|
||||
final Snackbar snackbar
|
||||
= bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout),
|
||||
R.string.add_bookmark, Snackbar.LENGTH_LONG)
|
||||
: Snackbar.make(findViewById(R.id.toolbar_layout), R.string.remove_bookmark,
|
||||
Snackbar.LENGTH_LONG);
|
||||
|
||||
snackbar.show();
|
||||
updateBookmarkState(item);
|
||||
}
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBookmarkState(final MenuItem item) {
|
||||
final boolean isBookmarked;
|
||||
if(getIntent().getStringExtra("fragment") != null) {
|
||||
isBookmarked
|
||||
= bookmarkItemsDao.findBookmarkItem(getIntent().getStringExtra("entityId"));
|
||||
} else {
|
||||
isBookmarked = bookmarkItemsDao.findBookmarkItem(wikidataItem.getId());
|
||||
}
|
||||
final int icon
|
||||
= isBookmarked ? R.drawable.menu_ic_round_star_filled_24px
|
||||
: R.drawable.menu_ic_round_star_border_24px;
|
||||
item.setIcon(icon);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,297 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.ViewPagerAdapter
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback
|
||||
import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding
|
||||
import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment
|
||||
import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment
|
||||
import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||
import fr.free.nrw.commons.media.MediaDetailProvider
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictModel
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.utils.applyEdgeToEdgeAllInsets
|
||||
import fr.free.nrw.commons.utils.handleWebUrl
|
||||
import fr.free.nrw.commons.wikidata.WikidataConstants
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.functions.Consumer
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Activity to show depiction media, parent classes and child classes of depicted items in Explore
|
||||
*/
|
||||
class WikidataItemDetailsActivity : BaseActivity(), MediaDetailProvider, CategoryImagesCallback {
|
||||
@JvmField
|
||||
@Inject
|
||||
var bookmarkItemsDao: BookmarkItemsDao? = null
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var depictModel: DepictModel? = null
|
||||
|
||||
private var supportFragmentManager: FragmentManager? = null
|
||||
private var depictionImagesListFragment: DepictedImagesFragment? = null
|
||||
private var mediaDetailPagerFragment: MediaDetailPagerFragment? = null
|
||||
private var binding: ActivityWikidataItemDetailsBinding? = null
|
||||
|
||||
var viewPagerAdapter: ViewPagerAdapter? = null
|
||||
private var wikidataItem: DepictedItem? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityWikidataItemDetailsBinding.inflate(layoutInflater)
|
||||
applyEdgeToEdgeAllInsets(binding!!.root)
|
||||
setContentView(binding!!.root)
|
||||
supportFragmentManager = getSupportFragmentManager()
|
||||
viewPagerAdapter = ViewPagerAdapter(this, getSupportFragmentManager())
|
||||
binding!!.viewPager.adapter = viewPagerAdapter
|
||||
binding!!.viewPager.offscreenPageLimit = 2
|
||||
binding!!.tabLayout.setupWithViewPager(binding!!.viewPager)
|
||||
|
||||
wikidataItem = intent.getParcelableExtra(WikidataConstants.BOOKMARKS_ITEMS)
|
||||
setSupportActionBar(binding!!.toolbarBinding.toolbar)
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
setTabs()
|
||||
setPageTitle()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passed wikidataItemName from the intents and displays it as the page title
|
||||
*/
|
||||
private fun setPageTitle() {
|
||||
if (intent != null && intent.getStringExtra("wikidataItemName") != null) {
|
||||
title = intent.getStringExtra("wikidataItemName")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for featured Images.
|
||||
* The viewpager will notified that number of items have changed.
|
||||
*/
|
||||
override fun viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetailPagerFragment != null) {
|
||||
mediaDetailPagerFragment!!.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab,
|
||||
* Set the fragments according to the tab selected in the viewPager.
|
||||
*/
|
||||
private fun setTabs() {
|
||||
depictionImagesListFragment = DepictedImagesFragment()
|
||||
val childDepictionsFragment = ChildDepictionsFragment()
|
||||
val parentDepictionsFragment = ParentDepictionsFragment()
|
||||
val wikidataItemName = intent.getStringExtra("wikidataItemName")
|
||||
val entityId = intent.getStringExtra("entityId")
|
||||
if (intent != null && wikidataItemName != null) {
|
||||
val arguments = bundleOf(
|
||||
"wikidataItemName" to wikidataItemName,
|
||||
"entityId" to entityId
|
||||
)
|
||||
depictionImagesListFragment!!.arguments = arguments
|
||||
parentDepictionsFragment.arguments = arguments
|
||||
childDepictionsFragment.arguments = arguments
|
||||
}
|
||||
|
||||
viewPagerAdapter!!.setTabs(
|
||||
R.string.title_for_media to depictionImagesListFragment!!,
|
||||
R.string.title_for_child_classes to childDepictionsFragment,
|
||||
R.string.title_for_parent_classes to parentDepictionsFragment
|
||||
)
|
||||
binding!!.viewPager.offscreenPageLimit = 2
|
||||
viewPagerAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Shows media detail fragment when user clicks on any image in the list
|
||||
*/
|
||||
override fun onMediaClicked(position: Int) {
|
||||
binding!!.tabLayout.visibility = View.GONE
|
||||
binding!!.viewPager.visibility = View.GONE
|
||||
binding!!.mediaContainer.visibility = View.VISIBLE
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment!!.isVisible) {
|
||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||
mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true)
|
||||
val supportFragmentManager = getSupportFragmentManager()
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.mediaContainer, mediaDetailPagerFragment!!)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
supportFragmentManager.executePendingTransactions()
|
||||
}
|
||||
mediaDetailPagerFragment!!.showImage(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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? {
|
||||
return depictionImagesListFragment!!.getMediaAtPosition(i)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on backPressed of anyFragment in the activity.
|
||||
* If condition is called when mediaDetailFragment is opened.
|
||||
*/
|
||||
override fun onBackPressed() {
|
||||
if (supportFragmentManager!!.backStackEntryCount == 1) {
|
||||
binding!!.tabLayout.visibility = View.VISIBLE
|
||||
binding!!.viewPager.visibility = View.VISIBLE
|
||||
binding!!.mediaContainer.visibility = View.GONE
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = depictionImagesListFragment!!.getTotalMediaCount()
|
||||
|
||||
override fun getContributionStateAt(position: Int): Int? = null
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
override fun refreshNominatedMedia(index: Int) {
|
||||
if (getSupportFragmentManager().backStackEntryCount == 1) {
|
||||
onBackPressed()
|
||||
onMediaClicked(index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function inflates the menu
|
||||
*/
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
val menuInflater = menuInflater
|
||||
menuInflater.inflate(R.menu.menu_wikidata_item, menu)
|
||||
|
||||
updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item))
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method handles the logic on item select in toolbar menu
|
||||
* Currently only 1 choice is available to open Wikidata item details page in browser
|
||||
*/
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.browser_actions_menu_items -> {
|
||||
val entityId = intent.getStringExtra("entityId")
|
||||
val uri = Uri.parse("https://www.wikidata.org/wiki/$entityId")
|
||||
handleWebUrl(this, uri)
|
||||
return true
|
||||
}
|
||||
|
||||
R.id.menu_bookmark_current_item -> {
|
||||
if (intent.getStringExtra("fragment") != null) {
|
||||
compositeDisposable!!.add(
|
||||
depictModel!!.getDepictions(
|
||||
intent.getStringExtra("entityId")!!
|
||||
).subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(Consumer<List<DepictedItem?>> { depictedItems: List<DepictedItem?> ->
|
||||
val bookmarkExists = bookmarkItemsDao!!.updateBookmarkItem(
|
||||
depictedItems[0]!!
|
||||
)
|
||||
val snackbar = if (bookmarkExists)
|
||||
Snackbar.make(
|
||||
findViewById(R.id.toolbar_layout),
|
||||
R.string.add_bookmark, Snackbar.LENGTH_LONG
|
||||
)
|
||||
else
|
||||
Snackbar.make(
|
||||
findViewById(R.id.toolbar_layout),
|
||||
R.string.remove_bookmark,
|
||||
Snackbar.LENGTH_LONG
|
||||
)
|
||||
|
||||
snackbar.show()
|
||||
updateBookmarkState(item)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
val bookmarkExists = bookmarkItemsDao!!.updateBookmarkItem(wikidataItem!!)
|
||||
val snackbar = if (bookmarkExists)
|
||||
Snackbar.make(
|
||||
findViewById(R.id.toolbar_layout),
|
||||
R.string.add_bookmark, Snackbar.LENGTH_LONG
|
||||
)
|
||||
else
|
||||
Snackbar.make(
|
||||
findViewById(R.id.toolbar_layout), R.string.remove_bookmark,
|
||||
Snackbar.LENGTH_LONG
|
||||
)
|
||||
|
||||
snackbar.show()
|
||||
updateBookmarkState(item)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateBookmarkState(item: MenuItem) {
|
||||
val isBookmarked: Boolean = if (intent.getStringExtra("fragment") != null) {
|
||||
bookmarkItemsDao!!.findBookmarkItem(intent.getStringExtra("entityId"))
|
||||
} else {
|
||||
bookmarkItemsDao!!.findBookmarkItem(wikidataItem!!.id)
|
||||
}
|
||||
item.setIcon(if (isBookmarked) {
|
||||
R.drawable.menu_ic_round_star_filled_24px
|
||||
} else {
|
||||
R.drawable.menu_ic_round_star_border_24px
|
||||
})
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Consumers should be simply using this method to use this activity.
|
||||
*
|
||||
* @param context A Context of the application package implementing this class.
|
||||
* @param depictedItem Name of the depicts for displaying its details
|
||||
*/
|
||||
fun startYourself(context: Context, depictedItem: DepictedItem) {
|
||||
val intent = Intent(context, WikidataItemDetailsActivity::class.java).apply {
|
||||
putExtra("wikidataItemName", depictedItem.name)
|
||||
putExtra("entityId", depictedItem.id)
|
||||
putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem)
|
||||
}
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class ExploreMapCalls {
|
||||
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
@Inject
|
||||
public ExploreMapCalls() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls method to query Commons for uploads around a location
|
||||
*
|
||||
* @param currentLatLng coordinates of search location
|
||||
* @return list of places obtained
|
||||
*/
|
||||
List<Media> callCommonsQuery(final LatLng currentLatLng) {
|
||||
String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude();
|
||||
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package fr.free.nrw.commons.explore.map
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.media.MediaClient
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ExploreMapCalls @Inject constructor() {
|
||||
@Inject
|
||||
@JvmField
|
||||
var mediaClient: MediaClient? = null
|
||||
|
||||
/**
|
||||
* Calls method to query Commons for uploads around a location
|
||||
*
|
||||
* @param currentLatLng coordinates of search location
|
||||
* @return list of places obtained
|
||||
*/
|
||||
fun callCommonsQuery(currentLatLng: LatLng): List<Media> {
|
||||
val coordinates = currentLatLng.latitude.toString() + "|" + currentLatLng.longitude
|
||||
return mediaClient!!.getMediaListFromGeoSearch(coordinates).blockingGet()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import android.content.Context;
|
||||
import fr.free.nrw.commons.BaseMarker;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import java.util.List;
|
||||
|
||||
public class ExploreMapContract {
|
||||
|
||||
interface View {
|
||||
boolean isNetworkConnectionEstablished();
|
||||
void populatePlaces(LatLng curlatLng);
|
||||
void askForLocationPermission();
|
||||
void recenterMap(LatLng curLatLng);
|
||||
void hideBottomDetailsSheet();
|
||||
LatLng getMapCenter();
|
||||
LatLng getMapFocus();
|
||||
LatLng getLastMapFocus();
|
||||
void addMarkersToMap(final List<BaseMarker> nearbyBaseMarkers);
|
||||
void clearAllMarkers();
|
||||
void addSearchThisAreaButtonAction();
|
||||
void setSearchThisAreaButtonVisibility(boolean isVisible);
|
||||
void setProgressBarVisibility(boolean isVisible);
|
||||
boolean isDetailsBottomSheetVisible();
|
||||
boolean isSearchThisAreaButtonVisible();
|
||||
Context getContext();
|
||||
LatLng getLastLocation();
|
||||
void disableFABRecenter();
|
||||
void enableFABRecenter();
|
||||
void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
|
||||
boolean backButtonClicked();
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
void updateMap(LocationServiceManager.LocationChangeType locationChangeType);
|
||||
void lockUnlockNearby(boolean isNearbyLocked);
|
||||
void attachView(View view);
|
||||
void detachView();
|
||||
void setActionListeners(JsonKvStore applicationKvStore);
|
||||
boolean backButtonClicked();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package fr.free.nrw.commons.explore.map
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import fr.free.nrw.commons.BaseMarker
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
|
||||
|
||||
class ExploreMapContract {
|
||||
interface View {
|
||||
fun isNetworkConnectionEstablished(): Boolean
|
||||
fun populatePlaces(curlatLng: LatLng?)
|
||||
fun askForLocationPermission()
|
||||
fun recenterMap(curLatLng: LatLng?)
|
||||
fun hideBottomDetailsSheet()
|
||||
fun getMapCenter(): LatLng?
|
||||
fun getMapFocus(): LatLng?
|
||||
fun getLastMapFocus(): LatLng?
|
||||
fun addMarkersToMap(nearbyBaseMarkers: List<BaseMarker?>?)
|
||||
fun clearAllMarkers()
|
||||
fun addSearchThisAreaButtonAction()
|
||||
fun setSearchThisAreaButtonVisibility(isVisible: Boolean)
|
||||
fun setProgressBarVisibility(isVisible: Boolean)
|
||||
fun isDetailsBottomSheetVisible(): Boolean
|
||||
fun isSearchThisAreaButtonVisible(): Boolean
|
||||
fun getContext(): Context?
|
||||
fun getLastLocation(): LatLng?
|
||||
fun disableFABRecenter()
|
||||
fun enableFABRecenter()
|
||||
fun setFABRecenterAction(onClickListener: android.view.View.OnClickListener?)
|
||||
fun backButtonClicked(): Boolean
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
fun updateMap(locationChangeType: LocationChangeType)
|
||||
fun lockUnlockNearby(isNearbyLocked: Boolean)
|
||||
fun attachView(view: View?)
|
||||
fun detachView()
|
||||
fun setActionListeners(applicationKvStore: JsonKvStore?)
|
||||
fun backButtonClicked(): Boolean
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
import fr.free.nrw.commons.BaseMarker;
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.LocationUtils;
|
||||
import fr.free.nrw.commons.utils.PlaceUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExploreMapController extends MapController {
|
||||
|
||||
private final ExploreMapCalls exploreMapCalls;
|
||||
public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used
|
||||
public LatLng currentLocation; // current location of user
|
||||
public double latestSearchRadius = 0; // Any last search radius
|
||||
public double currentLocationSearchRadius = 0; // Search radius of only searches around current location
|
||||
|
||||
|
||||
@Inject
|
||||
public ExploreMapController(ExploreMapCalls explorePlaces) {
|
||||
this.exploreMapCalls = explorePlaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList,
|
||||
* explorePlaceList and boundaryCoordinates
|
||||
*
|
||||
* @param currentLatLng is current geolocation
|
||||
* @param searchLatLng is the location that we want to search around
|
||||
* @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around
|
||||
* current location, false if another location
|
||||
* @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and
|
||||
* boundaryCoordinates
|
||||
*/
|
||||
public ExplorePlacesInfo loadAttractionsFromLocation(LatLng currentLatLng, LatLng searchLatLng,
|
||||
boolean checkingAroundCurrentLocation) {
|
||||
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions explore map, but search is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo();
|
||||
try {
|
||||
explorePlacesInfo.currentLatLng = currentLatLng;
|
||||
latestSearchLocation = searchLatLng;
|
||||
|
||||
List<Media> mediaList = exploreMapCalls.callCommonsQuery(searchLatLng);
|
||||
LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south
|
||||
mediaList.get(0).getCoordinates(), // north
|
||||
mediaList.get(0).getCoordinates(), // west
|
||||
mediaList.get(0).getCoordinates()};// east, init with a random location
|
||||
|
||||
if (searchLatLng != null) {
|
||||
Timber.d("Sorting places by distance...");
|
||||
final Map<Media, Double> distances = new HashMap<>();
|
||||
for (Media media : mediaList) {
|
||||
distances.put(media,
|
||||
computeDistanceBetween(media.getCoordinates(), searchLatLng));
|
||||
// Find boundaries with basic find max approach
|
||||
if (media.getCoordinates().getLatitude()
|
||||
< boundaryCoordinates[0].getLatitude()) {
|
||||
boundaryCoordinates[0] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLatitude()
|
||||
> boundaryCoordinates[1].getLatitude()) {
|
||||
boundaryCoordinates[1] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLongitude()
|
||||
< boundaryCoordinates[2].getLongitude()) {
|
||||
boundaryCoordinates[2] = media.getCoordinates();
|
||||
}
|
||||
if (media.getCoordinates().getLongitude()
|
||||
> boundaryCoordinates[3].getLongitude()) {
|
||||
boundaryCoordinates[3] = media.getCoordinates();
|
||||
}
|
||||
}
|
||||
}
|
||||
explorePlacesInfo.mediaList = mediaList;
|
||||
explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList);
|
||||
explorePlacesInfo.boundaryCoordinates = boundaryCoordinates;
|
||||
|
||||
// Sets latestSearchRadius to maximum distance among boundaries and search location
|
||||
for (LatLng bound : boundaryCoordinates) {
|
||||
double distance = LocationUtils.calculateDistance(bound.getLatitude(),
|
||||
bound.getLongitude(), searchLatLng.getLatitude(), searchLatLng.getLongitude());
|
||||
if (distance > latestSearchRadius) {
|
||||
latestSearchRadius = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
|
||||
if (checkingAroundCurrentLocation) {
|
||||
currentLocationSearchRadius = latestSearchRadius;
|
||||
currentLocation = currentLatLng;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return explorePlacesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads attractions from location for map view, we need to return places in Place data type
|
||||
*
|
||||
* @return baseMarkerOptions list that holds nearby places with their icons
|
||||
*/
|
||||
public static List<BaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
LatLng currentLatLng,
|
||||
final List<Place> placeList,
|
||||
Context context,
|
||||
NearbyBaseMarkerThumbCallback callback,
|
||||
ExplorePlacesInfo explorePlacesInfo) {
|
||||
List<BaseMarker> baseMarkerList = new ArrayList<>();
|
||||
|
||||
if (placeList == null) {
|
||||
return baseMarkerList;
|
||||
}
|
||||
|
||||
VectorDrawableCompat vectorDrawable = null;
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme());
|
||||
|
||||
} catch (Resources.NotFoundException e) {
|
||||
// ignore when running tests.
|
||||
}
|
||||
if (vectorDrawable != null) {
|
||||
for (Place explorePlace : placeList) {
|
||||
final BaseMarker baseMarker = new BaseMarker();
|
||||
String distance = formatDistanceBetween(currentLatLng, explorePlace.location);
|
||||
explorePlace.setDistance(distance);
|
||||
|
||||
baseMarker.setTitle(
|
||||
explorePlace.name.substring(5, explorePlace.name.lastIndexOf(".")));
|
||||
baseMarker.setPosition(
|
||||
new fr.free.nrw.commons.location.LatLng(
|
||||
explorePlace.location.getLatitude(),
|
||||
explorePlace.location.getLongitude(), 0));
|
||||
baseMarker.setPlace(explorePlace);
|
||||
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(explorePlace.getThumb())
|
||||
.placeholder(R.drawable.image_placeholder_96)
|
||||
.apply(new RequestOptions().override(96, 96).centerCrop())
|
||||
.into(new CustomTarget<Bitmap>() {
|
||||
// We add icons to markers when bitmaps are ready
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource,
|
||||
@Nullable Transition<? super Bitmap> transition) {
|
||||
baseMarker.setIcon(
|
||||
ImageUtils.addRedBorder(resource, 6, context));
|
||||
baseMarkerList.add(baseMarker);
|
||||
if (baseMarkerList.size()
|
||||
== placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(baseMarkerList,
|
||||
explorePlacesInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadCleared(@Nullable Drawable placeholder) {
|
||||
}
|
||||
|
||||
// We add thumbnail icon for images that couldn't be loaded
|
||||
@Override
|
||||
public void onLoadFailed(@Nullable final Drawable errorDrawable) {
|
||||
super.onLoadFailed(errorDrawable);
|
||||
baseMarker.fromResource(context, R.drawable.image_placeholder_96);
|
||||
baseMarkerList.add(baseMarker);
|
||||
if (baseMarkerList.size()
|
||||
== placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(baseMarkerList,
|
||||
explorePlacesInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return baseMarkerList;
|
||||
}
|
||||
|
||||
interface NearbyBaseMarkerThumbCallback {
|
||||
|
||||
// Callback to notify thumbnails of explore markers are added as icons and ready
|
||||
void onNearbyBaseMarkerThumbsReady(List<BaseMarker> baseMarkers,
|
||||
ExplorePlacesInfo explorePlacesInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
package fr.free.nrw.commons.explore.map
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import fr.free.nrw.commons.BaseMarker
|
||||
import fr.free.nrw.commons.MapController
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.utils.ImageUtils.addRedBorder
|
||||
import fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween
|
||||
import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween
|
||||
import fr.free.nrw.commons.utils.LocationUtils.calculateDistance
|
||||
import fr.free.nrw.commons.utils.PlaceUtils.mediaToExplorePlace
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExploreMapController @Inject constructor(
|
||||
private val exploreMapCalls: ExploreMapCalls
|
||||
) : MapController() {
|
||||
// Can be current and camera target on search this area button is used
|
||||
private var latestSearchLocation: LatLng? = null
|
||||
|
||||
// Any last search radius
|
||||
private var latestSearchRadius: Double = 0.0
|
||||
|
||||
// Search radius of only searches around current location
|
||||
private var currentLocationSearchRadius: Double = 0.0
|
||||
|
||||
@JvmField
|
||||
// current location of user
|
||||
var currentLocation: LatLng? = null
|
||||
|
||||
/**
|
||||
* Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList,
|
||||
* explorePlaceList and boundaryCoordinates
|
||||
*
|
||||
* @param currentLatLng is current geolocation
|
||||
* @param searchLatLng is the location that we want to search around
|
||||
* @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around
|
||||
* current location, false if another location
|
||||
* @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and
|
||||
* boundaryCoordinates
|
||||
*/
|
||||
fun loadAttractionsFromLocation(
|
||||
currentLatLng: LatLng?, searchLatLng: LatLng?,
|
||||
checkingAroundCurrentLocation: Boolean
|
||||
): ExplorePlacesInfo? {
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions explore map, but search is null")
|
||||
return null
|
||||
}
|
||||
|
||||
val explorePlacesInfo = ExplorePlacesInfo()
|
||||
try {
|
||||
explorePlacesInfo.currentLatLng = currentLatLng
|
||||
latestSearchLocation = searchLatLng
|
||||
|
||||
val mediaList = exploreMapCalls.callCommonsQuery(searchLatLng)
|
||||
val boundaryCoordinates = arrayOf(
|
||||
mediaList[0].coordinates!!, // south
|
||||
mediaList[0].coordinates!!, // north
|
||||
mediaList[0].coordinates!!, // west
|
||||
mediaList[0].coordinates!!
|
||||
) // east, init with a random location
|
||||
|
||||
Timber.d("Sorting places by distance...")
|
||||
val distances: MutableMap<Media, Double> = HashMap()
|
||||
for (media in mediaList) {
|
||||
distances[media] = computeDistanceBetween(media.coordinates!!, searchLatLng)
|
||||
// Find boundaries with basic find max approach
|
||||
if (media.coordinates!!.latitude
|
||||
< boundaryCoordinates[0]!!.latitude
|
||||
) {
|
||||
boundaryCoordinates[0] = media.coordinates!!
|
||||
}
|
||||
if (media.coordinates!!.latitude
|
||||
> boundaryCoordinates[1]!!.latitude
|
||||
) {
|
||||
boundaryCoordinates[1] = media.coordinates!!
|
||||
}
|
||||
if (media.coordinates!!.longitude
|
||||
< boundaryCoordinates[2]!!.longitude
|
||||
) {
|
||||
boundaryCoordinates[2] = media.coordinates!!
|
||||
}
|
||||
if (media.coordinates!!.longitude
|
||||
> boundaryCoordinates[3]!!.longitude
|
||||
) {
|
||||
boundaryCoordinates[3] = media.coordinates!!
|
||||
}
|
||||
}
|
||||
explorePlacesInfo.mediaList = mediaList
|
||||
explorePlacesInfo.explorePlaceList = mediaToExplorePlace(mediaList)
|
||||
explorePlacesInfo.boundaryCoordinates = boundaryCoordinates
|
||||
|
||||
// Sets latestSearchRadius to maximum distance among boundaries and search location
|
||||
for ((latitude, longitude) in boundaryCoordinates) {
|
||||
val distance = calculateDistance(
|
||||
latitude,
|
||||
longitude, searchLatLng.latitude, searchLatLng.longitude
|
||||
)
|
||||
if (distance > latestSearchRadius) {
|
||||
latestSearchRadius = distance
|
||||
}
|
||||
}
|
||||
|
||||
// Our radius searched around us, will be used to understand when user search their own location, we will follow them
|
||||
if (checkingAroundCurrentLocation) {
|
||||
currentLocationSearchRadius = latestSearchRadius
|
||||
currentLocation = currentLatLng
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
return explorePlacesInfo
|
||||
}
|
||||
|
||||
interface NearbyBaseMarkerThumbCallback {
|
||||
// Callback to notify thumbnails of explore markers are added as icons and ready
|
||||
fun onNearbyBaseMarkerThumbsReady(
|
||||
baseMarkers: List<BaseMarker>?,
|
||||
explorePlacesInfo: ExplorePlacesInfo?
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Loads attractions from location for map view, we need to return places in Place data type
|
||||
*
|
||||
* @return baseMarkerOptions list that holds nearby places with their icons
|
||||
*/
|
||||
fun loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
currentLatLng: LatLng?,
|
||||
placeList: List<Place>?,
|
||||
context: Context,
|
||||
callback: NearbyBaseMarkerThumbCallback,
|
||||
explorePlacesInfo: ExplorePlacesInfo?
|
||||
): List<BaseMarker> {
|
||||
val baseMarkerList: MutableList<BaseMarker> = ArrayList()
|
||||
|
||||
if (placeList == null) {
|
||||
return baseMarkerList
|
||||
}
|
||||
|
||||
var vectorDrawable: VectorDrawableCompat? = null
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
context.resources, R.drawable.ic_custom_map_marker_dark, context.theme
|
||||
)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
// ignore when running tests.
|
||||
}
|
||||
if (vectorDrawable != null) {
|
||||
for (explorePlace in placeList) {
|
||||
val baseMarker = BaseMarker()
|
||||
val distance = formatDistanceBetween(currentLatLng, explorePlace.location)
|
||||
explorePlace.setDistance(distance)
|
||||
|
||||
baseMarker.title =
|
||||
explorePlace.name.substring(5, explorePlace.name.lastIndexOf("."))
|
||||
baseMarker.position = LatLng(
|
||||
explorePlace.location.latitude,
|
||||
explorePlace.location.longitude, 0f
|
||||
)
|
||||
baseMarker.place = explorePlace
|
||||
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(explorePlace.thumb)
|
||||
.placeholder(R.drawable.image_placeholder_96)
|
||||
.apply(RequestOptions().override(96, 96).centerCrop())
|
||||
.into(object : CustomTarget<Bitmap>() {
|
||||
// We add icons to markers when bitmaps are ready
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap,
|
||||
transition: Transition<in Bitmap>?
|
||||
) {
|
||||
baseMarker.icon = addRedBorder(resource, 6, context)
|
||||
baseMarkerList.add(baseMarker)
|
||||
if (baseMarkerList.size == placeList.size) {
|
||||
// if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(
|
||||
baseMarkerList,
|
||||
explorePlacesInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) = Unit
|
||||
|
||||
// We add thumbnail icon for images that couldn't be loaded
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
super.onLoadFailed(errorDrawable)
|
||||
baseMarker.fromResource(context, R.drawable.image_placeholder_96)
|
||||
baseMarkerList.add(baseMarker)
|
||||
if (baseMarkerList.size == placeList.size) {
|
||||
// if true, we added all markers to list and can trigger thumbs ready callback
|
||||
callback.onNearbyBaseMarkerThumbsReady(
|
||||
baseMarkerList,
|
||||
explorePlacesInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return baseMarkerList
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -1,240 +0,0 @@
|
|||
package fr.free.nrw.commons.explore.map;
|
||||
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA;
|
||||
|
||||
|
||||
import android.location.Location;
|
||||
import android.view.View;
|
||||
import fr.free.nrw.commons.BaseMarker;
|
||||
import fr.free.nrw.commons.MapController;
|
||||
import fr.free.nrw.commons.MapController.ExplorePlacesInfo;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
|
||||
import io.reactivex.Observable;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ExploreMapPresenter
|
||||
implements ExploreMapContract.UserActions,
|
||||
NearbyBaseMarkerThumbCallback {
|
||||
|
||||
BookmarkLocationsDao bookmarkLocationDao;
|
||||
private boolean isNearbyLocked;
|
||||
private LatLng currentLatLng;
|
||||
private ExploreMapController exploreMapController;
|
||||
|
||||
private static final ExploreMapContract.View DUMMY = (ExploreMapContract.View) Proxy
|
||||
.newProxyInstance(
|
||||
ExploreMapContract.View.class.getClassLoader(),
|
||||
new Class[]{ExploreMapContract.View.class}, (proxy, method, args) -> {
|
||||
if (method.getName().equals("onMyEvent")) {
|
||||
return null;
|
||||
} else if (String.class == method.getReturnType()) {
|
||||
return "";
|
||||
} else if (Integer.class == method.getReturnType()) {
|
||||
return Integer.valueOf(0);
|
||||
} else if (int.class == method.getReturnType()) {
|
||||
return 0;
|
||||
} else if (Boolean.class == method.getReturnType()) {
|
||||
return Boolean.FALSE;
|
||||
} else if (boolean.class == method.getReturnType()) {
|
||||
return false;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
);
|
||||
private ExploreMapContract.View exploreMapFragmentView = DUMMY;
|
||||
|
||||
public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao) {
|
||||
this.bookmarkLocationDao = bookmarkLocationDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMap(LocationChangeType locationChangeType) {
|
||||
Timber.d("Presenter updates map and list" + locationChangeType.toString());
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!exploreMapFragmentView.isNetworkConnectionEstablished()) {
|
||||
Timber.d("Network connection is not established");
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Significant changed - Markers and current location will be updated together
|
||||
* Slightly changed - Only current position marker will be updated
|
||||
*/
|
||||
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)) {
|
||||
Timber.d("LOCATION_SIGNIFICANTLY_CHANGED");
|
||||
LatLng populateLatLng = exploreMapFragmentView.getMapCenter();
|
||||
|
||||
//If "Show in Explore" was selected in Nearby, use the previous LatLng
|
||||
if (exploreMapFragmentView instanceof ExploreMapFragment) {
|
||||
ExploreMapFragment exploreMapFragment = (ExploreMapFragment)exploreMapFragmentView;
|
||||
if (exploreMapFragment.recentlyCameFromNearbyMap()) {
|
||||
//Ensure this LatLng will not be used again if user searches their GPS location
|
||||
exploreMapFragment.setRecentlyCameFromNearbyMap(false);
|
||||
|
||||
populateLatLng = exploreMapFragment.getPreviousLatLng();
|
||||
}
|
||||
}
|
||||
|
||||
lockUnlockNearby(true);
|
||||
exploreMapFragmentView.setProgressBarVisibility(true);
|
||||
exploreMapFragmentView.populatePlaces(populateLatLng);
|
||||
} else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) {
|
||||
Timber.d("SEARCH_CUSTOM_AREA");
|
||||
lockUnlockNearby(true);
|
||||
exploreMapFragmentView.setProgressBarVisibility(true);
|
||||
exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus());
|
||||
} else { // Means location changed slightly, ie user is walking or driving.
|
||||
Timber.d("Means location changed slightly");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nearby updates takes time, since they are network operations. During update time, we don't
|
||||
* want to get any other calls from user. So locking nearby.
|
||||
*
|
||||
* @param isNearbyLocked true means lock, false means unlock
|
||||
*/
|
||||
@Override
|
||||
public void lockUnlockNearby(boolean isNearbyLocked) {
|
||||
this.isNearbyLocked = isNearbyLocked;
|
||||
if (isNearbyLocked) {
|
||||
exploreMapFragmentView.disableFABRecenter();
|
||||
} else {
|
||||
exploreMapFragmentView.enableFABRecenter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachView(ExploreMapContract.View view) {
|
||||
exploreMapFragmentView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachView() {
|
||||
exploreMapFragmentView = DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener of FAB
|
||||
*/
|
||||
@Override
|
||||
public void setActionListeners(JsonKvStore applicationKvStore) {
|
||||
exploreMapFragmentView.setFABRecenterAction(v -> {
|
||||
exploreMapFragmentView.recenterMap(currentLatLng);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backButtonClicked() {
|
||||
return exploreMapFragmentView.backButtonClicked();
|
||||
}
|
||||
|
||||
public void onMapReady(ExploreMapController exploreMapController) {
|
||||
this.exploreMapController = exploreMapController;
|
||||
if (null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction();
|
||||
initializeMapOperations();
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
public Observable<ExplorePlacesInfo> loadAttractionsFromLocation(LatLng currentLatLng,
|
||||
LatLng searchLatLng, boolean checkingAroundCurrent) {
|
||||
return Observable
|
||||
.fromCallable(() -> exploreMapController
|
||||
.loadAttractionsFromLocation(currentLatLng, searchLatLng, checkingAroundCurrent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
*
|
||||
* @param explorePlacesInfo This variable has placeToCenter list information and distances.
|
||||
*/
|
||||
public void updateMapMarkers(
|
||||
MapController.ExplorePlacesInfo explorePlacesInfo) {
|
||||
if (explorePlacesInfo.mediaList != null) {
|
||||
prepareNearbyBaseMarkers(explorePlacesInfo);
|
||||
} else {
|
||||
lockUnlockNearby(false); // So that new location updates wont come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
void prepareNearbyBaseMarkers(MapController.ExplorePlacesInfo explorePlacesInfo) {
|
||||
exploreMapController
|
||||
.loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng,
|
||||
// Curlatlang will be used to calculate distances
|
||||
explorePlacesInfo.explorePlaceList,
|
||||
exploreMapFragmentView.getContext(),
|
||||
this,
|
||||
explorePlacesInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNearbyBaseMarkerThumbsReady(List<BaseMarker> baseMarkers,
|
||||
ExplorePlacesInfo explorePlacesInfo) {
|
||||
if (null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addMarkersToMap(baseMarkers);
|
||||
lockUnlockNearby(false); // So that new location updates wont come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
public View.OnClickListener onSearchThisAreaClicked() {
|
||||
return v -> {
|
||||
// Lock map operations during search this area operation
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
|
||||
if (searchCloseToCurrentLocation()) {
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
updateMap(SEARCH_CUSTOM_AREA);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that we can
|
||||
* continue following our current location again
|
||||
*
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
public boolean searchCloseToCurrentLocation() {
|
||||
if (null == exploreMapFragmentView.getLastMapFocus()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Location mylocation = new Location("");
|
||||
Location dest_location = new Location("");
|
||||
dest_location.setLatitude(exploreMapFragmentView.getMapFocus().getLatitude());
|
||||
dest_location.setLongitude(exploreMapFragmentView.getMapFocus().getLongitude());
|
||||
mylocation.setLatitude(exploreMapFragmentView.getLastMapFocus().getLatitude());
|
||||
mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude());
|
||||
Float distance = mylocation.distanceTo(dest_location);
|
||||
|
||||
if (distance > 2000.0 * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue