mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge branch 'master' into 3.0-release
This commit is contained in:
commit
3ce1343e64
326 changed files with 11357 additions and 9719 deletions
12
README.md
12
README.md
|
|
@ -25,13 +25,13 @@ We try to have an extensive documentation at our [documentation repository][4]:
|
|||
|
||||
Thank you all for your work!
|
||||
|
||||
| [<img src="https://avatars1.githubusercontent.com/u/3611199?v=4" width="100px;"/><br /><sub><b>misaochan</b></sub>](https://github.com/misaochan) | [<img src="https://avatars2.githubusercontent.com/u/30430?v=4" width="100px;"/><br /><sub><b>yuvipanda</b></sub>](https://github.com/yuvipanda) | [<img src="https://avatars1.githubusercontent.com/u/3127881?v=4" width="100px;"/><br /><sub><b>neslihanturan</b></sub>](https://github.com/neslihanturan) | [<img src="https://avatars2.githubusercontent.com/u/3069373?v=4" width="100px;"/><br /><sub><b>maskaravivek</b></sub>](https://github.com/maskaravivek) | [<img src="https://avatars3.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/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://avatars1.githubusercontent.com/u/99590?v=4" width="100px;"/><br /><sub><b>nicolas-raoul</b></sub>](https://github.com/nicolas-raoul) | [<img src="https://avatars0.githubusercontent.com/u/407647?v=4" width="100px;"/><br /><sub><b>psh</b></sub>](https://github.com/psh) | [<img src="https://avatars2.githubusercontent.com/u/103075?v=4" width="100px;"/><br /><sub><b>brion</b></sub>](https://github.com/brion) | [<img src="https://avatars3.githubusercontent.com/u/10674?v=4" width="100px;"/><br /><sub><b>whym</b></sub>](https://github.com/whym) | [<img src="https://avatars0.githubusercontent.com/u/4953590?v=4" width="100px;"/><br /><sub><b>domdomegg</b></sub>](https://github.com/domdomegg) |
|
||||
| [<img src="https://avatars2.githubusercontent.com/u/10153800?v=4" width="100px;"/><br /><sub><b>akaita</b></sub>](https://github.com/akaita) | [<img src="https://avatars0.githubusercontent.com/u/6900601?v=4" width="100px;"/><br /><sub><b>veyndan</b></sub>](https://github.com/veyndan) | [<img src="https://avatars0.githubusercontent.com/u/19607555?v=4" width="100px;"/><br /><sub><b>ujjwalagrawal17</b></sub>](https://github.com/ujjwalagrawal17) | [<img src="https://avatars1.githubusercontent.com/u/1682214?v=4" width="100px;"/><br /><sub><b>dbrant</b></sub>](https://github.com/dbrant) | [<img src="https://avatars3.githubusercontent.com/u/1345681?v=4" width="100px;"/><br /><sub><b>sandarumk</b></sub>](https://github.com/sandarumk) |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/6953323?v=4" width="100px;"/><br /><sub><b>tobias47n9e</b></sub>](https://github.com/tobias47n9e) | [<img src="https://avatars0.githubusercontent.com/u/29161745?v=4" width="100px;"/><br /><sub><b>tanvidadu</b></sub>](https://github.com/tanvidadu) | [<img src="https://avatars1.githubusercontent.com/u/25305892?v=4" width="100px;"/><br /><sub><b>hismaeel</b></sub>](https://github.com/hismaeel) | [<img src="https://avatars0.githubusercontent.com/u/12574756?v=4" width="100px;"/><br /><sub><b>tshradheya</b></sub>](https://github.com/tshradheya) | [<img src="https://avatars1.githubusercontent.com/u/27244688?v=4" width="100px;"/><br /><sub><b>diddypod</b></sub>](https://github.com/diddypod) |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/32291277?v=4" width="100px;"/><br /><sub><b>prxjeen</b></sub>](https://github.com/prxjeen) | [<img src="https://avatars2.githubusercontent.com/u/3308769?v=4" width="100px;"/><br /><sub><b>addshore</b></sub>](https://github.com/addshore) | [<img src="https://avatars3.githubusercontent.com/u/20313518?v=4" width="100px;"/><br /><sub><b>knight-shade</b></sub>](https://github.com/knight-shade) | [<img src="https://avatars3.githubusercontent.com/u/17375274?v=4" width="100px;"/><br /><sub><b>ashishkumar468</b></sub>](https://github.com/ashishkumar468) | [<img src="https://avatars0.githubusercontent.com/u/210297?v=4" width="100px;"/><br /><sub><b>siebrand</b></sub>](https://github.com/siebrand) |
|
||||
| [<img src="https://avatars0.githubusercontent.com/u/5329780?v=4" width="100px;"/><br /><sub><b>Bluesir9</b></sub>](https://github.com/Bluesir9) | [<img src="https://avatars1.githubusercontent.com/u/17095817?v=4" width="100px;"/><br /><sub><b>Jatin0312</b></sub>](https://github.com/Jatin0312) | [<img src="https://avatars3.githubusercontent.com/u/3415851?v=4" width="100px;"/><br /><sub><b>mashawan</b></sub>](https://github.com/mashawan) | [<img src="https://avatars3.githubusercontent.com/u/594179?v=4" width="100px;"/><br /><sub><b>ford-prefect</b></sub>](https://github.com/ford-prefect) | [<img src="https://avatars1.githubusercontent.com/u/21229885?v=4" width="100px;"/><br /><sub><b>seantnemann</b></sub>](https://github.com/seantnemann) |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/4953590?v=4" width="100px;"/><br /><sub><b>domdomegg</b></sub>](https://github.com/domdomegg) | [<img src="https://avatars.githubusercontent.com/u/3069373?v=4" width="100px;"/><br /><sub><b>maskaravivek</b></sub>](https://github.com/maskaravivek) | [<img src="https://avatars.githubusercontent.com/u/407647?v=4" width="100px;"/><br /><sub><b>psh</b></sub>](https://github.com/psh) | [<img src="https://avatars.githubusercontent.com/u/103075?v=4" width="100px;"/><br /><sub><b>brion</b></sub>](https://github.com/brion) | [<img src="https://avatars.githubusercontent.com/u/17375274?v=4" width="100px;"/><br /><sub><b>ashishkumar468</b></sub>](https://github.com/ashishkumar468) |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/10674?v=4" width="100px;"/><br /><sub><b>whym</b></sub>](https://github.com/whym) | [<img src="https://avatars.githubusercontent.com/u/10153800?v=4" width="100px;"/><br /><sub><b>akaita</b></sub>](https://github.com/akaita) | [<img src="https://avatars.githubusercontent.com/u/30932899?v=4" width="100px;"/><br /><sub><b>madhurgupta10</b></sub>](https://github.com/madhurgupta10) | [<img src="https://avatars.githubusercontent.com/u/6900601?v=4" width="100px;"/><br /><sub><b>veyndan</b></sub>](https://github.com/veyndan) | [<img src="https://avatars.githubusercontent.com/u/19607555?v=4" width="100px;"/><br /><sub><b>ujjwalagrawal17</b></sub>](https://github.com/ujjwalagrawal17) |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/3358282?v=4" width="100px;"/><br /><sub><b>macgills</b></sub>](https://github.com/macgills) | [<img src="https://avatars.githubusercontent.com/u/1682214?v=4" width="100px;"/><br /><sub><b>dbrant</b></sub>](https://github.com/dbrant) | [<img src="https://avatars.githubusercontent.com/u/34261945?v=4" width="100px;"/><br /><sub><b>vanshikaarora</b></sub>](https://github.com/vanshikaarora) | [<img src="https://avatars.githubusercontent.com/u/1345681?v=4" width="100px;"/><br /><sub><b>sandarumk</b></sub>](https://github.com/sandarumk) | [<img src="https://avatars.githubusercontent.com/u/29161745?v=4" width="100px;"/><br /><sub><b>tanvidadu</b></sub>](https://github.com/tanvidadu) |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/39745544?v=4" width="100px;"/><br /><sub><b>cypherop</b></sub>](https://github.com/cypherop) | [<img src="https://avatars.githubusercontent.com/u/6953323?v=4" width="100px;"/><br /><sub><b>tobias47n9e</b></sub>](https://github.com/tobias47n9e) | [<img src="https://avatars.githubusercontent.com/u/25305892?v=4" width="100px;"/><br /><sub><b>hismaeel</b></sub>](https://github.com/hismaeel) | [<img src="https://avatars.githubusercontent.com/u/12574756?v=4" width="100px;"/><br /><sub><b>tshradheya</b></sub>](https://github.com/tshradheya) | [<img src="https://avatars.githubusercontent.com/u/3308769?v=4" width="100px;"/><br /><sub><b>addshore</b></sub>](https://github.com/addshore) |
|
||||
| [<img src="https://avatars.githubusercontent.com/u/20313518?v=4" width="100px;"/><br /><sub><b>knight-shade</b></sub>](https://github.com/knight-shade) | [<img src="https://avatars.githubusercontent.com/u/210297?v=4" width="100px;"/><br /><sub><b>siebrand</b></sub>](https://github.com/siebrand) | [<img src="https://avatars.githubusercontent.com/u/12448084?v=4" width="100px;"/><br /><sub><b>sivaraam</b></sub>](https://github.com/sivaraam) | [<img src="https://avatars.githubusercontent.com/u/5329780?v=4" width="100px;"/><br /><sub><b>Bluesir9</b></sub>](https://github.com/Bluesir9) | [<img src="https://avatars.githubusercontent.com/u/44129798?v=4" width="100px;"/><br /><sub><b>kbhardwaj123</b></sub>](https://github.com/kbhardwaj123) |
|
||||
|
||||
|
||||
.. and [many more](https://github.com/commons-app/apps-android-commons/graphs/contributors).
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ dependencies {
|
|||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
||||
implementation 'com.jakewharton.rxbinding3:rxbinding-appcompat:3.0.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.1.1'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.1.1'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.1.1'
|
||||
|
|
@ -66,9 +67,11 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION"
|
||||
|
||||
// Dependency injector
|
||||
implementation "com.google.dagger:dagger-android:$DAGGER_VERSION"
|
||||
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
||||
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
|
||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||
annotationProcessor "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$KOTLIN_VERSION"
|
||||
|
|
@ -76,28 +79,20 @@ dependencies {
|
|||
//Mocking
|
||||
testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0'
|
||||
testImplementation 'org.mockito:mockito-inline:2.13.0'
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
|
||||
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
|
||||
testImplementation 'org.mockito:mockito-core:2.25.1'
|
||||
testImplementation "org.powermock:powermock-module-junit4:2.0.2"
|
||||
testImplementation "org.powermock:powermock-api-mockito2:2.0.2"
|
||||
|
||||
// Unit testing
|
||||
testImplementation 'junit:junit:4.13.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.3'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.5.1'
|
||||
testImplementation 'androidx.test:core:1.3.0'
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:$OKHTTP_VERSION"
|
||||
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
|
||||
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
|
||||
testImplementation 'org.mockito:mockito-core:2.23.0'
|
||||
testImplementation "com.jraska.livedata:testing-ktx:1.1.2"
|
||||
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api:5.3.1"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.3.1"
|
||||
|
||||
// Mockito and PowerMock
|
||||
androidTestCompile ('org.powermock:powermock-mockito-release-full:1.6.0') {
|
||||
exclude module: 'hamcrest-core'
|
||||
exclude module: 'objenesis'
|
||||
}
|
||||
testImplementation 'com.facebook.soloader:soloader:0.9.0'
|
||||
|
||||
// Android testing
|
||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||
|
|
@ -117,10 +112,10 @@ dependencies {
|
|||
|
||||
// Support libraries
|
||||
implementation "com.google.android.material:material:1.1.0-alpha04"
|
||||
implementation "androidx.browser:browser:1.0.0"
|
||||
implementation "androidx.browser:browser:1.3.0"
|
||||
implementation "androidx.cardview:cardview:1.0.0"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation "androidx.exifinterface:exifinterface:1.0.0"
|
||||
implementation "androidx.exifinterface:exifinterface:1.3.2"
|
||||
implementation "androidx.core:core-ktx:$CORE_KTX_VERSION"
|
||||
implementation "androidx.multidex:multidex:2.0.1"
|
||||
|
||||
|
|
@ -143,6 +138,10 @@ dependencies {
|
|||
implementation "androidx.preference:preference-ktx:$PREFERENCE_VERSION"
|
||||
|
||||
implementation "androidx.multidex:multidex:$MULTIDEX_VERSION"
|
||||
|
||||
def work_version = "2.4.0"
|
||||
// Kotlin + coroutines
|
||||
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
@ -150,6 +149,7 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
//applicationId 'fr.free.nrw.commons'
|
||||
|
||||
versionCode 1016
|
||||
versionName '3.0.1'
|
||||
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ class AchievementsActivityTest {
|
|||
@Test
|
||||
fun testAchievements() {
|
||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withId(R.id.user_icon)).perform(click())
|
||||
|
||||
Intents.intended(hasComponent(ProfileActivity::class.java.name))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CategoryImagesActivityTest {
|
||||
@get:Rule
|
||||
var activityRule = ActivityTestRule(CategoryImagesActivity::class.java)
|
||||
|
||||
@Test
|
||||
fun orientationChange() {
|
||||
UITestHelper.changeOrientation(activityRule)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,6 @@ class LeaderboardActivityTest {
|
|||
@Test
|
||||
fun testScrollToRankFromAbove() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.user_icon)).perform(ViewActions.click())
|
||||
|
||||
Espresso.onView(ViewMatchers.withId(R.id.tab_layout)).perform(ViewActions.click())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.tab_layout)).perform(selectTabAtPosition(1))
|
||||
|
|
@ -58,7 +57,6 @@ class LeaderboardActivityTest {
|
|||
@Test
|
||||
fun testScrollToRankFromBelow() {
|
||||
Espresso.onView(ViewMatchers.withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.user_icon)).perform(ViewActions.click())
|
||||
|
||||
Espresso.onView(ViewMatchers.withId(R.id.tab_layout)).perform(ViewActions.click())
|
||||
Espresso.onView(ViewMatchers.withId(R.id.tab_layout)).perform(selectTabAtPosition(1))
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
package fr.free.nrw.commons
|
||||
|
||||
import androidx.test.espresso.Espresso
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.contrib.NavigationViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.filters.LargeTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NavigationBaseActivityTest {
|
||||
@get:Rule
|
||||
var activityRule: ActivityTestRule<*> = ActivityTestRule(AboutActivity::class.java)
|
||||
|
||||
/**
|
||||
* Goes through all the activities in the app and checks we don't crash
|
||||
* NB: This is not realistic if we're not logged in; we can access 'home', 'notifications', 'settings' and 'achievements' which we wouldn't otherwise be able to.
|
||||
*/
|
||||
@Test
|
||||
fun goThroughNavigationBaseActivityActivities() {
|
||||
// Home
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_home)
|
||||
|
||||
// Explore
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_explore)
|
||||
|
||||
// Bookmarks
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_bookmarks)
|
||||
|
||||
// Reviews
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_review)
|
||||
|
||||
// Settings
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_settings)
|
||||
|
||||
// About
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_about)
|
||||
|
||||
// Tutorial
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_introduction)
|
||||
Espresso.pressBack()
|
||||
|
||||
// Achievements
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_login)
|
||||
|
||||
// Feedback
|
||||
openNavigationDrawerAndNavigateTo(R.id.action_feedback)
|
||||
}
|
||||
|
||||
private fun openNavigationDrawerAndNavigateTo(menuItemId: Int) {
|
||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
UITestHelper.sleep(500)
|
||||
onView(withId(R.id.navigation_view)).perform(NavigationViewActions.navigateTo(menuItemId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun orientationChange() {
|
||||
UITestHelper.changeOrientation(activityRule)
|
||||
}
|
||||
}
|
||||
18
app/src/beta/res/xml/shortcuts.xml
Normal file
18
app/src/beta/res/xml/shortcuts.xml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_settings_black"
|
||||
android:shortcutId="Setting"
|
||||
android:shortcutLongLabel="@string/title_app_shortcut_setting"
|
||||
android:shortcutShortLabel="@string/title_app_shortcut_setting"
|
||||
tools:targetApi="n_mr1">
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetClass="fr.free.nrw.commons.settings.SettingsActivity"
|
||||
android:targetPackage="fr.free.nrw.commons.beta" />
|
||||
</shortcut>
|
||||
|
||||
</shortcuts>
|
||||
|
|
@ -108,20 +108,16 @@
|
|||
<activity android:name=".quiz.QuizResultActivity"
|
||||
android:label="@string/result"/>
|
||||
|
||||
<activity
|
||||
android:name=".category.CategoryImagesActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:parentActivityName=".contributions.MainActivity"
|
||||
android:configChanges="orientation|screenSize|keyboard"/>
|
||||
|
||||
<activity
|
||||
android:name=".category.CategoryDetailsActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:configChanges="screenSize|keyboard|orientation"
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".explore.depictions.WikidataItemDetailsActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:configChanges="screenSize|keyboard|orientation"
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
|
|
@ -141,8 +137,7 @@
|
|||
android:name=".review.ReviewActivity"
|
||||
android:label="@string/title_activity_review" />
|
||||
|
||||
<service android:name=".upload.UploadService" />
|
||||
<service
|
||||
<service
|
||||
android:name=".auth.WikiAccountAuthenticatorService"
|
||||
android:exported="true"
|
||||
android:process=":auth">
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ import io.reactivex.internal.functions.Functions;
|
|||
import io.reactivex.plugins.RxJavaPlugins;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
|
@ -127,7 +129,12 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
|
||||
@Inject ContributionDao contributionDao;
|
||||
|
||||
/**
|
||||
/**
|
||||
* In memory list of contributios whose uploads ahve been paused by the user
|
||||
*/
|
||||
public static Map<String, Boolean> pauseUploads = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Used to declare and initialize various components and dependencies
|
||||
*/
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import android.widget.TextView;
|
|||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams;
|
||||
import androidx.browser.customtabs.CustomTabsIntent;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
|
|
@ -139,9 +140,13 @@ public class Utils {
|
|||
return;
|
||||
}
|
||||
|
||||
final CustomTabColorSchemeParams color = new CustomTabColorSchemeParams.Builder()
|
||||
.setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor))
|
||||
.setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor))
|
||||
.build();
|
||||
|
||||
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
||||
builder.setToolbarColor(ContextCompat.getColor(context, R.color.primaryColor));
|
||||
builder.setSecondaryToolbarColor(ContextCompat.getColor(context, R.color.primaryDarkColor));
|
||||
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.
|
||||
|
|
|
|||
114
app/src/main/java/fr/free/nrw/commons/explore/ViewPagerAdapter.java → app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.java
Executable file → Normal file
114
app/src/main/java/fr/free/nrw/commons/explore/ViewPagerAdapter.java → app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.java
Executable file → Normal file
|
|
@ -1,57 +1,57 @@
|
|||
package fr.free.nrw.commons.explore;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This adapter will be used to display fragments in a ViewPager
|
||||
*/
|
||||
public class ViewPagerAdapter extends FragmentPagerAdapter {
|
||||
private List<Fragment> fragmentList = new ArrayList<>();
|
||||
private List<String> fragmentTitleList = new ArrayList<>();
|
||||
|
||||
public ViewPagerAdapter(FragmentManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the fragment of the viewpager at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the total number of fragments in the viewpager.
|
||||
* @return size
|
||||
*/
|
||||
@Override
|
||||
public int getCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the fragment and title list in the viewpager
|
||||
* @param fragmentList List of all fragments to be displayed in the viewpager
|
||||
* @param fragmentTitleList List of all titles of the fragments
|
||||
*/
|
||||
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
|
||||
this.fragmentList = fragmentList;
|
||||
this.fragmentTitleList = fragmentTitleList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the title of the page at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return fragmentTitleList.get(position);
|
||||
}
|
||||
}
|
||||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This adapter will be used to display fragments in a ViewPager
|
||||
*/
|
||||
public class ViewPagerAdapter extends FragmentPagerAdapter {
|
||||
private List<Fragment> fragmentList = new ArrayList<>();
|
||||
private List<String> fragmentTitleList = new ArrayList<>();
|
||||
|
||||
public ViewPagerAdapter(FragmentManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the fragment of the viewpager at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the total number of fragments in the viewpager.
|
||||
* @return size
|
||||
*/
|
||||
@Override
|
||||
public int getCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the fragment and title list in the viewpager
|
||||
* @param fragmentList List of all fragments to be displayed in the viewpager
|
||||
* @param fragmentTitleList List of all titles of the fragments
|
||||
*/
|
||||
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
|
||||
this.fragmentList = fragmentList;
|
||||
this.fragmentTitleList = fragmentTitleList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the title of the page at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return fragmentTitleList.get(position);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
package fr.free.nrw.commons.actions;
|
||||
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleOnSubscribe;
|
||||
import org.wikipedia.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* This class acts as a Client to facilitate wiki page editing
|
||||
* services to various dependency providing modules such as the Network module, the Review Controller ,etc
|
||||
*
|
||||
* The methods provided by this class will post to the Media wiki api
|
||||
* documented at: https://commons.wikimedia.org/w/api.php?action=help&modules=edit
|
||||
*/
|
||||
public class PageEditClient {
|
||||
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
private final PageEditInterface pageEditInterface;
|
||||
private final Service service;
|
||||
|
||||
public PageEditClient(CsrfTokenClient csrfTokenClient,
|
||||
PageEditInterface pageEditInterface,
|
||||
Service service) {
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
this.pageEditInterface = pageEditInterface;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used when the content of the page is to be replaced by new content received
|
||||
* @param pagetitle Title of the page to edit
|
||||
* @param text Holds the page content
|
||||
* @param summary Edit summary
|
||||
*/
|
||||
public Observable<Boolean> edit(String pageTitle, String text, String summary) {
|
||||
try {
|
||||
return pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking())
|
||||
.map(editResponse -> editResponse.edit().editSucceeded());
|
||||
} catch (Throwable throwable) {
|
||||
return Observable.just(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used when we need to append something to the end of wiki page content
|
||||
* @param pagetitle Title of the page to edit
|
||||
* @param appendText The received page content is added to beginning of the page
|
||||
* @param summary Edit summary
|
||||
*/
|
||||
public Observable<Boolean> appendEdit(String pageTitle, String appendText, String summary) {
|
||||
return Single.create((SingleOnSubscribe<String>) emitter -> {
|
||||
try {
|
||||
emitter.onSuccess(csrfTokenClient.getTokenBlocking());
|
||||
} catch (Throwable throwable) {
|
||||
emitter.onError(throwable);
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
}).flatMapObservable(token -> pageEditInterface.postAppendEdit(pageTitle, summary, appendText, token)
|
||||
.map(editResponse -> editResponse.edit().editSucceeded()));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used when we need to add something to the starting of the page
|
||||
* @param pagetitle Title of the page to edit
|
||||
* @param prependText The received page content is added to beginning of the page
|
||||
* @param summary Edit summary
|
||||
*/
|
||||
public Observable<Boolean> prependEdit(String pageTitle, String prependText, String summary) {
|
||||
try {
|
||||
return pageEditInterface.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.getTokenBlocking())
|
||||
.map(editResponse -> editResponse.edit().editSucceeded());
|
||||
} catch (Throwable throwable) {
|
||||
return Observable.just(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
package fr.free.nrw.commons.actions
|
||||
|
||||
import io.reactivex.Observable
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
|
||||
/**
|
||||
* This class acts as a Client to facilitate wiki page editing
|
||||
* services to various dependency providing modules such as the Network module, the Review Controller ,etc
|
||||
*
|
||||
* The methods provided by this class will post to the Media wiki api
|
||||
* documented at: https://commons.wikimedia.org/w/api.php?action=help&modules=edit
|
||||
*/
|
||||
class PageEditClient(
|
||||
private val csrfTokenClient: CsrfTokenClient,
|
||||
private val pageEditInterface: PageEditInterface
|
||||
) {
|
||||
|
||||
/**
|
||||
* Replace the content of a wiki page
|
||||
* @param pageTitle Title of the page to edit
|
||||
* @param text Holds the page content
|
||||
* @param summary Edit summary
|
||||
* @return whether the edit was successful
|
||||
*/
|
||||
fun edit(pageTitle: String, text: String, summary: String): Observable<Boolean> {
|
||||
return try {
|
||||
pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.tokenBlocking)
|
||||
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
|
||||
} catch (throwable: Throwable) {
|
||||
Observable.just(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append text to the end of a wiki page
|
||||
* @param pageTitle Title of the page to edit
|
||||
* @param appendText The received page content is added to the end of the page
|
||||
* @param summary Edit summary
|
||||
* @return whether the edit was successful
|
||||
*/
|
||||
fun appendEdit(pageTitle: String, appendText: String, summary: String): Observable<Boolean> {
|
||||
return try {
|
||||
pageEditInterface.postAppendEdit(pageTitle, summary, appendText, csrfTokenClient.tokenBlocking)
|
||||
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
|
||||
} catch (throwable: Throwable) {
|
||||
Observable.just(false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend text to the beginning of a wiki page
|
||||
* @param pageTitle Title of the page to edit
|
||||
* @param prependText The received page content is added to the beginning of the page
|
||||
* @param summary Edit summary
|
||||
* @return whether the edit was successful
|
||||
*/
|
||||
fun prependEdit(pageTitle: String, prependText: String, summary: String): Observable<Boolean> {
|
||||
return try {
|
||||
pageEditInterface.postPrependEdit(pageTitle, summary, prependText, csrfTokenClient.tokenBlocking)
|
||||
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
|
||||
} catch (throwable: Throwable) {
|
||||
Observable.just(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
package fr.free.nrw.commons.actions;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.wikipedia.edit.Edit;
|
||||
|
||||
import io.reactivex.Observable;
|
||||
import retrofit2.http.Field;
|
||||
import retrofit2.http.FormUrlEncoded;
|
||||
import retrofit2.http.Headers;
|
||||
import retrofit2.http.POST;
|
||||
|
||||
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
|
||||
|
||||
/**
|
||||
* This interface facilitates wiki commons page editing services to the Networking module
|
||||
* which provides all network related services used by the app.
|
||||
*
|
||||
* This interface posts a form encoded request to the wikimedia API
|
||||
* with editing action as argument to edit a particular page
|
||||
*/
|
||||
public interface PageEditInterface {
|
||||
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be completely replaced by the value being passed to the
|
||||
* "text" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param text Holds the page content
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(MW_API_PREFIX + "action=edit")
|
||||
@NonNull
|
||||
Observable<Edit> postEdit(@NonNull @Field("title") String title,
|
||||
@NonNull @Field("summary") String summary,
|
||||
@NonNull @Field("text") String text,
|
||||
// NOTE: This csrf shold always be sent as the last field of form data
|
||||
@NonNull @Field("token") String token);
|
||||
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be completely replaced by the value being passed to the
|
||||
* "text" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param text The received page content is added to beginning of the page
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(MW_API_PREFIX + "action=edit")
|
||||
@NonNull Observable<Edit> postAppendEdit(@NonNull @Field("title") String title,
|
||||
@NonNull @Field("summary") String summary,
|
||||
@NonNull @Field("appendtext") String text,
|
||||
@NonNull @Field("token") String token);
|
||||
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be completely replaced by the value being passed to the
|
||||
* "text" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param text The received page content is added to beginning of the page
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(MW_API_PREFIX + "action=edit")
|
||||
@NonNull Observable<Edit> postPrependEdit(@NonNull @Field("title") String title,
|
||||
@NonNull @Field("summary") String summary,
|
||||
@NonNull @Field("prependtext") String text,
|
||||
@NonNull @Field("token") String token);
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package fr.free.nrw.commons.actions
|
||||
|
||||
import io.reactivex.Observable
|
||||
import org.wikipedia.dataclient.Service
|
||||
import org.wikipedia.edit.Edit
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.POST
|
||||
|
||||
/**
|
||||
* This interface facilitates wiki commons page editing services to the Networking module
|
||||
* which provides all network related services used by the app.
|
||||
*
|
||||
* This interface posts a form encoded request to the wikimedia API
|
||||
* with editing action as argument to edit a particular page
|
||||
*/
|
||||
interface PageEditInterface {
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be completely replaced by the value being passed to the
|
||||
* "text" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param text Holds the page content
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(Service.MW_API_PREFIX + "action=edit")
|
||||
fun postEdit(
|
||||
@Field("title") title: String,
|
||||
@Field("summary") summary: String,
|
||||
@Field("text") text: String,
|
||||
// NOTE: This csrf shold always be sent as the last field of form data
|
||||
@Field("token") token: String
|
||||
): Observable<Edit>
|
||||
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be appended with the value being passed to the
|
||||
* "appendText" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param appendText Text to add to the end of the page
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(Service.MW_API_PREFIX + "action=edit")
|
||||
fun postAppendEdit(
|
||||
@Field("title") title: String,
|
||||
@Field("summary") summary: String,
|
||||
@Field("appendtext") appendText: String,
|
||||
@Field("token") token: String
|
||||
): Observable<Edit>
|
||||
|
||||
/**
|
||||
* This method posts such that the Content which the page
|
||||
* has will be prepended with the value being passed to the
|
||||
* "prependText" field of the encoded form data
|
||||
* @param title Title of the page to edit. Cannot be used together with pageid.
|
||||
* @param summary Edit summary. Also section title when section=new and sectiontitle is not set
|
||||
* @param prependText Text to add to the beginning of the page
|
||||
* @param token A "csrf" token
|
||||
*/
|
||||
@FormUrlEncoded
|
||||
@Headers("Cache-Control: no-cache")
|
||||
@POST(Service.MW_API_PREFIX + "action=edit")
|
||||
fun postPrependEdit(
|
||||
@Field("title") title: String,
|
||||
@Field("summary") summary: String,
|
||||
@Field("prependtext") prependText: String,
|
||||
@Field("token") token: String
|
||||
): Observable<Edit>
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
package fr.free.nrw.commons.actions;
|
||||
|
||||
import org.wikipedia.csrf.CsrfTokenClient;
|
||||
import org.wikipedia.dataclient.Service;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import io.reactivex.Observable;
|
||||
|
||||
/**
|
||||
* Facilitates the Wkikimedia Thanks api extention, as described in the
|
||||
* api documentation: "The Thanks extension includes an API for sending thanks"
|
||||
*
|
||||
* In simple terms this class is used by a user to thank someone for adding
|
||||
* contribution to the commons platform
|
||||
*/
|
||||
@Singleton
|
||||
public class ThanksClient {
|
||||
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
private final Service service;
|
||||
|
||||
@Inject
|
||||
public ThanksClient(@Named("commons-csrf") CsrfTokenClient csrfTokenClient,
|
||||
@Named("commons-service") Service service) {
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the Thanking logic
|
||||
* @param revesionID The revision ID you would like to thank someone for
|
||||
* @return if thanks was successfully sent to intended recepient, returned as a boolean observable
|
||||
*/
|
||||
public Observable<Boolean> thank(long revisionId) {
|
||||
try {
|
||||
return service.thank(String.valueOf(revisionId), null,
|
||||
csrfTokenClient.getTokenBlocking(),
|
||||
CommonsApplication.getInstance().getUserAgent())
|
||||
.map(mwQueryResponse -> mwQueryResponse.getSuccessVal() == 1);
|
||||
} catch (Throwable throwable) {
|
||||
return Observable.just(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package fr.free.nrw.commons.actions
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication
|
||||
import fr.free.nrw.commons.di.NetworkingModule.NAMED_COMMONS_CSRF
|
||||
import io.reactivex.Observable
|
||||
import org.wikipedia.csrf.CsrfTokenClient
|
||||
import org.wikipedia.dataclient.Service
|
||||
import org.wikipedia.dataclient.mwapi.MwPostResponse
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Client for the Wkikimedia Thanks API extension
|
||||
* Thanks are used by a user to show gratitude to another user for their contributions
|
||||
*/
|
||||
@Singleton
|
||||
class ThanksClient @Inject constructor(
|
||||
@param:Named(NAMED_COMMONS_CSRF) private val csrfTokenClient: CsrfTokenClient,
|
||||
@param:Named("commons-service") private val service: Service
|
||||
) {
|
||||
/**
|
||||
* Thanks a user for a particular revision
|
||||
* @param revisionId The revision ID the user would like to thank someone for
|
||||
* @return if thanks was successfully sent to intended recipient
|
||||
*/
|
||||
fun thank(revisionId: Long): Observable<Boolean> {
|
||||
return try {
|
||||
service.thank(revisionId.toString(), null, csrfTokenClient.tokenBlocking, CommonsApplication.getInstance().userAgent)
|
||||
.map { mwThankPostResponse -> mwThankPostResponse.result.success== 1 }
|
||||
} catch (throwable: Throwable) {
|
||||
Observable.just(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
|
|||
import com.google.android.material.textfield.TextInputLayout;
|
||||
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
import java.util.Locale;
|
||||
import org.wikipedia.AppAdapter;
|
||||
import org.wikipedia.dataclient.ServiceFactory;
|
||||
import org.wikipedia.dataclient.WikiSite;
|
||||
|
|
@ -265,7 +266,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
public void onResponse(Call<MwQueryResponse> call,
|
||||
Response<MwQueryResponse> response) {
|
||||
loginClient.login(commonsWikiSite, username, password, null, twoFactorCode,
|
||||
response.body().query().loginToken(), new LoginCallback() {
|
||||
response.body().query().loginToken(), Locale.getDefault().getLanguage(), new LoginCallback() {
|
||||
@Override
|
||||
public void success(@NonNull LoginResult result) {
|
||||
Timber.d("Login Success");
|
||||
|
|
@ -338,6 +339,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
// no longer attached to activity!
|
||||
return;
|
||||
}
|
||||
compositeDisposable.clear();
|
||||
sessionManager.setUserLoggedIn(true);
|
||||
AppAdapter.get().updateAccount(loginResult);
|
||||
progressDialog.dismiss();
|
||||
|
|
|
|||
|
|
@ -136,4 +136,14 @@ public class SessionManager {
|
|||
currentAccount = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a corresponding boolean preference
|
||||
*
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
public boolean getPreference(String key) {
|
||||
return defaultKvStore.getBoolean(key);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,35 +4,33 @@ import android.os.Bundle;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import android.widget.FrameLayout;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.explore.ParentViewPager;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.contributions.ContributionController;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import javax.inject.Named;
|
||||
|
||||
public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
private FragmentManager supportFragmentManager;
|
||||
private BookmarksPagerAdapter adapter;
|
||||
@BindView(R.id.viewPagerBookmarks)
|
||||
ViewPager viewPager;
|
||||
ParentViewPager viewPager;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.fragmentContainer)
|
||||
|
|
@ -40,6 +38,13 @@ public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
/**
|
||||
* To check if the user is loggedIn or not.
|
||||
*/
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public
|
||||
JsonKvStore applicationKvStore;
|
||||
|
||||
@NonNull
|
||||
public static BookmarkFragment newInstance() {
|
||||
|
|
@ -48,6 +53,10 @@ public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
|||
return fragment;
|
||||
}
|
||||
|
||||
public void setScroll(boolean canScroll){
|
||||
viewPager.setCanScroll(canScroll);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
|
@ -66,15 +75,36 @@ public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
|||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||
supportFragmentManager = getChildFragmentManager();
|
||||
|
||||
adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext());
|
||||
adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(),
|
||||
applicationKvStore.getBoolean("login_skipped"));
|
||||
viewPager.setAdapter(adapter);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
setupTabLayout();
|
||||
return view;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets up the tab layout.
|
||||
* If the adapter has only one element it sets the visibility of tabLayout to gone.
|
||||
*/
|
||||
public void setupTabLayout(){
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
if (adapter.getCount() == 1) {
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onBackPressed() {
|
||||
((BookmarkListRootFragment) (adapter.getItem(tabLayout.getSelectedTabPosition())))
|
||||
.backPressed();
|
||||
if(((BookmarkListRootFragment)(adapter.getItem(tabLayout.getSelectedTabPosition()))).backPressed()) {
|
||||
// The event is handled internally by the adapter , no further action required.
|
||||
return;
|
||||
}
|
||||
// Event is not handled by the adapter ( performed back action ) change action bar.
|
||||
((BaseActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@ import fr.free.nrw.commons.R;
|
|||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.category.GridViewAdapter;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.navtab.NavTab;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
|
|
@ -166,6 +169,22 @@ public class BookmarkListRootFragment extends CommonsDaggerSupportFragment imple
|
|||
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 = new MediaDetailPagerFragment(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.
|
||||
|
|
@ -189,12 +208,17 @@ public class BookmarkListRootFragment extends CommonsDaggerSupportFragment imple
|
|||
((MainActivity) getActivity()).showTabs();
|
||||
}
|
||||
|
||||
void moveToContributionsFragment(){
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
}
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Log.d("deneme8","on media clicked");
|
||||
container.setVisibility(View.VISIBLE);
|
||||
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, listFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,14 @@ public class BookmarksPagerAdapter extends FragmentPagerAdapter {
|
|||
|
||||
private ArrayList<BookmarkPages> pages;
|
||||
|
||||
BookmarksPagerAdapter(FragmentManager fm, Context context) {
|
||||
/**
|
||||
* 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();
|
||||
|
|
@ -28,13 +35,16 @@ public class BookmarksPagerAdapter extends FragmentPagerAdapter {
|
|||
pages.add(new BookmarkPages(
|
||||
new BookmarkListRootFragment(picturesBundle, this),
|
||||
context.getString(R.string.title_page_bookmarks_pictures)));
|
||||
|
||||
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)));
|
||||
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)));
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ public class BookmarkLocationsDao {
|
|||
builder.setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)));
|
||||
|
||||
return new Place(
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_LANGUAGE)),
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
|
||||
Label.fromText((cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT)))),
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
|
||||
|
|
@ -164,13 +165,14 @@ public class BookmarkLocationsDao {
|
|||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
|
||||
builder.build(),
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESTROYED))
|
||||
Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)))
|
||||
);
|
||||
}
|
||||
|
||||
private ContentValues toContentValues(Place bookmarkLocation) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_NAME, bookmarkLocation.getName());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel().getText());
|
||||
|
|
@ -181,7 +183,7 @@ public class BookmarkLocationsDao {
|
|||
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_DESTROYED, bookmarkLocation.destroyed);
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_EXISTS, bookmarkLocation.exists.toString());
|
||||
return cv;
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +191,7 @@ public class BookmarkLocationsDao {
|
|||
public static final String TABLE_NAME = "bookmarksLocations";
|
||||
|
||||
static final String COLUMN_NAME = "location_name";
|
||||
static final String COLUMN_LANGUAGE = "location_language";
|
||||
static final String COLUMN_DESCRIPTION = "location_description";
|
||||
static final String COLUMN_LAT = "location_lat";
|
||||
static final String COLUMN_LONG = "location_long";
|
||||
|
|
@ -200,11 +203,12 @@ public class BookmarkLocationsDao {
|
|||
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
|
||||
static final String COLUMN_COMMONS_LINK = "location_commons_link";
|
||||
static final String COLUMN_PIC = "location_pic";
|
||||
static final String COLUMN_DESTROYED = "location_destroyed";
|
||||
static final String COLUMN_EXISTS = "location_exists";
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
public static final String[] ALL_FIELDS = {
|
||||
COLUMN_NAME,
|
||||
COLUMN_LANGUAGE,
|
||||
COLUMN_DESCRIPTION,
|
||||
COLUMN_CATEGORY,
|
||||
COLUMN_LABEL_TEXT,
|
||||
|
|
@ -216,13 +220,14 @@ public class BookmarkLocationsDao {
|
|||
COLUMN_WIKIDATA_LINK,
|
||||
COLUMN_COMMONS_LINK,
|
||||
COLUMN_PIC,
|
||||
COLUMN_DESTROYED
|
||||
COLUMN_EXISTS
|
||||
};
|
||||
|
||||
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 PRIMARY KEY,"
|
||||
+ COLUMN_LANGUAGE + " STRING,"
|
||||
+ COLUMN_DESCRIPTION + " STRING,"
|
||||
+ COLUMN_CATEGORY + " STRING,"
|
||||
+ COLUMN_LABEL_TEXT + " STRING,"
|
||||
|
|
@ -234,7 +239,7 @@ public class BookmarkLocationsDao {
|
|||
+ COLUMN_WIKIDATA_LINK + " STRING,"
|
||||
+ COLUMN_COMMONS_LINK + " STRING,"
|
||||
+ COLUMN_PIC + " STRING,"
|
||||
+ COLUMN_DESTROYED + " STRING"
|
||||
+ COLUMN_EXISTS + " STRING"
|
||||
+ ");";
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
|
|
@ -287,6 +292,20 @@ public class BookmarkLocationsDao {
|
|||
Timber.e(exception);
|
||||
}
|
||||
}
|
||||
if (from == 13){
|
||||
try {
|
||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;");
|
||||
} catch (SQLiteException exception){
|
||||
Timber.e(exception);
|
||||
}
|
||||
}
|
||||
if (from == 14){
|
||||
try {
|
||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;");
|
||||
} catch (SQLiteException exception){
|
||||
Timber.e(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ public class CampaignView extends SwipableCardView {
|
|||
|
||||
@Override public boolean onSwipe(View view) {
|
||||
view.setVisibility(View.GONE);
|
||||
/*((MainActivity) getContext()).defaultKvStore
|
||||
.putBoolean("displayCampaignsCardView", false);*/
|
||||
((MainActivity) getContext()).defaultKvStore
|
||||
.putBoolean("displayCampaignsCardView", false);
|
||||
ViewUtil.showLongToast(getContext(),
|
||||
getResources().getString(R.string.nearby_campaign_dismiss_message));
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ 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;
|
||||
|
|
@ -20,7 +21,7 @@ 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.explore.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
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;
|
||||
|
|
@ -47,7 +48,7 @@ public class CategoryDetailsActivity extends BaseActivity
|
|||
@BindView(R.id.mediaContainer) FrameLayout mediaContainer;
|
||||
@BindView(R.id.tab_layout) TabLayout tabLayout;
|
||||
@BindView(R.id.viewPager) ViewPager viewPager;
|
||||
|
||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
|
||||
@Override
|
||||
|
|
@ -60,6 +61,8 @@ public class CategoryDetailsActivity extends BaseActivity
|
|||
viewPager.setAdapter(viewPagerAdapter);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setTabs();
|
||||
setPageTitle();
|
||||
}
|
||||
|
|
@ -162,6 +165,19 @@ public class CategoryDetailsActivity extends BaseActivity
|
|||
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 inflates the menu in the toolbar
|
||||
*/
|
||||
|
|
@ -185,6 +201,9 @@ public class CategoryDetailsActivity extends BaseActivity
|
|||
PageTitle title = Utils.getPageTitle(CATEGORY_PREFIX + categoryName);
|
||||
Utils.handleWebUrl(this, Uri.parse(title.getCanonicalUri()));
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
|
@ -197,7 +216,12 @@ public class CategoryDetailsActivity extends BaseActivity
|
|||
@Override
|
||||
public void onBackPressed() {
|
||||
if (supportFragmentManager.getBackStackEntryCount() == 1){
|
||||
// back to search so show search toolbar and hide navigation toolbar
|
||||
|
||||
// the back press is handled by the mediaDetails , no further action required.
|
||||
if(mediaDetails.backButtonClicked()){
|
||||
return;
|
||||
}
|
||||
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
viewPager.setVisibility(View.VISIBLE);
|
||||
mediaContainer.setVisibility(View.GONE);
|
||||
|
|
|
|||
|
|
@ -1,197 +0,0 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.explore.SearchActivity;
|
||||
import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
|
||||
/**
|
||||
* This activity displays pictures of a particular category
|
||||
* Its generic and simply takes the name of category name in its start intent to load all images in
|
||||
* a particular category. This activity is currently being used to display a list of featured images,
|
||||
* which is nothing but another category on wikimedia commons.
|
||||
*/
|
||||
|
||||
public class CategoryImagesActivity
|
||||
extends BaseActivity
|
||||
implements FragmentManager.OnBackStackChangedListener,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
AdapterView.OnItemClickListener, CategoryImagesCallback {
|
||||
|
||||
|
||||
private FragmentManager supportFragmentManager;
|
||||
private CategoriesMediaFragment categoriesMediaFragment;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
|
||||
/**
|
||||
* This method is called on backPressed of anyFragment in the activity.
|
||||
* We are changing the icon here from back to hamburger icon.
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_category_images);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
// Activity can call methods in the fragment by acquiring a
|
||||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||
supportFragmentManager = getSupportFragmentManager();
|
||||
setCategoryImagesFragment();
|
||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||
setPageTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the categoryName from the intent and initializes the fragment for showing images of that category
|
||||
*/
|
||||
private void setCategoryImagesFragment() {
|
||||
categoriesMediaFragment = new CategoriesMediaFragment();
|
||||
String categoryName = getIntent().getStringExtra("categoryName");
|
||||
if (getIntent() != null && categoryName != null) {
|
||||
Bundle arguments = new Bundle();
|
||||
arguments.putString("categoryName", categoryName);
|
||||
categoriesMediaFragment.setArguments(arguments);
|
||||
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
|
||||
transaction
|
||||
.add(R.id.fragmentContainer, categoriesMediaFragment)
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passed title from the intents and displays it as the page title
|
||||
*/
|
||||
private void setPageTitle() {
|
||||
if (getIntent() != null && getIntent().getStringExtra("title") != null) {
|
||||
setTitle(getIntent().getStringExtra("title"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called onClick of media inside category details (CategoryImageListFragment).
|
||||
*/
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||
// set isFeaturedImage true for featured images, to include author field on media detail
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.hide(supportFragmentManager.getFragments().get(supportFragmentManager.getBackStackEntryCount()))
|
||||
.add(R.id.fragmentContainer, 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(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consumers should be simply using this method to use this activity.
|
||||
* @param context A Context of the application package implementing this class.
|
||||
* @param title Page title
|
||||
* @param categoryName Name of the category for displaying its images
|
||||
*/
|
||||
public static void startYourself(Context context, String title, String categoryName) {
|
||||
Intent intent = new Intent(context, CategoryImagesActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
intent.putExtra("title", title);
|
||||
intent.putExtra("categoryName", categoryName);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 categoriesMediaFragment.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (mediaDetails!=null){
|
||||
mediaDetails.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 categoriesMediaFragment.getTotalMediaCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getContributionStateAt(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method inflates the menu in the toolbar
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.menu_search, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this, SearchActivity.class);
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMediaClicked(int position) {
|
||||
// this class is unused and will be deleted
|
||||
}
|
||||
}
|
||||
|
|
@ -38,4 +38,15 @@ abstract class ContinuationClient<Network, Domain> {
|
|||
continuationStore.remove("$prefix$category")
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the existing the key from continuationExists and continuationStore
|
||||
*
|
||||
* @param prefix
|
||||
* @param userName the username
|
||||
*/
|
||||
protected fun resetUserContinuation(prefix: String, userName: String) {
|
||||
continuationExists.remove("$prefix$userName")
|
||||
continuationStore.remove("$prefix$userName")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.os.Parcelable
|
|||
import fr.free.nrw.commons.upload.UploadResult
|
||||
|
||||
data class ChunkInfo(
|
||||
val uploadResult: UploadResult,
|
||||
val uploadResult: UploadResult?,
|
||||
val indexOfNextChunkToUpload: Int,
|
||||
val totalChunks: Int
|
||||
) : Parcelable {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ class ContributionBoundaryCallback @Inject constructor(
|
|||
* network
|
||||
*/
|
||||
override fun onZeroItemsLoaded() {
|
||||
if (sessionManager.userName != null) {
|
||||
mediaClient.resetUserNameContinuation(sessionManager.userName!!)
|
||||
}
|
||||
fetchContributions()
|
||||
}
|
||||
|
||||
|
|
@ -50,21 +53,25 @@ class ContributionBoundaryCallback @Inject constructor(
|
|||
* Fetches contributions using the MediaWiki API
|
||||
*/
|
||||
fun fetchContributions() {
|
||||
compositeDisposable.add(
|
||||
mediaClient.getMediaListForUser(sessionManager.userName!!)
|
||||
.map { mediaList ->
|
||||
mediaList.map {
|
||||
Contribution(media=it, state=Contribution.STATE_COMPLETED)
|
||||
if (sessionManager.userName != null) {
|
||||
compositeDisposable.add(
|
||||
mediaClient.getMediaListForUser(sessionManager.userName!!)
|
||||
.map { mediaList ->
|
||||
mediaList.map {
|
||||
Contribution(media = it, state = Contribution.STATE_COMPLETED)
|
||||
}
|
||||
}
|
||||
}
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(::saveContributionsToDB) { error: Throwable ->
|
||||
Timber.e(
|
||||
"Failed to fetch contributions: %s",
|
||||
error.message
|
||||
)
|
||||
}
|
||||
)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(::saveContributionsToDB) { error: Throwable ->
|
||||
Timber.e(
|
||||
"Failed to fetch contributions: %s",
|
||||
error.message
|
||||
)
|
||||
}
|
||||
)
|
||||
}else {
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
import androidx.annotation.NonNull;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.filepicker.DefaultCallback;
|
||||
|
|
@ -29,7 +27,6 @@ import javax.inject.Singleton;
|
|||
public class ContributionController {
|
||||
|
||||
public static final String ACTION_INTERNAL_UPLOADS = "internalImageUploads";
|
||||
|
||||
private final JsonKvStore defaultKvStore;
|
||||
|
||||
@Inject
|
||||
|
|
@ -55,20 +52,16 @@ public class ContributionController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check for permissions and initiate gallery picker
|
||||
* Initiate gallery picker
|
||||
*/
|
||||
public void initiateGalleryPick(Activity activity, boolean allowMultipleUploads) {
|
||||
PermissionUtils.checkPermissionsAndPerformAction(activity,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
() -> initiateGalleryUpload(activity, allowMultipleUploads),
|
||||
R.string.storage_permission_title,
|
||||
R.string.read_storage_permission_rationale);
|
||||
public void initiateGalleryPick(final Activity activity, final boolean allowMultipleUploads) {
|
||||
initiateGalleryUpload(activity, allowMultipleUploads);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open chooser for gallery uploads
|
||||
*/
|
||||
private void initiateGalleryUpload(Activity activity, boolean allowMultipleUploads) {
|
||||
private void initiateGalleryUpload(final Activity activity, final boolean allowMultipleUploads) {
|
||||
setPickerConfiguration(activity, allowMultipleUploads);
|
||||
FilePicker.openGallery(activity, 0);
|
||||
}
|
||||
|
|
@ -130,7 +123,8 @@ public class ContributionController {
|
|||
List<UploadableFile> imagesFiles) {
|
||||
Intent shareIntent = new Intent(context, UploadActivity.class);
|
||||
shareIntent.setAction(ACTION_INTERNAL_UPLOADS);
|
||||
shareIntent.putParcelableArrayListExtra(EXTRA_FILES, new ArrayList<>(imagesFiles));
|
||||
shareIntent
|
||||
.putParcelableArrayListExtra(UploadActivity.EXTRA_FILES, new ArrayList<>(imagesFiles));
|
||||
Place place = defaultKvStore.getJson(PLACE_OBJECT, Place.class);
|
||||
|
||||
if (place != null) {
|
||||
|
|
|
|||
|
|
@ -39,12 +39,6 @@ public abstract class ContributionDao {
|
|||
saveSynchronous(newContribution);
|
||||
}
|
||||
|
||||
public Completable saveAndDelete(final Contribution oldContribution,
|
||||
final Contribution newContribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteAndSaveContribution(oldContribution, newContribution));
|
||||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||
|
||||
|
|
@ -62,11 +56,8 @@ public abstract class ContributionDao {
|
|||
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
||||
public abstract Contribution getContribution(String pageId);
|
||||
|
||||
@Query("SELECT * from contribution WHERE state=:state")
|
||||
public abstract Single<List<Contribution>> getContribution(int state);
|
||||
|
||||
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
||||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
||||
|
||||
@Query("SELECT COUNT(*) from contribution WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> getPendingUploads(int[] toUpdateStates);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Context;
|
||||
import fr.free.nrw.commons.BasePresenter;
|
||||
|
||||
/**
|
||||
|
|
@ -10,6 +11,8 @@ public class ContributionsContract {
|
|||
public interface View {
|
||||
|
||||
void showMessage(String localizedMessage);
|
||||
|
||||
Context getContext();
|
||||
}
|
||||
|
||||
public interface UserActionListener extends BasePresenter<ContributionsContract.View> {
|
||||
|
|
@ -18,5 +21,6 @@ public class ContributionsContract {
|
|||
|
||||
void deleteUpload(Contribution contribution);
|
||||
|
||||
void saveContribution(Contribution contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import android.Manifest;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -28,21 +25,20 @@ import androidx.annotation.Nullable;
|
|||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
import fr.free.nrw.commons.notification.NotificationController;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.campaigns.Campaign;
|
||||
import fr.free.nrw.commons.campaigns.CampaignView;
|
||||
import fr.free.nrw.commons.campaigns.CampaignsPresenter;
|
||||
|
|
@ -60,8 +56,10 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
|||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
import fr.free.nrw.commons.nearby.NearbyNotificationCardView;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.notification.Notification;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.notification.NotificationController;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
|
|
@ -71,6 +69,9 @@ import io.reactivex.Observable;
|
|||
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 javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsFragment
|
||||
|
|
@ -79,7 +80,7 @@ public class ContributionsFragment
|
|||
OnBackStackChangedListener,
|
||||
LocationUpdateListener,
|
||||
MediaDetailProvider,
|
||||
ICampaignsView, ContributionsContract.View, Callback {
|
||||
ICampaignsView, ContributionsContract.View, Callback{
|
||||
@Inject @Named("default_preferences") JsonKvStore store;
|
||||
@Inject NearbyController nearbyController;
|
||||
@Inject OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
|
|
@ -87,8 +88,6 @@ public class ContributionsFragment
|
|||
@Inject LocationServiceManager locationManager;
|
||||
@Inject NotificationController notificationController;
|
||||
|
||||
private UploadService uploadService;
|
||||
private boolean isUploadServiceConnected;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
private ContributionsListFragment contributionsListFragment;
|
||||
|
|
@ -99,6 +98,7 @@ public class ContributionsFragment
|
|||
@BindView(R.id.card_view_nearby) public NearbyNotificationCardView nearbyNotificationCardView;
|
||||
@BindView(R.id.campaigns_view) CampaignView campaignView;
|
||||
@BindView(R.id.limited_connection_enabled_layout) LinearLayout limitedConnectionEnabledLayout;
|
||||
@BindView(R.id.limited_connection_description_text_view) TextView limitedConnectionDescriptionTextView;
|
||||
|
||||
@Inject ContributionsPresenter contributionsPresenter;
|
||||
|
||||
|
|
@ -121,32 +121,7 @@ public class ContributionsFragment
|
|||
return fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since we will need to use parent activity on onAuthCookieAcquired, we have to wait
|
||||
* fragment to be attached. Latch will be responsible for this sync.
|
||||
*/
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
uploadService = (UploadService) ((UploadService.UploadServiceLocalBinder) binder)
|
||||
.getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
// this should never happen
|
||||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||
isUploadServiceConnected = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(final ComponentName name) {
|
||||
isUploadServiceConnected = false;
|
||||
}
|
||||
};
|
||||
private boolean shouldShowMediaDetailsFragment;
|
||||
private boolean isAuthCookieAcquired;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
|
|
@ -183,6 +158,9 @@ public class ContributionsFragment
|
|||
if(shouldShowMediaDetailsFragment){
|
||||
showMediaDetailPagerFragment();
|
||||
}else{
|
||||
if (mediaDetailPagerFragment != null) {
|
||||
removeFragment(mediaDetailPagerFragment);
|
||||
}
|
||||
showContributionsListFragment();
|
||||
}
|
||||
|
||||
|
|
@ -190,6 +168,7 @@ public class ContributionsFragment
|
|||
&& sessionManager.getCurrentAccount() != null) {
|
||||
setUploadCount();
|
||||
}
|
||||
limitedConnectionEnabledLayout.setOnClickListener(toggleDescriptionListener);
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
}
|
||||
|
|
@ -265,7 +244,6 @@ public class ContributionsFragment
|
|||
until fragment life time ends.
|
||||
*/
|
||||
if (!isFragmentAttachedBefore && getActivity() != null) {
|
||||
onAuthCookieAcquired();
|
||||
isFragmentAttachedBefore = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -305,19 +283,6 @@ public class ContributionsFragment
|
|||
fetchCampaigns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when onAuthCookieAcquired is called on authenticated parent activity
|
||||
*/
|
||||
void onAuthCookieAcquired() {
|
||||
// Since we call onAuthCookieAcquired method from onAttach, isAdded is still false. So don't use it
|
||||
isAuthCookieAcquired=true;
|
||||
if (getActivity() != null) { // If fragment is attached to parent activity
|
||||
getActivity().bindService(getUploadServiceIntent(), uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initFragments() {
|
||||
if (null == contributionsListFragment) {
|
||||
contributionsListFragment = new ContributionsListFragment();
|
||||
|
|
@ -374,7 +339,6 @@ public class ContributionsFragment
|
|||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
|
||||
public Intent getUploadServiceIntent(){
|
||||
Intent intent = new Intent(getActivity(), UploadService.class);
|
||||
intent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
|
|
@ -424,19 +388,22 @@ public class ContributionsFragment
|
|||
showNearbyCardPermissionRationale();
|
||||
});
|
||||
|
||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||
checkPermissionsAndShowNearbyCardView();
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||
// Notification cards should only be seen on contributions list, not in media details
|
||||
if (mediaDetailPagerFragment == null) {
|
||||
if (store.getBoolean("displayNearbyCardView", true)) {
|
||||
checkPermissionsAndShowNearbyCardView();
|
||||
if (nearbyNotificationCardView.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
nearbyNotificationCardView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Hide nearby notification card view if related shared preferences is false
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Hide nearby notification card view if related shared preferences is false
|
||||
nearbyNotificationCardView.setVisibility(View.GONE);
|
||||
setNotificationCount();
|
||||
fetchCampaigns();
|
||||
}
|
||||
|
||||
setNotificationCount();
|
||||
fetchCampaigns();
|
||||
}
|
||||
|
||||
private void checkPermissionsAndShowNearbyCardView() {
|
||||
|
|
@ -444,6 +411,7 @@ public class ContributionsFragment
|
|||
onLocationPermissionGranted();
|
||||
} else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
&& store.getBoolean("displayLocationPermissionForCardView", true)
|
||||
&& !store.getBoolean("doNotAskForLocationPermission", false)
|
||||
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
|
||||
nearbyNotificationCardView.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
|
||||
showNearbyCardPermissionRationale();
|
||||
|
|
@ -476,6 +444,7 @@ public class ContributionsFragment
|
|||
|
||||
private void displayYouWontSeeNearbyMessage() {
|
||||
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.unable_to_display_nearest_place));
|
||||
store.putBoolean("doNotAskForLocationPermission", true);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -512,13 +481,6 @@ public class ContributionsFragment
|
|||
locationManager.unregisterLocationManager();
|
||||
locationManager.removeLocationListener(this);
|
||||
super.onDestroy();
|
||||
|
||||
if (isUploadServiceConnected) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().unbindService(uploadServiceConnection);
|
||||
isUploadServiceConnected = false;
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException | IllegalStateException exception) {
|
||||
Timber.e(exception);
|
||||
}
|
||||
|
|
@ -564,6 +526,9 @@ public class ContributionsFragment
|
|||
if (store.getBoolean("displayCampaignsCardView", true)) {
|
||||
presenter.getCampaigns();
|
||||
}
|
||||
else{
|
||||
campaignView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void showMessage(String message) {
|
||||
|
|
@ -578,7 +543,6 @@ public class ContributionsFragment
|
|||
|
||||
@Override public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
isUploadServiceConnected = false;
|
||||
presenter.onDetachView();
|
||||
}
|
||||
|
||||
|
|
@ -597,8 +561,9 @@ public class ContributionsFragment
|
|||
@Override
|
||||
public void retryUpload(Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE && null != uploadService) {
|
||||
uploadService.queue(contribution);
|
||||
if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE) {
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
contributionsPresenter.saveContribution(contribution);
|
||||
Timber.d("Restarting for %s", contribution.toString());
|
||||
} else {
|
||||
Timber.d("Skipping re-upload for non-failed %s", contribution.toString());
|
||||
|
|
@ -615,7 +580,21 @@ public class ContributionsFragment
|
|||
*/
|
||||
@Override
|
||||
public void pauseUpload(Contribution contribution) {
|
||||
uploadService.pauseUpload(contribution);
|
||||
//Pause the upload in the global singleton
|
||||
CommonsApplication.pauseUploads.put(contribution.getPageId(), true);
|
||||
//Retain the paused state in DB
|
||||
contribution.setState(STATE_PAUSED);
|
||||
contributionsPresenter.saveContribution(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the viewpager that number of items have changed.
|
||||
*/
|
||||
@Override
|
||||
public void viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetailPagerFragment != null) {
|
||||
mediaDetailPagerFragment.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -665,5 +644,41 @@ public class ContributionsFragment
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Getter for mediaDetailPagerFragment
|
||||
public MediaDetailPagerFragment getMediaDetailPagerFragment() {
|
||||
return mediaDetailPagerFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
@Override
|
||||
public void refreshNominatedMedia(int index) {
|
||||
if(mediaDetailPagerFragment != null && !contributionsListFragment.isVisible()) {
|
||||
removeFragment(mediaDetailPagerFragment);
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment(false, true);
|
||||
mediaDetailPagerFragment.showImage(index);
|
||||
showMediaDetailPagerFragment();
|
||||
}
|
||||
}
|
||||
|
||||
// click listener to toggle description that means uses can press the limited connection
|
||||
// banner and description will hide. Tap again to show description.
|
||||
private View.OnClickListener toggleDescriptionListener = new View.OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
View view2 = limitedConnectionDescriptionTextView;
|
||||
if (view2.getVisibility() == View.GONE) {
|
||||
view2.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
view2.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
|
@ -24,6 +25,7 @@ import androidx.recyclerview.widget.GridLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver;
|
||||
import androidx.recyclerview.widget.RecyclerView.ItemAnimator;
|
||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener;
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
|
@ -161,6 +163,60 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever items in the list have changed
|
||||
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
|
||||
*/
|
||||
@Override
|
||||
public void onItemRangeChanged(final int positionStart, final int itemCount) {
|
||||
super.onItemRangeChanged(positionStart, itemCount);
|
||||
callback.viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
//Fab close on touch outside (Scrolling or taping on item triggers this action).
|
||||
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() {
|
||||
|
||||
/**
|
||||
* Silently observe and/or take over touch events sent to the RecyclerView before
|
||||
* they are handled by either the RecyclerView itself or its child views.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (isFabOpen) {
|
||||
animateFAB(isFabOpen);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a touch event as part of a gesture that was claimed by returning true
|
||||
* from a previous call to {@link #onInterceptTouchEvent}.
|
||||
*
|
||||
* @param rv
|
||||
* @param e MotionEvent describing the touch event. All coordinates are in the
|
||||
* RecyclerView's coordinate system.
|
||||
*/
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
//required abstract method DO NOT DELETE
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child of RecyclerView does not want RecyclerView and its ancestors
|
||||
* to intercept touch events with {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
|
||||
*
|
||||
* @param disallowIntercept True if the child does not want the parent to intercept
|
||||
* touch events.
|
||||
*/
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
//required abstract method DO NOT DELETE
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -330,7 +386,10 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
|
||||
|
||||
public Media getMediaAtPosition(final int i) {
|
||||
return adapter.getContributionForPosition(i).getMedia();
|
||||
if(adapter.getContributionForPosition(i) != null) {
|
||||
return adapter.getContributionForPosition(i).getMedia();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getTotalMediaCount() {
|
||||
|
|
@ -368,5 +427,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
void showDetail(int position, boolean isWikipediaButtonDisplayed);
|
||||
|
||||
void pauseUpload(Contribution contribution);
|
||||
|
||||
// Notify the viewpager that number of items have changed.
|
||||
void viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.contributions;
|
|||
import androidx.paging.DataSource.Factory;
|
||||
import io.reactivex.Completable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -22,8 +21,8 @@ class ContributionsLocalDataSource {
|
|||
|
||||
@Inject
|
||||
public ContributionsLocalDataSource(
|
||||
@Named("default_preferences") JsonKvStore defaultKVStore,
|
||||
ContributionDao contributionDao) {
|
||||
@Named("default_preferences") final JsonKvStore defaultKVStore,
|
||||
final ContributionDao contributionDao) {
|
||||
this.defaultKVStore = defaultKVStore;
|
||||
this.contributionDao = contributionDao;
|
||||
}
|
||||
|
|
@ -31,14 +30,14 @@ class ContributionsLocalDataSource {
|
|||
/**
|
||||
* Fetch default number of contributions to be show, based on user preferences
|
||||
*/
|
||||
public String getString(String key) {
|
||||
public String getString(final String key) {
|
||||
return defaultKVStore.getString(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch default number of contributions to be show, based on user preferences
|
||||
*/
|
||||
public long getLong(String key) {
|
||||
public long getLong(final String key) {
|
||||
return defaultKVStore.getLong(key);
|
||||
}
|
||||
|
||||
|
|
@ -47,8 +46,8 @@ class ContributionsLocalDataSource {
|
|||
* @param uri
|
||||
* @return
|
||||
*/
|
||||
public Contribution getContributionWithFileName(String uri) {
|
||||
List<Contribution> contributionWithUri = contributionDao.getContributionWithTitle(uri);
|
||||
public Contribution getContributionWithFileName(final String uri) {
|
||||
final List<Contribution> contributionWithUri = contributionDao.getContributionWithTitle(uri);
|
||||
if(!contributionWithUri.isEmpty()){
|
||||
return contributionWithUri.get(0);
|
||||
}
|
||||
|
|
@ -60,7 +59,7 @@ class ContributionsLocalDataSource {
|
|||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
public Completable deleteContribution(Contribution contribution) {
|
||||
public Completable deleteContribution(final Contribution contribution) {
|
||||
return contributionDao.delete(contribution);
|
||||
}
|
||||
|
||||
|
|
@ -68,10 +67,10 @@ class ContributionsLocalDataSource {
|
|||
return contributionDao.fetchContributions();
|
||||
}
|
||||
|
||||
public Single<List<Long>> saveContributions(List<Contribution> contributions) {
|
||||
List<Contribution> contributionList = new ArrayList<>();
|
||||
for(Contribution contribution: contributions) {
|
||||
Contribution oldContribution = contributionDao.getContribution(contribution.getPageId());
|
||||
public Single<List<Long>> saveContributions(final List<Contribution> contributions) {
|
||||
final List<Contribution> contributionList = new ArrayList<>();
|
||||
for(final Contribution contribution: contributions) {
|
||||
final Contribution oldContribution = contributionDao.getContribution(contribution.getPageId());
|
||||
if(oldContribution != null) {
|
||||
contribution.setWikidataPlace(oldContribution.getWikidataPlace());
|
||||
}
|
||||
|
|
@ -80,11 +79,15 @@ class ContributionsLocalDataSource {
|
|||
return contributionDao.save(contributionList);
|
||||
}
|
||||
|
||||
public void set(String key, long value) {
|
||||
public Completable saveContributions(Contribution contribution) {
|
||||
return contributionDao.save(contribution);
|
||||
}
|
||||
|
||||
public void set(final String key, final long value) {
|
||||
defaultKVStore.putLong(key,value);
|
||||
}
|
||||
|
||||
public Completable updateContribution(Contribution contribution) {
|
||||
public Completable updateContribution(final Contribution contribution) {
|
||||
return contributionDao.update(contribution);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import fr.free.nrw.commons.MediaDataExtractor;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Action;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
|
|
@ -14,7 +22,6 @@ import javax.inject.Named;
|
|||
public class ContributionsPresenter implements UserActionListener {
|
||||
|
||||
private final ContributionsRepository repository;
|
||||
private final Scheduler mainThreadScheduler;
|
||||
private final Scheduler ioThreadScheduler;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
private ContributionsContract.View view;
|
||||
|
|
@ -23,9 +30,9 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
MediaDataExtractor mediaDataExtractor;
|
||||
|
||||
@Inject
|
||||
ContributionsPresenter(ContributionsRepository repository, @Named(CommonsApplicationModule.MAIN_THREAD) Scheduler mainThreadScheduler,@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
||||
ContributionsPresenter(ContributionsRepository repository,
|
||||
@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
||||
this.repository = repository;
|
||||
this.mainThreadScheduler=mainThreadScheduler;
|
||||
this.ioThreadScheduler=ioThreadScheduler;
|
||||
}
|
||||
|
||||
|
|
@ -57,4 +64,23 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the contribution's state in the databse, upon completion, trigger the workmanager to
|
||||
* process this contribution
|
||||
*
|
||||
* @param contribution
|
||||
*/
|
||||
@Override
|
||||
public void saveContribution(Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(() -> {
|
||||
WorkManager.getInstance(view.getContext().getApplicationContext())
|
||||
.enqueueUniqueWork(
|
||||
UploadWorker.class.getSimpleName(),
|
||||
ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,10 @@ public class ContributionsRepository {
|
|||
return localDataSource.saveContributions(contributions);
|
||||
}
|
||||
|
||||
public Completable save(Contribution contributions){
|
||||
return localDataSource.saveContributions(contributions);
|
||||
}
|
||||
|
||||
public void set(String key, long value) {
|
||||
localDataSource.set(key,value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -13,13 +15,15 @@ import androidx.annotation.Nullable;
|
|||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarkFragment;
|
||||
import fr.free.nrw.commons.category.CategoryImagesCallback;
|
||||
import fr.free.nrw.commons.explore.ExploreFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
|
|
@ -29,15 +33,15 @@ import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment;
|
|||
import fr.free.nrw.commons.navtab.NavTab;
|
||||
import fr.free.nrw.commons.navtab.NavTabLayout;
|
||||
import fr.free.nrw.commons.navtab.NavTabLoggedOut;
|
||||
import fr.free.nrw.commons.nearby.NearbyNotificationCardView;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment;
|
||||
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment.NearbyParentFragmentInstanceReadyCallback;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
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.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
|
@ -64,6 +68,7 @@ public class MainActivity extends BaseActivity
|
|||
private ExploreFragment exploreFragment;
|
||||
private BookmarkFragment bookmarkFragment;
|
||||
public ActiveFragment activeFragment;
|
||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||
|
||||
@Inject
|
||||
public LocationServiceManager locationManager;
|
||||
|
|
@ -107,6 +112,7 @@ public class MainActivity extends BaseActivity
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
loadLocale();
|
||||
setContentView(R.layout.main);
|
||||
ButterKnife.bind(this);
|
||||
setSupportActionBar(toolbar);
|
||||
|
|
@ -114,7 +120,7 @@ public class MainActivity extends BaseActivity
|
|||
onSupportNavigateUp();
|
||||
});
|
||||
if (applicationKvStore.getBoolean("login_skipped") == true) {
|
||||
setTitle(getString(R.string.explore_tab_title_mobile));
|
||||
setTitle(getString(R.string.navigation_item_explore));
|
||||
setUpLoggedOutPager();
|
||||
} else {
|
||||
if(savedInstanceState == null){
|
||||
|
|
@ -123,7 +129,6 @@ public class MainActivity extends BaseActivity
|
|||
loadFragment(ContributionsFragment.newInstance(),false);
|
||||
}
|
||||
setUpPager();
|
||||
initMain();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,13 +241,26 @@ public class MainActivity extends BaseActivity
|
|||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt("viewPagerCurrentItem", viewPager.getCurrentItem());
|
||||
outState.putString("activeFragment", activeFragment.name());
|
||||
}
|
||||
|
||||
private void initMain() {
|
||||
//Do not remove this, this triggers the sync service
|
||||
Intent uploadServiceIntent = new Intent(this, UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
startService(uploadServiceIntent);
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
String currentFragmentName = savedInstanceState.getString("activeFragment");
|
||||
if(currentFragmentName == ActiveFragment.CONTRIBUTIONS.name()) {
|
||||
setTitle(getString(R.string.contributions_fragment));
|
||||
loadFragment(ContributionsFragment.newInstance(),false);
|
||||
}else if(currentFragmentName == ActiveFragment.NEARBY.name()) {
|
||||
setTitle(getString(R.string.nearby_fragment));
|
||||
loadFragment(NearbyParentFragment.newInstance(),false);
|
||||
}else if(currentFragmentName == ActiveFragment.EXPLORE.name()) {
|
||||
setTitle(getString(R.string.navigation_item_explore));
|
||||
loadFragment(ExploreFragment.newInstance(),false);
|
||||
}else if(currentFragmentName == ActiveFragment.BOOKMARK.name()) {
|
||||
setTitle(getString(R.string.favorites));
|
||||
loadFragment(BookmarkFragment.newInstance(),false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -298,13 +316,10 @@ public class MainActivity extends BaseActivity
|
|||
viewUtilWrapper
|
||||
.showShortToast(getBaseContext(), getString(R.string.limited_connection_enabled));
|
||||
} else {
|
||||
Intent intent = new Intent(this, UploadService.class);
|
||||
intent.setAction(UploadService.PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS);
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
|
||||
UploadWorker.class.getSimpleName(),
|
||||
ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
|
||||
|
||||
viewUtilWrapper
|
||||
.showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled));
|
||||
}
|
||||
|
|
@ -350,4 +365,14 @@ public class MainActivity extends BaseActivity
|
|||
BOOKMARK,
|
||||
MORE
|
||||
}
|
||||
|
||||
/**
|
||||
* Load default language in onCreate from SharedPreferences
|
||||
*/
|
||||
private void loadLocale(){
|
||||
final SharedPreferences preferences = getSharedPreferences("Settings", Activity.MODE_PRIVATE);
|
||||
final String language = preferences.getString("language", "");
|
||||
final SettingsFragment settingsFragment = new SettingsFragment();
|
||||
settingsFragment.setLocale(this, language);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() {
|
|||
callback?.onConfirmClicked(contribution, checkbox_copy_wikicode.isChecked)
|
||||
}
|
||||
|
||||
dialog!!.window.setSoftInputMode(
|
||||
dialog!!.window?.setSoftInputMode(
|
||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ public class DBOpenHelper extends SQLiteOpenHelper {
|
|||
public void deleteTable(SQLiteDatabase db, String tableName) {
|
||||
try {
|
||||
db.execSQL(String.format(DROP_TABLE_STATEMENT, tableName));
|
||||
onCreate(db);
|
||||
} catch (SQLiteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,16 @@ import androidx.room.RoomDatabase
|
|||
import androidx.room.TypeConverters
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import fr.free.nrw.commons.contributions.ContributionDao
|
||||
import fr.free.nrw.commons.upload.depicts.Depicts
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsDao
|
||||
|
||||
/**
|
||||
* The database for accessing the respective DAOs
|
||||
*
|
||||
*/
|
||||
@Database(entities = [Contribution::class], version = 7, exportSchema = false)
|
||||
@Database(entities = [Contribution::class,Depicts::class], version = 7, exportSchema = false)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun contributionDao(): ContributionDao
|
||||
abstract fun DepictsDao (): DepictsDao;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,27 @@ public class Converters {
|
|||
return ApplicationlessInjection.getInstance(CommonsApplication.getInstance()).getCommonsApplicationComponent().gson();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert DepictedItem object to string
|
||||
* input Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* output Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
public static String depictsItemToString(DepictedItem objects) {
|
||||
return writeObjectToString(objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert string to DepictedItem object
|
||||
* output Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* input Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
public static DepictedItem stringToDepicts(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<DepictedItem>() {
|
||||
});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Date fromTimestamp(Long value) {
|
||||
return value == null ? null : new Date(value);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import fr.free.nrw.commons.WelcomeActivity;
|
|||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.auth.SignupActivity;
|
||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.explore.SearchActivity;
|
||||
|
|
@ -47,9 +46,6 @@ public abstract class ActivityBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract NotificationActivity bindNotificationActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract UploadActivity bindUploadActivity();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import android.content.Context;
|
|||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import dagger.android.HasAndroidInjector;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.android.AndroidInjector;
|
||||
|
|
@ -25,6 +26,7 @@ import dagger.android.support.HasSupportFragmentInjector;
|
|||
*/
|
||||
public class ApplicationlessInjection
|
||||
implements
|
||||
HasAndroidInjector,
|
||||
HasActivityInjector,
|
||||
HasFragmentInjector,
|
||||
HasSupportFragmentInjector,
|
||||
|
|
@ -34,6 +36,7 @@ public class ApplicationlessInjection
|
|||
|
||||
private static ApplicationlessInjection instance = null;
|
||||
|
||||
@Inject DispatchingAndroidInjector<Object> androidInjector;
|
||||
@Inject DispatchingAndroidInjector<Activity> activityInjector;
|
||||
@Inject DispatchingAndroidInjector<BroadcastReceiver> broadcastReceiverInjector;
|
||||
@Inject DispatchingAndroidInjector<android.app.Fragment> fragmentInjector;
|
||||
|
|
@ -49,6 +52,11 @@ public class ApplicationlessInjection
|
|||
commonsApplicationComponent.inject(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AndroidInjector<Object> androidInjector() {
|
||||
return androidInjector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatchingAndroidInjector<Activity> activityInjector() {
|
||||
return activityInjector;
|
||||
|
|
@ -94,5 +102,4 @@ public class ApplicationlessInjection
|
|||
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import fr.free.nrw.commons.explore.categories.CategoriesModule;
|
|||
import fr.free.nrw.commons.navtab.MoreBottomSheetFragment;
|
||||
import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment;
|
||||
import fr.free.nrw.commons.navtab.NavTabLayout;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
|
@ -47,6 +48,8 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
|||
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
||||
void inject(CommonsApplication application);
|
||||
|
||||
void inject(UploadWorker worker);
|
||||
|
||||
void inject(LoginActivity activity);
|
||||
|
||||
void inject(SettingsFragment fragment);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadController;
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsDao;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||
|
|
@ -165,7 +166,7 @@ public class CommonsApplicationModule {
|
|||
@Provides
|
||||
public UploadController providesUploadController(SessionManager sessionManager,
|
||||
@Named("default_preferences") JsonKvStore kvStore,
|
||||
Context context) {
|
||||
Context context, ContributionDao contributionDao) {
|
||||
return new UploadController(sessionManager, context, kvStore);
|
||||
}
|
||||
|
||||
|
|
@ -242,6 +243,14 @@ public class CommonsApplicationModule {
|
|||
return appDatabase.contributionDao();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference of DepictsDao class
|
||||
*/
|
||||
@Provides
|
||||
public DepictsDao providesDepictDao(AppDatabase appDatabase) {
|
||||
return appDatabase.DepictsDao();
|
||||
}
|
||||
|
||||
@Provides
|
||||
public ContentResolver providesContentResolver(Context context){
|
||||
return context.getContentResolver();
|
||||
|
|
|
|||
|
|
@ -216,9 +216,8 @@ public class NetworkingModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
public PageEditClient provideCommonsPageEditClient(@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient,
|
||||
@Named("commons-page-edit-service") PageEditInterface pageEditInterface,
|
||||
@Named("commons-service") Service service) {
|
||||
return new PageEditClient(csrfTokenClient, pageEditInterface, service);
|
||||
@Named("commons-page-edit-service") PageEditInterface pageEditInterface) {
|
||||
return new PageEditClient(csrfTokenClient, pageEditInterface);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.di;
|
|||
import dagger.Module;
|
||||
import dagger.android.ContributesAndroidInjector;
|
||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticatorService;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
|
||||
/**
|
||||
* This Class Represents the Module for dependency injection (using dagger)
|
||||
|
|
@ -14,9 +13,6 @@ import fr.free.nrw.commons.upload.UploadService;
|
|||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public abstract class ServiceBuilderModule {
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract UploadService bindUploadService();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract WikiAccountAuthenticatorService bindWikiAccountAuthenticatorService();
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,12 @@ import android.view.ViewGroup;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
|
|
@ -30,11 +31,15 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.viewPager)
|
||||
ViewPager viewPager;
|
||||
ParentViewPager viewPager;
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
private ExploreListRootFragment featuredRootFragment;
|
||||
private ExploreListRootFragment mobileRootFragment;
|
||||
|
||||
public void setScroll(boolean canScroll){
|
||||
viewPager.setCanScroll(canScroll);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ExploreFragment newInstance() {
|
||||
ExploreFragment fragment = new ExploreFragment();
|
||||
|
|
@ -83,16 +88,26 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
|
|||
fragmentList.add(mobileRootFragment);
|
||||
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase());
|
||||
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
viewPagerAdapter.setTabData(fragmentList, titleList);
|
||||
viewPagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void onBackPressed() {
|
||||
if (tabLayout.getSelectedTabPosition() == 0) {
|
||||
featuredRootFragment.backPressed();
|
||||
if(featuredRootFragment.backPressed()){
|
||||
// Event is handled by the Fragment we need not do anything.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
mobileRootFragment.backPressed();
|
||||
if(mobileRootFragment.backPressed()){
|
||||
// Event is handled by the Fragment we need not do anything.
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Event is not handled by the fragment ( i.e performed back action ) therefore change action bar.
|
||||
((BaseActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package fr.free.nrw.commons.explore;
|
|||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -10,20 +10,16 @@ import android.widget.FrameLayout;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
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.ContributionsListFragment;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
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;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
|
||||
public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
|
||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
|
@ -80,7 +76,7 @@ public class ExploreListRootFragment extends CommonsDaggerSupportFragment implem
|
|||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}else if (!fragment.isAdded() && otherFragment != null ) {
|
||||
} else if (!fragment.isAdded() && otherFragment != null ) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.hide(otherFragment)
|
||||
|
|
@ -113,7 +109,6 @@ public class ExploreListRootFragment extends CommonsDaggerSupportFragment implem
|
|||
|
||||
@Override
|
||||
public void onMediaClicked(int position) {
|
||||
Log.d("deneme8","on media clicked");
|
||||
container.setVisibility(View.VISIBLE);
|
||||
((ExploreFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
|
|
@ -157,6 +152,19 @@ public class ExploreListRootFragment extends CommonsDaggerSupportFragment implem
|
|||
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.
|
||||
|
|
@ -168,15 +176,26 @@ public class ExploreListRootFragment extends CommonsDaggerSupportFragment implem
|
|||
}
|
||||
}
|
||||
|
||||
public void backPressed() {
|
||||
/**
|
||||
* 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()) {
|
||||
// todo add get list fragment
|
||||
if(mediaDetails.backButtonClicked()) {
|
||||
// MediaDetails handled the event no further action required.
|
||||
return true;
|
||||
}
|
||||
((ExploreFragment)getParentFragment()).tabLayout.setVisibility(View.VISIBLE);
|
||||
removeFragment(mediaDetails);
|
||||
((ExploreFragment) getParentFragment()).setScroll(true);
|
||||
setFragment(listFragment, mediaDetails);
|
||||
} else {
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ 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.explore.categories.search.SearchCategoryFragment;
|
||||
import fr.free.nrw.commons.explore.depictions.search.SearchDepictionsFragment;
|
||||
|
|
@ -182,6 +183,19 @@ public class SearchActivity extends BaseActivity
|
|||
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.
|
||||
|
|
@ -244,6 +258,12 @@ public class SearchActivity extends BaseActivity
|
|||
@Override
|
||||
public void onBackPressed() {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 1){
|
||||
|
||||
// the back press is handled by the mediaDetails , no further action required.
|
||||
if(mediaDetails.backButtonClicked()){
|
||||
return;
|
||||
}
|
||||
|
||||
// back to search so show search toolbar and hide navigation toolbar
|
||||
searchView.setVisibility(View.VISIBLE);//set the searchview
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,14 @@ 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;
|
||||
|
|
@ -13,11 +18,12 @@ import butterknife.ButterKnife;
|
|||
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.category.CategoryImagesCallback;
|
||||
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.explore.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
|
|
@ -43,6 +49,8 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
|
|||
TabLayout tabLayout;
|
||||
@BindView(R.id.viewPager)
|
||||
ViewPager viewPager;
|
||||
@BindView(R.id.toolbar)
|
||||
Toolbar toolbar;
|
||||
|
||||
ViewPagerAdapter viewPagerAdapter;
|
||||
|
||||
|
|
@ -56,6 +64,8 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
|
|||
viewPager.setAdapter(viewPagerAdapter);
|
||||
viewPager.setOffscreenPageLimit(2);
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
setTabs();
|
||||
setPageTitle();
|
||||
}
|
||||
|
|
@ -153,7 +163,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
|
|||
@Override
|
||||
public void onBackPressed() {
|
||||
if (supportFragmentManager.getBackStackEntryCount() == 1){
|
||||
// back to search so show search toolbar and hide navigation toolbar
|
||||
|
||||
// back pressed is handled by the mediaDetails , no further action required.
|
||||
if(mediaDetailPagerFragment.backButtonClicked()){
|
||||
return;
|
||||
}
|
||||
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
viewPager.setVisibility(View.VISIBLE);
|
||||
mediaContainer.setVisibility(View.GONE);
|
||||
|
|
@ -176,6 +191,19 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
|
|||
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.
|
||||
*
|
||||
|
|
@ -188,4 +216,34 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
|
|||
intent.putExtra("entityId", depictedItem.getId());
|
||||
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);
|
||||
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 android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,4 +55,14 @@ abstract class PageableMediaFragment : BasePagingFragment<Media>(), MediaDetailP
|
|||
override fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
|
||||
|
||||
override fun getContributionStateAt(position: Int) = null
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
override fun refreshNominatedMedia(index: Int) {
|
||||
activity?.onBackPressed()
|
||||
categoryImagesCallback.onMediaClicked(index)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ abstract class BasePagingFragment<T> : CommonsDaggerSupportFragment(),
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on configuration change, update the spanCount according to the orientation state.
|
||||
*/
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
paginatedSearchResultsList.apply {
|
||||
layoutManager = GridLayoutManager(context, if (isPortrait) 1 else 2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun observePagingResults(searchResults: LiveData<PagedList<T>>) {
|
||||
this.searchResults?.removeObservers(viewLifecycleOwner)
|
||||
this.searchResults = searchResults
|
||||
|
|
|
|||
|
|
@ -124,8 +124,8 @@ public class UploadableFile implements Parcelable {
|
|||
private DateTimeWithSource getDateTimeFromExif() {
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
|
||||
@SuppressLint("RestrictedApi") long dateTime = exif.getDateTime();
|
||||
if (dateTime != -1) {
|
||||
@SuppressLint("RestrictedApi") Long dateTime = exif.getDateTime();
|
||||
if(dateTime != null){
|
||||
Date date = new Date(dateTime);
|
||||
return new DateTimeWithSource(date, DateTimeWithSource.EXIF_SOURCE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
package fr.free.nrw.commons.location;
|
||||
|
||||
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
|
||||
import android.Manifest.permission;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
|
@ -37,11 +42,42 @@ public class LocationServiceManager implements LocationListener {
|
|||
|
||||
public LatLng getLastLocation() {
|
||||
if (lastLocation == null) {
|
||||
return null;
|
||||
lastLocation = getLastKnownLocation();
|
||||
if(lastLocation != null) {
|
||||
return LatLng.from(lastLocation);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return LatLng.from(lastLocation);
|
||||
}
|
||||
|
||||
private Location getLastKnownLocation() {
|
||||
List<String> providers = locationManager.getProviders(true);
|
||||
Location bestLocation = null;
|
||||
for (String provider : providers) {
|
||||
Location l=null;
|
||||
if (ActivityCompat.checkSelfPermission(getApplicationContext(), permission.ACCESS_FINE_LOCATION)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
&& ActivityCompat.checkSelfPermission(getApplicationContext(), permission.ACCESS_COARSE_LOCATION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
l = locationManager.getLastKnownLocation(provider);
|
||||
}
|
||||
if (l == null) {
|
||||
continue;
|
||||
}
|
||||
if (bestLocation == null
|
||||
|| l.getAccuracy() < bestLocation.getAccuracy()) {
|
||||
bestLocation = l;
|
||||
}
|
||||
}
|
||||
if (bestLocation == null) {
|
||||
return null;
|
||||
}
|
||||
return bestLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a LocationManager to listen for current location.
|
||||
*/
|
||||
|
|
@ -58,11 +94,10 @@ public class LocationServiceManager implements LocationListener {
|
|||
* @param locationProvider the location provider
|
||||
* @return true if successful
|
||||
*/
|
||||
private boolean requestLocationUpdatesFromProvider(String locationProvider) {
|
||||
public boolean requestLocationUpdatesFromProvider(String locationProvider) {
|
||||
try {
|
||||
// If both providers are not available
|
||||
if (locationManager == null || !(locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER))
|
||||
|| !(locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER))) {
|
||||
if (locationManager == null || !(locationManager.getAllProviders().contains(locationProvider))) {
|
||||
return false;
|
||||
}
|
||||
locationManager.requestLocationUpdates(locationProvider,
|
||||
|
|
|
|||
|
|
@ -149,6 +149,14 @@ class MediaClient @Inject constructor(
|
|||
resetContinuation(CATEGORY_CONTINUATION_PREFIX, category)
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the resetUserContinuation method
|
||||
*
|
||||
* @param userName the username
|
||||
*/
|
||||
fun resetUserNameContinuation(userName: String) =
|
||||
resetUserContinuation("user_", userName)
|
||||
|
||||
override fun responseMapper(
|
||||
networkResult: Single<MwQueryResponse>,
|
||||
key: String?
|
||||
|
|
|
|||
|
|
@ -16,14 +16,18 @@ import android.os.Bundle;
|
|||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnKeyListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
|
|
@ -51,6 +55,7 @@ import fr.free.nrw.commons.MediaDataExtractor;
|
|||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.AccountUtil;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.category.CategoryClient;
|
||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.category.CategoryEditHelper;
|
||||
|
|
@ -61,6 +66,7 @@ import fr.free.nrw.commons.delete.DeleteHelper;
|
|||
import fr.free.nrw.commons.delete.ReasonBuilder;
|
||||
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.nearby.Label;
|
||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
|
|
@ -74,6 +80,7 @@ import java.util.Locale;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.wikipedia.util.DateUtil;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -87,6 +94,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
private int index;
|
||||
private boolean isDeleted = false;
|
||||
private boolean isWikipediaButtonDisplayed;
|
||||
private Callback callback;
|
||||
|
||||
|
||||
public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage, boolean isWikipediaButtonDisplayed) {
|
||||
|
|
@ -105,6 +113,9 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
return mf;
|
||||
}
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
MediaDataExtractor mediaDataExtractor;
|
||||
@Inject
|
||||
|
|
@ -117,13 +128,16 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
ViewUtilWrapper viewUtil;
|
||||
@Inject
|
||||
CategoryClient categoryClient;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
JsonKvStore applicationKvStore;
|
||||
|
||||
private int initialListTop = 0;
|
||||
|
||||
@BindView(R.id.mediaDetailFrameLayout)
|
||||
FrameLayout frameLayout;
|
||||
@BindView(R.id.mediaDetailImageView)
|
||||
SimpleDraweeView image;
|
||||
@BindView(R.id.mediaDetailImageViewLandscape)
|
||||
SimpleDraweeView imageLandscape;
|
||||
@BindView(R.id.mediaDetailImageViewSpacer)
|
||||
LinearLayout imageSpacer;
|
||||
@BindView(R.id.mediaDetailTitle)
|
||||
|
|
@ -182,6 +196,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
TextView existingCategories;
|
||||
@BindView(R.id.no_results_found)
|
||||
TextView noResultsFound;
|
||||
@BindView(R.id.progressBarDeletion)
|
||||
ProgressBar progressBarDeletion;
|
||||
|
||||
private ArrayList<String> categoryNames = new ArrayList<>();
|
||||
private String categorySearchQuery;
|
||||
|
|
@ -202,6 +218,20 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
private Media media;
|
||||
private ArrayList<String> reasonList;
|
||||
|
||||
/**
|
||||
* Height stores the height of the frame layout as soon as it is initialised and updates itself on
|
||||
* configuration changes.
|
||||
* Used to adjust aspect ratio of image when length of the image is too large.
|
||||
*/
|
||||
private int frameLayoutHeight;
|
||||
|
||||
/**
|
||||
* Minimum height of the metadata, in pixels.
|
||||
* Images with a very narrow aspect ratio will be reduced so that the metadata information panel always has at least this height.
|
||||
*/
|
||||
private int minimumHeightOfMetadata = 200;
|
||||
|
||||
final static String NOMINATING_FOR_DELETION_MEDIA = "Nominating for deletion %s";
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
|
|
@ -258,9 +288,36 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
authorLayout.setVisibility(GONE);
|
||||
}
|
||||
|
||||
if (!sessionManager.isUserLoggedIn()) {
|
||||
categoryEditButton.setVisibility(GONE);
|
||||
}
|
||||
|
||||
if(applicationKvStore.getBoolean("login_skipped")){
|
||||
delete.setVisibility(GONE);
|
||||
}
|
||||
/**
|
||||
* Gets the height of the frame layout as soon as the view is ready and updates aspect ratio
|
||||
* of the picture.
|
||||
*/
|
||||
view.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameLayoutHeight = frameLayout.getMeasuredHeight();
|
||||
updateAspectRatio(scrollView.getWidth());
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
if (getParentFragment() != null) {
|
||||
callback = (Callback) getParentFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@OnClick(R.id.mediaDetailImageViewSpacer)
|
||||
public void launchZoomActivity(View view) {
|
||||
if (media.getImageUrl() != null) {
|
||||
|
|
@ -288,8 +345,19 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
Label.valuesAsList()), categoryRecyclerView, categoryClient, this);
|
||||
categoryRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
categoryRecyclerView.setAdapter(categoryEditSearchRecyclerViewAdapter);
|
||||
// detail provider is null when fragment is shown in review activity
|
||||
if (detailProvider != null) {
|
||||
media = detailProvider.getMediaAtPosition(index);
|
||||
} else {
|
||||
media = getArguments().getParcelable("media");
|
||||
}
|
||||
|
||||
media = detailProvider.getMediaAtPosition(index);
|
||||
|
||||
if(media != null && applicationKvStore.getBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), false)) {
|
||||
enableProgressBar();
|
||||
}
|
||||
|
||||
scrollView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
|
|
@ -298,11 +366,10 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
return;
|
||||
}
|
||||
scrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
imageLandscape.setVisibility(VISIBLE);
|
||||
}
|
||||
oldWidthOfImageView = scrollView.getWidth();
|
||||
displayMediaDetails();
|
||||
if(media != null) {
|
||||
displayMediaDetails();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -315,6 +382,16 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
new OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
/**
|
||||
* We update the height of the frame layout as the configuration changes.
|
||||
*/
|
||||
frameLayout.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
frameLayoutHeight = frameLayout.getMeasuredHeight();
|
||||
updateAspectRatio(scrollView.getWidth());
|
||||
}
|
||||
});
|
||||
if (scrollView.getWidth() != oldWidthOfImageView) {
|
||||
if (newWidthOfImageView == 0) {
|
||||
newWidthOfImageView = scrollView.getWidth();
|
||||
|
|
@ -325,13 +402,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
}
|
||||
}
|
||||
);
|
||||
// check orientation
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
imageLandscape.setVisibility(VISIBLE);
|
||||
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
imageLandscape.setVisibility(GONE);
|
||||
}
|
||||
// ensuring correct aspect ratio for landscape mode
|
||||
// Ensuring correct aspect ratio for landscape mode
|
||||
if (heightVerifyingBoolean) {
|
||||
updateAspectRatio(newWidthOfImageView);
|
||||
heightVerifyingBoolean = false;
|
||||
|
|
@ -381,6 +452,10 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
|
||||
private void onDeletionPageExists(Boolean deletionPageExists) {
|
||||
if (deletionPageExists){
|
||||
if(applicationKvStore.getBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), false)) {
|
||||
applicationKvStore.remove(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()));
|
||||
progressBarDeletion.setVisibility(GONE);
|
||||
}
|
||||
delete.setVisibility(GONE);
|
||||
nominatedForDeletion.setVisibility(VISIBLE);
|
||||
} else if (!isCategoryImage) {
|
||||
|
|
@ -397,6 +472,12 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
* The imageSpacer is Basically a transparent overlay for the SimpleDraweeView
|
||||
* which holds the image to be displayed( moreover this image is out of
|
||||
* the scroll view )
|
||||
*
|
||||
*
|
||||
* If the image is sufficiently large i.e. the image height extends the view height, we reduce
|
||||
* the height and change the width to maintain the aspect ratio, otherwise image takes up the
|
||||
* total possible width and height is adjusted accordingly.
|
||||
*
|
||||
* @param scrollWidth the current width of the scrollView
|
||||
*/
|
||||
private void updateAspectRatio(int scrollWidth) {
|
||||
|
|
@ -404,11 +485,19 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
int finalHeight = (scrollWidth*imageInfoCache.getHeight()) / imageInfoCache.getWidth();
|
||||
ViewGroup.LayoutParams params = image.getLayoutParams();
|
||||
ViewGroup.LayoutParams spacerParams = imageSpacer.getLayoutParams();
|
||||
params.width = scrollWidth;
|
||||
if(finalHeight > frameLayoutHeight - minimumHeightOfMetadata) {
|
||||
|
||||
// Adjust the height and width of image.
|
||||
int temp = frameLayoutHeight - minimumHeightOfMetadata;
|
||||
params.width = (scrollWidth*temp) / finalHeight;
|
||||
finalHeight = temp;
|
||||
|
||||
}
|
||||
params.height = finalHeight;
|
||||
spacerParams.height = finalHeight;
|
||||
image.setLayoutParams(params);
|
||||
imageSpacer.setLayoutParams(spacerParams);
|
||||
imageLandscape.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -435,23 +524,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
image.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
||||
image.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
||||
|
||||
imageLandscape.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
||||
imageLandscape.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
||||
|
||||
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setLowResImageRequest(ImageRequest.fromUri(media.getThumbUrl()))
|
||||
.setImageRequest(ImageRequest.fromUri(media.getImageUrl()))
|
||||
.setControllerListener(aspectRatioListener)
|
||||
.setOldController(image.getController())
|
||||
.build();
|
||||
DraweeController controllerLandscape = Fresco.newDraweeControllerBuilder()
|
||||
.setLowResImageRequest(ImageRequest.fromUri(media.getThumbUrl()))
|
||||
.setImageRequest(ImageRequest.fromUri(media.getImageUrl()))
|
||||
.setControllerListener(aspectRatioListener)
|
||||
.setOldController(imageLandscape.getController())
|
||||
.build();
|
||||
image.setController(controller);
|
||||
imageLandscape.setController(controllerLandscape);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -598,15 +677,27 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
*/
|
||||
private void buildDepictionList(List<IdAndCaptions> idAndCaptions) {
|
||||
depictionContainer.removeAllViews();
|
||||
String locale = Locale.getDefault().getLanguage();
|
||||
for (IdAndCaptions idAndCaption : idAndCaptions) {
|
||||
depictionContainer.addView(buildDepictLabel(
|
||||
idAndCaption.getCaptions().values().iterator().next(),
|
||||
getDepictionCaption(idAndCaption, locale),
|
||||
idAndCaption.getId(),
|
||||
depictionContainer
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private String getDepictionCaption(IdAndCaptions idAndCaption, String locale) {
|
||||
//Check if the Depiction Caption is available in user's locale if not then check for english, else show any available.
|
||||
if(idAndCaption.getCaptions().get(locale) != null) {
|
||||
return idAndCaption.getCaptions().get(locale);
|
||||
}
|
||||
if(idAndCaption.getCaptions().get("en") != null) {
|
||||
return idAndCaption.getCaptions().get("en");
|
||||
}
|
||||
return idAndCaption.getCaptions().values().iterator().next();
|
||||
}
|
||||
|
||||
@OnClick(R.id.mediaDetailLicense)
|
||||
public void onMediaDetailLicenceClicked(){
|
||||
String url = media.getLicenseUrl();
|
||||
|
|
@ -645,6 +736,22 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
displayHideCategorySearch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the categoryEditContainer.
|
||||
* returns true after closing the categoryEditContainer if open, implying that event was handled.
|
||||
* else returns false
|
||||
* @return
|
||||
*/
|
||||
public boolean hideCategoryEditContainerIfOpen(){
|
||||
if (dummyCategoryEditContainer.getVisibility() == VISIBLE) {
|
||||
// editCategory is open, close it and return true as the event was handled.
|
||||
dummyCategoryEditContainer.setVisibility(GONE);
|
||||
return true;
|
||||
}
|
||||
// Event was not handled.
|
||||
return false;
|
||||
}
|
||||
|
||||
public void displayHideCategorySearch() {
|
||||
if (dummyCategoryEditContainer.getVisibility() != VISIBLE) {
|
||||
dummyCategoryEditContainer.setVisibility(VISIBLE);
|
||||
|
|
@ -722,7 +829,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
input.addTextChangedListener(new TextWatcher() {
|
||||
private void handleText() {
|
||||
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
if (input.getText().length() == 0) {
|
||||
if (input.getText().length() == 0 || isDeleted) {
|
||||
okButton.setEnabled(false);
|
||||
} else {
|
||||
okButton.setEnabled(true);
|
||||
|
|
@ -749,35 +856,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
|
||||
@SuppressLint("CheckResult")
|
||||
private void onDeleteClicked(Spinner spinner) {
|
||||
applicationKvStore.putBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), true);
|
||||
enableProgressBar();
|
||||
String reason = spinner.getSelectedItem().toString();
|
||||
Single<Boolean> resultSingle = reasonBuilder.getReason(media, reason)
|
||||
.flatMap(reasonString -> deleteHelper.makeDeletion(getContext(), media, reason));
|
||||
compositeDisposable.add(resultSingle
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(s -> {
|
||||
if (getActivity() != null) {
|
||||
isDeleted = true;
|
||||
enableDeleteButton(false);
|
||||
}
|
||||
}));
|
||||
|
||||
resultSingle
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(s -> {
|
||||
if(applicationKvStore.getBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), false)) {
|
||||
applicationKvStore.remove(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()));
|
||||
callback.nominatingForDeletion(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void onDeleteClickeddialogtext(String reason) {
|
||||
applicationKvStore.putBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), true);
|
||||
enableProgressBar();
|
||||
Single<Boolean> resultSingletext = reasonBuilder.getReason(media, reason)
|
||||
.flatMap(reasonString -> deleteHelper.makeDeletion(getContext(), media, reason));
|
||||
compositeDisposable.add(resultSingletext
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(s -> {
|
||||
if (getActivity() != null) {
|
||||
isDeleted = true;
|
||||
enableDeleteButton(false);
|
||||
}
|
||||
}));
|
||||
|
||||
resultSingletext
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(s -> {
|
||||
if(applicationKvStore.getBoolean(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()), false)) {
|
||||
applicationKvStore.remove(String.format(NOMINATING_FOR_DELETION_MEDIA, media.getImageUrl()));
|
||||
callback.nominatingForDeletion(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@OnClick(R.id.seeMore)
|
||||
|
|
@ -787,13 +896,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
}
|
||||
}
|
||||
|
||||
private void enableDeleteButton(boolean visibility) {
|
||||
delete.setEnabled(visibility);
|
||||
if (visibility) {
|
||||
delete.setTextColor(getResources().getColor(R.color.primaryTextColor));
|
||||
} else {
|
||||
delete.setTextColor(getResources().getColor(R.color.deleteButtonLight));
|
||||
}
|
||||
/**
|
||||
* Enable Progress Bar and Update delete button text.
|
||||
*/
|
||||
private void enableProgressBar() {
|
||||
progressBarDeletion.setVisibility(VISIBLE);
|
||||
delete.setText("Nominating for Deletion");
|
||||
isDeleted = true;
|
||||
}
|
||||
|
||||
private void rebuildCatList(List<String> categories) {
|
||||
|
|
@ -923,4 +1032,8 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void nominatingForDeletion(int index);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import android.view.MenuInflater;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
|
|
@ -35,11 +36,12 @@ import fr.free.nrw.commons.utils.ImageUtils;
|
|||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener {
|
||||
public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment implements ViewPager.OnPageChangeListener, MediaDetailFragment.Callback {
|
||||
|
||||
@Inject BookmarkPicturesDao bookmarkDao;
|
||||
|
||||
|
|
@ -61,6 +63,15 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
private boolean isFromFeaturedRootFragment;
|
||||
private int position;
|
||||
|
||||
private ArrayList<Integer> removedItems=new ArrayList<Integer>();
|
||||
|
||||
public void clearRemoved(){
|
||||
removedItems.clear();
|
||||
}
|
||||
public ArrayList<Integer> getRemovedItems() {
|
||||
return removedItems;
|
||||
}
|
||||
|
||||
public MediaDetailPagerFragment() {
|
||||
this(false, false);
|
||||
}
|
||||
|
|
@ -89,8 +100,6 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
pager.addOnPageChangeListener(this);
|
||||
|
||||
adapter = new MediaDetailAdapter(getChildFragmentManager());
|
||||
((BaseActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
pager.setAdapter(adapter);
|
||||
if (savedInstanceState != null) {
|
||||
final int pageNumber = savedInstanceState.getInt("current-page");
|
||||
|
|
@ -279,12 +288,27 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
menu.findItem(R.id.menu_set_as_wallpaper).setEnabled(false)
|
||||
.setVisible(false);
|
||||
}
|
||||
|
||||
if (!sessionManager.isUserLoggedIn()) {
|
||||
menu.findItem(R.id.menu_set_as_avatar).setVisible(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBookmarkState(MenuItem item) {
|
||||
boolean isBookmarked = bookmarkDao.findBookmark(bookmark);
|
||||
if(isBookmarked) {
|
||||
if(removedItems.contains(pager.getCurrentItem())) {
|
||||
removedItems.remove(new Integer(pager.getCurrentItem()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(!removedItems.contains(pager.getCurrentItem())) {
|
||||
removedItems.add(pager.getCurrentItem());
|
||||
}
|
||||
}
|
||||
int icon = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px : R.drawable.menu_ic_round_star_border_24px;
|
||||
item.setIcon(icon);
|
||||
}
|
||||
|
|
@ -351,17 +375,45 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the media is nominated for deletion
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
@Override
|
||||
public void nominatingForDeletion(int index) {
|
||||
provider.refreshNominatedMedia(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* backButtonClicked is called on a back event in the media details pager.
|
||||
* returns true after closing the categoryEditContainer if open, implying that event was handled.
|
||||
* else returns false
|
||||
* @return
|
||||
*/
|
||||
public boolean backButtonClicked(){
|
||||
return ((MediaDetailFragment)(adapter.getCurrentFragment())).hideCategoryEditContainerIfOpen();
|
||||
}
|
||||
|
||||
public interface MediaDetailProvider {
|
||||
Media getMediaAtPosition(int i);
|
||||
|
||||
int getTotalMediaCount();
|
||||
|
||||
Integer getContributionStateAt(int position);
|
||||
|
||||
// Reload media detail fragment once media is nominated
|
||||
void refreshNominatedMedia(int index);
|
||||
}
|
||||
|
||||
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
|
||||
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
/**
|
||||
* Keeps track of the current displayed fragment.
|
||||
*/
|
||||
private Fragment mCurrentFragment;
|
||||
|
||||
public MediaDetailAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
}
|
||||
|
|
@ -391,5 +443,30 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
}
|
||||
return provider.getTotalMediaCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently displayed fragment.
|
||||
* @return
|
||||
*/
|
||||
public Fragment getCurrentFragment() {
|
||||
return mCurrentFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to inform the adapter of which item is currently considered to be the "primary",
|
||||
* that is the one show to the user as the current page.
|
||||
* @param container
|
||||
* @param position
|
||||
* @param object
|
||||
*/
|
||||
@Override
|
||||
public void setPrimaryItem(@NonNull final ViewGroup container, final int position,
|
||||
@NonNull final Object object) {
|
||||
// Update the current fragment if changed
|
||||
if(getCurrentFragment() != object) {
|
||||
mCurrentFragment = ((Fragment)object);
|
||||
}
|
||||
super.setPrimaryItem(container, position, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@ import fr.free.nrw.commons.R;
|
|||
import fr.free.nrw.commons.WelcomeActivity;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||
import fr.free.nrw.commons.profile.ProfileActivity;
|
||||
import fr.free.nrw.commons.review.ReviewActivity;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
|
||||
|
|
@ -40,6 +42,11 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
|
|||
@BindView(R.id.more_profile)
|
||||
TextView moreProfile;
|
||||
|
||||
@BindView((R.id.more_peer_review)) TextView morePeerReview;
|
||||
|
||||
@Inject @Named("default_preferences")
|
||||
JsonKvStore store;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||
|
|
@ -47,6 +54,9 @@ public class MoreBottomSheetFragment extends BottomSheetDialogFragment {
|
|||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
final View view = inflater.inflate(R.layout.fragment_more_bottom_sheet, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
if(store.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED)){
|
||||
morePeerReview.setVisibility(View.GONE);
|
||||
}
|
||||
setUserName();
|
||||
return view;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarkFragment;
|
||||
import fr.free.nrw.commons.explore.ExploreFragment;
|
||||
import org.wikipedia.model.EnumCode;
|
||||
import org.wikipedia.model.EnumCodeMap;
|
||||
|
|
@ -19,6 +20,13 @@ public enum NavTabLoggedOut implements EnumCode {
|
|||
return ExploreFragment.newInstance();
|
||||
}
|
||||
},
|
||||
FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return BookmarkFragment.newInstance();
|
||||
}
|
||||
},
|
||||
MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
|
||||
@NonNull
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ public class NearbyController {
|
|||
nearbyBaseMarker.icon(IconFactory.getInstance(context)
|
||||
.fromBitmap(iconGreen));
|
||||
}
|
||||
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
||||
} else if (!place.exists) { // Means that the topic of the Wikidata item does not exist in the real world anymore, for instance it is a past event, or a place that was destroyed
|
||||
if (iconGrey != null) {
|
||||
nearbyBaseMarker.icon(IconFactory.getInstance(context)
|
||||
.fromBitmap(iconGrey));
|
||||
|
|
|
|||
|
|
@ -18,19 +18,23 @@ import timber.log.Timber;
|
|||
*/
|
||||
public class Place implements Parcelable {
|
||||
|
||||
public final String language;
|
||||
public final String name;
|
||||
private final Label label;
|
||||
private final String longDescription;
|
||||
public final LatLng location;
|
||||
private final String category;
|
||||
public final String pic;
|
||||
public final String destroyed;
|
||||
// exists boolean will tell whether the place exists or not,
|
||||
// For a place to be existing both destroyed and endTime property should be null but it is also not necessary for a non-existing place to have both properties either one property is enough (in such case that not given property will be considered as null).
|
||||
public final Boolean exists;
|
||||
|
||||
public String distance;
|
||||
public final Sitelinks siteLinks;
|
||||
|
||||
|
||||
public Place(String name, Label label, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, String destroyed) {
|
||||
public Place(String language,String name, Label label, String longDescription, LatLng location, String category, Sitelinks siteLinks, String pic, Boolean exists) {
|
||||
this.language = language;
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
this.longDescription = longDescription;
|
||||
|
|
@ -38,10 +42,10 @@ public class Place implements Parcelable {
|
|||
this.category = category;
|
||||
this.siteLinks = siteLinks;
|
||||
this.pic = (pic == null) ? "":pic;
|
||||
this.destroyed = (destroyed == null) ? "":destroyed;
|
||||
this.exists = exists;
|
||||
}
|
||||
|
||||
public Place(Parcel in) {
|
||||
this.language = in.readString();
|
||||
this.name = in.readString();
|
||||
this.label = (Label) in.readSerializable();
|
||||
this.longDescription = in.readString();
|
||||
|
|
@ -50,10 +54,9 @@ public class Place implements Parcelable {
|
|||
this.siteLinks = in.readParcelable(Sitelinks.class.getClassLoader());
|
||||
String picString = in.readString();
|
||||
this.pic = (picString == null) ? "":picString;
|
||||
String destroyedString = in.readString();
|
||||
this.destroyed = (destroyedString == null) ? "":destroyedString;
|
||||
String existString = in.readString();
|
||||
this.exists = Boolean.parseBoolean(existString);
|
||||
}
|
||||
|
||||
public static Place from(NearbyResultItem item) {
|
||||
String itemClass = item.getClassName().getValue();
|
||||
String classEntityId = "";
|
||||
|
|
@ -77,6 +80,7 @@ public class Place implements Parcelable {
|
|||
? " (" + description + ")" : "")
|
||||
: description);
|
||||
return new Place(
|
||||
item.getLabel().getLanguage(),
|
||||
item.getLabel().getValue(),
|
||||
Label.fromText(classEntityId), // list
|
||||
description, // description and label of Wikidata item
|
||||
|
|
@ -88,7 +92,16 @@ public class Place implements Parcelable {
|
|||
.setWikidataLink(item.getItem().getValue())
|
||||
.build(),
|
||||
item.getPic().getValue(),
|
||||
item.getDestroyed().getValue());
|
||||
// Checking if the place exists or not
|
||||
(item.getDestroyed().getValue() == "") && (item.getEndTime().getValue() == ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the language of the caption ie name.
|
||||
* @return language
|
||||
*/
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -192,6 +205,7 @@ public class Place implements Parcelable {
|
|||
public String toString() {
|
||||
return "Place{" +
|
||||
"name='" + name + '\'' +
|
||||
", lang='" + language + '\'' +
|
||||
", label='" + label + '\'' +
|
||||
", longDescription='" + longDescription + '\'' +
|
||||
", location='" + location + '\'' +
|
||||
|
|
@ -199,7 +213,7 @@ public class Place implements Parcelable {
|
|||
", distance='" + distance + '\'' +
|
||||
", siteLinks='" + siteLinks.toString() + '\'' +
|
||||
", pic='" + pic + '\'' +
|
||||
", destroyed='" + destroyed + '\'' +
|
||||
", exists='" + exists.toString() + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +224,7 @@ public class Place implements Parcelable {
|
|||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeString(language);
|
||||
dest.writeString(name);
|
||||
dest.writeSerializable(label);
|
||||
dest.writeString(longDescription);
|
||||
|
|
@ -217,7 +232,7 @@ public class Place implements Parcelable {
|
|||
dest.writeString(category);
|
||||
dest.writeParcelable(siteLinks, 0);
|
||||
dest.writeString(pic);
|
||||
dest.writeString(destroyed);
|
||||
dest.writeString(exists.toString());
|
||||
}
|
||||
|
||||
public static final Creator<Place> CREATOR = new Creator<Place>() {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ public interface NearbyParentFragmentContract {
|
|||
void listOptionMenuItemClicked();
|
||||
void populatePlaces(LatLng curlatLng);
|
||||
boolean isListBottomSheetExpanded();
|
||||
void checkPermissionsAndPerformAction(Runnable runnable);
|
||||
void checkPermissionsAndPerformAction();
|
||||
void displayLoginSkippedWarning();
|
||||
void setFABPlusAction(android.view.View.OnClickListener onClickListener);
|
||||
void setFABRecenterAction(android.view.View.OnClickListener onClickListener);
|
||||
|
|
@ -89,6 +89,7 @@ public interface NearbyParentFragmentContract {
|
|||
void detachView();
|
||||
|
||||
void setActionListeners(JsonKvStore applicationKvStore);
|
||||
void removeNearbyPreferences(JsonKvStore applicationKvStore);
|
||||
boolean backButtonClicked();
|
||||
void onCameraMove(com.mapbox.mapboxsdk.geometry.LatLng latLng);
|
||||
void filterByMarkerType(List<Label> selectedLabels, int state, boolean filterForPlaceState, boolean filterForAllNoneType);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
|||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
|
@ -16,6 +17,7 @@ import android.content.pm.PackageManager;
|
|||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.location.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Html;
|
||||
|
|
@ -27,6 +29,7 @@ import android.view.Menu;
|
|||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MenuItem.OnMenuItemClickListener;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
|
|
@ -36,13 +39,13 @@ import android.widget.ImageView;
|
|||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
|
|
@ -57,7 +60,7 @@ import com.google.android.material.chip.ChipGroup;
|
|||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
||||
import com.jakewharton.rxbinding3.appcompat.RxSearchView;
|
||||
import com.mapbox.mapboxsdk.annotations.Icon;
|
||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
|
|
@ -188,6 +191,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
private Animation rotate_forward;
|
||||
|
||||
private static final float ZOOM_LEVEL = 14f;
|
||||
private static final float ZOOM_OUT = 0f;
|
||||
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
private boolean isNetworkErrorOccurred;
|
||||
|
|
@ -203,6 +207,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004;
|
||||
|
||||
private boolean isMapBoxReady;
|
||||
private boolean isPermissionDenied;
|
||||
private boolean recenterToUserLocation;
|
||||
private MapboxMap mapBox;
|
||||
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
|
||||
private Marker currentLocationMarker;
|
||||
|
|
@ -256,6 +262,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
cameraMoveListener= () -> presenter.onCameraMove(mapBox.getCameraPosition().target);
|
||||
addCheckBoxCallback();
|
||||
presenter.attachView(this);
|
||||
isPermissionDenied = false;
|
||||
recenterToUserLocation = false;
|
||||
initRvNearbyList();
|
||||
initThemePreferences();
|
||||
mapView.onCreate(savedInstanceState);
|
||||
|
|
@ -278,7 +286,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
performMapReadyActions();
|
||||
final CameraPosition cameraPosition = new CameraPosition.Builder()
|
||||
.target(new LatLng(51.50550, -0.07520))
|
||||
.zoom(ZOOM_LEVEL)
|
||||
.zoom(ZOOM_OUT)
|
||||
.build();
|
||||
mapBoxMap.setCameraPosition(cameraPosition);
|
||||
|
||||
|
|
@ -339,28 +347,45 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
private void performMapReadyActions() {
|
||||
if (((MainActivity)getActivity()).activeFragment == ActiveFragment.NEARBY && isMapBoxReady) {
|
||||
checkPermissionsAndPerformAction(() -> {
|
||||
lastKnownLocation = locationManager.getLastLocation();
|
||||
fr.free.nrw.commons.location.LatLng target=lastFocusLocation;
|
||||
if(null==lastFocusLocation){
|
||||
target=lastKnownLocation;
|
||||
}
|
||||
if (lastKnownLocation != null) {
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(target)) // Sets the new camera position
|
||||
.zoom(ZOOM_LEVEL) // Same zoom level
|
||||
.build();
|
||||
mapBox.moveCamera(CameraUpdateFactory.newCameraPosition(position));
|
||||
} else {
|
||||
Toast.makeText(getContext(), getString(R.string.nearby_location_not_available), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
presenter.onMapReady();
|
||||
registerUnregisterLocationListener(false);
|
||||
if(!applicationKvStore.getBoolean("doNotAskForLocationPermission", false) ||
|
||||
PermissionUtils.hasPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)){
|
||||
checkPermissionsAndPerformAction();
|
||||
}else{
|
||||
isPermissionDenied = true;
|
||||
addOnCameraMoveListener();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void locationPermissionGranted() {
|
||||
isPermissionDenied = false;
|
||||
|
||||
applicationKvStore.putBoolean("doNotAskForLocationPermission", false);
|
||||
lastKnownLocation = locationManager.getLastLocation();
|
||||
fr.free.nrw.commons.location.LatLng target=lastFocusLocation;
|
||||
if(null==lastFocusLocation){
|
||||
target=lastKnownLocation;
|
||||
}
|
||||
if (lastKnownLocation != null) {
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(target)) // Sets the new camera position
|
||||
.zoom(ZOOM_LEVEL) // Same zoom level
|
||||
.build();
|
||||
mapBox.moveCamera(CameraUpdateFactory.newCameraPosition(position));
|
||||
}
|
||||
else if(locationManager.isGPSProviderEnabled()||locationManager.isNetworkProviderEnabled()){
|
||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER);
|
||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
||||
setProgressBarVisibility(true);
|
||||
}
|
||||
else {
|
||||
Toast.makeText(getContext(), getString(R.string.nearby_location_not_available), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
presenter.onMapReady();
|
||||
registerUnregisterLocationListener(false);
|
||||
addOnCameraMoveListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
|
@ -368,10 +393,31 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
presenter.attachView(this);
|
||||
registerNetworkReceiver();
|
||||
if (isResumed() && ((MainActivity)getActivity()).activeFragment == ActiveFragment.NEARBY) {
|
||||
startTheMap();
|
||||
if(!isPermissionDenied && !applicationKvStore.getBoolean("doNotAskForLocationPermission", false)){
|
||||
startTheMap();
|
||||
}else{
|
||||
startMapWithoutPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startMapWithoutPermission() {
|
||||
mapView.onStart();
|
||||
|
||||
applicationKvStore.putBoolean("doNotAskForLocationPermission", true);
|
||||
lastKnownLocation = new fr.free.nrw.commons.location.LatLng(51.50550,-0.07520,1f);
|
||||
final CameraPosition position = new CameraPosition.Builder()
|
||||
.target(LocationUtils.commonsLatLngToMapBoxLatLng(lastKnownLocation))
|
||||
.zoom(ZOOM_OUT)
|
||||
.build();
|
||||
if(mapBox != null){
|
||||
mapBox.moveCamera(CameraUpdateFactory.newCameraPosition(position));
|
||||
addOnCameraMoveListener();
|
||||
}
|
||||
presenter.onMapReady();
|
||||
removeCurrentLocationMarker();
|
||||
}
|
||||
|
||||
private void registerNetworkReceiver() {
|
||||
if (getActivity() != null) {
|
||||
getActivity().registerReceiver(broadcastReceiver, intentFilter);
|
||||
|
|
@ -426,6 +472,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
mapView.onDestroy();
|
||||
presenter.removeNearbyPreferences(applicationKvStore);
|
||||
}
|
||||
|
||||
private void initViews() {
|
||||
|
|
@ -438,14 +485,41 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates bottom sheet behaviours from bottom sheets, sets initial states and visibility
|
||||
* a) Creates bottom sheet behaviours from bottom sheets, sets initial states and visibility
|
||||
* b) Gets the touch event on the map to perform following actions:
|
||||
* if fab is open then close fab.
|
||||
* if bottom sheet details are expanded then collapse bottom sheet details.
|
||||
* if bottom sheet details are collapsed then hide the bottom sheet details.
|
||||
* if listBottomSheet is open then hide the list bottom sheet.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initBottomSheets() {
|
||||
bottomSheetListBehavior = BottomSheetBehavior.from(rlBottomSheet);
|
||||
bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails);
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetDetails.setVisibility(View.VISIBLE);
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
|
||||
mapView.setOnTouchListener((v, event) -> {
|
||||
|
||||
// Motion event is triggered two times on a touch event, one as ACTION_UP
|
||||
// and other as ACTION_DOWN, we only want one trigger per touch event.
|
||||
|
||||
if(event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (isFABsExpanded) {
|
||||
collapseFABs(true);
|
||||
} else if (bottomSheetDetailsBehavior.getState()
|
||||
== BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
} else if (bottomSheetDetailsBehavior.getState()
|
||||
== BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
} else if (isListBottomSheetExpanded()) {
|
||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public void initNearbyFilter() {
|
||||
|
|
@ -758,11 +832,14 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
@Override
|
||||
public void populatePlaces(final fr.free.nrw.commons.location.LatLng curlatLng) {
|
||||
if (curlatLng.equals(lastFocusLocation)|| lastFocusLocation==null) { // Means we are checking around current location
|
||||
if (curlatLng.equals(lastFocusLocation) || lastFocusLocation == null || recenterToUserLocation) { // Means we are checking around current location
|
||||
populatePlacesForCurrentLocation(lastKnownLocation, curlatLng);
|
||||
} else {
|
||||
populatePlacesForAnotherLocation(lastKnownLocation, curlatLng);
|
||||
}
|
||||
if(recenterToUserLocation) {
|
||||
recenterToUserLocation = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void populatePlacesForCurrentLocation(final fr.free.nrw.commons.location.LatLng curlatLng,
|
||||
|
|
@ -881,12 +958,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}
|
||||
|
||||
@Override
|
||||
public void checkPermissionsAndPerformAction(final Runnable runnable) {
|
||||
public void checkPermissionsAndPerformAction() {
|
||||
Timber.d("Checking permission and perfoming action");
|
||||
PermissionUtils.checkPermissionsAndPerformAction(getActivity(),
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
runnable,
|
||||
() -> ((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code()),
|
||||
() -> locationPermissionGranted(),
|
||||
() -> isPermissionDenied = true,
|
||||
R.string.location_permission_title,
|
||||
R.string.location_permission_rationale_nearby);
|
||||
}
|
||||
|
|
@ -1058,7 +1135,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
*/
|
||||
@Override
|
||||
public void addCurrentLocationMarker(final fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
if (null != curLatLng) {
|
||||
if (null != curLatLng && !isPermissionDenied) {
|
||||
ExecutorUtils.get().submit(() -> {
|
||||
mapView.post(() -> removeCurrentLocationMarker());
|
||||
Timber.d("Adds current location marker");
|
||||
|
|
@ -1104,8 +1181,15 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
@Override
|
||||
public void updateMapToTrackPosition(final fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
Timber.d("Updates map camera to track user position");
|
||||
final CameraPosition cameraPosition = new CameraPosition.Builder().target
|
||||
final CameraPosition cameraPosition;
|
||||
if(isPermissionDenied){
|
||||
cameraPosition = new CameraPosition.Builder().target
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(curLatLng)).build();
|
||||
}else{
|
||||
cameraPosition = new CameraPosition.Builder().target
|
||||
(LocationUtils.commonsLatLngToMapBoxLatLng(curLatLng))
|
||||
.zoom(ZOOM_LEVEL).build();
|
||||
}
|
||||
if(null!=mapBox) {
|
||||
mapBox.setCameraPosition(cameraPosition);
|
||||
mapBox.animateCamera(CameraUpdateFactory
|
||||
|
|
@ -1170,12 +1254,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
if (displayExists && displayNeedsPhoto) {
|
||||
// Exists and needs photo
|
||||
if (place.destroyed.trim().isEmpty() && place.pic.trim().isEmpty()) {
|
||||
if (place.exists && place.pic.trim().isEmpty()) {
|
||||
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
|
||||
}
|
||||
} else if (displayExists && !displayNeedsPhoto) {
|
||||
// Exists and all included needs and doesn't needs photo
|
||||
if (place.destroyed.trim().isEmpty()) {
|
||||
if (place.exists) {
|
||||
updateMarker(markerPlaceGroup.getIsBookmarked(), place, NearbyController.currentLocation);
|
||||
}
|
||||
} else if (!displayExists && displayNeedsPhoto) {
|
||||
|
|
@ -1233,7 +1317,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
return (isBookmarked ?
|
||||
R.drawable.ic_custom_map_marker_green_bookmarked :
|
||||
R.drawable.ic_custom_map_marker_green);
|
||||
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
||||
} else if (!place.exists) { // Means that the topic of the Wikidata item does not exist in the real world anymore, for instance it is a past event, or a place that was destroyed
|
||||
return (isBookmarked ?
|
||||
R.drawable.ic_custom_map_marker_grey_bookmarked :
|
||||
R.drawable.ic_custom_map_marker_grey);
|
||||
|
|
@ -1289,8 +1373,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
|
||||
@Override
|
||||
public void recenterMap(final fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
if (curLatLng == null) {
|
||||
if (!(locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled())) {
|
||||
if (isPermissionDenied || curLatLng == null) {
|
||||
recenterToUserLocation = true;
|
||||
checkPermissionsAndPerformAction();
|
||||
if (!isPermissionDenied && !(locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled())) {
|
||||
showLocationOffDialog();
|
||||
}
|
||||
return;
|
||||
|
|
@ -1548,4 +1634,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
public void setNearbyParentFragmentInstanceReadyCallback(NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback) {
|
||||
this.nearbyParentFragmentInstanceReadyCallback = nearbyParentFragmentInstanceReadyCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull final Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
ViewGroup.LayoutParams rlBottomSheetLayoutParams = rlBottomSheet.getLayoutParams();
|
||||
rlBottomSheetLayoutParams.height = getActivity().getWindowManager().getDefaultDisplay().getHeight() / 16 * 9;
|
||||
rlBottomSheet.setLayoutParams(rlBottomSheetLayoutParams);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class NearbyResultItem(private val item: ResultTuple?,
|
|||
@field:SerializedName("commonsCategory") private val commonsCategory: ResultTuple?,
|
||||
@field:SerializedName("pic") private val pic: ResultTuple?,
|
||||
@field:SerializedName("destroyed") private val destroyed: ResultTuple?,
|
||||
@field:SerializedName("description") private val description: ResultTuple?) {
|
||||
@field:SerializedName("description") private val description: ResultTuple?,
|
||||
@field:SerializedName("endTime") private val endTime: ResultTuple?) {
|
||||
|
||||
fun getItem(): ResultTuple {
|
||||
return item ?: ResultTuple()
|
||||
|
|
@ -60,6 +61,9 @@ class NearbyResultItem(private val item: ResultTuple?,
|
|||
|
||||
fun getDescription(): ResultTuple {
|
||||
return description ?: ResultTuple()
|
||||
|
||||
fun getEndTime(): ResultTuple {
|
||||
return endTime ?: ResultTuple()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,15 +1,21 @@
|
|||
package fr.free.nrw.commons.nearby.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class ResultTuple {
|
||||
@SerializedName("xml:lang")
|
||||
val language: String
|
||||
val type: String
|
||||
val value: String
|
||||
|
||||
constructor(type: String, value: String) {
|
||||
constructor(lang: String, type: String, value: String) {
|
||||
this.language = lang
|
||||
this.type = type
|
||||
this.value = value
|
||||
}
|
||||
|
||||
constructor() {
|
||||
language = ""
|
||||
type = ""
|
||||
value = ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import static fr.free.nrw.commons.location.LocationServiceManager.LocationChange
|
|||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.CHECKED;
|
||||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNCHECKED;
|
||||
import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNKNOWN;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
public class NearbyParentFragmentPresenter
|
||||
implements NearbyParentFragmentContract.UserActions,
|
||||
|
|
@ -83,6 +84,12 @@ public class NearbyParentFragmentPresenter
|
|||
this.nearbyParentFragmentView=DUMMY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNearbyPreferences(JsonKvStore applicationKvStore) {
|
||||
Timber.d("Remove place objects");
|
||||
applicationKvStore.remove(PLACE_OBJECT);
|
||||
}
|
||||
|
||||
public void initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
|
|
|
|||
|
|
@ -65,10 +65,15 @@ public class NotificationActivity extends BaseActivity {
|
|||
private NotificatinAdapter adapter;
|
||||
private List<Notification> notificationList;
|
||||
MenuItem notificationMenuItem;
|
||||
/**
|
||||
* Boolean isRead is true if this notification activity is for read section of notification.
|
||||
*/
|
||||
private boolean isRead;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
isRead = getIntent().getStringExtra("title").equals("read");
|
||||
setContentView(R.layout.activity_notification);
|
||||
ButterKnife.bind(this);
|
||||
mNotificationWorkerFragment = (NotificationWorkerFragment) getFragmentManager()
|
||||
|
|
@ -85,8 +90,21 @@ public class NotificationActivity extends BaseActivity {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is unread section of the notifications, removeNotification method
|
||||
* Marks the notification as read,
|
||||
* Removes the notification from unread,
|
||||
* Displays the Snackbar.
|
||||
*
|
||||
* Otherwise returns (read section).
|
||||
*
|
||||
* @param notification
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
public void removeNotification(Notification notification) {
|
||||
if (isRead) {
|
||||
return;
|
||||
}
|
||||
Disposable disposable = Observable.defer((Callable<ObservableSource<Boolean>>)
|
||||
() -> controller.markAsRead(notification))
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -126,7 +144,7 @@ public class NotificationActivity extends BaseActivity {
|
|||
recyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
DividerItemDecoration itemDecor = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
|
||||
recyclerView.addItemDecoration(itemDecor);
|
||||
if (getIntent().getStringExtra("title").equals("read")) {
|
||||
if (isRead) {
|
||||
refresh(true);
|
||||
} else {
|
||||
refresh(false);
|
||||
|
|
@ -240,7 +258,7 @@ public class NotificationActivity extends BaseActivity {
|
|||
|
||||
private void setPageTitle() {
|
||||
if (getSupportActionBar() != null) {
|
||||
if (getIntent().getStringExtra("title").equals("read")) {
|
||||
if (isRead) {
|
||||
getSupportActionBar().setTitle(R.string.read_notifications);
|
||||
} else {
|
||||
getSupportActionBar().setTitle(R.string.notifications);
|
||||
|
|
@ -249,7 +267,7 @@ public class NotificationActivity extends BaseActivity {
|
|||
}
|
||||
|
||||
private void setEmptyView() {
|
||||
if (getIntent().getStringExtra("title").equals("read")) {
|
||||
if (isRead) {
|
||||
noNotificationText.setText(R.string.no_read_notification);
|
||||
}else {
|
||||
noNotificationText.setText(R.string.no_notification);
|
||||
|
|
@ -257,7 +275,7 @@ public class NotificationActivity extends BaseActivity {
|
|||
}
|
||||
|
||||
private void setMenuItemTitle() {
|
||||
if (getIntent().getStringExtra("title").equals("read")) {
|
||||
if (isRead) {
|
||||
notificationMenuItem.setTitle(R.string.menu_option_unread);
|
||||
|
||||
}else {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import butterknife.BindView;
|
|||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.ViewPagerAdapter;
|
||||
import fr.free.nrw.commons.profile.achievements.AchievementsFragment;
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package fr.free.nrw.commons.profile;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This View Pager Adapter will set the fragments for profile activity
|
||||
*/
|
||||
public class ViewPagerAdapter extends FragmentPagerAdapter {
|
||||
private List<Fragment> fragmentList = new ArrayList<>();
|
||||
private List<String> fragmentTitleList = new ArrayList<>();
|
||||
|
||||
public ViewPagerAdapter(FragmentManager manager) {
|
||||
super(manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the fragment of the viewpager at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the total number of fragments in the viewpager.
|
||||
* @return size
|
||||
*/
|
||||
@Override
|
||||
public int getCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method sets the fragment and title list in the viewpager
|
||||
* @param fragmentList List of all fragments to be displayed in the viewpager
|
||||
* @param fragmentTitleList List of all titles of the fragments
|
||||
*/
|
||||
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
|
||||
this.fragmentList = fragmentList;
|
||||
this.fragmentTitleList = fragmentTitleList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the title of the page at a particular position
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return fragmentTitleList.get(position);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.profile.achievements;
|
|||
|
||||
import android.accounts.Account;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AlertDialog.Builder;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
|
|
@ -36,7 +37,6 @@ import javax.inject.Inject;
|
|||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import com.dinuscxj.progressbar.CircleProgressBar;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
|
|
@ -46,12 +46,6 @@ 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.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -62,6 +56,17 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment {
|
|||
private static final double BADGE_IMAGE_WIDTH_RATIO = 0.4;
|
||||
private static final double BADGE_IMAGE_HEIGHT_RATIO = 0.3;
|
||||
|
||||
/**
|
||||
* Help link URLs
|
||||
*/
|
||||
private static final String IMAGES_UPLOADED_URL = "https://commons.wikimedia.org/wiki/Commons:Project_scope";
|
||||
private static final String IMAGES_REVERT_URL = "https://commons.wikimedia.org/wiki/Commons:Deletion_policy#Reasons_for_deletion";
|
||||
private static final String IMAGES_USED_URL = "https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style/Images";
|
||||
private static final String IMAGES_NEARBY_PLACES_URL = "https://www.wikidata.org/wiki/Property:P18";
|
||||
private static final String IMAGES_FEATURED_URL = "https://commons.wikimedia.org/wiki/Commons:Featured_pictures";
|
||||
private static final String QUALITY_IMAGE_URL = "https://commons.wikimedia.org/wiki/Commons:Quality_images";
|
||||
private static final String THANKS_URL = "https://www.mediawiki.org/wiki/Extension:Thanks";
|
||||
|
||||
private LevelController.LevelInfo levelInfo;
|
||||
|
||||
@BindView(R.id.achievement_badge_image)
|
||||
|
|
@ -242,8 +247,9 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment {
|
|||
*/
|
||||
@OnClick(R.id.achievement_info)
|
||||
public void showInfoDialog(){
|
||||
launchAlert(getResources().getString(R.string.Achievements)
|
||||
,getResources().getString(R.string.achievements_info_message));
|
||||
launchAlert(
|
||||
getResources().getString(R.string.Achievements),
|
||||
getResources().getString(R.string.achievements_info_message));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -456,44 +462,58 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@OnClick(R.id.images_upload_info)
|
||||
public void showUploadInfo(){
|
||||
launchAlert(getResources().getString(R.string.images_uploaded)
|
||||
,getResources().getString(R.string.images_uploaded_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.images_uploaded),
|
||||
getResources().getString(R.string.images_uploaded_explanation),
|
||||
IMAGES_UPLOADED_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.images_reverted_info)
|
||||
public void showRevertedInfo(){
|
||||
launchAlert(getResources().getString(R.string.image_reverts)
|
||||
,getResources().getString(R.string.images_reverted_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.image_reverts),
|
||||
getResources().getString(R.string.images_reverted_explanation),
|
||||
IMAGES_REVERT_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.images_used_by_wiki_info)
|
||||
public void showUsedByWikiInfo(){
|
||||
launchAlert(getResources().getString(R.string.images_used_by_wiki)
|
||||
,getResources().getString(R.string.images_used_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.images_used_by_wiki),
|
||||
getResources().getString(R.string.images_used_explanation),
|
||||
IMAGES_USED_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.images_nearby_info)
|
||||
public void showImagesViaNearbyInfo(){
|
||||
launchAlert(getResources().getString(R.string.statistics_wikidata_edits)
|
||||
,getResources().getString(R.string.images_via_nearby_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_wikidata_edits),
|
||||
getResources().getString(R.string.images_via_nearby_explanation),
|
||||
IMAGES_NEARBY_PLACES_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.images_featured_info)
|
||||
public void showFeaturedImagesInfo(){
|
||||
launchAlert(getResources().getString(R.string.statistics_featured)
|
||||
,getResources().getString(R.string.images_featured_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_featured),
|
||||
getResources().getString(R.string.images_featured_explanation),
|
||||
IMAGES_FEATURED_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.thanks_received_info)
|
||||
public void showThanksReceivedInfo(){
|
||||
launchAlert(getResources().getString(R.string.statistics_thanks)
|
||||
,getResources().getString(R.string.thanks_received_explanation));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_thanks),
|
||||
getResources().getString(R.string.thanks_received_explanation),
|
||||
THANKS_URL);
|
||||
}
|
||||
|
||||
@OnClick(R.id.quality_images_info)
|
||||
public void showQualityImagesInfo() {
|
||||
launchAlert(getResources().getString(R.string.statistics_quality)
|
||||
, getResources().getString(R.string.quality_images_info));
|
||||
launchAlertWithHelpLink(
|
||||
getResources().getString(R.string.statistics_quality),
|
||||
getResources().getString(R.string.quality_images_info),
|
||||
QUALITY_IMAGE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -511,6 +531,22 @@ public class AchievementsFragment extends CommonsDaggerSupportFragment {
|
|||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch Alert with a READ MORE button and clicking it open a custom webpage
|
||||
*/
|
||||
private void launchAlertWithHelpLink(String title, String message, String helpLinkUrl){
|
||||
new Builder(getActivity())
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.setNegativeButton(R.string.read_help_link, (dialog ,id) ->{
|
||||
Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl));;
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.repository;
|
|||
import fr.free.nrw.commons.category.CategoriesModel;
|
||||
import fr.free.nrw.commons.category.CategoryItem;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||
|
|
@ -36,18 +37,21 @@ public class UploadRepository {
|
|||
private final DepictModel depictModel;
|
||||
|
||||
private static final double NEARBY_RADIUS_IN_KILO_METERS = 0.1; //100 meters
|
||||
private final ContributionDao contributionDao;
|
||||
|
||||
@Inject
|
||||
public UploadRepository(UploadModel uploadModel,
|
||||
UploadController uploadController,
|
||||
CategoriesModel categoriesModel,
|
||||
NearbyPlaces nearbyPlaces,
|
||||
DepictModel depictModel) {
|
||||
DepictModel depictModel,
|
||||
ContributionDao contributionDao) {
|
||||
this.uploadModel = uploadModel;
|
||||
this.uploadController = uploadController;
|
||||
this.categoriesModel = categoriesModel;
|
||||
this.nearbyPlaces = nearbyPlaces;
|
||||
this.depictModel = depictModel;
|
||||
this.contributionDao=contributionDao;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,8 +68,14 @@ public class UploadRepository {
|
|||
*
|
||||
* @param contribution
|
||||
*/
|
||||
public void startUpload(Contribution contribution) {
|
||||
uploadController.startUpload(contribution);
|
||||
|
||||
public void prepareMedia(Contribution contribution) {
|
||||
uploadController.prepareMedia(contribution);
|
||||
}
|
||||
|
||||
|
||||
public void saveContribution(Contribution contribution) {
|
||||
contributionDao.save(contribution).blockingAwait();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -77,13 +87,6 @@ public class UploadRepository {
|
|||
return uploadModel.getUploads();
|
||||
}
|
||||
|
||||
/**
|
||||
* asks the RemoteDataSource to prepare the Upload Service
|
||||
*/
|
||||
public void prepareService() {
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
/**
|
||||
*Prepare for a fresh upload
|
||||
*/
|
||||
|
|
@ -205,16 +208,16 @@ public class UploadRepository {
|
|||
}
|
||||
|
||||
/**
|
||||
* fetches and returns the previous upload item
|
||||
* fetches and returns the upload item
|
||||
*
|
||||
* @param index
|
||||
* @return
|
||||
*/
|
||||
public UploadItem getPreviousUploadItem(int index) {
|
||||
if (index - 1 >= 0) {
|
||||
return uploadModel.getItems().get(index - 1);
|
||||
public UploadItem getUploadItem(int index) {
|
||||
if (index >= 0) {
|
||||
return uploadModel.getItems().get(index);
|
||||
}
|
||||
return null; //There is no previous item to copy details
|
||||
return null; //There is no item to copy details
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,10 +12,14 @@ import android.view.MenuItem;
|
|||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
|
@ -23,6 +27,7 @@ import com.viewpagerindicator.CirclePageIndicator;
|
|||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.delete.DeleteHelper;
|
||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
|
|
@ -49,12 +54,22 @@ public class ReviewActivity extends BaseActivity {
|
|||
ProgressBar progressBar;
|
||||
@BindView(R.id.tv_image_caption)
|
||||
TextView imageCaption;
|
||||
@BindView(R.id.mediaDetailContainer)
|
||||
FrameLayout mediaDetailContainer;
|
||||
MediaDetailFragment mediaDetailFragment;
|
||||
@BindView(R.id.reviewActivityContainer)
|
||||
LinearLayout reviewContainer;
|
||||
public ReviewPagerAdapter reviewPagerAdapter;
|
||||
public ReviewController reviewController;
|
||||
@Inject
|
||||
ReviewHelper reviewHelper;
|
||||
@Inject
|
||||
DeleteHelper deleteHelper;
|
||||
/**
|
||||
* Represent fragment for ReviewImage
|
||||
* Use to call some methods of ReviewImage fragment
|
||||
*/
|
||||
private ReviewImageFragment reviewImageFragment;
|
||||
|
||||
final String SAVED_MEDIA = "saved_media";
|
||||
private Media media;
|
||||
|
|
@ -98,25 +113,27 @@ public class ReviewActivity extends BaseActivity {
|
|||
|
||||
reviewPagerAdapter = new ReviewPagerAdapter(getSupportFragmentManager());
|
||||
reviewPager.setAdapter(reviewPagerAdapter);
|
||||
reviewPagerAdapter.getItem(0);
|
||||
pagerIndicator.setViewPager(reviewPager);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
Drawable d[]=btnSkipImage.getCompoundDrawablesRelative();
|
||||
d[2].setColorFilter(getApplicationContext().getResources().getColor(R.color.button_blue), PorterDuff.Mode.SRC_IN);
|
||||
|
||||
|
||||
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA)!=null) {
|
||||
if (savedInstanceState != null && savedInstanceState.getParcelable(SAVED_MEDIA) != null) {
|
||||
updateImage(savedInstanceState.getParcelable(SAVED_MEDIA)); // Use existing media if we have one
|
||||
setUpMediaDetailOnOrientation();
|
||||
} else {
|
||||
runRandomizer(); //Run randomizer whenever everything is ready so that a first random image will be added
|
||||
}
|
||||
|
||||
btnSkipImage.setOnClickListener(view -> {
|
||||
reviewPagerAdapter.disableButtons();
|
||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
||||
reviewImageFragment.disableButtons();
|
||||
runRandomizer();
|
||||
});
|
||||
|
||||
simpleDraweeView.setOnClickListener(view ->setUpMediaDetailFragment());
|
||||
|
||||
btnSkipImage.setOnTouchListener((view, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP && event.getRawX() >= (
|
||||
btnSkipImage.getRight() - btnSkipImage
|
||||
|
|
@ -142,7 +159,8 @@ public class ReviewActivity extends BaseActivity {
|
|||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(media -> {
|
||||
reviewPagerAdapter.disableButtons();
|
||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
||||
reviewImageFragment.disableButtons();
|
||||
updateImage(media);
|
||||
}));
|
||||
return true;
|
||||
|
|
@ -169,7 +187,8 @@ public class ReviewActivity extends BaseActivity {
|
|||
String caption = String.format(getString(R.string.review_is_uploaded_by), fileName, revision.getUser());
|
||||
imageCaption.setText(caption);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
reviewPagerAdapter.enableButtons();
|
||||
reviewImageFragment = getInstanceOfReviewImageFragment();
|
||||
reviewImageFragment.enableButtons();
|
||||
}));
|
||||
reviewPager.setCurrentItem(0);
|
||||
}
|
||||
|
|
@ -226,4 +245,57 @@ public class ReviewActivity extends BaseActivity {
|
|||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* this function return the instance of reviewImageFragment
|
||||
*/
|
||||
public ReviewImageFragment getInstanceOfReviewImageFragment(){
|
||||
int currentItemOfReviewPager = reviewPager.getCurrentItem();
|
||||
reviewImageFragment = (ReviewImageFragment) reviewPagerAdapter.instantiateItem(reviewPager, currentItemOfReviewPager);
|
||||
return reviewImageFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* set up the media detail fragment when click on the review image
|
||||
*/
|
||||
private void setUpMediaDetailFragment() {
|
||||
if (mediaDetailContainer.getVisibility() == View.GONE && media != null) {
|
||||
mediaDetailContainer.setVisibility(View.VISIBLE);
|
||||
reviewContainer.setVisibility(View.INVISIBLE);
|
||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
||||
mediaDetailFragment = new MediaDetailFragment();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable("media", media);
|
||||
mediaDetailFragment.setArguments(bundle);
|
||||
fragmentManager.beginTransaction().add(R.id.mediaDetailContainer, mediaDetailFragment).
|
||||
addToBackStack("MediaDetail").commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle the back pressed event of this activity
|
||||
* this function call every time when back button is pressed
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mediaDetailContainer.getVisibility() == View.VISIBLE) {
|
||||
mediaDetailContainer.setVisibility(View.GONE);
|
||||
reviewContainer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* set up media detail fragment after orientation change
|
||||
*/
|
||||
private void setUpMediaDetailOnOrientation() {
|
||||
Fragment mediaDetailFragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.mediaDetailContainer);
|
||||
if (mediaDetailFragment != null) {
|
||||
mediaDetailContainer.setVisibility(View.VISIBLE);
|
||||
reviewContainer.setVisibility(View.INVISIBLE);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.mediaDetailContainer, mediaDetailFragment).commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
// Constant variable used to store user's key name for onSaveInstanceState method
|
||||
private final String SAVED_USER = "saved_user";
|
||||
|
||||
//Variable that stores the value of user
|
||||
// Variable that stores the value of user
|
||||
private String user;
|
||||
|
||||
public void update(int position) {
|
||||
|
|
@ -81,18 +81,18 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
case SPAM:
|
||||
question = getString(R.string.review_spam);
|
||||
explanation = getString(R.string.review_spam_explanation);
|
||||
yesButtonText = getString(R.string.review_spam_yes_button_text);
|
||||
noButtonText = getString(R.string.review_spam_no_button_text);
|
||||
yesButton.setOnClickListener(view -> getReviewActivity()
|
||||
yesButtonText = getString(R.string.yes);
|
||||
noButtonText = getString(R.string.no);
|
||||
noButton.setOnClickListener(view -> getReviewActivity()
|
||||
.reviewController.reportSpam(requireActivity(), getReviewCallback()));
|
||||
break;
|
||||
case COPYRIGHT:
|
||||
enableButtons();
|
||||
question = getString(R.string.review_copyright);
|
||||
explanation = getString(R.string.review_copyright_explanation);
|
||||
yesButtonText = getString(R.string.review_copyright_yes_button_text);
|
||||
noButtonText = getString(R.string.review_copyright_no_button_text);
|
||||
yesButton.setOnClickListener(view -> getReviewActivity()
|
||||
yesButtonText = getString(R.string.yes);
|
||||
noButtonText = getString(R.string.no);
|
||||
noButton.setOnClickListener(view -> getReviewActivity()
|
||||
.reviewController
|
||||
.reportPossibleCopyRightViolation(requireActivity(), getReviewCallback()));
|
||||
break;
|
||||
|
|
@ -100,9 +100,9 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
enableButtons();
|
||||
question = getString(R.string.review_category);
|
||||
explanation = updateCategoriesQuestion();
|
||||
yesButtonText = getString(R.string.review_category_yes_button_text);
|
||||
noButtonText = getString(R.string.review_category_no_button_text);
|
||||
yesButton.setOnClickListener(view -> {
|
||||
yesButtonText = getString(R.string.yes);
|
||||
noButtonText = getString(R.string.no);
|
||||
noButton.setOnClickListener(view -> {
|
||||
getReviewActivity()
|
||||
.reviewController
|
||||
.reportWrongCategory(requireActivity(), getReviewCallback());
|
||||
|
|
@ -113,7 +113,7 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
enableButtons();
|
||||
question = getString(R.string.review_thanks);
|
||||
|
||||
//Get existing user name if it is already saved using savedInstanceState else get from reviewController
|
||||
// Get existing user name if it is already saved using savedInstanceState else get from reviewController
|
||||
if (savedInstanceState == null) {
|
||||
if (getReviewActivity().reviewController.firstRevision != null) {
|
||||
user = getReviewActivity().reviewController.firstRevision.getUser();
|
||||
|
|
@ -127,11 +127,12 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
explanation = getString(R.string.review_thanks_explanation, user);
|
||||
}
|
||||
|
||||
// Note that the yes and no buttons are swapped in this section
|
||||
yesButtonText = getString(R.string.review_thanks_yes_button_text);
|
||||
noButtonText = getString(R.string.review_thanks_no_button_text);
|
||||
yesButton.setTextColor(Color.parseColor("#228b22"));
|
||||
noButton.setTextColor(Color.parseColor("#116aaa"));
|
||||
yesButton.setOnClickListener(view -> {
|
||||
yesButton.setTextColor(Color.parseColor("#116aaa"));
|
||||
noButton.setTextColor(Color.parseColor("#228b22"));
|
||||
noButton.setOnClickListener(view -> {
|
||||
getReviewActivity().reviewController.sendThanks(getReviewActivity());
|
||||
getReviewActivity().swipeToNext();
|
||||
});
|
||||
|
|
@ -202,8 +203,8 @@ public class ReviewImageFragment extends CommonsDaggerSupportFragment {
|
|||
noButton.setAlpha(0.5f);
|
||||
}
|
||||
|
||||
@OnClick(R.id.button_no)
|
||||
void onNoButtonClicked() {
|
||||
@OnClick(R.id.button_yes)
|
||||
void onYesButtonClicked() {
|
||||
getReviewActivity().swipeToNext();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package fr.free.nrw.commons.review;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
|
|
@ -9,6 +11,13 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
|
|||
public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
|
||||
private ReviewImageFragment[] reviewImageFragments;
|
||||
|
||||
/**
|
||||
* this function return the instance of ReviewviewPage current item
|
||||
*/
|
||||
@Override
|
||||
public Object instantiateItem(@NonNull ViewGroup container, int position) {
|
||||
return super.instantiateItem(container, position);
|
||||
}
|
||||
|
||||
ReviewPagerAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
|
|
@ -32,25 +41,6 @@ public class ReviewPagerAdapter extends FragmentStatePagerAdapter {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when an image has
|
||||
* been loaded to enable the review buttons.
|
||||
*/
|
||||
public void enableButtons() {
|
||||
if (reviewImageFragments != null){
|
||||
reviewImageFragments[0].enableButtons();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when an image is being loaded
|
||||
* to disable the review buttons
|
||||
*/
|
||||
public void disableButtons() {
|
||||
if (reviewImageFragments != null){
|
||||
reviewImageFragments[0].disableButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ public class Prefs {
|
|||
public static final String DEFAULT_LICENSE = "defaultLicense";
|
||||
public static final String UPLOADS_SHOWING = "uploadsshowing";
|
||||
public static final String MANAGED_EXIF_TAGS = "managed_exif_tags";
|
||||
public static final String KEY_LANGUAGE_VALUE = "languageDescription";
|
||||
public static final String DESCRIPTION_LANGUAGE = "descriptionLanguage";
|
||||
public static final String APP_UI_LANGUAGE = "appUiLanguage";
|
||||
public static final String KEY_THEME_VALUE = "appThemePref";
|
||||
public static final String TELEMETRY_PREFERENCE = "telemetryPref";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,23 @@
|
|||
package fr.free.nrw.commons.settings;
|
||||
|
||||
import static android.content.Context.MODE_PRIVATE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import android.view.View;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.MultiSelectListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import androidx.preference.PreferenceGroupAdapter;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
import androidx.recyclerview.widget.RecyclerView.Adapter;
|
||||
import com.karumi.dexter.Dexter;
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener;
|
||||
|
|
@ -18,19 +25,20 @@ import com.mapbox.mapboxsdk.Mapbox;
|
|||
import com.mapbox.mapboxsdk.maps.TelemetryDefinition;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.CommonsLogSender;
|
||||
import fr.free.nrw.commons.upload.Language;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.wikipedia.language.AppLanguageLookUpTable;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
|
|
@ -42,7 +50,9 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
CommonsLogSender commonsLogSender;
|
||||
|
||||
private ListPreference themeListPreference;
|
||||
private ListPreference langListPreference;
|
||||
private ListPreference descriptionLanguageListPreference;
|
||||
private ListPreference appUiLanguageListPreference;
|
||||
private String keyLanguageListPreference;
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
|
|
@ -67,8 +77,16 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
});
|
||||
}
|
||||
|
||||
langListPreference = findPreference("descriptionDefaultLanguagePref");
|
||||
prepareLanguages();
|
||||
appUiLanguageListPreference = findPreference("appUiDefaultLanguagePref");
|
||||
assert appUiLanguageListPreference != null;
|
||||
keyLanguageListPreference = appUiLanguageListPreference.getKey();
|
||||
prepareAppLanguages(keyLanguageListPreference);
|
||||
|
||||
descriptionLanguageListPreference = findPreference("descriptionDefaultLanguagePref");
|
||||
assert descriptionLanguageListPreference != null;
|
||||
keyLanguageListPreference = descriptionLanguageListPreference.getKey();
|
||||
prepareAppLanguages(keyLanguageListPreference);
|
||||
|
||||
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
||||
betaTesterPreference.setOnPreferenceClickListener(preference -> {
|
||||
Utils.handleWebUrl(getActivity(), Uri.parse(getResources().getString(R.string.beta_opt_in_link)));
|
||||
|
|
@ -84,6 +102,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
findPreference("useExternalStorage").setEnabled(false);
|
||||
findPreference("useAuthorName").setEnabled(false);
|
||||
findPreference("displayNearbyCardView").setEnabled(false);
|
||||
findPreference("descriptionDefaultLanguagePref").setEnabled(false);
|
||||
findPreference("displayLocationPermissionForCardView").setEnabled(false);
|
||||
findPreference("displayCampaignsCardView").setEnabled(false);
|
||||
}
|
||||
|
|
@ -107,6 +126,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
|
||||
return new PreferenceGroupAdapter(preferenceScreen) {
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder, int position) {
|
||||
super.onBindViewHolder(holder, position);
|
||||
Preference preference = getItem(position);
|
||||
View iconFrame = holder.itemView.findViewById(R.id.icon_frame);
|
||||
if (iconFrame != null) {
|
||||
iconFrame.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the theme pref
|
||||
*/
|
||||
|
|
@ -119,65 +153,118 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||
|
||||
/**
|
||||
* Prepares language summary and language codes list and adds them to list preference as pairs.
|
||||
* Uses previously saved language if there is any, if not uses phone local as initial language.
|
||||
* Uses previously saved language if there is any, if not uses phone locale as initial language.
|
||||
* Get ListPreference key and act accordingly for each ListPreference.
|
||||
* Adds preference changed listener and saves value chosen by user to shared preferences
|
||||
* to remember later
|
||||
* to remember later and recall MainActivity to reflect language changes
|
||||
* @param keyListPreference
|
||||
*/
|
||||
private void prepareLanguages() {
|
||||
List<String> languageNamesList = new ArrayList<>();
|
||||
List<String> languageCodesList = new ArrayList<>();
|
||||
List<Language> languages = getLanguagesSupportedByDevice();
|
||||
private void prepareAppLanguages(final String keyListPreference) {
|
||||
final List<String> languageNamesList;
|
||||
final List<String> languageCodesList;
|
||||
final AppLanguageLookUpTable appLanguageLookUpTable = new AppLanguageLookUpTable(
|
||||
Objects.requireNonNull(getContext()));
|
||||
languageNamesList = appLanguageLookUpTable.getLocalizedNames();
|
||||
languageCodesList = appLanguageLookUpTable.getCodes();
|
||||
List<String> languageNameWithCodeList = new ArrayList<>();
|
||||
|
||||
for(Language language: languages) {
|
||||
// Go through all languages and add them to lists
|
||||
if(!languageCodesList.contains(language.getLocale().getLanguage())) {
|
||||
// This if prevents us from adding same language twice
|
||||
languageNamesList.add(language.getLocale().getDisplayName());
|
||||
languageCodesList.add(language.getLocale().getLanguage());
|
||||
}
|
||||
for (int i = 0; i < languageNamesList.size(); i++) {
|
||||
languageNameWithCodeList.add(languageNamesList.get(i) + "[" + languageCodesList.get(i) + "]");
|
||||
}
|
||||
|
||||
CharSequence[] languageNames = languageNamesList.toArray(new CharSequence[0]);
|
||||
CharSequence[] languageCodes = languageCodesList.toArray(new CharSequence[0]);
|
||||
final CharSequence[] languageNames = languageNamesList.toArray(new CharSequence[0]);
|
||||
final CharSequence[] languageCodes = languageCodesList.toArray(new CharSequence[0]);
|
||||
// Add all languages and languages codes to lists preference as pair
|
||||
langListPreference.setEntries(languageNames);
|
||||
langListPreference.setEntryValues(languageCodes);
|
||||
|
||||
// Gets current language code from shared preferences
|
||||
String languageCode = getCurrentLanguageCode();
|
||||
if (languageCode.equals("")){
|
||||
// If current language code is empty, means none selected by user yet so use phone local
|
||||
langListPreference.setValue(Locale.getDefault().getLanguage());
|
||||
} else {
|
||||
// If any language is selected by user previously, use it
|
||||
langListPreference.setValue(languageCode);
|
||||
final String languageCode = getCurrentLanguageCode(keyListPreference);
|
||||
|
||||
if (keyListPreference.equals("appUiDefaultLanguagePref")) {
|
||||
appUiLanguageListPreference.setEntries(languageNames);
|
||||
appUiLanguageListPreference.setEntryValues(languageCodes);
|
||||
|
||||
assert languageCode != null;
|
||||
if (languageCode.equals("")) {
|
||||
// If current language code is empty, means none selected by user yet so use phone local
|
||||
appUiLanguageListPreference.setValue(Locale.getDefault().getLanguage());
|
||||
} else {
|
||||
// If any language is selected by user previously, use it
|
||||
appUiLanguageListPreference.setValue(languageCode);
|
||||
}
|
||||
|
||||
appUiLanguageListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final String userSelectedValue = (String) newValue;
|
||||
setLocale(Objects.requireNonNull(getActivity()), userSelectedValue);
|
||||
saveLanguageValue(userSelectedValue, keyListPreference);
|
||||
getActivity().recreate();
|
||||
final Intent intent = new Intent(getActivity(), MainActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
} else if (keyListPreference.equals("descriptionDefaultLanguagePref")) {
|
||||
descriptionLanguageListPreference.setEntries(languageNames);
|
||||
descriptionLanguageListPreference.setEntryValues(languageCodes);
|
||||
|
||||
assert languageCode != null;
|
||||
if (languageCode.equals("")) {
|
||||
// If current language code is empty, means none selected by user yet so use phone local
|
||||
descriptionLanguageListPreference.setValue(Locale.getDefault().getLanguage());
|
||||
} else {
|
||||
// If any language is selected by user previously, use it
|
||||
descriptionLanguageListPreference.setValue(languageCode);
|
||||
}
|
||||
|
||||
descriptionLanguageListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final String userSelectedValue = (String) newValue;
|
||||
saveLanguageValue(userSelectedValue, keyListPreference);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
langListPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String userSelectedValue = (String) newValue;
|
||||
saveLanguageValue(userSelectedValue);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void saveLanguageValue(String userSelectedValue) {
|
||||
defaultKvStore.putString(Prefs.KEY_LANGUAGE_VALUE, userSelectedValue);
|
||||
/**
|
||||
* Changing the default app language with selected one and save it to SharedPreferences
|
||||
*/
|
||||
public void setLocale(final Activity activity, final String userSelectedValue) {
|
||||
final Locale locale = new Locale(userSelectedValue);
|
||||
Locale.setDefault(locale);
|
||||
final Configuration configuration = new Configuration();
|
||||
configuration.locale = locale;
|
||||
activity.getBaseContext().getResources().updateConfiguration(configuration,
|
||||
activity.getBaseContext().getResources().getDisplayMetrics());
|
||||
|
||||
final SharedPreferences.Editor editor = activity.getSharedPreferences("Settings", MODE_PRIVATE).edit();
|
||||
editor.putString("language", userSelectedValue);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
private String getCurrentLanguageCode() {
|
||||
return defaultKvStore.getString(Prefs.KEY_LANGUAGE_VALUE, "");
|
||||
}
|
||||
|
||||
private List<Language> getLanguagesSupportedByDevice() {
|
||||
List<Language> languages = new ArrayList<>();
|
||||
Locale[] localesArray = Locale.getAvailableLocales();
|
||||
for (Locale locale : localesArray) {
|
||||
languages.add(new Language(locale));
|
||||
/**
|
||||
* Save userselected language in List Preference
|
||||
* @param userSelectedValue
|
||||
* @param preferenceKey
|
||||
*/
|
||||
private void saveLanguageValue(final String userSelectedValue, final String preferenceKey) {
|
||||
if (preferenceKey.equals("appUiDefaultLanguagePref")) {
|
||||
defaultKvStore.putString(Prefs.APP_UI_LANGUAGE, userSelectedValue);
|
||||
} else if (preferenceKey.equals("descriptionDefaultLanguagePref")) {
|
||||
defaultKvStore.putString(Prefs.DESCRIPTION_LANGUAGE, userSelectedValue);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(languages, (language, t1) -> language.getLocale().getDisplayName()
|
||||
.compareTo(t1.getLocale().getDisplayName()));
|
||||
return languages;
|
||||
/**
|
||||
* Gets current language code from shared preferences
|
||||
* @param preferenceKey
|
||||
* @return
|
||||
*/
|
||||
private String getCurrentLanguageCode(final String preferenceKey) {
|
||||
if (preferenceKey.equals("appUiDefaultLanguagePref")) {
|
||||
return defaultKvStore.getString(Prefs.APP_UI_LANGUAGE, "");
|
||||
}
|
||||
if (preferenceKey.equals("descriptionDefaultLanguagePref")) {
|
||||
return defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package fr.free.nrw.commons.theme;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.WindowManager;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
|
|
@ -27,6 +28,11 @@ public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
wasPreviouslyDarkTheme = systemThemeUtils.isDeviceInNightMode();
|
||||
setTheme(wasPreviouslyDarkTheme ? R.style.DarkAppTheme : R.style.LightAppTheme);
|
||||
float fontScale = android.provider.Settings.System.getFloat(
|
||||
getBaseContext().getContentResolver(),
|
||||
android.provider.Settings.System.FONT_SCALE,
|
||||
1f);
|
||||
adjustFontScale(getResources().getConfiguration(), fontScale);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -44,4 +50,17 @@ public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
|
|||
super.onDestroy();
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply fontScale on device
|
||||
*/
|
||||
public void adjustFontScale(Configuration configuration, float scale) {
|
||||
configuration.fontScale = scale;
|
||||
final DisplayMetrics metrics = getResources().getDisplayMetrics();
|
||||
final WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
wm.getDefaultDisplay().getMetrics(metrics);
|
||||
metrics.scaledDensity = configuration.fontScale * metrics.density;
|
||||
getBaseContext().getResources().updateConfiguration(configuration, metrics);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import timber.log.Timber;
|
|||
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_ARTIST;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_BODY_SERIAL_NUMBER;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_CAMARA_OWNER_NAME;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_CAMERA_OWNER_NAME;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_COPYRIGHT;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE;
|
||||
import static androidx.exifinterface.media.ExifInterface.TAG_GPS_ALTITUDE_REF;
|
||||
|
|
@ -36,7 +36,7 @@ public class FileMetadataUtils {
|
|||
Timber.d("Retuning tags for pref:%s", pref);
|
||||
switch (pref) {
|
||||
case "Author":
|
||||
return new String[]{TAG_ARTIST, TAG_CAMARA_OWNER_NAME};
|
||||
return new String[]{TAG_ARTIST, TAG_CAMERA_OWNER_NAME};
|
||||
case "Copyright":
|
||||
return new String[]{TAG_COPYRIGHT};
|
||||
case "Location":
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||
|
||||
import android.Manifest;
|
||||
|
|
@ -24,6 +23,9 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
|
@ -42,6 +44,7 @@ import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
|||
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
|
@ -55,6 +58,7 @@ import javax.inject.Named;
|
|||
import timber.log.Timber;
|
||||
|
||||
public class UploadActivity extends BaseActivity implements UploadContract.View, UploadBaseFragment.Callback {
|
||||
|
||||
@Inject
|
||||
ContributionController contributionController;
|
||||
@Inject
|
||||
|
|
@ -104,6 +108,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
|||
private List<UploadableFile> uploadableFiles = Collections.emptyList();
|
||||
private int currentSelectedPosition = 0;
|
||||
|
||||
public static final String EXTRA_FILES = "commons_image_exta";
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
|
@ -279,6 +285,13 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
|||
.getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeUploadRequest() {
|
||||
WorkManager.getInstance(getApplicationContext()).enqueueUniqueWork(
|
||||
UploadWorker.class.getSimpleName(),
|
||||
ExistingWorkPolicy.KEEP, OneTimeWorkRequest.from(UploadWorker.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void askUserToLogIn() {
|
||||
Timber.d("current session is null, asking user to login");
|
||||
|
|
|
|||
|
|
@ -9,12 +9,11 @@ import com.google.gson.Gson;
|
|||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.contributions.ChunkInfo;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.upload.UploadService.NotificationUpdateProgressListener;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker.NotificationUpdateProgressListener;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
|
@ -48,8 +47,6 @@ public class UploadClient {
|
|||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final Gson gson;
|
||||
|
||||
private Map<String, Boolean> pauseUploads;
|
||||
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
|
|
@ -62,14 +59,13 @@ public class UploadClient {
|
|||
this.pageContentsCreator = pageContentsCreator;
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.gson = gson;
|
||||
this.pauseUploads = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to stash in chunks of specified size. Uploading files in chunks will make handling
|
||||
* of large files easier. Also, it will be useful in supporting pause/resume of uploads
|
||||
*/
|
||||
Observable<StashUploadResult> uploadFileToStash(
|
||||
public Observable<StashUploadResult> uploadFileToStash(
|
||||
final Context context, final String filename, final Contribution contribution,
|
||||
final NotificationUpdateProgressListener notificationUpdater) throws IOException {
|
||||
if (contribution.getChunkInfo() != null
|
||||
|
|
@ -79,7 +75,7 @@ public class UploadClient {
|
|||
contribution.getChunkInfo().getUploadResult().getFilekey()));
|
||||
}
|
||||
|
||||
pauseUploads.put(contribution.getPageId(), false);
|
||||
CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
|
||||
|
||||
final File file = new File(contribution.getLocalUri().getPath());
|
||||
final List<File> fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
|
||||
|
|
@ -102,7 +98,7 @@ public class UploadClient {
|
|||
final AtomicBoolean failures = new AtomicBoolean();
|
||||
|
||||
compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
|
||||
if (pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +137,7 @@ public class UploadClient {
|
|||
}));
|
||||
}));
|
||||
|
||||
if (pauseUploads.get(contribution.getPageId())) {
|
||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId())) {
|
||||
Timber.d("Upload stash paused %s", contribution.getPageId());
|
||||
return Observable.just(new StashUploadResult(StashUploadState.PAUSED, null));
|
||||
} else if (failures.get()) {
|
||||
|
|
@ -201,18 +197,6 @@ public class UploadClient {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispose the active disposable and sets the pause variable
|
||||
* @param pageId
|
||||
*/
|
||||
public void pauseUpload(String pageId) {
|
||||
pauseUploads.put(pageId, true);
|
||||
if (!compositeDisposable.isDisposed()) {
|
||||
compositeDisposable.dispose();
|
||||
}
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts string value to request body
|
||||
*/
|
||||
|
|
@ -222,7 +206,7 @@ public class UploadClient {
|
|||
}
|
||||
|
||||
|
||||
Observable<UploadResult> uploadFileFromStash(final Context context,
|
||||
public Observable<UploadResult> uploadFileFromStash(
|
||||
final Contribution contribution,
|
||||
final String uniqueFileName,
|
||||
final String fileKey) {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ public interface UploadContract {
|
|||
void onUploadMediaDeleted(int index);
|
||||
|
||||
void updateTopCardTitle();
|
||||
|
||||
void makeUploadRequest();
|
||||
}
|
||||
|
||||
public interface UserActionListener extends BasePresenter<View> {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,16 @@ import android.net.Uri;
|
|||
import android.os.IBinder;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
|
@ -35,62 +39,27 @@ import timber.log.Timber;
|
|||
|
||||
@Singleton
|
||||
public class UploadController {
|
||||
private UploadService uploadService;
|
||||
|
||||
private final SessionManager sessionManager;
|
||||
private final Context context;
|
||||
private final JsonKvStore store;
|
||||
|
||||
@Inject
|
||||
public UploadController(final SessionManager sessionManager,
|
||||
final Context context,
|
||||
final JsonKvStore store) {
|
||||
final Context context,
|
||||
final JsonKvStore store) {
|
||||
this.sessionManager = sessionManager;
|
||||
this.context = context;
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
private boolean isUploadServiceConnected;
|
||||
public ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(final ComponentName componentName, final IBinder binder) {
|
||||
uploadService = ((UploadService.UploadServiceLocalBinder) binder).getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(final ComponentName componentName) {
|
||||
// this should never happen
|
||||
isUploadServiceConnected = false;
|
||||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prepares the upload service.
|
||||
*/
|
||||
public void prepareService() {
|
||||
final Intent uploadServiceIntent = new Intent(context, UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
context.startService(uploadServiceIntent);
|
||||
context.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the upload service.
|
||||
*/
|
||||
public void cleanup() {
|
||||
if (isUploadServiceConnected) {
|
||||
context.unbindService(uploadServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new upload task.
|
||||
*
|
||||
* @param contribution the contribution object
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public void startUpload(final Contribution contribution) {
|
||||
public void prepareMedia(final Contribution contribution) {
|
||||
//Set creator, desc, and license
|
||||
|
||||
// If author name is enabled and set, use it
|
||||
|
|
@ -118,20 +87,7 @@ public class UploadController {
|
|||
final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
media.setLicense(license);
|
||||
|
||||
uploadTask(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the upload task
|
||||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
private Disposable uploadTask(final Contribution contribution) {
|
||||
return Single.just(contribution)
|
||||
.map(this::buildUpload)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::upload);
|
||||
buildUpload(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -139,7 +95,7 @@ public class UploadController {
|
|||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
private Contribution buildUpload(final Contribution contribution) {
|
||||
private void buildUpload(final Contribution contribution) {
|
||||
final ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
contribution.setDataLength(resolveDataLength(contentResolver, contribution));
|
||||
|
|
@ -153,8 +109,6 @@ public class UploadController {
|
|||
contribution.setDateCreated(resolveDateTakenOrNow(contentResolver, contribution));
|
||||
}
|
||||
}
|
||||
|
||||
return contribution;
|
||||
}
|
||||
|
||||
private String resolveMimeType(final ContentResolver contentResolver, final Contribution contribution) {
|
||||
|
|
@ -202,15 +156,6 @@ public class UploadController {
|
|||
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the contribution object is completely formed, the item is queued to the upload service
|
||||
* @param contribution
|
||||
*/
|
||||
private void upload(final Contribution contribution) {
|
||||
//Starts the upload. If commented out, user can proceed to next Fragment but upload doesn't happen
|
||||
uploadService.queue(contribution);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Counts the number of bytes in {@code stream}.
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class UploadItem {
|
|||
private final String mimeType;
|
||||
private ImageCoordinates gpsCoords;
|
||||
private List<UploadMediaDetail> uploadMediaDetails;
|
||||
private final Place place;
|
||||
private Place place;
|
||||
private final long createdTimestamp;
|
||||
private final String createdTimestampSource;
|
||||
private final BehaviorSubject<Integer> imageQuality;
|
||||
|
|
@ -70,6 +70,14 @@ public class UploadItem {
|
|||
this.imageQuality.onNext(imageQuality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the corresponding place to the uploadItem
|
||||
* @param place geolocated Wikidata item
|
||||
*/
|
||||
public void setPlace(Place place) {
|
||||
this.place = place;
|
||||
}
|
||||
|
||||
public Place getPlace() {
|
||||
return place;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Holds a description of an item being uploaded by [UploadActivity]
|
||||
|
|
@ -20,7 +19,7 @@ data class UploadMediaDetail constructor(
|
|||
fun javaCopy() = copy()
|
||||
|
||||
constructor(place: Place) : this(
|
||||
Locale.getDefault().language,
|
||||
place.language,
|
||||
place.longDescription,
|
||||
place.name
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatEditText;
|
||||
|
|
@ -13,6 +15,7 @@ import androidx.appcompat.widget.AppCompatSpinner;
|
|||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
||||
|
|
@ -72,24 +75,38 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
notifyItemInserted(uploadMediaDetails.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove description based on position from the list and notifies the RecyclerView Adapter that
|
||||
* data in adapter has been removed at that particular position.
|
||||
* @param uploadMediaDetail
|
||||
* @param position
|
||||
*/
|
||||
public void removeDescription(final UploadMediaDetail uploadMediaDetail, final int position) {
|
||||
this.uploadMediaDetails.remove(uploadMediaDetail);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.spinner_description_languages)
|
||||
AppCompatSpinner spinnerDescriptionLanguages;
|
||||
Spinner spinnerDescriptionLanguages;
|
||||
|
||||
@BindView(R.id.description_item_edit_text)
|
||||
AppCompatEditText descItemEditText;
|
||||
TextInputEditText descItemEditText;
|
||||
|
||||
@BindView(R.id.description_item_edit_text_input_layout)
|
||||
TextInputLayout descInputLayout;
|
||||
|
||||
@BindView(R.id.caption_item_edit_text)
|
||||
AppCompatEditText captionItemEditText;
|
||||
TextInputEditText captionItemEditText;
|
||||
|
||||
@BindView(R.id.caption_item_edit_text_input_layout)
|
||||
TextInputLayout captionInputLayout;
|
||||
|
||||
@BindView(R.id.btn_remove)
|
||||
ImageView removeButton;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
|
|
@ -110,6 +127,7 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
descItemEditText.setText(uploadMediaDetail.getDescriptionText());
|
||||
|
||||
if (position == 0) {
|
||||
removeButton.setVisibility(View.GONE);
|
||||
captionInputLayout.setEndIconMode(TextInputLayout.END_ICON_CUSTOM);
|
||||
captionInputLayout.setEndIconDrawable(R.drawable.mapbox_info_icon_default);
|
||||
captionInputLayout.setEndIconOnClickListener(v ->
|
||||
|
|
@ -121,10 +139,13 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
callback.showAlert(R.string.media_detail_description, R.string.description_info));
|
||||
|
||||
} else {
|
||||
removeButton.setVisibility(View.VISIBLE);
|
||||
captionInputLayout.setEndIconDrawable(null);
|
||||
descInputLayout.setEndIconDrawable(null);
|
||||
}
|
||||
|
||||
removeButton.setOnClickListener(v -> removeDescription(uploadMediaDetail, position));
|
||||
|
||||
captionItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
||||
captionText -> uploadMediaDetails.get(position).setCaptionText(captionText)));
|
||||
initLanguageSpinner(position, uploadMediaDetail);
|
||||
|
|
@ -174,24 +195,30 @@ public class UploadMediaDetailAdapter extends RecyclerView.Adapter<UploadMediaDe
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
if (description.getSelectedLanguageIndex() == -1) {
|
||||
if (!TextUtils.isEmpty(savedLanguageValue)) {
|
||||
// If user has chosen a default language from settings activity savedLanguageValue is not null
|
||||
spinnerDescriptionLanguages.setSelection(languagesAdapter.getIndexOfLanguageCode(savedLanguageValue));
|
||||
// If user has chosen a default language from settings activity
|
||||
// savedLanguageValue is not null
|
||||
spinnerDescriptionLanguages.setSelection(languagesAdapter
|
||||
.getIndexOfLanguageCode(savedLanguageValue));
|
||||
} else {
|
||||
//Checking whether Language Code attribute is null or not.
|
||||
if(uploadMediaDetails.get(position).getLanguageCode() != null){
|
||||
//If it is not null that means it is fetching details from the previous upload (i.e. when user has pressed copy previous caption & description)
|
||||
if (uploadMediaDetails.get(position).getLanguageCode() != null) {
|
||||
//If it is not null that means it is fetching details from the previous
|
||||
// upload (i.e. when user has pressed copy previous caption & description)
|
||||
//hence providing same language code for the current upload.
|
||||
spinnerDescriptionLanguages.setSelection(languagesAdapter
|
||||
.getIndexOfLanguageCode(uploadMediaDetails.get(position).getLanguageCode()), true);
|
||||
.getIndexOfLanguageCode(uploadMediaDetails.get(position)
|
||||
.getLanguageCode()), true);
|
||||
} else {
|
||||
if (position == 0) {
|
||||
int defaultLocaleIndex = languagesAdapter
|
||||
.getIndexOfUserDefaultLocale(spinnerDescriptionLanguages.getContext());
|
||||
final int defaultLocaleIndex = languagesAdapter
|
||||
.getIndexOfUserDefaultLocale(spinnerDescriptionLanguages
|
||||
.getContext());
|
||||
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex, true);
|
||||
} else {
|
||||
spinnerDescriptionLanguages.setSelection(0,true);
|
||||
spinnerDescriptionLanguages.setSelection(0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import fr.free.nrw.commons.filepicker.UploadableFile;
|
|||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsFragment;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
|||
|
||||
@Override
|
||||
public void onNext(Contribution contribution) {
|
||||
repository.startUpload(contribution);
|
||||
repository.prepareMedia(contribution);
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
repository.saveContribution(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -83,6 +85,7 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
|||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
view.makeUploadRequest();
|
||||
repository.cleanup();
|
||||
view.finish();
|
||||
compositeDisposable.clear();
|
||||
|
|
@ -119,7 +122,6 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
|||
@Override
|
||||
public void onAttachView(UploadContract.View view) {
|
||||
this.view = view;
|
||||
repository.prepareService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ data class UploadResult(
|
|||
val filename: String
|
||||
) : Parcelable {
|
||||
constructor(parcel: Parcel) : this(
|
||||
parcel.readString(),
|
||||
parcel.readString(),
|
||||
parcel.readInt(),
|
||||
parcel.readString()
|
||||
parcel.readString()?:"",
|
||||
parcel.readString()?:"",
|
||||
parcel.readInt()?:0,
|
||||
parcel.readString()?:""
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,474 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.ChunkInfo;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerService;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadService extends CommonsDaggerService {
|
||||
|
||||
private static final String EXTRA_PREFIX = "fr.free.nrw.commons.upload";
|
||||
|
||||
private static final List<String> STASH_ERROR_CODES = Arrays
|
||||
.asList("uploadstash-file-not-found", "stashfailed", "verification-error", "chunk-too-small");
|
||||
|
||||
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
||||
public static final String PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS = EXTRA_PREFIX + "process_limited_connection_mode_uploads";
|
||||
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
|
||||
@Inject
|
||||
WikidataEditService wikidataEditService;
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
ContributionDao contributionDao;
|
||||
@Inject
|
||||
UploadClient uploadClient;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
@Inject
|
||||
@Named(CommonsApplicationModule.MAIN_THREAD)
|
||||
Scheduler mainThreadScheduler;
|
||||
@Inject
|
||||
@Named(CommonsApplicationModule.IO_THREAD)
|
||||
Scheduler ioThreadScheduler;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public JsonKvStore defaultKvStore;
|
||||
|
||||
private NotificationManagerCompat notificationManager;
|
||||
private NotificationCompat.Builder curNotification;
|
||||
private int toUpload;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
|
||||
/**
|
||||
* The filePath names of unfinished uploads, used to prevent overwriting
|
||||
*/
|
||||
private Set<String> unfinishedUploads = new HashSet<>();
|
||||
|
||||
// DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING
|
||||
// See http://stackoverflow.com/questions/8725909/startforeground-does-not-show-my-notification
|
||||
// Seriously, Android?
|
||||
public static final int NOTIFICATION_UPLOAD_IN_PROGRESS = 1;
|
||||
public static final int NOTIFICATION_UPLOAD_FAILED = 3;
|
||||
public static final int NOTIFICATION_UPLOAD_PAUSED = 4;
|
||||
|
||||
protected class NotificationUpdateProgressListener {
|
||||
|
||||
String notificationTag;
|
||||
boolean notificationTitleChanged;
|
||||
Contribution contribution;
|
||||
|
||||
String notificationProgressTitle;
|
||||
String notificationFinishingTitle;
|
||||
|
||||
NotificationUpdateProgressListener(String notificationTag, String notificationProgressTitle,
|
||||
String notificationFinishingTitle, Contribution contribution) {
|
||||
this.notificationTag = notificationTag;
|
||||
this.notificationProgressTitle = notificationProgressTitle;
|
||||
this.notificationFinishingTitle = notificationFinishingTitle;
|
||||
this.contribution = contribution;
|
||||
}
|
||||
|
||||
public void onProgress(long transferred, long total) {
|
||||
if (!notificationTitleChanged) {
|
||||
curNotification.setContentTitle(notificationProgressTitle);
|
||||
notificationTitleChanged = true;
|
||||
contribution.setState(Contribution.STATE_IN_PROGRESS);
|
||||
}
|
||||
if (transferred == total) {
|
||||
// Completed!
|
||||
curNotification.setContentTitle(notificationFinishingTitle)
|
||||
.setTicker(notificationFinishingTitle)
|
||||
.setProgress(0, 100, true);
|
||||
} else {
|
||||
curNotification
|
||||
.setProgress(100, (int) (((double) transferred / (double) total) * 100), false);
|
||||
}
|
||||
notificationManager
|
||||
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||
|
||||
contribution.setTransferred(transferred);
|
||||
|
||||
compositeDisposable.add(contributionDao.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
public void onChunkUploaded(Contribution contribution, ChunkInfo chunkInfo) {
|
||||
contribution.setChunkInfo(chunkInfo);
|
||||
compositeDisposable.add(contributionDao.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets contribution state to paused and disposes the active disposable
|
||||
* @param contribution
|
||||
*/
|
||||
public void pauseUpload(Contribution contribution) {
|
||||
uploadClient.pauseUpload(contribution.getPageId());
|
||||
contribution.setState(Contribution.STATE_PAUSED);
|
||||
compositeDisposable.add(contributionDao.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
compositeDisposable.dispose();
|
||||
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
|
||||
}
|
||||
|
||||
public class UploadServiceLocalBinder extends Binder {
|
||||
|
||||
public UploadService getService() {
|
||||
return UploadService.this;
|
||||
}
|
||||
}
|
||||
|
||||
private final IBinder localBinder = new UploadServiceLocalBinder();
|
||||
|
||||
private PublishProcessor<Contribution> contributionsToUpload;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return localBinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
CommonsApplication.createNotificationChannel(getApplicationContext());
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
notificationManager = NotificationManagerCompat.from(this);
|
||||
curNotification = getNotificationBuilder(CommonsApplication.NOTIFICATION_CHANNEL_ID_ALL);
|
||||
contributionsToUpload = PublishProcessor.create();
|
||||
compositeDisposable.add(contributionsToUpload.subscribe(this::handleUpload));
|
||||
}
|
||||
|
||||
public void handleUpload(Contribution contribution) {
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
contribution.setTransferred(0);
|
||||
toUpload++;
|
||||
if (curNotification != null && toUpload != 1) {
|
||||
curNotification.setContentText(getResources()
|
||||
.getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
|
||||
Timber.d("%d uploads left", toUpload);
|
||||
notificationManager
|
||||
.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_IN_PROGRESS,
|
||||
curNotification.build());
|
||||
}
|
||||
|
||||
compositeDisposable.add(contributionDao
|
||||
.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(() -> uploadContribution(contribution)));
|
||||
}
|
||||
|
||||
private boolean freshStart = true;
|
||||
|
||||
public void queue(Contribution contribution) {
|
||||
if (defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
|
||||
contribution.setState(Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE);
|
||||
contributionDao.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe();
|
||||
return;
|
||||
}
|
||||
contributionsToUpload.offer(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
showUploadNotification();
|
||||
if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
|
||||
compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED,
|
||||
new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS})
|
||||
.observeOn(mainThreadScheduler)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
freshStart = false;
|
||||
} else if (PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS.equals(intent.getAction())) {
|
||||
contributionDao.getContribution(Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE)
|
||||
.flatMapObservable(
|
||||
(Function<List<Contribution>, ObservableSource<Contribution>>) contributions -> Observable
|
||||
.fromIterable(contributions))
|
||||
.concatMapCompletable(contribution -> Completable.fromAction(() -> queue(contribution)))
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe();
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void showUploadNotification() {
|
||||
compositeDisposable.add(contributionDao
|
||||
.getPendingUploads(new int[]{Contribution.STATE_IN_PROGRESS, Contribution.STATE_QUEUED})
|
||||
.subscribe(count -> {
|
||||
if (count > 0) {
|
||||
startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS,
|
||||
curNotification.setContentText(getText(R.string.starting_uploads)).build());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private NotificationCompat.Builder getNotificationBuilder(String channelId) {
|
||||
return new NotificationCompat.Builder(this, channelId)
|
||||
.setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
|
||||
.setAutoCancel(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setProgress(100, 0, true)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0));
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void uploadContribution(Contribution contribution) {
|
||||
if (contribution.getLocalUri() == null || contribution.getLocalUri().getPath() == null) {
|
||||
Timber.d("localUri/path is null");
|
||||
return;
|
||||
}
|
||||
String notificationTag = contribution.getLocalUri().toString();
|
||||
|
||||
Timber.d("Before execution!");
|
||||
final Media media = contribution.getMedia();
|
||||
final String displayTitle = media.getDisplayTitle();
|
||||
curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start,
|
||||
displayTitle))
|
||||
.setContentText(getResources()
|
||||
.getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload,
|
||||
toUpload))
|
||||
.setTicker(getString(R.string.upload_progress_notification_title_in_progress,
|
||||
displayTitle))
|
||||
.setOngoing(true);
|
||||
notificationManager
|
||||
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
|
||||
|
||||
String filename = media.getFilename();
|
||||
|
||||
NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(
|
||||
notificationTag,
|
||||
getString(R.string.upload_progress_notification_title_in_progress,
|
||||
displayTitle),
|
||||
getString(R.string.upload_progress_notification_title_finishing,
|
||||
displayTitle),
|
||||
contribution
|
||||
);
|
||||
|
||||
Observable.fromCallable(() -> "Temp_" + contribution.hashCode() + filename)
|
||||
.flatMap(stashFilename -> uploadClient
|
||||
.uploadFileToStash(getApplicationContext(), stashFilename, contribution,
|
||||
notificationUpdater))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.doFinally(() -> {
|
||||
if (filename != null) {
|
||||
unfinishedUploads.remove(filename);
|
||||
}
|
||||
toUpload--;
|
||||
if (toUpload == 0) {
|
||||
// Sync modifications right after all uploads are processed
|
||||
ContentResolver
|
||||
.requestSync(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY,
|
||||
new Bundle());
|
||||
stopForeground(true);
|
||||
}
|
||||
})
|
||||
.flatMap(uploadStash -> {
|
||||
Timber.d("Upload stash result %s", uploadStash.toString());
|
||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||
|
||||
if (uploadStash.getState() == StashUploadState.SUCCESS) {
|
||||
Timber.d("making sure of uniqueness of name: %s", filename);
|
||||
String uniqueFilename = findUniqueFilename(filename);
|
||||
unfinishedUploads.add(uniqueFilename);
|
||||
return uploadClient.uploadFileFromStash(
|
||||
getApplicationContext(),
|
||||
contribution,
|
||||
uniqueFilename,
|
||||
uploadStash.getFileKey()).doOnError(new Consumer<Throwable>() {
|
||||
@Override
|
||||
public void accept(Throwable throwable) throws Exception {
|
||||
Timber.e(throwable, "Error occurred in uploading file from stash");
|
||||
if (STASH_ERROR_CODES.contains(throwable.getMessage())) {
|
||||
clearChunks(contribution);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (uploadStash.getState() == StashUploadState.PAUSED) {
|
||||
showPausedNotification(contribution);
|
||||
return Observable.never();
|
||||
} else {
|
||||
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
||||
showFailedNotification(contribution);
|
||||
return Observable.never();
|
||||
}
|
||||
})
|
||||
.subscribe(
|
||||
uploadResult -> onUpload(contribution, notificationTag, uploadResult),
|
||||
throwable -> {
|
||||
Timber.w(throwable, "Exception during upload");
|
||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||
showFailedNotification(contribution);
|
||||
});
|
||||
}
|
||||
|
||||
private void clearChunks(Contribution contribution) {
|
||||
contribution.setChunkInfo(null);
|
||||
compositeDisposable.add(contributionDao.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
private void onUpload(Contribution contribution, String notificationTag,
|
||||
UploadResult uploadResult) {
|
||||
|
||||
notificationManager.cancel(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS);
|
||||
|
||||
if (uploadResult.isSuccessful()) {
|
||||
onSuccessfulUpload(contribution, uploadResult);
|
||||
} else {
|
||||
Timber.d("Contribution upload failed. Wikidata entity won't be edited");
|
||||
showFailedNotification(contribution);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSuccessfulUpload(Contribution contribution, UploadResult uploadResult) {
|
||||
compositeDisposable
|
||||
.add(wikidataEditService.addDepictionsAndCaptions(uploadResult, contribution));
|
||||
WikidataPlace wikidataPlace = contribution.getWikidataPlace();
|
||||
if (wikidataPlace != null && wikidataPlace.getImageValue() == null) {
|
||||
if (!contribution.hasInvalidLocation()) {
|
||||
wikidataEditService.createClaim(wikidataPlace, uploadResult.getFilename(),
|
||||
contribution.getMedia().getCaptions());
|
||||
} else {
|
||||
ViewUtil.showShortToast(this, getString(R.string.wikidata_edit_failure));
|
||||
Timber
|
||||
.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
|
||||
}
|
||||
}
|
||||
saveCompletedContribution(contribution, uploadResult);
|
||||
}
|
||||
|
||||
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
|
||||
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
|
||||
.map(contribution::completeWith)
|
||||
.flatMapCompletable(
|
||||
newContribution -> {
|
||||
newContribution.setDateModified(new Date());
|
||||
return contributionDao.saveAndDelete(contribution, newContribution);
|
||||
})
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showFailedNotification(final Contribution contribution) {
|
||||
final String displayTitle = contribution.getMedia().getDisplayTitle();
|
||||
curNotification.setTicker(getString(R.string.upload_failed_notification_title, displayTitle))
|
||||
.setContentTitle(getString(R.string.upload_failed_notification_title, displayTitle))
|
||||
.setContentText(getString(R.string.upload_failed_notification_subtitle))
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false);
|
||||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_FAILED,
|
||||
curNotification.build());
|
||||
|
||||
contribution.setState(Contribution.STATE_FAILED);
|
||||
contribution.setChunkInfo(null);
|
||||
|
||||
compositeDisposable.add(contributionDao
|
||||
.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
private void showPausedNotification(final Contribution contribution) {
|
||||
final String displayTitle = contribution.getMedia().getDisplayTitle();
|
||||
curNotification.setTicker(getString(R.string.upload_paused_notification_title, displayTitle))
|
||||
.setContentTitle(getString(R.string.upload_paused_notification_title, displayTitle))
|
||||
.setContentText(getString(R.string.upload_paused_notification_subtitle))
|
||||
.setProgress(0, 0, false)
|
||||
.setOngoing(false);
|
||||
notificationManager.notify(contribution.getLocalUri().toString(), NOTIFICATION_UPLOAD_PAUSED,
|
||||
curNotification.build());
|
||||
|
||||
contribution.setState(Contribution.STATE_PAUSED);
|
||||
|
||||
compositeDisposable.add(contributionDao
|
||||
.update(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
String sequenceFileName;
|
||||
for (int sequenceNumber = 1; true; sequenceNumber++) {
|
||||
if (sequenceNumber == 1) {
|
||||
sequenceFileName = fileName;
|
||||
} else {
|
||||
if (fileName.indexOf('.') == -1) {
|
||||
// We really should have appended a filePath type suffix already.
|
||||
// But... we might not.
|
||||
sequenceFileName = fileName + " " + sequenceNumber;
|
||||
} else {
|
||||
Pattern regex = Pattern.compile("^(.*)(\\..+?)$");
|
||||
Matcher regexMatcher = regex.matcher(fileName);
|
||||
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
||||
}
|
||||
}
|
||||
if (!mediaClient.checkPageExistsUsingTitle(String.format("File:%s", sequenceFileName))
|
||||
.blockingGet()
|
||||
&& !unfinishedUploads.contains(sequenceFileName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sequenceFileName;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package fr.free.nrw.commons.upload.depicts
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* entity class for DepictsRoomDateBase
|
||||
*/
|
||||
@Entity(tableName = "depicts_table")
|
||||
data class Depicts (@PrimaryKey val item: DepictedItem, val lastUsed:Date)
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
package fr.free.nrw.commons.upload.depicts
|
||||
|
||||
import androidx.room.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Dao class for DepictsRoomDataBase
|
||||
*/
|
||||
@Dao
|
||||
abstract class DepictsDao {
|
||||
|
||||
/**
|
||||
* insert Depicts in DepictsRoomDataBase
|
||||
*/
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract suspend fun insert(depictedItem: Depicts)
|
||||
|
||||
/**
|
||||
* get all Depicts from roomdatabase
|
||||
*/
|
||||
@Query("Select * From depicts_table order by lastUsed DESC")
|
||||
abstract suspend fun getAllDepict(): List<Depicts>
|
||||
|
||||
/**
|
||||
* get all Depicts which need to delete from roomdatabase
|
||||
*/
|
||||
@Query("Select * From depicts_table order by lastUsed DESC LIMIT :n OFFSET 10")
|
||||
abstract suspend fun getItemToDelete(n: Int): List<Depicts>
|
||||
|
||||
/**
|
||||
* Delete Depicts from roomdatabase
|
||||
*/
|
||||
@Delete
|
||||
abstract suspend fun delete(depicts: Depicts)
|
||||
|
||||
lateinit var allDepict: List<Depicts>
|
||||
lateinit var listOfDelete: List<Depicts>
|
||||
|
||||
/**
|
||||
* get all depicts from DepictsRoomDatabase
|
||||
*/
|
||||
fun depictsList(): List<Depicts> {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
allDepict = getAllDepict()
|
||||
}
|
||||
}
|
||||
return allDepict
|
||||
}
|
||||
|
||||
/**
|
||||
* insert Depicts in DepictsRoomDataBase
|
||||
*/
|
||||
fun insertDepict(depictes: Depicts) {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
insert(depictes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get all Depicts item which need to delete
|
||||
*/
|
||||
fun getItemTodelete(number: Int): List<Depicts> {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
listOfDelete = getItemToDelete(number)
|
||||
}
|
||||
}
|
||||
return listOfDelete
|
||||
}
|
||||
|
||||
/**
|
||||
* delete Depicts in DepictsRoomDataBase
|
||||
*/
|
||||
fun deleteDepicts(depictes: Depicts) {
|
||||
runBlocking {
|
||||
launch(Dispatchers.IO) {
|
||||
delete(depictes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* save Depicts in DepictsRoomDataBase
|
||||
*/
|
||||
fun savingDepictsInRoomDataBase(listDepictedItem: List<DepictedItem>) {
|
||||
var numberofItemInRoomDataBase: Int
|
||||
val maxNumberOfItemSaveInRoom = 10
|
||||
|
||||
for (depictsItem in listDepictedItem) {
|
||||
depictsItem.isSelected = false
|
||||
insertDepict(Depicts(depictsItem, Date()))
|
||||
}
|
||||
|
||||
numberofItemInRoomDataBase = depictsList().size
|
||||
// delete the depictItem from depictsroomdataBase when number of element in depictsroomdataBase is greater than 10
|
||||
if (numberofItemInRoomDataBase > maxNumberOfItemSaveInRoom) {
|
||||
|
||||
val listOfDepictsToDelete: List<Depicts> =
|
||||
getItemTodelete(numberofItemInRoomDataBase)
|
||||
for (i in listOfDepictsToDelete) {
|
||||
deleteDepicts(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,10 +21,12 @@ import com.jakewharton.rxbinding2.view.RxView;
|
|||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||
import fr.free.nrw.commons.upload.UploadModel;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -54,7 +56,6 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
|||
DepictsContract.UserActionListener presenter;
|
||||
private UploadDepictsAdapter adapter;
|
||||
private Disposable subscribe;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public android.view.View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
|
|
@ -174,8 +175,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
|||
*
|
||||
* @param query query string
|
||||
*/
|
||||
private void searchForDepictions(String query) {
|
||||
private void searchForDepictions(final String query) {
|
||||
presenter.searchForDepictions(query);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import io.reactivex.disposables.CompositeDisposable
|
|||
import io.reactivex.processors.PublishProcessor
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Proxy
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -34,6 +35,8 @@ class DepictsPresenter @Inject constructor(
|
|||
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||
private val searchTerm: PublishProcessor<String> = PublishProcessor.create()
|
||||
private val depictedItems: MutableLiveData<List<DepictedItem>> = MutableLiveData()
|
||||
@Inject
|
||||
lateinit var depictsDao: DepictsDao;
|
||||
|
||||
override fun onAttachView(view: DepictsContract.View) {
|
||||
this.view = view
|
||||
|
|
@ -62,10 +65,15 @@ class DepictsPresenter @Inject constructor(
|
|||
return searchResults(term).map { Pair(it, term) }
|
||||
}
|
||||
|
||||
private fun searchResults(it: String): Flowable<List<DepictedItem>> {
|
||||
return repository.searchAllEntities(it)
|
||||
private fun searchResults(querystring: String): Flowable<List<DepictedItem>> {
|
||||
var recentDepictedItemList: MutableList<DepictedItem> = ArrayList();
|
||||
//show recentDepictedItemList when queryString is empty
|
||||
if (querystring.isEmpty()) {
|
||||
recentDepictedItemList = getRecentDepictedItems();
|
||||
}
|
||||
return repository.searchAllEntities(querystring)
|
||||
.subscribeOn(ioScheduler)
|
||||
.map { repository.selectedDepictions + it }
|
||||
.map { repository.selectedDepictions + it + recentDepictedItemList }
|
||||
.map { it.filterNot { item -> WikidataDisambiguationItems.isDisambiguationItem(item.instanceOfs) } }
|
||||
.map { it.distinctBy(DepictedItem::id) }
|
||||
}
|
||||
|
|
@ -102,11 +110,28 @@ class DepictsPresenter @Inject constructor(
|
|||
*/
|
||||
override fun verifyDepictions() {
|
||||
if (repository.selectedDepictions.isNotEmpty()) {
|
||||
if (::depictsDao.isInitialized) {
|
||||
//save all the selected Depicted item in room Database
|
||||
depictsDao.savingDepictsInRoomDataBase(repository.selectedDepictions)
|
||||
}
|
||||
view.goToNextScreen()
|
||||
} else {
|
||||
view.noDepictionSelected()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the depicts from DepictsRoomdataBase
|
||||
*/
|
||||
fun getRecentDepictedItems(): MutableList<DepictedItem> {
|
||||
val depictedItemList: MutableList<DepictedItem> = ArrayList()
|
||||
val depictsList = depictsDao.depictsList()
|
||||
for (i in depictsList.indices) {
|
||||
val depictedItem = depictsList[i].item
|
||||
depictedItemList.add(depictedItem)
|
||||
}
|
||||
return depictedItemList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import android.widget.CheckBox;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatButton;
|
||||
|
|
@ -38,8 +39,6 @@ import fr.free.nrw.commons.upload.UploadItem;
|
|||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -69,8 +68,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
@BindView(R.id.tooltip)
|
||||
ImageView tooltip;
|
||||
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
|
||||
@BindView(R.id.btn_copy_prev_title_desc)
|
||||
AppCompatButton btnCopyPreviousTitleDesc;
|
||||
@BindView(R.id.btn_copy_subsequent_media)
|
||||
AppCompatButton btnCopyToSubsequentMedia;
|
||||
|
||||
@Inject
|
||||
UploadMediaDetailsContract.UserActionListener presenter;
|
||||
|
|
@ -84,6 +83,19 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
|
||||
private boolean isExpanded = true;
|
||||
|
||||
/**
|
||||
* showNearbyFound will be true, if any nearby location found that needs pictures and the nearby popup is yet to be shown
|
||||
* Used to show and check if the nearby found popup is already shown
|
||||
*/
|
||||
private boolean showNearbyFound;
|
||||
|
||||
/**
|
||||
* nearbyPlace holds the detail of nearby place that need pictures, if any found
|
||||
*/
|
||||
private Place nearbyPlace;
|
||||
private UploadItem uploadItem;
|
||||
|
||||
|
||||
private UploadMediaDetailFragmentCallback callback;
|
||||
|
||||
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
||||
|
|
@ -123,9 +135,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip);
|
||||
}
|
||||
});
|
||||
initRecyclerView();
|
||||
initPresenter();
|
||||
presenter.receiveImage(uploadableFile, place);
|
||||
initRecyclerView();
|
||||
|
||||
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||
btnPrevious.setEnabled(false);
|
||||
|
|
@ -135,11 +147,11 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
btnPrevious.setAlpha(1.0f);
|
||||
}
|
||||
|
||||
//If this is the first media, we have nothing to copy, lets not show the button
|
||||
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||
btnCopyPreviousTitleDesc.setVisibility(View.GONE);
|
||||
//If this is the last media, we have nothing to copy, lets not show the button
|
||||
if (callback.getIndexInViewFlipper(this) == callback.getTotalNumberOfSteps()-4) {
|
||||
btnCopyToSubsequentMedia.setVisibility(View.GONE);
|
||||
} else {
|
||||
btnCopyPreviousTitleDesc.setVisibility(View.VISIBLE);
|
||||
btnCopyToSubsequentMedia.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
attachImageViewScaleChangeListener();
|
||||
|
|
@ -168,7 +180,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
* init the description recycler veiw and caption recyclerview
|
||||
*/
|
||||
private void initRecyclerView() {
|
||||
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(defaultKvStore.getString(Prefs.KEY_LANGUAGE_VALUE, ""));
|
||||
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""));
|
||||
uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
|
||||
uploadMediaDetailAdapter.setEventListener(this);
|
||||
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
|
@ -199,7 +211,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
UploadMediaDetail uploadMediaDetail = new UploadMediaDetail();
|
||||
uploadMediaDetail.setManuallyAdded(true);//This was manually added by the user
|
||||
uploadMediaDetailAdapter.addDescription(uploadMediaDetail);
|
||||
rvDescriptions.scrollToPosition(uploadMediaDetailAdapter.getItemCount()-1);
|
||||
rvDescriptions.smoothScrollToPosition(uploadMediaDetailAdapter.getItemCount()-1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -231,13 +243,30 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
/**
|
||||
* Shows popup if any nearby location needing pictures matches uploadable picture's GPS location
|
||||
* Sets variables to Show popup if any nearby location needing pictures matches uploadable picture's GPS location
|
||||
* @param uploadItem
|
||||
* @param place
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
@Override
|
||||
public void onNearbyPlaceFound(UploadItem uploadItem, Place place) {
|
||||
nearbyPlace = place;
|
||||
this.uploadItem = uploadItem;
|
||||
showNearbyFound = true;
|
||||
if(callback.getIndexInViewFlipper(this) == 0) {
|
||||
showNearbyPlaceFound(nearbyPlace);
|
||||
showNearbyFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows nearby place found popup
|
||||
* @param place
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid") // To avoid the unwanted lint warning that string 'upload_nearby_place_found_description' is not of a valid format
|
||||
private void showNearbyPlaceFound(Place place) {
|
||||
final View customLayout = getLayoutInflater().inflate(R.layout.custom_nearby_found, null);
|
||||
ImageView nearbyFoundImage = customLayout.findViewById(R.id.nearbyItemImage);
|
||||
nearbyFoundImage.setImageURI(uploadItem.getMediaUri());
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
getString(R.string.upload_nearby_place_found_title),
|
||||
String.format(Locale.getDefault(),
|
||||
|
|
@ -248,7 +277,8 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
},
|
||||
() -> {
|
||||
|
||||
});
|
||||
},
|
||||
customLayout, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -261,6 +291,19 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets called whenever the next/previous button is pressed
|
||||
*/
|
||||
@Override
|
||||
protected void onBecameVisible() {
|
||||
super.onBecameVisible();
|
||||
presenter.fetchTitleAndDescription(callback.getIndexInViewFlipper(this));
|
||||
if(showNearbyFound) {
|
||||
showNearbyPlaceFound(nearbyPlace);
|
||||
showNearbyFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(int stringResourceId, int colorResourceId) {
|
||||
ViewUtil.showLongToast(getContext(), stringResourceId);
|
||||
|
|
@ -322,6 +365,14 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
//If the error message is null, we will probably not show anything
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showConnectionErrorPopup() {
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
getString(R.string.upload_connection_error_alert_title),
|
||||
getString(R.string.upload_connection_error_alert_detail), getString(R.string.ok),
|
||||
() -> {}, true);
|
||||
}
|
||||
|
||||
@Override public void showMapWithImageCoordinates(boolean shouldShow) {
|
||||
ibMap.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
|
@ -369,6 +420,9 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
|
||||
@Override
|
||||
public void onPrimaryCaptionTextChange(boolean isNotEmpty) {
|
||||
btnCopyToSubsequentMedia.setEnabled(isNotEmpty);
|
||||
btnCopyToSubsequentMedia.setClickable(isNotEmpty);
|
||||
btnCopyToSubsequentMedia.setAlpha(isNotEmpty ? 1.0f: 0.5f);
|
||||
btnNext.setEnabled(isNotEmpty);
|
||||
btnNext.setClickable(isNotEmpty);
|
||||
btnNext.setAlpha(isNotEmpty ? 1.0f: 0.5f);
|
||||
|
|
@ -380,9 +434,10 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
}
|
||||
|
||||
|
||||
@OnClick(R.id.btn_copy_prev_title_desc)
|
||||
public void onButtonCopyPreviousTitleDesc(){
|
||||
presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this));
|
||||
@OnClick(R.id.btn_copy_subsequent_media)
|
||||
public void onButtonCopyTitleDescToSubsequentMedia(){
|
||||
presenter.copyTitleAndDescriptionToSubsequentMedia(callback.getIndexInViewFlipper(this));
|
||||
Toast.makeText(getContext(), getResources().getString(R.string.copied_successfully), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ public interface UploadMediaDetailsContract {
|
|||
|
||||
void showBadImagePopup(Integer errorCode, UploadItem uploadItem);
|
||||
|
||||
void showConnectionErrorPopup();
|
||||
|
||||
void showMapWithImageCoordinates(boolean shouldShow);
|
||||
|
||||
void showExternalMap(UploadItem uploadItem);
|
||||
|
|
@ -45,7 +47,9 @@ public interface UploadMediaDetailsContract {
|
|||
|
||||
void verifyImageQuality(int uploadItemIndex);
|
||||
|
||||
void fetchPreviousTitleAndDescription(int indexInViewFlipper);
|
||||
void copyTitleAndDescriptionToSubsequentMedia(int indexInViewFlipper);
|
||||
|
||||
void fetchTitleAndDescription(int indexInViewFlipper);
|
||||
|
||||
void useSimilarPictureCoordinates(ImageCoordinates imageCoordinates, int uploadItemIndex);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import io.reactivex.Scheduler;
|
|||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -92,7 +93,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
gpsCoords != null && gpsCoords.getImageCoordsExists();
|
||||
view.showMapWithImageCoordinates(hasImageCoordinates);
|
||||
view.showProgress(false);
|
||||
if (hasImageCoordinates) {
|
||||
if (hasImageCoordinates && place == null) {
|
||||
checkNearbyPlaces(uploadItem);
|
||||
}
|
||||
},
|
||||
|
|
@ -138,8 +139,12 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
},
|
||||
throwable -> {
|
||||
view.showProgress(false);
|
||||
view.showMessage("" + throwable.getLocalizedMessage(),
|
||||
R.color.color_error);
|
||||
if (throwable instanceof UnknownHostException) {
|
||||
view.showConnectionErrorPopup();
|
||||
} else {
|
||||
view.showMessage("" + throwable.getLocalizedMessage(),
|
||||
R.color.color_error);
|
||||
}
|
||||
Timber.e(throwable, "Error occurred while handling image");
|
||||
})
|
||||
);
|
||||
|
|
@ -147,22 +152,29 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
|
||||
|
||||
/**
|
||||
* Fetches and sets the caption and desctiption of the previous item
|
||||
* Copies the caption and description of the current item to the subsequent media
|
||||
*
|
||||
* @param indexInViewFlipper
|
||||
*/
|
||||
@Override
|
||||
public void fetchPreviousTitleAndDescription(int indexInViewFlipper) {
|
||||
UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper);
|
||||
if (null != previousUploadItem) {
|
||||
final UploadItem currentUploadItem = repository.getUploads().get(indexInViewFlipper);
|
||||
currentUploadItem.setMediaDetails(deepCopy(previousUploadItem.getUploadMediaDetails()));
|
||||
view.updateMediaDetails(currentUploadItem.getUploadMediaDetails());
|
||||
} else {
|
||||
view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error);
|
||||
}
|
||||
public void copyTitleAndDescriptionToSubsequentMedia(int indexInViewFlipper) {
|
||||
for(int i = indexInViewFlipper+1; i < repository.getCount(); i++){
|
||||
final UploadItem subsequentUploadItem = repository.getUploads().get(i);
|
||||
subsequentUploadItem.setMediaDetails(deepCopy(repository.getUploads().get(indexInViewFlipper).getUploadMediaDetails()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and set the caption and description of the item
|
||||
*
|
||||
* @param indexInViewFlipper
|
||||
*/
|
||||
@Override
|
||||
public void fetchTitleAndDescription(int indexInViewFlipper) {
|
||||
final UploadItem currentUploadItem = repository.getUploads().get(indexInViewFlipper);
|
||||
view.updateMediaDetails(currentUploadItem.getUploadMediaDetails());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<UploadMediaDetail> deepCopy(List<UploadMediaDetail> uploadMediaDetails) {
|
||||
final ArrayList<UploadMediaDetail> newList = new ArrayList<>();
|
||||
|
|
@ -187,6 +199,9 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
|
|||
final List<UploadMediaDetail> uploadMediaDetails = repository.getUploads()
|
||||
.get(uploadItemPosition)
|
||||
.getUploadMediaDetails();
|
||||
UploadItem uploadItem = repository.getUploads()
|
||||
.get(uploadItemPosition);
|
||||
uploadItem.setPlace(place);
|
||||
uploadMediaDetails.set(0, new UploadMediaDetail(place));
|
||||
view.updateMediaDetails(uploadMediaDetails);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package fr.free.nrw.commons.upload.structure.depictions
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.WikidataItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||
|
|
@ -20,6 +22,7 @@ const val THUMB_IMAGE_SIZE = "70px"
|
|||
* Model class for Depicted Item in Upload and Explore
|
||||
*/
|
||||
@Parcelize
|
||||
@Entity
|
||||
data class DepictedItem constructor(
|
||||
override val name: String,
|
||||
val description: String?,
|
||||
|
|
@ -27,7 +30,7 @@ data class DepictedItem constructor(
|
|||
val instanceOfs: List<String>,
|
||||
val commonsCategories: List<String>,
|
||||
var isSelected: Boolean,
|
||||
override val id: String
|
||||
@PrimaryKey override val id: String
|
||||
) : WikidataItem, Parcelable {
|
||||
|
||||
constructor(entity: Entities.Entity) : this(
|
||||
|
|
|
|||
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