Merge branch 'master' of git://github.com/commons-app/apps-android-commons
|
|
@ -19,12 +19,13 @@ android:
|
||||||
components:
|
components:
|
||||||
- tools
|
- tools
|
||||||
- platform-tools
|
- platform-tools
|
||||||
- build-tools-26.0.2
|
- build-tools-27.0.0
|
||||||
- extra-google-m2repository
|
- extra-google-m2repository
|
||||||
- extra-android-m2repository
|
- extra-android-m2repository
|
||||||
- ${ANDROID_TARGET}
|
- ${ANDROID_TARGET}
|
||||||
- android-25
|
- android-25
|
||||||
- android-26
|
- android-26
|
||||||
|
- android-27
|
||||||
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
|
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
|
||||||
licenses:
|
licenses:
|
||||||
- 'android-sdk-license-.+'
|
- 'android-sdk-license-.+'
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,14 @@
|
||||||
# Wikimedia Commons for Android
|
# Wikimedia Commons for Android
|
||||||
|
|
||||||
|
## v2.7.2
|
||||||
|
- Modified subtext for "automatically get current location" setting to emphasize that it will reveal user's location
|
||||||
|
|
||||||
|
## v2.7.1
|
||||||
|
- Fixed UI and permission issues with Nearby
|
||||||
|
- Fixed issue with My Recent Uploads being empty
|
||||||
|
- Fixed blank category issue when uploading directly from Nearby
|
||||||
|
- Various crash fixes
|
||||||
|
|
||||||
## v2.7.0
|
## v2.7.0
|
||||||
- New Nearby Places UI with direct uploads (and associated category suggestions)
|
- New Nearby Places UI with direct uploads (and associated category suggestions)
|
||||||
- Added two-factor authentication login
|
- Added two-factor authentication login
|
||||||
|
|
|
||||||
|
|
@ -1 +1,34 @@
|
||||||
Please see our guidelines in the wiki: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
|
Thanks for considering to contribute to this project! A few guidelines for
|
||||||
|
people who want to contribute their code to this software are documented in
|
||||||
|
[this project's Wiki](https://github.com/commons-app/apps-android-commons/wiki/Contributing-Guidelines).
|
||||||
|
If you're not sure where to start head on to [this wiki page](https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome!).
|
||||||
|
|
||||||
|
Here's a gist of the guidelines,
|
||||||
|
|
||||||
|
1. Make separate commits for logically separate changes
|
||||||
|
|
||||||
|
1. Describe your changes well in the commit message
|
||||||
|
|
||||||
|
The first line of the commit message should be a short description of what has
|
||||||
|
changed. It is also good to prefix the first line with "area: " where the "area"
|
||||||
|
is a filename or identifier for the general area of the code being modified.
|
||||||
|
The body should provide a meaningful commit message.
|
||||||
|
|
||||||
|
1. Write Javadocs
|
||||||
|
|
||||||
|
We require contributors to include Javadocs for all new methods and classes
|
||||||
|
submitted via PRs (after 1 May 2018). This is aimed at making it easier for
|
||||||
|
new contributors to dive into our codebase, especially those who are new to
|
||||||
|
Android development. A few things to note:
|
||||||
|
|
||||||
|
- This should not replace the need for code that is easily-readable in
|
||||||
|
and of itself
|
||||||
|
- Please make sure that your Javadocs are reasonably descriptive, not just
|
||||||
|
a copy of the method name
|
||||||
|
- Please do not use `@author` tags - we aim for collective code ownership,
|
||||||
|
and if needed, Git allows us to see who wrote something without needing
|
||||||
|
to add these tags (`git blame`)
|
||||||
|
|
||||||
|
1. Write tests for your code (if possible)
|
||||||
|
|
||||||
|
1. Make sure the Wiki pages don't become stale by updating them (if needed)
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,19 @@
|
||||||
## Description
|
## Title (required)
|
||||||
|
|
||||||
Fixes #{GitHub issue number}
|
Fixes #{GitHub issue number and title (Please do not forget adding title) }
|
||||||
|
|
||||||
|
## Description (required)
|
||||||
|
|
||||||
|
Fixes #{GitHub issue number and title}
|
||||||
|
|
||||||
{Describe the changes made and why they were made.}
|
{Describe the changes made and why they were made.}
|
||||||
|
|
||||||
## Tests performed
|
## Tests performed (required)
|
||||||
|
|
||||||
Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}.
|
Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}.
|
||||||
|
|
||||||
{Please test your PR at least once before submitting.}
|
## Screenshots showing what changed (optional)
|
||||||
|
|
||||||
## Screenshots showing what changed
|
|
||||||
|
|
||||||
{Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)}
|
{Only for user interface changes, otherwise remove this section. See [how to take a screenshot](https://android.stackexchange.com/questions/1759/how-to-take-a-screenshot-with-an-android-device)}
|
||||||
|
|
||||||
|
_Note: Please ensure that you have read CONTRIBUTING.md if this is your first pull request._
|
||||||
|
|
@ -7,10 +7,12 @@ apply from: 'quality.gradle'
|
||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
|
implementation 'com.prof.rssparser:rssparser:1.1'
|
||||||
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
implementation 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
||||||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
implementation 'in.yuvi:http.fluent:1.3'
|
implementation 'in.yuvi:http.fluent:1.3'
|
||||||
implementation 'com.android.volley:volley:1.0.0'
|
implementation 'com.github.chrisbanes:PhotoView:2.0.0'
|
||||||
implementation 'ch.acra:acra:4.9.2'
|
implementation 'ch.acra:acra:4.9.2'
|
||||||
implementation 'org.mediawiki:api:1.3'
|
implementation 'org.mediawiki:api:1.3'
|
||||||
implementation 'commons-codec:commons-codec:1.10'
|
implementation 'commons-codec:commons-codec:1.10'
|
||||||
|
|
@ -18,69 +20,59 @@ dependencies {
|
||||||
implementation 'com.google.code.gson:gson:2.8.1'
|
implementation 'com.google.code.gson:gson:2.8.1'
|
||||||
implementation 'com.jakewharton.timber:timber:4.5.1'
|
implementation 'com.jakewharton.timber:timber:4.5.1'
|
||||||
implementation 'info.debatty:java-string-similarity:0.24'
|
implementation 'info.debatty:java-string-similarity:0.24'
|
||||||
implementation ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar'){
|
implementation 'com.borjabravo:readmoretextview:2.1.0'
|
||||||
transitive=true
|
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||||
|
implementation('com.mapbox.mapboxsdk:mapbox-android-sdk:5.4.1@aar') {
|
||||||
|
transitive = true
|
||||||
}
|
}
|
||||||
|
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
|
||||||
|
|
||||||
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:design:$SUPPORT_LIB_VERSION"
|
||||||
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:customtabs:$SUPPORT_LIB_VERSION"
|
||||||
|
|
||||||
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
|
implementation "com.android.support:cardview-v7:$SUPPORT_LIB_VERSION"
|
||||||
|
|
||||||
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.8.1'
|
|
||||||
implementation 'com.squareup.okio:okio:1.13.0'
|
implementation 'com.squareup.okio:okio:1.13.0'
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||||
// Because RxAndroid releases are few and far between, it is recommended you also
|
// Because RxAndroid releases are few and far between, it is recommended you also
|
||||||
// explicitly depend on RxJava's latest version for bug fixes and new features.
|
// explicitly depend on RxJava's latest version for bug fixes and new features.
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
implementation 'com.android.support:multidex:1.0.3'
|
||||||
|
|
||||||
testImplementation "org.robolectric:multidex:3.4.2"
|
|
||||||
|
|
||||||
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
|
implementation 'io.reactivex.rxjava2:rxjava:2.1.2'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
|
||||||
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
implementation 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
|
||||||
|
implementation 'org.jsoup:jsoup:1.11.3'
|
||||||
implementation 'com.facebook.fresco:fresco:1.5.0'
|
implementation 'com.facebook.fresco:fresco:1.5.0'
|
||||||
implementation 'com.facebook.stetho:stetho:1.5.0'
|
implementation 'com.facebook.stetho:stetho:1.5.0'
|
||||||
|
|
||||||
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
||||||
implementation "com.google.dagger:dagger-android-support:$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-android-processor:$DAGGER_VERSION"
|
||||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
||||||
|
testImplementation 'org.robolectric:multidex:3.4.2'
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.robolectric:robolectric:3.7.1'
|
testImplementation 'org.robolectric:robolectric:3.7.1'
|
||||||
testImplementation 'org.mockito:mockito-all:1.10.19'
|
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||||
|
|
||||||
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
testImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||||
|
|
||||||
|
implementation 'com.caverock:androidsvg:1.2.1'
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.7.1'
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.7.1'
|
||||||
|
|
||||||
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
|
||||||
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||||
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
|
androidTestImplementation "com.android.support:support-annotations:$SUPPORT_LIB_VERSION"
|
||||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
|
androidTestImplementation 'com.android.support.test:rules:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
|
||||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
|
debugImplementation "com.squareup.leakcanary:leakcanary-android:$LEAK_CANARY"
|
||||||
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
releaseImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
||||||
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
testImplementation "com.squareup.leakcanary:leakcanary-android-no-op:$LEAK_CANARY"
|
||||||
|
|
||||||
implementation "com.google.dagger:dagger:$DAGGER_VERSION"
|
|
||||||
implementation "com.google.dagger:dagger-android-support:$DAGGER_VERSION"
|
|
||||||
kapt "com.google.dagger:dagger-compiler:$DAGGER_VERSION"
|
|
||||||
kapt "com.google.dagger:dagger-android-processor:$DAGGER_VERSION"
|
|
||||||
|
|
||||||
implementation 'com.borjabravo:readmoretextview:2.1.0'
|
|
||||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -91,8 +83,8 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'fr.free.nrw.commons'
|
applicationId 'fr.free.nrw.commons'
|
||||||
versionCode 83
|
versionCode 85
|
||||||
versionName '2.7.0'
|
versionName '2.7.2'
|
||||||
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
|
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
|
||||||
|
|
||||||
minSdkVersion project.minSdkVersion
|
minSdkVersion project.minSdkVersion
|
||||||
|
|
@ -121,7 +113,7 @@ android {
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
|
minifyEnabled false // See https://stackoverflow.com/questions/40232404/google-play-apk-and-android-studio-apk-usb-debug-behaving-differently - proguard.cfg modification alone insufficient.
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt', 'proguard-glide.txt'
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
|
|
@ -133,7 +125,9 @@ android {
|
||||||
flavorDimensions 'tier'
|
flavorDimensions 'tier'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
prod {
|
prod {
|
||||||
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.org/w/api.php\""
|
||||||
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
|
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.wikimedia.org/wikipedia/commons\""
|
||||||
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
|
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.org/wiki/\""
|
||||||
|
|
@ -149,7 +143,9 @@ android {
|
||||||
|
|
||||||
beta {
|
beta {
|
||||||
// What values do we need to hit the BETA versions of the site / api ?
|
// What values do we need to hit the BETA versions of the site / api ?
|
||||||
|
buildConfigField "String", "WIKIMEDIA_API_POTD", "\"https://commons.wikimedia.org/w/api.php?action=featuredfeed&feed=potd&feedformat=rss&language=en\""
|
||||||
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
buildConfigField "String", "WIKIMEDIA_API_HOST", "\"https://commons.wikimedia.beta.wmflabs.org/w/api.php\""
|
||||||
|
buildConfigField "String", "WIKIDATA_API_HOST", "\"https://www.wikidata.org/w/api.php\""
|
||||||
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
buildConfigField "String", "WIKIMEDIA_FORGE_API_HOST", "\"https://tools.wmflabs.org/\""
|
||||||
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
|
buildConfigField "String", "IMAGE_URL_BASE", "\"https://upload.beta.wmflabs.org/wikipedia/commons\""
|
||||||
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""
|
buildConfigField "String", "HOME_URL", "\"https://commons.wikimedia.beta.wmflabs.org/wiki/\""
|
||||||
|
|
|
||||||
BIN
app/libs/java-json.jar
Normal file
9
app/proguard-glide.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# for DexGuard only
|
||||||
|
-keepresourcexmlelements manifest/application/meta-data@value=GlideModule
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-keep class org.apache.http.** { *; }
|
-keep class org.apache.http.** { *; }
|
||||||
-dontwarn org.apache.http.**
|
-dontwarn org.apache.http.**
|
||||||
-keep class fr.free.nrw.commons.upload.MwVolleyApi$Page {*;}
|
|
||||||
-keep class android.support.v7.widget.ShareActionProvider { *; }
|
-keep class android.support.v7.widget.ShareActionProvider { *; }
|
||||||
|
|
@ -18,7 +18,7 @@ task checkstyle(type: Checkstyle) {
|
||||||
reports {
|
reports {
|
||||||
html {
|
html {
|
||||||
enabled true
|
enabled true
|
||||||
destination "${project.buildDir}/reports/checkstyle/checkstyle.html"
|
destination file("${project.buildDir}/reports/checkstyle/checkstyle.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -36,10 +36,10 @@ task pmd(type: Pmd) {
|
||||||
xml.enabled = false
|
xml.enabled = false
|
||||||
html.enabled = true
|
html.enabled = true
|
||||||
xml {
|
xml {
|
||||||
destination "${project.buildDir}/reports/pmd/pmd.xml"
|
destination file("${project.buildDir}/reports/pmd/pmd.xml")
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
destination "${project.buildDir}/reports/pmd/pmd.html"
|
destination file("${project.buildDir}/reports/pmd/pmd.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
|
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
|
||||||
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
|
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
|
||||||
<uses-permission android:name="android.permission.READ_LOGS"/>
|
<uses-permission android:name="android.permission.READ_LOGS"/>
|
||||||
|
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
||||||
|
|
||||||
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
|
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
|
||||||
<uses-feature android:name="android.hardware.location.gps" />
|
<uses-feature android:name="android.hardware.location.gps" />
|
||||||
|
|
@ -26,10 +27,10 @@
|
||||||
android:theme="@style/LightAppTheme"
|
android:theme="@style/LightAppTheme"
|
||||||
android:supportsRtl="true" >
|
android:supportsRtl="true" >
|
||||||
<activity android:name="org.acra.CrashReportDialog"
|
<activity android:name="org.acra.CrashReportDialog"
|
||||||
android:theme="@android:style/Theme.Dialog"
|
android:theme="@android:style/Theme.Dialog"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:finishOnTaskLaunch="true" />
|
android:finishOnTaskLaunch="true" />
|
||||||
|
|
||||||
<activity android:name=".auth.LoginActivity">
|
<activity android:name=".auth.LoginActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
@ -91,6 +92,11 @@
|
||||||
android:name=".notification.NotificationActivity"
|
android:name=".notification.NotificationActivity"
|
||||||
android:label="@string/navigation_item_notification" />
|
android:label="@string/navigation_item_notification" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".category.CategoryImagesActivity"
|
||||||
|
android:label="@string/title_activity_featured_images"
|
||||||
|
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||||
|
|
||||||
<service android:name=".upload.UploadService" />
|
<service android:name=".upload.UploadService" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
|
|
@ -159,6 +165,16 @@
|
||||||
android:label="@string/provider_categories"
|
android:label="@string/provider_categories"
|
||||||
android:syncable="false" />
|
android:syncable="false" />
|
||||||
|
|
||||||
|
<receiver android:name=".widget.PicOfDayAppWidget">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/pic_of_day_app_widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,6 @@ import android.os.Bundle;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.style.UnderlineSpan;
|
import android.text.style.UnderlineSpan;
|
||||||
import android.util.Log;
|
|
||||||
import android.support.customtabs.CustomTabsIntent;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -20,7 +17,6 @@ import android.widget.ArrayAdapter;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
@ -28,8 +24,6 @@ import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents about screen of this app
|
* Represents about screen of this app
|
||||||
*/
|
*/
|
||||||
|
|
@ -135,9 +129,10 @@ public class AboutActivity extends NavigationBaseActivity {
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.share_app_icon:
|
case R.id.share_app_icon:
|
||||||
|
String shareText = "Upload photos to Wikimedia Commons on your phone\nDownload the Commons app: http://play.google.com/store/apps/details?id=fr.free.nrw.commons";
|
||||||
Intent sendIntent = new Intent();
|
Intent sendIntent = new Intent();
|
||||||
sendIntent.setAction(Intent.ACTION_SEND);
|
sendIntent.setAction(Intent.ACTION_SEND);
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, "http://play.google.com/store/apps/details?id=fr.free.nrw.commons");
|
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
|
||||||
sendIntent.setType("text/plain");
|
sendIntent.setType("text/plain");
|
||||||
startActivity(Intent.createChooser(sendIntent, "Share app via..."));
|
startActivity(Intent.createChooser(sendIntent, "Share app via..."));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
|
@ -27,7 +26,7 @@ import fr.free.nrw.commons.contributions.ContributionDao;
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||||
import fr.free.nrw.commons.utils.FileUtils;
|
import fr.free.nrw.commons.upload.FileUtils;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ public class MediaDataExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
Timber.d("Nominated for deletion: " + mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename));
|
deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/" + filename);
|
||||||
deletionStatus = mediaWikiApi.pageExists("Commons:Deletion_requests/"+filename);
|
Timber.d("Nominated for deletion: " + deletionStatus);
|
||||||
}
|
}
|
||||||
catch (Exception e){
|
catch (Exception e){
|
||||||
Timber.d(e.getMessage());
|
Timber.d(e.getMessage());
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,7 @@ public class Utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void handleWebUrl(Context context, Uri url) {
|
public static void handleWebUrl(Context context, Uri url) {
|
||||||
|
Timber.d("Launching web url %s", url.toString());
|
||||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, url);
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, url);
|
||||||
if (browserIntent.resolveActivity(context.getPackageManager()) == null) {
|
if (browserIntent.resolveActivity(context.getPackageManager()) == null) {
|
||||||
Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT);
|
Toast toast = Toast.makeText(context, context.getString(R.string.no_web_browser), LENGTH_SHORT);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.view.PagerAdapter;
|
import android.support.v4.view.PagerAdapter;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -9,6 +10,7 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
|
import butterknife.Optional;
|
||||||
|
|
||||||
public class WelcomePagerAdapter extends PagerAdapter {
|
public class WelcomePagerAdapter extends PagerAdapter {
|
||||||
static final int[] PAGE_LAYOUTS = new int[]{
|
static final int[] PAGE_LAYOUTS = new int[]{
|
||||||
|
|
@ -20,6 +22,7 @@ public class WelcomePagerAdapter extends PagerAdapter {
|
||||||
};
|
};
|
||||||
private static final int PAGE_FINAL = 4;
|
private static final int PAGE_FINAL = 4;
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
private ViewGroup container;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes callback to provided one
|
* Changes callback to provided one
|
||||||
|
|
@ -53,6 +56,7 @@ public class WelcomePagerAdapter extends PagerAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object instantiateItem(ViewGroup container, int position) {
|
public Object instantiateItem(ViewGroup container, int position) {
|
||||||
|
this.container=container;
|
||||||
LayoutInflater inflater = LayoutInflater.from(container.getContext());
|
LayoutInflater inflater = LayoutInflater.from(container.getContext());
|
||||||
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
|
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
|
||||||
if( BuildConfig.FLAVOR == "beta"){
|
if( BuildConfig.FLAVOR == "beta"){
|
||||||
|
|
@ -102,5 +106,15 @@ public class WelcomePagerAdapter extends PagerAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Optional
|
||||||
|
@OnClick(R.id.welcomeInfo)
|
||||||
|
void onHelpClicked () {
|
||||||
|
try {
|
||||||
|
Utils.handleWebUrl(container.getContext(),Uri.parse("https://commons.wikimedia.org/wiki/Help:Contents" ));
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import android.accounts.Account;
|
||||||
import android.accounts.AccountAuthenticatorActivity;
|
import android.accounts.AccountAuthenticatorActivity;
|
||||||
import android.accounts.AccountAuthenticatorResponse;
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
import android.accounts.AccountManager;
|
import android.accounts.AccountManager;
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -19,18 +19,16 @@ import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AppCompatDelegate;
|
import android.support.v7.app.AppCompatDelegate;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
@ -48,6 +46,7 @@ import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
@ -85,6 +84,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
private LoginTextWatcher textWatcher = new LoginTextWatcher();
|
private LoginTextWatcher textWatcher = new LoginTextWatcher();
|
||||||
|
|
||||||
private Boolean loginCurrentlyInProgress = false;
|
private Boolean loginCurrentlyInProgress = false;
|
||||||
|
private Boolean errorMessageShown = false;
|
||||||
|
private String resultantError;
|
||||||
|
private static final String RESULTANT_ERROR = "resultantError";
|
||||||
|
private static final String ERROR_MESSAGE_SHOWN = "errorMessageShown";
|
||||||
private static final String LOGING_IN = "logingIn";
|
private static final String LOGING_IN = "logingIn";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -106,14 +109,14 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
usernameEdit.addTextChangedListener(textWatcher);
|
usernameEdit.addTextChangedListener(textWatcher);
|
||||||
usernameEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
usernameEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
passwordEdit.addTextChangedListener(textWatcher);
|
passwordEdit.addTextChangedListener(textWatcher);
|
||||||
passwordEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
passwordEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -125,13 +128,18 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
forgotPasswordText.setOnClickListener(view -> forgotPassword());
|
forgotPasswordText.setOnClickListener(view -> forgotPassword());
|
||||||
|
|
||||||
if(BuildConfig.FLAVOR == "beta"){
|
if(BuildConfig.FLAVOR.equals("beta")){
|
||||||
loginCredentials.setText(getString(R.string.login_credential));
|
loginCredentials.setText(getString(R.string.login_credential));
|
||||||
} else {
|
} else {
|
||||||
loginCredentials.setVisibility(View.GONE);
|
loginCredentials.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent intent = new Intent(context, LoginActivity.class);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
private void forgotPassword() {
|
private void forgotPassword() {
|
||||||
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
|
Utils.handleWebUrl(this, Uri.parse(BuildConfig.FORGOT_PASSWORD_URL));
|
||||||
}
|
}
|
||||||
|
|
@ -141,12 +149,6 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\"));
|
Utils.handleWebUrl(this,Uri.parse("https://github.com/commons-app/apps-android-commons/wiki/Privacy-policy\\"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideKeyboard(View view) {
|
|
||||||
InputMethodManager inputMethodManager =(InputMethodManager)this.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostCreate(Bundle savedInstanceState) {
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
super.onPostCreate(savedInstanceState);
|
super.onPostCreate(savedInstanceState);
|
||||||
|
|
@ -160,7 +162,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
WelcomeActivity.startYourself(this);
|
WelcomeActivity.startYourself(this);
|
||||||
prefs.edit().putBoolean("firstrun", false).apply();
|
prefs.edit().putBoolean("firstrun", false).apply();
|
||||||
}
|
}
|
||||||
if (sessionManager.getCurrentAccount() != null) {
|
|
||||||
|
if (sessionManager.getCurrentAccount() != null
|
||||||
|
&& sessionManager.isUserLoggedIn()
|
||||||
|
&& sessionManager.getCachedAuthCookie() != null) {
|
||||||
startMainActivity();
|
startMainActivity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -215,6 +220,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
handlePassResult(username, password);
|
handlePassResult(username, password);
|
||||||
} else {
|
} else {
|
||||||
loginCurrentlyInProgress = false;
|
loginCurrentlyInProgress = false;
|
||||||
|
errorMessageShown = true;
|
||||||
|
resultantError = result;
|
||||||
handleOtherResults(result);
|
handleOtherResults(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -266,18 +273,18 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
if (result.equals("NetworkFailure")) {
|
if (result.equals("NetworkFailure")) {
|
||||||
// Matches NetworkFailure which is created by the doInBackground method
|
// Matches NetworkFailure which is created by the doInBackground method
|
||||||
showMessageAndCancelDialog(R.string.login_failed_network);
|
showMessageAndCancelDialog(R.string.login_failed_network);
|
||||||
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
} else if (result.toLowerCase(Locale.getDefault()).contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
||||||
// Matches nosuchuser, nosuchusershort, noname
|
// Matches nosuchuser, nosuchusershort, noname
|
||||||
showMessageAndCancelDialog(R.string.login_failed_username);
|
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
|
||||||
emptySensitiveEditFields();
|
emptySensitiveEditFields();
|
||||||
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
|
} else if (result.toLowerCase(Locale.getDefault()).contains("wrongpassword".toLowerCase())) {
|
||||||
// Matches wrongpassword, wrongpasswordempty
|
// Matches wrongpassword, wrongpasswordempty
|
||||||
showMessageAndCancelDialog(R.string.login_failed_password);
|
showMessageAndCancelDialog(R.string.login_failed_wrong_credentials);
|
||||||
emptySensitiveEditFields();
|
emptySensitiveEditFields();
|
||||||
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
|
} else if (result.toLowerCase(Locale.getDefault()).contains("throttle".toLowerCase())) {
|
||||||
// Matches unknown throttle error codes
|
// Matches unknown throttle error codes
|
||||||
showMessageAndCancelDialog(R.string.login_failed_throttled);
|
showMessageAndCancelDialog(R.string.login_failed_throttled);
|
||||||
} else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
|
} else if (result.toLowerCase(Locale.getDefault()).contains("userblocked".toLowerCase())) {
|
||||||
// Matches login-userblocked
|
// Matches login-userblocked
|
||||||
showMessageAndCancelDialog(R.string.login_failed_blocked);
|
showMessageAndCancelDialog(R.string.login_failed_blocked);
|
||||||
} else if (result.equals("2FA")) {
|
} else if (result.equals("2FA")) {
|
||||||
|
|
@ -341,15 +348,22 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putBoolean(LOGING_IN, loginCurrentlyInProgress);
|
outState.putBoolean(LOGING_IN, loginCurrentlyInProgress);
|
||||||
|
outState.putBoolean(ERROR_MESSAGE_SHOWN, errorMessageShown);
|
||||||
|
outState.putString(RESULTANT_ERROR, resultantError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
loginCurrentlyInProgress = savedInstanceState.getBoolean(LOGING_IN, false);
|
loginCurrentlyInProgress = savedInstanceState.getBoolean(LOGING_IN, false);
|
||||||
|
errorMessageShown = savedInstanceState.getBoolean(ERROR_MESSAGE_SHOWN, false);
|
||||||
if(loginCurrentlyInProgress){
|
if(loginCurrentlyInProgress){
|
||||||
performLogin();
|
performLogin();
|
||||||
}
|
}
|
||||||
|
if(errorMessageShown){
|
||||||
|
resultantError = savedInstanceState.getString(RESULTANT_ERROR);
|
||||||
|
handleOtherResults(resultantError);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void askUserForTwoFactorAuth() {
|
public void askUserForTwoFactorAuth() {
|
||||||
|
|
@ -361,7 +375,9 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
public void showMessageAndCancelDialog(@StringRes int resId) {
|
public void showMessageAndCancelDialog(@StringRes int resId) {
|
||||||
showMessage(resId, R.color.secondaryDarkColor);
|
showMessage(resId, R.color.secondaryDarkColor);
|
||||||
progressDialog.cancel();
|
if(progressDialog != null){
|
||||||
|
progressDialog.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showSuccessAndDismissDialog() {
|
public void showSuccessAndDismissDialog() {
|
||||||
|
|
|
||||||
|
|
@ -61,13 +61,11 @@ public class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAuthCookie() {
|
public String getAuthCookie() {
|
||||||
boolean isLoggedIn = sharedPreferences.getBoolean("isUserLoggedIn", false);
|
if (!isUserLoggedIn()) {
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
Timber.e("User is not logged in");
|
Timber.e("User is not logged in");
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
String authCookie = sharedPreferences.getString("getAuthCookie", null);
|
String authCookie = getCachedAuthCookie();
|
||||||
if (authCookie == null) {
|
if (authCookie == null) {
|
||||||
Timber.e("Auth cookie is null even after login");
|
Timber.e("Auth cookie is null even after login");
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +73,20 @@ public class SessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCachedAuthCookie() {
|
||||||
|
return sharedPreferences.getString("getAuthCookie", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUserLoggedIn() {
|
||||||
|
return sharedPreferences.getBoolean("isUserLoggedIn", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forceLogin(Context context) {
|
||||||
|
if (context != null) {
|
||||||
|
LoginActivity.startYourself(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Completable clearAllAccounts() {
|
public Completable clearAllAccounts() {
|
||||||
AccountManager accountManager = AccountManager.get(context);
|
AccountManager accountManager = AccountManager.get(context);
|
||||||
Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
|
Account[] allAccounts = accountManager.getAccountsByType(ACCOUNT_TYPE);
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,25 @@ import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class CacheController {
|
public class CacheController {
|
||||||
|
|
||||||
|
private final GpsCategoryModel gpsCategoryModel;
|
||||||
|
private final QuadTree<List<String>> quadTree;
|
||||||
private double x, y;
|
private double x, y;
|
||||||
private QuadTree<List<String>> quadTree;
|
|
||||||
private double xMinus, xPlus, yMinus, yPlus;
|
private double xMinus, xPlus, yMinus, yPlus;
|
||||||
|
|
||||||
private static final int EARTH_RADIUS = 6378137;
|
private static final int EARTH_RADIUS = 6378137;
|
||||||
|
|
||||||
public CacheController() {
|
@Inject
|
||||||
|
CacheController(GpsCategoryModel gpsCategoryModel) {
|
||||||
|
this.gpsCategoryModel = gpsCategoryModel;
|
||||||
quadTree = new QuadTree<>(-180, -90, +180, +90);
|
quadTree = new QuadTree<>(-180, -90, +180, +90);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +38,8 @@ public class CacheController {
|
||||||
|
|
||||||
public void cacheCategory() {
|
public void cacheCategory() {
|
||||||
List<String> pointCatList = new ArrayList<>();
|
List<String> pointCatList = new ArrayList<>();
|
||||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
if (gpsCategoryModel.getGpsCatExists()) {
|
||||||
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
pointCatList.addAll(gpsCategoryModel.getCategoryList());
|
||||||
Timber.d("Categories being cached: %s", pointCatList);
|
Timber.d("Categories being cached: %s", pointCatList);
|
||||||
} else {
|
} else {
|
||||||
Timber.d("No categories found, so no categories cached");
|
Timber.d("No categories found, so no categories cached");
|
||||||
|
|
@ -65,7 +72,7 @@ public class CacheController {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
//Based on algorithm at http://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters
|
||||||
public void convertCoordRange() {
|
private void convertCoordRange() {
|
||||||
//Position, decimal degrees
|
//Position, decimal degrees
|
||||||
double lat = y;
|
double lat = y;
|
||||||
double lon = x;
|
double lon = x;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
@ -10,14 +9,12 @@ import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
@ -42,9 +39,9 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||||
import fr.free.nrw.commons.upload.SingleUploadFragment;
|
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
@ -76,6 +73,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
||||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||||
@Inject CategoryDao categoryDao;
|
@Inject CategoryDao categoryDao;
|
||||||
|
@Inject GpsCategoryModel gpsCategoryModel;
|
||||||
|
|
||||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||||
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
||||||
|
|
@ -118,7 +116,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> {
|
categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -130,11 +128,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideKeyboard(View view) {
|
|
||||||
InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
categoriesFilter.removeTextChangedListener(textWatcher);
|
categoriesFilter.removeTextChangedListener(textWatcher);
|
||||||
|
|
@ -261,7 +254,6 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<CategoryItem> defaultCategories() {
|
private Observable<CategoryItem> defaultCategories() {
|
||||||
|
|
||||||
Observable<CategoryItem> directCat = directCategories();
|
Observable<CategoryItem> directCat = directCategories();
|
||||||
if (hasDirectCategories) {
|
if (hasDirectCategories) {
|
||||||
Timber.d("Image has direct Cat");
|
Timber.d("Image has direct Cat");
|
||||||
|
|
@ -295,9 +287,7 @@ public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<CategoryItem> gpsCategories() {
|
private Observable<CategoryItem> gpsCategories() {
|
||||||
return Observable.fromIterable(
|
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||||
MwVolleyApi.GpsCatExists.getGpsCatExists()
|
|
||||||
? MwVolleyApi.getGpsCat() : new ArrayList<>())
|
|
||||||
.map(name -> new CategoryItem(name, false));
|
.map(name -> new CategoryItem(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,7 @@ public class CategoryDao {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
Category fromCursor(Cursor cursor) {
|
Category fromCursor(Cursor cursor) {
|
||||||
// Hardcoding column positions!
|
// Hardcoding column positions!
|
||||||
return new Category(
|
return new Category(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CategoryImageController {
|
||||||
|
|
||||||
|
private MediaWikiApi mediaWikiApi;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CategoryImageController(MediaWikiApi mediaWikiApi) {
|
||||||
|
this.mediaWikiApi = mediaWikiApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a category name as input and calls the API to get a list of images for that category
|
||||||
|
* @param categoryName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Media> getCategoryImages(String categoryName) {
|
||||||
|
return mediaWikiApi.getCategoryImages(categoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class CategoryImageUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method iterates over the child nodes to return a list of Media objects
|
||||||
|
* @param childNodes
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static List<Media> getMediaList(NodeList childNodes) {
|
||||||
|
List<Media> categoryImages = new ArrayList<>();
|
||||||
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
|
Node node = childNodes.item(i);
|
||||||
|
categoryImages.add(getMediaFromPage(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
return categoryImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Media object from the XML response as received by the API
|
||||||
|
* @param node
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Media getMediaFromPage(Node node) {
|
||||||
|
Media media = new Media(null,
|
||||||
|
getImageUrl(node),
|
||||||
|
getFileName(node),
|
||||||
|
getDescription(node),
|
||||||
|
getDataLength(node),
|
||||||
|
getDateCreated(node),
|
||||||
|
getDateCreated(node),
|
||||||
|
getCreator(node)
|
||||||
|
);
|
||||||
|
|
||||||
|
media.setLicense(getLicense(node));
|
||||||
|
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the filename of the uploaded image
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getFileName(Node document) {
|
||||||
|
Element element = (Element) document;
|
||||||
|
return element.getAttribute("title");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the image description for that particular upload
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getDescription(Node document) {
|
||||||
|
return getMetaDataValue(document, "ImageDescription");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts license information from the image meta data
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getLicense(Node document) {
|
||||||
|
return getMetaDataValue(document, "License");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parsed value of artist from the response
|
||||||
|
* The artist information is returned as a HTML string from the API. Jsoup library parses the HTML string
|
||||||
|
* to extract just the text value
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getCreator(Node document) {
|
||||||
|
String artist = getMetaDataValue(document, "Artist");
|
||||||
|
if (artist != null) {
|
||||||
|
return Jsoup.parse(artist).text();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parsed date of creation of the image
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static Date getDateCreated(Node document) {
|
||||||
|
String dateTime = getMetaDataValue(document, "DateTime");
|
||||||
|
if (dateTime != null && !dateTime.equals("")) {
|
||||||
|
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||||
|
try {
|
||||||
|
return format.parse(dateTime);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Timber.d("Error occurred while parsing date %s", dateTime);
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param document
|
||||||
|
* @return Returns the url attribute from the imageInfo node
|
||||||
|
*/
|
||||||
|
private static String getImageUrl(Node document) {
|
||||||
|
Element element = (Element) getImageInfo(document);
|
||||||
|
if (element != null) {
|
||||||
|
return element.getAttribute("url");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes the node document and gives out the attribute length from the node document
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static long getDataLength(Node document) {
|
||||||
|
Element element = (Element) document;
|
||||||
|
if (element != null) {
|
||||||
|
String length = element.getAttribute("length");
|
||||||
|
if (length != null && !length.equals("")) {
|
||||||
|
return Long.parseLong(length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method to get the value of any meta as returned by the getMetaData function
|
||||||
|
* @param document node document as returned by API
|
||||||
|
* @param metaName the name of meta node to be returned
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getMetaDataValue(Node document, String metaName) {
|
||||||
|
Element metaData = getMetaData(document, metaName);
|
||||||
|
if (metaData != null) {
|
||||||
|
return metaData.getAttribute("value");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic method to return an element taking the node document and metaName as input
|
||||||
|
* @param document node document as returned by API
|
||||||
|
* @param metaName the name of meta node to be returned
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Element getMetaData(Node document, String metaName) {
|
||||||
|
Node extraMetaData = getExtraMetaData(document);
|
||||||
|
if (extraMetaData != null) {
|
||||||
|
Node node = getNode(extraMetaData, metaName);
|
||||||
|
if (node != null) {
|
||||||
|
return (Element) node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts extmetadata from the response XML
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Node getExtraMetaData(Node document) {
|
||||||
|
Node imageInfo = getImageInfo(document);
|
||||||
|
if (imageInfo != null) {
|
||||||
|
return getNode(imageInfo, "extmetadata");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the ii node from the imageinfo node
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Node getImageInfo(Node document) {
|
||||||
|
Node imageInfo = getNode(document, "imageinfo");
|
||||||
|
if (imageInfo != null) {
|
||||||
|
return getNode(imageInfo, "ii");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a parent node as input and returns a child node if present
|
||||||
|
* @param node parent node
|
||||||
|
* @param nodeName child node name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Node getNode(Node node, String nodeName) {
|
||||||
|
NodeList childNodes = node.getChildNodes();
|
||||||
|
for (int i = 0; i < childNodes.getLength(); i++) {
|
||||||
|
Node nodeItem = childNodes.item(i);
|
||||||
|
Element item = (Element) nodeItem;
|
||||||
|
if (item.getTagName().equals(nodeName)) {
|
||||||
|
return nodeItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.DataSetObserver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 AuthenticatedActivity
|
||||||
|
implements FragmentManager.OnBackStackChangedListener,
|
||||||
|
MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
|
AdapterView.OnItemClickListener{
|
||||||
|
|
||||||
|
|
||||||
|
private FragmentManager supportFragmentManager;
|
||||||
|
private CategoryImagesListFragment categoryImagesListFragment;
|
||||||
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAuthCookieAcquired(String authCookie) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAuthFailure() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mediaDetails = (MediaDetailPagerFragment) supportFragmentManager
|
||||||
|
.findFragmentById(R.id.fragmentContainer);
|
||||||
|
|
||||||
|
}
|
||||||
|
requestAuthToken();
|
||||||
|
initDrawer();
|
||||||
|
setPageTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the categoryName from the intent and initializes the fragment for showing images of that category
|
||||||
|
*/
|
||||||
|
private void setCategoryImagesFragment() {
|
||||||
|
categoryImagesListFragment = new CategoryImagesListFragment();
|
||||||
|
String categoryName = getIntent().getStringExtra("categoryName");
|
||||||
|
if (getIntent() != null && categoryName != null) {
|
||||||
|
Bundle arguments = new Bundle();
|
||||||
|
arguments.putString("categoryName", categoryName);
|
||||||
|
categoryImagesListFragment.setArguments(arguments);
|
||||||
|
FragmentTransaction transaction = supportFragmentManager.beginTransaction();
|
||||||
|
transaction
|
||||||
|
.add(R.id.fragmentContainer, categoryImagesListFragment)
|
||||||
|
.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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@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()
|
||||||
|
.replace(R.id.fragmentContainer, mediaDetails)
|
||||||
|
.addToBackStack(null)
|
||||||
|
.commit();
|
||||||
|
supportFragmentManager.executePendingTransactions();
|
||||||
|
}
|
||||||
|
mediaDetails.showImage(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumers should be simply using this method to use this activity.
|
||||||
|
* @param context
|
||||||
|
* @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.putExtra("title", title);
|
||||||
|
intent.putExtra("categoryName", categoryName);
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
// not yet ready to return data
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return (Media) categoryImagesListFragment.getAdapter().getItem(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (categoryImagesListFragment.getAdapter() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return categoryImagesListFragment.getAdapter().getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notifyDatasetChanged() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AbsListView;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.GridView;
|
||||||
|
import android.widget.ListAdapter;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import dagger.android.support.DaggerFragment;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays images for a particular category with load more on scrolling incorporated
|
||||||
|
*/
|
||||||
|
public class CategoryImagesListFragment extends DaggerFragment {
|
||||||
|
|
||||||
|
private static int TIMEOUT_SECONDS = 15;
|
||||||
|
|
||||||
|
private GridViewAdapter gridAdapter;
|
||||||
|
|
||||||
|
@BindView(R.id.statusMessage)
|
||||||
|
TextView statusTextView;
|
||||||
|
@BindView(R.id.loadingImagesProgressBar) ProgressBar progressBar;
|
||||||
|
@BindView(R.id.categoryImagesList) GridView gridView;
|
||||||
|
|
||||||
|
private boolean hasMoreImages = true;
|
||||||
|
private boolean isLoading;
|
||||||
|
private String categoryName = null;
|
||||||
|
|
||||||
|
@Inject CategoryImageController controller;
|
||||||
|
@Inject @Named("category_prefs") SharedPreferences categoryPreferences;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View v = inflater.inflate(R.layout.fragment_category_images, container, false);
|
||||||
|
ButterKnife.bind(this, v);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
gridView.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||||
|
initViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the UI elements for the fragment
|
||||||
|
* Setup the grid view to and scroll listener for it
|
||||||
|
*/
|
||||||
|
private void initViews() {
|
||||||
|
String categoryName = getArguments().getString("categoryName");
|
||||||
|
if (getArguments() != null && categoryName != null) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
resetQueryContinueValues(categoryName);
|
||||||
|
initList();
|
||||||
|
setScrollListener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query continue values determine the last page that was loaded for the particular keyword
|
||||||
|
* This method resets those values, so that the results can be queried from the first page itself
|
||||||
|
* @param keyword
|
||||||
|
*/
|
||||||
|
private void resetQueryContinueValues(String keyword) {
|
||||||
|
SharedPreferences.Editor editor = categoryPreferences.edit();
|
||||||
|
editor.remove(keyword);
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for internet connection and then initializes the grid view with first 10 images of that category
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void initList() {
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for no internet scenario
|
||||||
|
*/
|
||||||
|
private void handleNoInternet() {
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_internet));
|
||||||
|
} else {
|
||||||
|
ViewUtil.showSnackbar(gridView, R.string.no_internet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs and handles API error scenario
|
||||||
|
* @param throwable
|
||||||
|
*/
|
||||||
|
private void handleError(Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Error occurred while loading featured images");
|
||||||
|
initErrorView();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the UI updates for a error scenario
|
||||||
|
*/
|
||||||
|
private void initErrorView() {
|
||||||
|
ViewUtil.showSnackbar(gridView, R.string.error_loading_images);
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
if (gridAdapter == null || gridAdapter.isEmpty()) {
|
||||||
|
statusTextView.setVisibility(VISIBLE);
|
||||||
|
statusTextView.setText(getString(R.string.no_images_found));
|
||||||
|
} else {
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the adapter with a list of Media objects
|
||||||
|
* @param mediaList
|
||||||
|
*/
|
||||||
|
private void setAdapter(List<Media> mediaList) {
|
||||||
|
gridAdapter = new GridViewAdapter(this.getContext(), R.layout.layout_category_images, mediaList);
|
||||||
|
gridView.setAdapter(gridAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scroll listener for the grid view so that more images are fetched when the user scrolls down
|
||||||
|
* Checks if the category has more images before loading
|
||||||
|
* Also checks whether images are currently being fetched before triggering another request
|
||||||
|
*/
|
||||||
|
private void setScrollListener() {
|
||||||
|
gridView.setOnScrollListener(new AbsListView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
|
||||||
|
if (hasMoreImages && !isLoading && (firstVisibleItem + visibleItemCount + 1 >= totalItemCount)) {
|
||||||
|
isLoading = true;
|
||||||
|
fetchMoreImages();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches more images for the category and adds it to the grid view adapter
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void fetchMoreImages() {
|
||||||
|
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||||
|
handleNoInternet();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.setVisibility(VISIBLE);
|
||||||
|
Observable.fromCallable(() -> controller.getCategoryImages(categoryName))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
.subscribe(this::handleSuccess, this::handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the success scenario
|
||||||
|
* On first load, it initializes the grid view. On subsequent loads, it adds items to the adapter
|
||||||
|
* @param collection
|
||||||
|
*/
|
||||||
|
private void handleSuccess(List<Media> collection) {
|
||||||
|
if(collection == null || collection.isEmpty()) {
|
||||||
|
initErrorView();
|
||||||
|
hasMoreImages = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(gridAdapter == null) {
|
||||||
|
setAdapter(collection);
|
||||||
|
} else {
|
||||||
|
gridAdapter.addItems(collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
progressBar.setVisibility(GONE);
|
||||||
|
isLoading = false;
|
||||||
|
statusTextView.setVisibility(GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListAdapter getAdapter() {
|
||||||
|
return gridView.getAdapter();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will be called on back pressed of CategoryImagesActivity.
|
||||||
|
* It initializes the grid view by setting adapter.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
gridView.setAdapter(gridAdapter);
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is created to only display UI implementation. Needs to be changed in real implementation
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class GridViewAdapter extends ArrayAdapter {
|
||||||
|
private Context context;
|
||||||
|
private List<Media> data;
|
||||||
|
|
||||||
|
public GridViewAdapter(Context context, int layoutResourceId, List<Media> data) {
|
||||||
|
super(context, layoutResourceId, data);
|
||||||
|
this.context = context;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds more item to the list
|
||||||
|
* Its triggered on scrolling down in the list
|
||||||
|
* @param images
|
||||||
|
*/
|
||||||
|
public void addItems(List<Media> images) {
|
||||||
|
if (data == null) {
|
||||||
|
data = new ArrayList<>();
|
||||||
|
}
|
||||||
|
data.addAll(images);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return data == null || data.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the UI for the category image item
|
||||||
|
* @param position
|
||||||
|
* @param convertView
|
||||||
|
* @param parent
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
|
||||||
|
if (convertView == null) {
|
||||||
|
LayoutInflater inflater = ((Activity) context).getLayoutInflater();
|
||||||
|
convertView = inflater.inflate(R.layout.layout_category_images, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Media item = data.get(position);
|
||||||
|
MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||||
|
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||||
|
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||||
|
fileName.setText(item.getFilename());
|
||||||
|
setAuthorView(item, author);
|
||||||
|
imageView.setMedia(item);
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows author information if its present
|
||||||
|
* @param item
|
||||||
|
* @param author
|
||||||
|
*/
|
||||||
|
private void setAuthorView(Media item, TextView author) {
|
||||||
|
if (item.getCreator() != null && !item.getCreator().equals("")) {
|
||||||
|
String uploadedByTemplate = context.getString(R.string.image_uploaded_by);
|
||||||
|
author.setText(String.format(uploadedByTemplate, item.getCreator()));
|
||||||
|
} else {
|
||||||
|
author.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
||||||
|
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
||||||
|
*/
|
||||||
|
public class QueryContinue {
|
||||||
|
private String continueParam;
|
||||||
|
private String gcmContinueParam;
|
||||||
|
|
||||||
|
public QueryContinue(String continueParam, String gcmContinueParam) {
|
||||||
|
this.continueParam = continueParam;
|
||||||
|
this.gcmContinueParam = gcmContinueParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGcmContinueParam() {
|
||||||
|
return gcmContinueParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContinueParam() {
|
||||||
|
return continueParam;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -45,6 +45,7 @@ public class Contribution extends Media {
|
||||||
private long transferred;
|
private long transferred;
|
||||||
private String decimalCoords;
|
private String decimalCoords;
|
||||||
private boolean isMultiple;
|
private boolean isMultiple;
|
||||||
|
private String wikiDataEntityId;
|
||||||
|
|
||||||
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
|
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
|
||||||
int state, long dataLength, Date dateUploaded, long transferred,
|
int state, long dataLength, Date dateUploaded, long transferred,
|
||||||
|
|
@ -222,4 +223,17 @@ public class Contribution extends Media {
|
||||||
|
|
||||||
throw new RuntimeException("Unrecognized license value: " + license);
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getWikiDataEntityId() {
|
||||||
|
return wikiDataEntityId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
|
||||||
|
* using the setter method
|
||||||
|
* @param wikiDataEntityId
|
||||||
|
*/
|
||||||
|
public void setWikiDataEntityId(String wikiDataEntityId) {
|
||||||
|
this.wikiDataEntityId = wikiDataEntityId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public class ContributionController {
|
||||||
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
|
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload) {
|
public void handleImagePicked(int requestCode, Intent data, boolean isDirectUpload, String wikiDataEntityId) {
|
||||||
FragmentActivity activity = fragment.getActivity();
|
FragmentActivity activity = fragment.getActivity();
|
||||||
Timber.d("handleImagePicked() called with onActivityResult()");
|
Timber.d("handleImagePicked() called with onActivityResult()");
|
||||||
Intent shareIntent = new Intent(activity, ShareActivity.class);
|
Intent shareIntent = new Intent(activity, ShareActivity.class);
|
||||||
|
|
@ -102,9 +102,6 @@ public class ContributionController {
|
||||||
shareIntent.setType(activity.getContentResolver().getType(imageData));
|
shareIntent.setType(activity.getContentResolver().getType(imageData));
|
||||||
shareIntent.putExtra(EXTRA_STREAM, imageData);
|
shareIntent.putExtra(EXTRA_STREAM, imageData);
|
||||||
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
|
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
|
||||||
if (isDirectUpload) {
|
|
||||||
shareIntent.putExtra("isDirectUpload", true);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case SELECT_FROM_CAMERA:
|
case SELECT_FROM_CAMERA:
|
||||||
//FIXME: Find out appropriate mime type
|
//FIXME: Find out appropriate mime type
|
||||||
|
|
@ -113,9 +110,6 @@ public class ContributionController {
|
||||||
shareIntent.setType("image/jpeg");
|
shareIntent.setType("image/jpeg");
|
||||||
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
|
shareIntent.putExtra(EXTRA_STREAM, lastGeneratedCaptureUri);
|
||||||
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
|
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_CAMERA);
|
||||||
if (isDirectUpload) {
|
|
||||||
shareIntent.putExtra("isDirectUpload", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -123,6 +117,10 @@ public class ContributionController {
|
||||||
}
|
}
|
||||||
Timber.i("Image selected");
|
Timber.i("Image selected");
|
||||||
try {
|
try {
|
||||||
|
shareIntent.putExtra("isDirectUpload", isDirectUpload);
|
||||||
|
if (wikiDataEntityId != null && !wikiDataEntityId.equals("")) {
|
||||||
|
shareIntent.putExtra("wikiDataEntityId", wikiDataEntityId);
|
||||||
|
}
|
||||||
activity.startActivity(shareIntent);
|
activity.startActivity(shareIntent);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Timber.e(e, "Security Exception");
|
Timber.e(e, "Security Exception");
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import android.net.Uri;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
controller.handleImagePicked(requestCode, data, false);
|
controller.handleImagePicked(requestCode, data, false, null);
|
||||||
} else {
|
} else {
|
||||||
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public class DeleteTask extends AsyncTask<Void, Integer, Boolean> {
|
||||||
|
|
||||||
String logPageString = "\n{{Commons:Deletion requests/" + media.getFilename() +
|
String logPageString = "\n{{Commons:Deletion requests/" + media.getFilename() +
|
||||||
"}}\n";
|
"}}\n";
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
|
||||||
String date = sdf.format(calendar.getTime());
|
String date = sdf.format(calendar.getTime());
|
||||||
|
|
||||||
String userPageString = "\n{{subst:idw|" + media.getFilename() +
|
String userPageString = "\n{{subst:idw|" + media.getFilename() +
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import fr.free.nrw.commons.WelcomeActivity;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SignupActivity;
|
import fr.free.nrw.commons.auth.SignupActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
@ -46,4 +47,7 @@ public abstract class ActivityBuilderModule {
|
||||||
|
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract NotificationActivity bindNotificationActivity();
|
abstract NotificationActivity bindNotificationActivity();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,18 @@ import dagger.android.support.AndroidSupportInjectionModule;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
||||||
import fr.free.nrw.commons.delete.DeleteTask;
|
import fr.free.nrw.commons.delete.DeleteTask;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
|
||||||
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||||
|
import fr.free.nrw.commons.upload.FileProcessor;
|
||||||
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
CommonsApplicationModule.class,
|
CommonsApplicationModule.class,
|
||||||
|
NetworkingModule.class,
|
||||||
AndroidInjectionModule.class,
|
AndroidInjectionModule.class,
|
||||||
AndroidSupportInjectionModule.class,
|
AndroidSupportInjectionModule.class,
|
||||||
ActivityBuilderModule.class,
|
ActivityBuilderModule.class,
|
||||||
|
|
@ -47,6 +48,8 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
|
||||||
|
|
||||||
void inject(PlaceRenderer placeRenderer);
|
void inject(PlaceRenderer placeRenderer);
|
||||||
|
|
||||||
|
void inject(FileProcessor fileProcessor);
|
||||||
|
|
||||||
@Component.Builder
|
@Component.Builder
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
interface Builder {
|
interface Builder {
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,16 @@ import javax.inject.Singleton;
|
||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||||
import fr.free.nrw.commons.upload.UploadController;
|
import fr.free.nrw.commons.upload.UploadController;
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||||
|
|
||||||
import static android.content.Context.MODE_PRIVATE;
|
import static android.content.Context.MODE_PRIVATE;
|
||||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
|
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.CONTRIBUTION_AUTHORITY;
|
||||||
|
|
@ -31,7 +30,6 @@ import static fr.free.nrw.commons.modifications.ModificationsContentProvider.MOD
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
public class CommonsApplicationModule {
|
public class CommonsApplicationModule {
|
||||||
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
|
public static final String CATEGORY_AUTHORITY = "fr.free.nrw.commons.categories.contentprovider";
|
||||||
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
|
||||||
|
|
||||||
private Context applicationContext;
|
private Context applicationContext;
|
||||||
|
|
||||||
|
|
@ -85,6 +83,17 @@ public class CommonsApplicationModule {
|
||||||
return context.getSharedPreferences("prefs", MODE_PRIVATE);
|
return context.getSharedPreferences("prefs", MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param context
|
||||||
|
* @return returns categoryPrefs
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Named("category_prefs")
|
||||||
|
public SharedPreferences providesCategorySharedPreferences(Context context) {
|
||||||
|
return context.getSharedPreferences("categoryPrefs", MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Named("direct_nearby_upload_prefs")
|
@Named("direct_nearby_upload_prefs")
|
||||||
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
|
public SharedPreferences providesDirectNearbyUploadPreferences(Context context) {
|
||||||
|
|
@ -104,24 +113,12 @@ public class CommonsApplicationModule {
|
||||||
return new SessionManager(context, mediaWikiApi, sharedPreferences);
|
return new SessionManager(context, mediaWikiApi, sharedPreferences);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public MediaWikiApi provideMediaWikiApi(Context context, @Named("default_preferences") SharedPreferences sharedPreferences) {
|
|
||||||
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, sharedPreferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public LocationServiceManager provideLocationServiceManager(Context context) {
|
public LocationServiceManager provideLocationServiceManager(Context context) {
|
||||||
return new LocationServiceManager(context);
|
return new LocationServiceManager(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
public CacheController provideCacheController() {
|
|
||||||
return new CacheController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
public DBOpenHelper provideDBOpenHelper(Context context) {
|
public DBOpenHelper provideDBOpenHelper(Context context) {
|
||||||
|
|
@ -139,4 +136,10 @@ public class CommonsApplicationModule {
|
||||||
public LruCache<String, String> provideLruCache() {
|
public LruCache<String, String> provideLruCache() {
|
||||||
return new LruCache<>(1024);
|
return new LruCache<>(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public WikidataEditListener provideWikidataEditListener() {
|
||||||
|
return new WikidataEditListenerImpl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import dagger.Module;
|
||||||
import dagger.android.ContributesAndroidInjector;
|
import dagger.android.ContributesAndroidInjector;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
||||||
|
import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||||
|
|
@ -47,4 +48,7 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract SingleUploadFragment bindSingleUploadFragment();
|
abstract SingleUploadFragment bindSingleUploadFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package fr.free.nrw.commons.di;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
public class NetworkingModule {
|
||||||
|
public static final long OK_HTTP_CACHE_SIZE = 10 * 1024 * 1024;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public OkHttpClient provideOkHttpClient() {
|
||||||
|
return new OkHttpClient.Builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public MediaWikiApi provideMediaWikiApi(Context context,
|
||||||
|
@Named("default_preferences") SharedPreferences defaultPreferences,
|
||||||
|
@Named("category_prefs") SharedPreferences categoryPrefs,
|
||||||
|
Gson gson) {
|
||||||
|
return new ApacheHttpClientMediaWikiApi(context, BuildConfig.WIKIMEDIA_API_HOST, BuildConfig.WIKIDATA_API_HOST, defaultPreferences, categoryPrefs, gson);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("commons_mediawiki_url")
|
||||||
|
@NonNull
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
public HttpUrl provideMwUrl() {
|
||||||
|
return HttpUrl.parse(BuildConfig.COMMONS_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gson objects are very heavy. The app should ideally be using just one instance of it instead of creating new instances everywhere.
|
||||||
|
* @return returns a singleton Gson instance
|
||||||
|
*/
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public Gson provideGson() {
|
||||||
|
return new GsonBuilder().create();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
app/src/main/java/fr/free/nrw/commons/glide/SvgDecoder.java
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package fr.free.nrw.commons.glide;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Options;
|
||||||
|
import com.bumptech.glide.load.ResourceDecoder;
|
||||||
|
import com.bumptech.glide.load.engine.Resource;
|
||||||
|
import com.bumptech.glide.load.resource.SimpleResource;
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
import com.caverock.androidsvg.SVGParseException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes an SVG internal representation from an {@link InputStream}.
|
||||||
|
*/
|
||||||
|
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handles(@NonNull InputStream source, @NonNull Options options) {
|
||||||
|
// TODO: Can we tell?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Resource<SVG> decode(@NonNull InputStream source, int width, int height,
|
||||||
|
@NonNull Options options)
|
||||||
|
throws IOException {
|
||||||
|
try {
|
||||||
|
SVG svg = SVG.getFromInputStream(source);
|
||||||
|
return new SimpleResource<>(svg);
|
||||||
|
} catch (SVGParseException ex) {
|
||||||
|
throw new IOException("Cannot load SVG from stream", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package fr.free.nrw.commons.glide;
|
||||||
|
|
||||||
|
import android.graphics.Picture;
|
||||||
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.Options;
|
||||||
|
import com.bumptech.glide.load.engine.Resource;
|
||||||
|
import com.bumptech.glide.load.resource.SimpleResource;
|
||||||
|
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder;
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the {@link SVG}'s internal representation to an Android-compatible one
|
||||||
|
* ({@link Picture}).
|
||||||
|
*/
|
||||||
|
public class SvgDrawableTranscoder implements ResourceTranscoder<SVG, PictureDrawable> {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Resource<PictureDrawable> transcode(@NonNull Resource<SVG> toTranscode,
|
||||||
|
@NonNull Options options) {
|
||||||
|
SVG svg = toTranscode.get();
|
||||||
|
Picture picture = svg.renderToPicture();
|
||||||
|
PictureDrawable drawable = new PictureDrawable(picture);
|
||||||
|
return new SimpleResource<>(drawable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package fr.free.nrw.commons.glide;
|
||||||
|
|
||||||
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener which updates the {@link ImageView} to be software rendered, because
|
||||||
|
* {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on
|
||||||
|
* a hardware backed {@link android.graphics.Canvas Canvas}.
|
||||||
|
*/
|
||||||
|
public class SvgSoftwareLayerSetter implements RequestListener<PictureDrawable> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the layer type to none if the load fails
|
||||||
|
* @param e
|
||||||
|
* @param model
|
||||||
|
* @param target
|
||||||
|
* @param isFirstResource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(GlideException e, Object model, Target<PictureDrawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
ImageView view = ((ImageViewTarget<?>) target).getView();
|
||||||
|
view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the layer type to software when the resource is ready
|
||||||
|
* @param resource
|
||||||
|
* @param model
|
||||||
|
* @param target
|
||||||
|
* @param dataSource
|
||||||
|
* @param isFirstResource
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(PictureDrawable resource, Object model,
|
||||||
|
Target<PictureDrawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
ImageView view = ((ImageViewTarget<?>) target).getView();
|
||||||
|
view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.location;
|
package fr.free.nrw.commons.location;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
|
@ -10,9 +11,10 @@ import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
@ -29,6 +31,7 @@ public class LocationServiceManager implements LocationListener {
|
||||||
private Location lastLocation;
|
private Location lastLocation;
|
||||||
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
|
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
|
||||||
private boolean isLocationManagerRegistered = false;
|
private boolean isLocationManagerRegistered = false;
|
||||||
|
private Set<Activity> locationExplanationDisplayed = new HashSet<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new instance of LocationServiceManager.
|
* Constructs a new instance of LocationServiceManager.
|
||||||
|
|
@ -51,7 +54,6 @@ public class LocationServiceManager implements LocationListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the location permission is granted.
|
* Returns whether the location permission is granted.
|
||||||
*
|
|
||||||
* @return true if the location permission is granted
|
* @return true if the location permission is granted
|
||||||
*/
|
*/
|
||||||
public boolean isLocationPermissionGranted() {
|
public boolean isLocationPermissionGranted() {
|
||||||
|
|
@ -73,10 +75,41 @@ public class LocationServiceManager implements LocationListener {
|
||||||
LOCATION_REQUEST);
|
LOCATION_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The permission explanation dialog box is now displayed just once for a particular activity. We are subscribing
|
||||||
|
* to updates from multiple providers so its important to show the dialog just once. Otherwise it will be displayed
|
||||||
|
* once for every provider, which in our case currently is 2.
|
||||||
|
* @param activity
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public boolean isPermissionExplanationRequired(Activity activity) {
|
public boolean isPermissionExplanationRequired(Activity activity) {
|
||||||
return !activity.isFinishing() &&
|
if (activity.isFinishing()) {
|
||||||
ActivityCompat.shouldShowRequestPermissionRationale(activity,
|
return false;
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION);
|
}
|
||||||
|
boolean showRequestPermissionRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
|
if (showRequestPermissionRationale && !locationExplanationDisplayed.contains(activity)) {
|
||||||
|
locationExplanationDisplayed.add(activity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the last known location in cases where there wasn't time to register a listener
|
||||||
|
* (e.g. when Location permission just granted)
|
||||||
|
* @return last known LatLng
|
||||||
|
*/
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
public LatLng getLKL() {
|
||||||
|
if (isLocationPermissionGranted()) {
|
||||||
|
Location lastKL = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
if (lastKL == null) {
|
||||||
|
lastKL = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
|
||||||
|
}
|
||||||
|
return LatLng.from(lastKL);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LatLng getLastLocation() {
|
public LatLng getLastLocation() {
|
||||||
|
|
@ -90,9 +123,10 @@ public class LocationServiceManager implements LocationListener {
|
||||||
* Registers a LocationManager to listen for current location.
|
* Registers a LocationManager to listen for current location.
|
||||||
*/
|
*/
|
||||||
public void registerLocationManager() {
|
public void registerLocationManager() {
|
||||||
if (!isLocationManagerRegistered)
|
if (!isLocationManagerRegistered) {
|
||||||
isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
|
isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
|
||||||
&& requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
&& requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,7 +159,7 @@ public class LocationServiceManager implements LocationListener {
|
||||||
* @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly
|
* @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly
|
||||||
* LOCATION_SLIGHTLY_CHANGED if location changed slightly
|
* LOCATION_SLIGHTLY_CHANGED if location changed slightly
|
||||||
*/
|
*/
|
||||||
protected LocationChangeType isBetterLocation(Location location, Location currentBestLocation) {
|
private LocationChangeType isBetterLocation(Location location, Location currentBestLocation) {
|
||||||
|
|
||||||
if (currentBestLocation == null) {
|
if (currentBestLocation == null) {
|
||||||
// A new location is always better than no location
|
// A new location is always better than no location
|
||||||
|
|
@ -249,6 +283,8 @@ public class LocationServiceManager implements LocationListener {
|
||||||
public enum LocationChangeType{
|
public enum LocationChangeType{
|
||||||
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
|
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
|
||||||
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
|
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
|
||||||
LOCATION_NOT_CHANGED
|
LOCATION_NOT_CHANGED,
|
||||||
|
PERMISSION_JUST_GRANTED,
|
||||||
|
MAP_UPDATED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -22,6 +23,9 @@ import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -45,19 +49,23 @@ import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
import fr.free.nrw.commons.ui.widget.CompatTextView;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
|
||||||
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private boolean editable;
|
private boolean editable;
|
||||||
|
private boolean isCategoryImage;
|
||||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||||
private int index;
|
private int index;
|
||||||
|
|
||||||
public static MediaDetailFragment forMedia(int index, boolean editable) {
|
public static MediaDetailFragment forMedia(int index, boolean editable, boolean isCategoryImage) {
|
||||||
MediaDetailFragment mf = new MediaDetailFragment();
|
MediaDetailFragment mf = new MediaDetailFragment();
|
||||||
|
|
||||||
Bundle state = new Bundle();
|
Bundle state = new Bundle();
|
||||||
state.putBoolean("editable", editable);
|
state.putBoolean("editable", editable);
|
||||||
|
state.putBoolean("isCategoryImage", isCategoryImage);
|
||||||
state.putInt("index", index);
|
state.putInt("index", index);
|
||||||
state.putInt("listIndex", 0);
|
state.putInt("listIndex", 0);
|
||||||
state.putInt("listTop", 0);
|
state.putInt("listTop", 0);
|
||||||
|
|
@ -72,21 +80,37 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
@Inject
|
@Inject
|
||||||
MediaWikiApi mwApi;
|
MediaWikiApi mwApi;
|
||||||
|
|
||||||
|
|
||||||
private MediaWikiImageView image;
|
|
||||||
private MediaDetailSpacer spacer;
|
|
||||||
private int initialListTop = 0;
|
private int initialListTop = 0;
|
||||||
|
|
||||||
private TextView title;
|
@BindView(R.id.mediaDetailImage)
|
||||||
private TextView desc;
|
MediaWikiImageView image;
|
||||||
private TextView license;
|
@BindView(R.id.mediaDetailSpacer)
|
||||||
private TextView coordinates;
|
MediaDetailSpacer spacer;
|
||||||
private TextView uploadedDate;
|
@BindView(R.id.mediaDetailTitle)
|
||||||
private TextView seeMore;
|
TextView title;
|
||||||
private LinearLayout nominatedforDeletion;
|
@BindView(R.id.mediaDetailDesc)
|
||||||
private LinearLayout categoryContainer;
|
TextView desc;
|
||||||
private Button delete;
|
@BindView(R.id.mediaDetailAuthor)
|
||||||
private ScrollView scrollView;
|
TextView author;
|
||||||
|
@BindView(R.id.mediaDetailLicense)
|
||||||
|
TextView license;
|
||||||
|
@BindView(R.id.mediaDetailCoordinates)
|
||||||
|
TextView coordinates;
|
||||||
|
@BindView(R.id.mediaDetailuploadeddate)
|
||||||
|
TextView uploadedDate;
|
||||||
|
@BindView(R.id.seeMore)
|
||||||
|
TextView seeMore;
|
||||||
|
@BindView(R.id.nominatedDeletionBanner)
|
||||||
|
LinearLayout nominatedForDeletion;
|
||||||
|
@BindView(R.id.mediaDetailCategoryContainer)
|
||||||
|
LinearLayout categoryContainer;
|
||||||
|
@BindView(R.id.authorLinearLayout)
|
||||||
|
LinearLayout authorLayout;
|
||||||
|
@BindView(R.id.nominateDeletion)
|
||||||
|
Button delete;
|
||||||
|
@BindView(R.id.mediaDetailScrollView)
|
||||||
|
ScrollView scrollView;
|
||||||
|
|
||||||
private ArrayList<String> categoryNames;
|
private ArrayList<String> categoryNames;
|
||||||
private boolean categoriesLoaded = false;
|
private boolean categoriesLoaded = false;
|
||||||
private boolean categoriesPresent = false;
|
private boolean categoriesPresent = false;
|
||||||
|
|
@ -96,11 +120,15 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
private AsyncTask<Void, Void, Boolean> detailFetchTask;
|
private AsyncTask<Void, Void, Boolean> detailFetchTask;
|
||||||
private LicenseList licenseList;
|
private LicenseList licenseList;
|
||||||
|
|
||||||
|
//Had to make this class variable, to implement various onClicks, which access the media, also I fell why make separate variables when one can serve the purpose
|
||||||
|
private Media media;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("index", index);
|
outState.putInt("index", index);
|
||||||
outState.putBoolean("editable", editable);
|
outState.putBoolean("editable", editable);
|
||||||
|
outState.putBoolean("isCategoryImage", isCategoryImage);
|
||||||
|
|
||||||
getScrollPosition();
|
getScrollPosition();
|
||||||
outState.putInt("listTop", initialListTop);
|
outState.putInt("listTop", initialListTop);
|
||||||
|
|
@ -116,32 +144,28 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
editable = savedInstanceState.getBoolean("editable");
|
editable = savedInstanceState.getBoolean("editable");
|
||||||
|
isCategoryImage = savedInstanceState.getBoolean("isCategoryImage");
|
||||||
index = savedInstanceState.getInt("index");
|
index = savedInstanceState.getInt("index");
|
||||||
initialListTop = savedInstanceState.getInt("listTop");
|
initialListTop = savedInstanceState.getInt("listTop");
|
||||||
} else {
|
} else {
|
||||||
editable = getArguments().getBoolean("editable");
|
editable = getArguments().getBoolean("editable");
|
||||||
|
isCategoryImage = getArguments().getBoolean("isCategoryImage");
|
||||||
index = getArguments().getInt("index");
|
index = getArguments().getInt("index");
|
||||||
initialListTop = 0;
|
initialListTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
categoryNames = new ArrayList<>();
|
categoryNames = new ArrayList<>();
|
||||||
categoryNames.add(getString(R.string.detail_panel_cats_loading));
|
categoryNames.add(getString(R.string.detail_panel_cats_loading));
|
||||||
|
|
||||||
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
|
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
|
||||||
|
|
||||||
image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage);
|
ButterKnife.bind(this,view);
|
||||||
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
|
|
||||||
|
|
||||||
// Detail consists of a list view with main pane in header view, plus category list.
|
if (isCategoryImage){
|
||||||
spacer = (MediaDetailSpacer) view.findViewById(R.id.mediaDetailSpacer);
|
authorLayout.setVisibility(VISIBLE);
|
||||||
title = (TextView) view.findViewById(R.id.mediaDetailTitle);
|
} else {
|
||||||
desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
|
authorLayout.setVisibility(GONE);
|
||||||
license = (TextView) view.findViewById(R.id.mediaDetailLicense);
|
}
|
||||||
coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates);
|
|
||||||
uploadedDate = (TextView) view.findViewById(R.id.mediaDetailuploadeddate);
|
|
||||||
seeMore = (TextView) view.findViewById(R.id.seeMore);
|
|
||||||
nominatedforDeletion = (LinearLayout) view.findViewById(R.id.nominatedDeletionBanner);
|
|
||||||
delete = (Button) view.findViewById(R.id.nominateDeletion);
|
|
||||||
categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
|
|
||||||
|
|
||||||
licenseList = new LicenseList(getActivity());
|
licenseList = new LicenseList(getActivity());
|
||||||
|
|
||||||
|
|
@ -179,7 +203,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
Media media = detailProvider.getMediaAtPosition(index);
|
media = detailProvider.getMediaAtPosition(index);
|
||||||
if (media == null) {
|
if (media == null) {
|
||||||
// Ask the detail provider to ping us when we're ready
|
// Ask the detail provider to ping us when we're ready
|
||||||
Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
|
Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
|
||||||
|
|
@ -192,17 +216,18 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
Timber.d("MediaDetailFragment ready to display delayed details!");
|
Timber.d("MediaDetailFragment ready to display delayed details!");
|
||||||
detailProvider.unregisterDataSetObserver(dataObserver);
|
detailProvider.unregisterDataSetObserver(dataObserver);
|
||||||
dataObserver = null;
|
dataObserver = null;
|
||||||
displayMediaDetails(detailProvider.getMediaAtPosition(index));
|
media=detailProvider.getMediaAtPosition(index);
|
||||||
|
displayMediaDetails();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
detailProvider.registerDataSetObserver(dataObserver);
|
detailProvider.registerDataSetObserver(dataObserver);
|
||||||
} else {
|
} else {
|
||||||
Timber.d("MediaDetailFragment ready to display details");
|
Timber.d("MediaDetailFragment ready to display details");
|
||||||
displayMediaDetails(media);
|
displayMediaDetails();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayMediaDetails(final Media media) {
|
private void displayMediaDetails() {
|
||||||
//Always load image from Internet to allow viewing the desc, license, and cats
|
//Always load image from Internet to allow viewing the desc, license, and cats
|
||||||
image.setMedia(media);
|
image.setMedia(media);
|
||||||
|
|
||||||
|
|
@ -239,7 +264,6 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
if (success) {
|
if (success) {
|
||||||
extractor.fill(media);
|
extractor.fill(media);
|
||||||
setTextFields(media);
|
setTextFields(media);
|
||||||
setOnClickListeners(media);
|
|
||||||
} else {
|
} else {
|
||||||
Timber.d("Failed to load photo details.");
|
Timber.d("Failed to load photo details.");
|
||||||
}
|
}
|
||||||
|
|
@ -290,71 +314,99 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
rebuildCatList();
|
rebuildCatList();
|
||||||
|
|
||||||
|
if(media.getCreator() == null || media.getCreator().equals("")) {
|
||||||
|
authorLayout.setVisibility(GONE);
|
||||||
|
} else {
|
||||||
|
author.setText(media.getCreator());
|
||||||
|
}
|
||||||
|
|
||||||
checkDeletion(media);
|
checkDeletion(media);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setOnClickListeners(final Media media) {
|
@OnClick(R.id.mediaDetailLicense)
|
||||||
if (licenseLink(media) != null) {
|
public void onMediaDetailLicenceClicked(){
|
||||||
license.setOnClickListener(v -> openWebBrowser(licenseLink(media)));
|
if (!TextUtils.isEmpty(licenseLink(media))) {
|
||||||
|
openWebBrowser(licenseLink(media));
|
||||||
} else {
|
} else {
|
||||||
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
|
if(isCategoryImage) {
|
||||||
toast.show();
|
Timber.d("Unable to fetch license URL for %s", media.getLicense());
|
||||||
|
} else {
|
||||||
|
Toast toast = Toast.makeText(getContext(), getString(R.string.null_url), Toast.LENGTH_SHORT);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.mediaDetailCoordinates)
|
||||||
|
public void onMediaDetailCoordinatesClicked(){
|
||||||
if (media.getCoordinates() != null) {
|
if (media.getCoordinates() != null) {
|
||||||
coordinates.setOnClickListener(v -> openMap(media.getCoordinates()));
|
openMap(media.getCoordinates());
|
||||||
}
|
}
|
||||||
if (delete.getVisibility() == View.VISIBLE) {
|
}
|
||||||
delete.setOnClickListener(v -> {
|
|
||||||
delete.setEnabled(false);
|
|
||||||
delete.setTextColor(getResources().getColor(R.color.deleteButtonLight));
|
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
|
||||||
alert.setMessage("Why should this file be deleted?");
|
|
||||||
final EditText input = new EditText(getActivity());
|
|
||||||
alert.setView(input);
|
|
||||||
input.requestFocus();
|
|
||||||
alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
String reason = input.getText().toString();
|
|
||||||
DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason);
|
|
||||||
deleteTask.execute();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
AlertDialog d = alert.create();
|
|
||||||
input.addTextChangedListener(new TextWatcher() {
|
|
||||||
private void handleText() {
|
|
||||||
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
|
||||||
if (input.getText().length() == 0) {
|
|
||||||
okButton.setEnabled(false);
|
|
||||||
} else {
|
|
||||||
okButton.setEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@OnClick(R.id.nominateDeletion)
|
||||||
public void afterTextChanged(Editable arg0) {
|
public void onDeleteButtonClicked(){
|
||||||
handleText();
|
//Reviewer correct me if i have misunderstood something over here
|
||||||
}
|
//But how does this if (delete.getVisibility() == View.VISIBLE) {
|
||||||
|
// enableDeleteButton(true); makes sense ?
|
||||||
|
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
|
||||||
|
alert.setMessage("Why should this file be deleted?");
|
||||||
|
final EditText input = new EditText(getActivity());
|
||||||
|
alert.setView(input);
|
||||||
|
input.requestFocus();
|
||||||
|
alert.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
String reason = input.getText().toString();
|
||||||
|
DeleteTask deleteTask = new DeleteTask(getActivity(), media, reason);
|
||||||
|
deleteTask.execute();
|
||||||
|
enableDeleteButton(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alert.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int whichButton) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog d = alert.create();
|
||||||
|
input.addTextChangedListener(new TextWatcher() {
|
||||||
|
private void handleText() {
|
||||||
|
final Button okButton = d.getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
|
if (input.getText().length() == 0) {
|
||||||
|
okButton.setEnabled(false);
|
||||||
|
} else {
|
||||||
|
okButton.setEnabled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
public void afterTextChanged(Editable arg0) {
|
||||||
}
|
handleText();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||||
}
|
}
|
||||||
});
|
|
||||||
d.show();
|
@Override
|
||||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
d.show();
|
||||||
|
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.seeMore)
|
||||||
|
public void onSeeMoreClicked(){
|
||||||
|
if(nominatedForDeletion.getVisibility()== VISIBLE) {
|
||||||
|
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
|
||||||
}
|
}
|
||||||
if (nominatedforDeletion.getVisibility() == View.VISIBLE){
|
}
|
||||||
seeMore.setOnClickListener(v -> {
|
|
||||||
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,7 +483,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
if (date == null || date.toString() == null || date.toString().isEmpty()) {
|
if (date == null || date.toString() == null || date.toString().isEmpty()) {
|
||||||
return "Uploaded date not available";
|
return "Uploaded date not available";
|
||||||
}
|
}
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
|
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy", Locale.getDefault());
|
||||||
return formatter.format(date);
|
return formatter.format(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,12 +501,11 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private void checkDeletion(Media media){
|
private void checkDeletion(Media media){
|
||||||
if (media.getRequestedDeletion()){
|
if (media.getRequestedDeletion()){
|
||||||
delete.setVisibility(View.GONE);
|
delete.setVisibility(GONE);
|
||||||
nominatedforDeletion.setVisibility(View.VISIBLE);
|
nominatedForDeletion.setVisibility(VISIBLE);
|
||||||
}
|
} else if (!isCategoryImage) {
|
||||||
else{
|
delete.setVisibility(VISIBLE);
|
||||||
delete.setVisibility(View.VISIBLE);
|
nominatedForDeletion.setVisibility(GONE);
|
||||||
nominatedforDeletion.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
|
@ -36,6 +38,8 @@ import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
import static android.content.Context.DOWNLOAD_SERVICE;
|
import static android.content.Context.DOWNLOAD_SERVICE;
|
||||||
|
|
@ -53,16 +57,19 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
|
||||||
private ViewPager pager;
|
@BindView(R.id.mediaDetailsPager)
|
||||||
|
ViewPager pager;
|
||||||
private Boolean editable;
|
private Boolean editable;
|
||||||
|
private boolean isFeaturedImage;
|
||||||
|
|
||||||
public MediaDetailPagerFragment() {
|
public MediaDetailPagerFragment() {
|
||||||
this(false);
|
this(false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ValidFragment")
|
@SuppressLint("ValidFragment")
|
||||||
public MediaDetailPagerFragment(Boolean editable) {
|
public MediaDetailPagerFragment(Boolean editable, boolean isFeaturedImage) {
|
||||||
this.editable = editable;
|
this.editable = editable;
|
||||||
|
this.isFeaturedImage = isFeaturedImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -70,7 +77,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
ViewGroup container,
|
ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false);
|
View view = inflater.inflate(R.layout.fragment_media_detail_pager, container, false);
|
||||||
pager = (ViewPager) view.findViewById(R.id.mediaDetailsPager);
|
ButterKnife.bind(this,view);
|
||||||
pager.addOnPageChangeListener(this);
|
pager.addOnPageChangeListener(this);
|
||||||
|
|
||||||
final MediaDetailAdapter adapter = new MediaDetailAdapter(getChildFragmentManager());
|
final MediaDetailAdapter adapter = new MediaDetailAdapter(getChildFragmentManager());
|
||||||
|
|
@ -96,6 +103,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
outState.putInt("current-page", pager.getCurrentItem());
|
outState.putInt("current-page", pager.getCurrentItem());
|
||||||
outState.putBoolean("editable", editable);
|
outState.putBoolean("editable", editable);
|
||||||
|
outState.putBoolean("isFeaturedImage", isFeaturedImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -103,6 +111,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
editable = savedInstanceState.getBoolean("editable");
|
editable = savedInstanceState.getBoolean("editable");
|
||||||
|
isFeaturedImage = savedInstanceState.getBoolean("isFeaturedImage");
|
||||||
}
|
}
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +142,10 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
// Download
|
// Download
|
||||||
downloadMedia(m);
|
downloadMedia(m);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.menu_set_as_wallpaper:
|
||||||
|
// Set wallpaper
|
||||||
|
setWallpaper(m);
|
||||||
|
return true;
|
||||||
case R.id.menu_retry_current_image:
|
case R.id.menu_retry_current_image:
|
||||||
// Retry
|
// Retry
|
||||||
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
|
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
|
||||||
|
|
@ -148,6 +161,19 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the media as the device's wallpaper if the imageUrl is not null
|
||||||
|
* Fails silently if setting the wallpaper fails
|
||||||
|
* @param media
|
||||||
|
*/
|
||||||
|
private void setWallpaper(Media media) {
|
||||||
|
if(media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
|
||||||
|
Timber.d("Media URL not present");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ImageUtils.setWallpaperFromImageUrl(getActivity(), Uri.parse(media.getImageUrl()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the media file downloading to the local SD card/storage.
|
* Start the media file downloading to the local SD card/storage.
|
||||||
* The file can then be opened in Gallery or other apps.
|
* The file can then be opened in Gallery or other apps.
|
||||||
|
|
@ -291,7 +317,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
||||||
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
|
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
|
||||||
}
|
}
|
||||||
return MediaDetailFragment.forMedia(i, editable);
|
return MediaDetailFragment.forMedia(i, editable, isFeaturedImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ import android.support.annotation.VisibleForTesting;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.conn.ClientConnectionManager;
|
import org.apache.http.conn.ClientConnectionManager;
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
|
|
@ -23,6 +25,8 @@ import org.apache.http.params.CoreProtocolPNames;
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
import org.mediawiki.api.MWApi;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
import org.w3c.dom.NodeList;
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -38,7 +42,10 @@ import java.util.Locale;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.PageTitle;
|
import fr.free.nrw.commons.PageTitle;
|
||||||
|
import fr.free.nrw.commons.category.CategoryImageUtils;
|
||||||
|
import fr.free.nrw.commons.category.QueryContinue;
|
||||||
import fr.free.nrw.commons.notification.Notification;
|
import fr.free.nrw.commons.notification.Notification;
|
||||||
import fr.free.nrw.commons.notification.NotificationUtils;
|
import fr.free.nrw.commons.notification.NotificationUtils;
|
||||||
import in.yuvi.http.fluent.Http;
|
import in.yuvi.http.fluent.Http;
|
||||||
|
|
@ -46,6 +53,8 @@ import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.ContinueUtils.getQueryContinue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Addshore
|
* @author Addshore
|
||||||
*/
|
*/
|
||||||
|
|
@ -55,10 +64,18 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
private static final String THUMB_SIZE = "640";
|
private static final String THUMB_SIZE = "640";
|
||||||
private AbstractHttpClient httpClient;
|
private AbstractHttpClient httpClient;
|
||||||
private MWApi api;
|
private MWApi api;
|
||||||
|
private MWApi wikidataApi;
|
||||||
private Context context;
|
private Context context;
|
||||||
private SharedPreferences sharedPreferences;
|
private SharedPreferences defaultPreferences;
|
||||||
|
private SharedPreferences categoryPreferences;
|
||||||
|
private Gson gson;
|
||||||
|
|
||||||
public ApacheHttpClientMediaWikiApi(Context context, String apiURL, SharedPreferences sharedPreferences) {
|
public ApacheHttpClientMediaWikiApi(Context context,
|
||||||
|
String apiURL,
|
||||||
|
String wikidatApiURL,
|
||||||
|
SharedPreferences defaultPreferences,
|
||||||
|
SharedPreferences categoryPreferences,
|
||||||
|
Gson gson) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
BasicHttpParams params = new BasicHttpParams();
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||||
|
|
@ -69,7 +86,10 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
|
params.setParameter(CoreProtocolPNames.USER_AGENT, getUserAgent());
|
||||||
httpClient = new DefaultHttpClient(cm, params);
|
httpClient = new DefaultHttpClient(cm, params);
|
||||||
api = new MWApi(apiURL, httpClient);
|
api = new MWApi(apiURL, httpClient);
|
||||||
this.sharedPreferences = sharedPreferences;
|
wikidataApi = new MWApi(wikidatApiURL, httpClient);
|
||||||
|
this.defaultPreferences = defaultPreferences;
|
||||||
|
this.categoryPreferences = categoryPreferences;
|
||||||
|
this.gson = gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -160,7 +180,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAuthCookieOnLogin(boolean isLoggedIn) {
|
private void setAuthCookieOnLogin(boolean isLoggedIn) {
|
||||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
SharedPreferences.Editor editor = defaultPreferences.edit();
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
editor.putBoolean("isUserLoggedIn", true);
|
editor.putBoolean("isUserLoggedIn", true);
|
||||||
editor.putString("getAuthCookie", api.getAuthCookie());
|
editor.putString("getAuthCookie", api.getAuthCookie());
|
||||||
|
|
@ -191,6 +211,15 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
return api.getEditToken();
|
return api.getEditToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCentralAuthToken() throws IOException {
|
||||||
|
String centralAuthToken = api.action("centralauthtoken")
|
||||||
|
.get()
|
||||||
|
.getString("/api/centralauthtoken/@centralauthtoken");
|
||||||
|
Timber.d("MediaWiki Central auth token is %s", centralAuthToken);
|
||||||
|
return centralAuthToken;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean fileExistsWithName(String fileName) throws IOException {
|
public boolean fileExistsWithName(String fileName) throws IOException {
|
||||||
return api.action("query")
|
return api.action("query")
|
||||||
|
|
@ -336,6 +365,98 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
}).flatMapObservable(Observable::fromIterable);
|
}).flatMapObservable(Observable::fromIterable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the edit token for making wiki data edits
|
||||||
|
* https://www.mediawiki.org/wiki/API:Tokens
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private String getWikidataEditToken() throws IOException {
|
||||||
|
return wikidataApi.getEditToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getWikidataCsrfToken() throws IOException {
|
||||||
|
String wikidataCsrfToken = wikidataApi.action("query")
|
||||||
|
.param("action", "query")
|
||||||
|
.param("centralauthtoken", getCentralAuthToken())
|
||||||
|
.param("meta", "tokens")
|
||||||
|
.post()
|
||||||
|
.getString("/api/query/tokens/@csrftoken");
|
||||||
|
Timber.d("Wikidata csrf token is %s", wikidataCsrfToken);
|
||||||
|
return wikidataCsrfToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new claim using the wikidata API
|
||||||
|
* https://www.mediawiki.org/wiki/Wikibase/API
|
||||||
|
* @param entityId the wikidata entity to be edited
|
||||||
|
* @param property the property to be edited, for eg P18 for images
|
||||||
|
* @param snaktype the type of value stored for that property
|
||||||
|
* @param value the actual value to be stored for the property, for eg filename in case of P18
|
||||||
|
* @return returns revisionId if the claim is successfully created else returns null
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException {
|
||||||
|
Timber.d("Filename is %s", value);
|
||||||
|
ApiResult result = wikidataApi.action("wbcreateclaim")
|
||||||
|
.param("entity", entityId)
|
||||||
|
.param("centralauthtoken", getCentralAuthToken())
|
||||||
|
.param("token", getWikidataCsrfToken())
|
||||||
|
.param("snaktype", snaktype)
|
||||||
|
.param("property", property)
|
||||||
|
.param("value", value)
|
||||||
|
.post();
|
||||||
|
|
||||||
|
if (result == null || result.getNode("api") == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = result.getNode("api").getDocument();
|
||||||
|
Element element = (Element) node;
|
||||||
|
|
||||||
|
if (element != null && element.getAttribute("success").equals("1")) {
|
||||||
|
return result.getString("api/pageinfo/@lastrevid");
|
||||||
|
} else {
|
||||||
|
Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info"));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the wikimedia-commons-app tag to the edits made on wikidata
|
||||||
|
* @param revisionId
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public boolean addWikidataEditTag(String revisionId) throws IOException {
|
||||||
|
ApiResult result = wikidataApi.action("tag")
|
||||||
|
.param("revid", revisionId)
|
||||||
|
.param("centralauthtoken", getCentralAuthToken())
|
||||||
|
.param("token", getWikidataCsrfToken())
|
||||||
|
.param("add", "wikimedia-commons-app")
|
||||||
|
.param("reason", "Add tag for edits made using Android Commons app")
|
||||||
|
.post();
|
||||||
|
|
||||||
|
if (result == null || result.getNode("api") == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node node = result.getNode("api").getDocument();
|
||||||
|
Element element = (Element) node;
|
||||||
|
|
||||||
|
if (element != null && element.getAttribute("status").equals("success")) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
Timber.e(result.getString("api/error/@code") + " " + result.getString("api/error/@info"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public Observable<String> searchTitles(String title, int searchCatsLimit) {
|
public Observable<String> searchTitles(String title, int searchCatsLimit) {
|
||||||
|
|
@ -429,8 +550,8 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
.param("notprop", "list")
|
.param("notprop", "list")
|
||||||
.param("format", "xml")
|
.param("format", "xml")
|
||||||
.param("meta", "notifications")
|
.param("meta", "notifications")
|
||||||
// .param("meta", "notifications")
|
|
||||||
.param("notformat", "model")
|
.param("notformat", "model")
|
||||||
|
.param("notwikis", "wikidatawiki|commonswiki|enwiki")
|
||||||
.get()
|
.get()
|
||||||
.getNode("/api/query/notifications/list");
|
.getNode("/api/query/notifications/list");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -448,6 +569,83 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
return NotificationUtils.getNotificationsFromList(context, childNodes);
|
return NotificationUtils.getNotificationsFromList(context, childNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method takes categoryName as input and returns a List of Media objects
|
||||||
|
* It uses the generator query API to get the images in a category, 10 at a time.
|
||||||
|
* Uses the query continue values for fetching paginated responses
|
||||||
|
* @param categoryName Category name as defined on commons
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public List<Media> getCategoryImages(String categoryName) {
|
||||||
|
ApiResult apiResult = null;
|
||||||
|
try {
|
||||||
|
MWApi.RequestBuilder requestBuilder = api.action("query")
|
||||||
|
.param("generator", "categorymembers")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("gcmtype", "file")
|
||||||
|
.param("gcmtitle", categoryName)
|
||||||
|
.param("gcmsort", "timestamp")//property to sort by;timestamp
|
||||||
|
.param("gcmdir", "desc")//in which direction to sort;descending
|
||||||
|
.param("prop", "imageinfo")
|
||||||
|
.param("gcmlimit", "10")
|
||||||
|
.param("iiprop", "url|extmetadata");
|
||||||
|
|
||||||
|
QueryContinue queryContinueValues = getQueryContinueValues(categoryName);
|
||||||
|
if (queryContinueValues != null) {
|
||||||
|
requestBuilder.param("continue", queryContinueValues.getContinueParam());
|
||||||
|
requestBuilder.param("gcmcontinue", queryContinueValues.getGcmContinueParam());
|
||||||
|
}
|
||||||
|
|
||||||
|
apiResult = requestBuilder.get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e("Failed to obtain searchCategories", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apiResult == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResult categoryImagesNode = apiResult.getNode("/api/query/pages");
|
||||||
|
if (categoryImagesNode == null
|
||||||
|
|| categoryImagesNode.getDocument() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes() == null
|
||||||
|
|| categoryImagesNode.getDocument().getChildNodes().getLength() == 0) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QueryContinue queryContinue = getQueryContinue(apiResult.getNode("/api/continue").getDocument());
|
||||||
|
setQueryContinueValues(categoryName, queryContinue);
|
||||||
|
|
||||||
|
NodeList childNodes = categoryImagesNode.getDocument().getChildNodes();
|
||||||
|
return CategoryImageUtils.getMediaList(childNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For APIs that return paginated responses, MediaWiki APIs uses the QueryContinue to facilitate fetching of subsequent pages
|
||||||
|
* https://www.mediawiki.org/wiki/API:Raw_query_continue
|
||||||
|
* After fetching images a page of image for a particular category, shared prefs are updated with the latest QueryContinue Values
|
||||||
|
* @param keyword
|
||||||
|
* @param queryContinue
|
||||||
|
*/
|
||||||
|
private void setQueryContinueValues(String keyword, QueryContinue queryContinue) {
|
||||||
|
SharedPreferences.Editor editor = categoryPreferences.edit();
|
||||||
|
editor.putString(keyword, gson.toJson(queryContinue));
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before making a paginated API call, this method is called to get the latest query continue values to be used
|
||||||
|
* @param keyword
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private QueryContinue getQueryContinueValues(String keyword) {
|
||||||
|
String queryContinueString = categoryPreferences.getString(keyword, null);
|
||||||
|
return gson.fromJson(queryContinueString, QueryContinue.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean existingFile(String fileSha1) throws IOException {
|
public boolean existingFile(String fileSha1) throws IOException {
|
||||||
return api.action("query")
|
return api.action("query")
|
||||||
|
|
@ -496,6 +694,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
String resultStatus = result.getString("/api/upload/@result");
|
String resultStatus = result.getString("/api/upload/@result");
|
||||||
if (!resultStatus.equals("Success")) {
|
if (!resultStatus.equals("Success")) {
|
||||||
String errorCode = result.getString("/api/error/@code");
|
String errorCode = result.getString("/api/error/@code");
|
||||||
|
Timber.e(errorCode);
|
||||||
return new UploadResult(resultStatus, errorCode);
|
return new UploadResult(resultStatus, errorCode);
|
||||||
} else {
|
} else {
|
||||||
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
|
Date dateUploaded = parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
|
||||||
|
|
|
||||||
101
app/src/main/java/fr/free/nrw/commons/mwapi/CategoryApi.java
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
package fr.free.nrw.commons.mwapi;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.mwapi.model.ApiResponse;
|
||||||
|
import fr.free.nrw.commons.mwapi.model.Page;
|
||||||
|
import fr.free.nrw.commons.mwapi.model.PageCategory;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the OkHttp library to implement calls to the Commons MediaWiki API to match GPS coordinates
|
||||||
|
* with nearby Commons categories. Parses the results using GSON to obtain a list of relevant
|
||||||
|
* categories. Note: that caller is responsible for executing the request() method on a background
|
||||||
|
* thread.
|
||||||
|
*/
|
||||||
|
public class CategoryApi {
|
||||||
|
|
||||||
|
private final OkHttpClient okHttpClient;
|
||||||
|
private final HttpUrl mwUrl;
|
||||||
|
private final Gson gson;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CategoryApi(OkHttpClient okHttpClient, Gson gson,
|
||||||
|
@Named("commons_mediawiki_url") HttpUrl mwUrl) {
|
||||||
|
this.okHttpClient = okHttpClient;
|
||||||
|
this.mwUrl = mwUrl;
|
||||||
|
this.gson = gson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<List<String>> request(String coords) {
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
HttpUrl apiUrl = buildUrl(coords);
|
||||||
|
Timber.d("URL: %s", apiUrl.toString());
|
||||||
|
|
||||||
|
Request request = new Request.Builder().get().url(apiUrl).build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
ResponseBody body = response.body();
|
||||||
|
if (body == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiResponse apiResponse = gson.fromJson(body.charStream(), ApiResponse.class);
|
||||||
|
Set<String> categories = new LinkedHashSet<>();
|
||||||
|
if (apiResponse != null && apiResponse.hasPages()) {
|
||||||
|
for (Page page : apiResponse.query.pages) {
|
||||||
|
for (PageCategory category : page.getCategories()) {
|
||||||
|
categories.add(category.withoutPrefix());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(categories);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds URL with image coords for MediaWiki API calls
|
||||||
|
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
|
||||||
|
*
|
||||||
|
* @param coords Coordinates to build query with
|
||||||
|
* @return URL for API query
|
||||||
|
*/
|
||||||
|
private HttpUrl buildUrl(String coords) {
|
||||||
|
return mwUrl.newBuilder()
|
||||||
|
.addPathSegment("w")
|
||||||
|
.addPathSegment("api.php")
|
||||||
|
.addQueryParameter("action", "query")
|
||||||
|
.addQueryParameter("prop", "categories|coordinates|pageprops")
|
||||||
|
.addQueryParameter("format", "json")
|
||||||
|
.addQueryParameter("clshow", "!hidden")
|
||||||
|
.addQueryParameter("coprop", "type|name|dim|country|region|globe")
|
||||||
|
.addQueryParameter("codistancefrompoint", coords)
|
||||||
|
.addQueryParameter("generator", "geosearch")
|
||||||
|
.addQueryParameter("ggscoord", coords)
|
||||||
|
.addQueryParameter("ggsradius", "10000")
|
||||||
|
.addQueryParameter("ggslimit", "10")
|
||||||
|
.addQueryParameter("ggsnamespace", "6")
|
||||||
|
.addQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
||||||
|
.addQueryParameter("ggsprimary", "all")
|
||||||
|
.addQueryParameter("formatversion", "2")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -7,6 +7,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.notification.Notification;
|
import fr.free.nrw.commons.notification.Notification;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
|
|
@ -26,6 +27,10 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
String getEditToken() throws IOException;
|
String getEditToken() throws IOException;
|
||||||
|
|
||||||
|
String getWikidataCsrfToken() throws IOException;
|
||||||
|
|
||||||
|
String getCentralAuthToken() throws IOException;
|
||||||
|
|
||||||
boolean fileExistsWithName(String fileName) throws IOException;
|
boolean fileExistsWithName(String fileName) throws IOException;
|
||||||
|
|
||||||
boolean pageExists(String pageName) throws IOException;
|
boolean pageExists(String pageName) throws IOException;
|
||||||
|
|
@ -34,6 +39,8 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
boolean logEvents(LogBuilder[] logBuilders);
|
boolean logEvents(LogBuilder[] logBuilders);
|
||||||
|
|
||||||
|
List<Media> getCategoryImages(String categoryName);
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
||||||
|
|
||||||
|
|
@ -46,6 +53,12 @@ public interface MediaWikiApi {
|
||||||
@Nullable
|
@Nullable
|
||||||
String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
|
String appendEdit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
String wikidatCreateClaim(String entityId, String property, String snaktype, String value) throws IOException;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
boolean addWikidataEditTag(String revisionId) throws IOException;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
MediaResult fetchMediaByFilename(String filename) throws IOException;
|
MediaResult fetchMediaByFilename(String filename) throws IOException;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class ApiResponse {
|
||||||
|
public Query query;
|
||||||
|
|
||||||
|
public ApiResponse() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPages() {
|
||||||
|
return query != null && query.pages != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
app/src/main/java/fr/free/nrw/commons/mwapi/model/Page.java
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public class Page {
|
||||||
|
public String title;
|
||||||
|
public PageCategory[] categories;
|
||||||
|
public PageCategory category;
|
||||||
|
|
||||||
|
public Page() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public PageCategory[] getCategories() {
|
||||||
|
return categories != null ? categories : new PageCategory[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class PageCategory {
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
public PageCategory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public String withoutPrefix() {
|
||||||
|
return title != null ? title.replace("Category:", "") : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
10
app/src/main/java/fr/free/nrw/commons/mwapi/model/Query.java
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package fr.free.nrw.commons.mwapi.model;
|
||||||
|
|
||||||
|
public class Query {
|
||||||
|
public Page[] pages;
|
||||||
|
|
||||||
|
public Query() {
|
||||||
|
pages = new Page[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
|
||||||
|
|
@ -4,23 +4,24 @@ import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.design.widget.BottomSheetBehavior;
|
import android.support.design.widget.BottomSheetBehavior;
|
||||||
|
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
@ -28,28 +29,34 @@ import com.google.gson.GsonBuilder;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
|
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
|
||||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.UriSerializer;
|
import fr.free.nrw.commons.utils.UriSerializer;
|
||||||
|
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
import uk.co.deanwild.materialshowcaseview.IShowcaseListener;
|
||||||
|
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.*;
|
||||||
|
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||||
|
|
||||||
|
|
||||||
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener {
|
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener,
|
||||||
|
WikidataEditListener.WikidataP18EditListener {
|
||||||
|
|
||||||
private static final int LOCATION_REQUEST = 1;
|
private static final int LOCATION_REQUEST = 1;
|
||||||
|
|
||||||
|
|
@ -62,13 +69,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
LinearLayout bottomSheetDetails;
|
LinearLayout bottomSheetDetails;
|
||||||
@BindView(R.id.transparentView)
|
@BindView(R.id.transparentView)
|
||||||
View transparentView;
|
View transparentView;
|
||||||
|
@BindView(R.id.fab_recenter)
|
||||||
|
View fabRecenter;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
LocationServiceManager locationManager;
|
LocationServiceManager locationManager;
|
||||||
@Inject
|
@Inject
|
||||||
NearbyController nearbyController;
|
NearbyController nearbyController;
|
||||||
|
@Inject WikidataEditListener wikidataEditListener;
|
||||||
|
|
||||||
private LatLng curLatLang;
|
@Inject
|
||||||
|
@Named("application_preferences") SharedPreferences applicationPrefs;
|
||||||
|
private LatLng curLatLng;
|
||||||
private Bundle bundle;
|
private Bundle bundle;
|
||||||
private Disposable placesDisposable;
|
private Disposable placesDisposable;
|
||||||
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
|
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
|
||||||
|
|
@ -78,10 +90,18 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
private NearbyListFragment nearbyListFragment;
|
private NearbyListFragment nearbyListFragment;
|
||||||
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
|
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.class.getSimpleName();
|
||||||
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
|
private static final String TAG_RETAINED_LIST_FRAGMENT = NearbyListFragment.class.getSimpleName();
|
||||||
|
private View listButton; // Reference to list button to use in tutorial
|
||||||
|
|
||||||
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||||
private BroadcastReceiver broadcastReceiver;
|
private BroadcastReceiver broadcastReceiver;
|
||||||
|
|
||||||
|
private boolean isListShowcaseAdded = false;
|
||||||
|
private boolean isMapShowCaseAdded = false;
|
||||||
|
|
||||||
|
private LatLng lastKnownLocation;
|
||||||
|
|
||||||
|
private MaterialShowcaseView secondSingleShowCaseView;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -92,6 +112,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
|
|
||||||
initBottomSheetBehaviour();
|
initBottomSheetBehaviour();
|
||||||
initDrawer();
|
initDrawer();
|
||||||
|
wikidataEditListener.setAuthenticationStateListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resumeFragment() {
|
private void resumeFragment() {
|
||||||
|
|
@ -131,16 +152,55 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
MenuInflater inflater = getMenuInflater();
|
MenuInflater inflater = getMenuInflater();
|
||||||
inflater.inflate(R.menu.menu_nearby, menu);
|
inflater.inflate(R.menu.menu_nearby, menu);
|
||||||
|
|
||||||
|
new Handler().post(() -> {
|
||||||
|
|
||||||
|
listButton = findViewById(R.id.action_display_list);
|
||||||
|
|
||||||
|
secondSingleShowCaseView = new MaterialShowcaseView.Builder(this)
|
||||||
|
.setTarget(listButton)
|
||||||
|
.setDismissText(getString(R.string.showcase_view_got_it_button))
|
||||||
|
.setContentText(getString(R.string.showcase_view_list_icon))
|
||||||
|
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
|
||||||
|
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_1) // provide a unique ID used to ensure it is only shown once
|
||||||
|
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
|
||||||
|
.setListener(new IShowcaseListener() {
|
||||||
|
@Override
|
||||||
|
public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// If dismissed, we can inform fragment to start showcase sequence there
|
||||||
|
@Override
|
||||||
|
public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
|
||||||
|
nearbyMapFragment.onNearbyMaterialShowcaseDismissed();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
isListShowcaseAdded = true;
|
||||||
|
|
||||||
|
if (isMapShowCaseAdded) { // If map showcase is also ready, start ShowcaseSequence
|
||||||
|
// Probably this case is not possible. Just added to be careful
|
||||||
|
setMapViewTutorialShowCase();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
// Handle item selection
|
// Handle item selection
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case R.id.action_display_list:
|
case R.id.action_display_list:
|
||||||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
|
||||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||||
|
}else if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
|
||||||
|
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
|
|
@ -158,7 +218,11 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case LOCATION_REQUEST: {
|
case LOCATION_REQUEST: {
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
Timber.d("Location permission granted, refreshing view");
|
||||||
|
//Still need to check if GPS is enabled
|
||||||
|
checkGps();
|
||||||
|
lastKnownLocation = locationManager.getLKL();
|
||||||
|
refreshView(PERMISSION_JUST_GRANTED);
|
||||||
} else {
|
} else {
|
||||||
//If permission not granted, go to page that says Nearby Places cannot be displayed
|
//If permission not granted, go to page that says Nearby Places cannot be displayed
|
||||||
hideProgressBar();
|
hideProgressBar();
|
||||||
|
|
@ -218,7 +282,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
private void checkLocationPermission() {
|
private void checkLocationPermission() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (locationManager.isLocationPermissionGranted()) {
|
if (locationManager.isLocationPermissionGranted()) {
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||||
} else {
|
} else {
|
||||||
// Should we show an explanation?
|
// Should we show an explanation?
|
||||||
if (locationManager.isPermissionExplanationRequired(this)) {
|
if (locationManager.isPermissionExplanationRequired(this)) {
|
||||||
|
|
@ -244,7 +308,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,7 +317,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (requestCode == 1) {
|
if (requestCode == 1) {
|
||||||
Timber.d("User is back from Settings page");
|
Timber.d("User is back from Settings page");
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +325,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
locationManager.addLocationListener(this);
|
locationManager.addLocationListener(this);
|
||||||
locationManager.registerLocationManager();
|
registerLocationUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -312,8 +376,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
|
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
|
||||||
refreshView(LocationServiceManager
|
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||||
.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
|
||||||
} else {
|
} else {
|
||||||
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
|
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +392,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
*
|
*
|
||||||
* @param locationChangeType defines if location shanged significantly or slightly
|
* @param locationChangeType defines if location shanged significantly or slightly
|
||||||
*/
|
*/
|
||||||
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
|
private void refreshView(LocationChangeType locationChangeType) {
|
||||||
if (lockNearbyView) {
|
if (lockNearbyView) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -339,38 +402,91 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
locationManager.registerLocationManager();
|
registerLocationUpdates();
|
||||||
LatLng lastLocation = locationManager.getLastLocation();
|
LatLng lastLocation = locationManager.getLastLocation();
|
||||||
|
|
||||||
if (curLatLang != null && curLatLang.equals(lastLocation)) { //refresh view only if location has changed
|
if (curLatLng != null && curLatLng.equals(lastLocation)
|
||||||
|
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
curLatLang = lastLocation;
|
curLatLng = lastLocation;
|
||||||
|
|
||||||
if (curLatLang == null) {
|
if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
|
||||||
|
curLatLng = lastKnownLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curLatLng == null) {
|
||||||
Timber.d("Skipping update of nearby places as location is unavailable");
|
Timber.d("Skipping update of nearby places as location is unavailable");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (locationChangeType
|
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|
||||||
.equals(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) {
|
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
|
||||||
|
|| locationChangeType.equals(MAP_UPDATED)) {
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
progressBar.setVisibility(View.VISIBLE);
|
||||||
placesDisposable = Observable.fromCallable(() -> nearbyController
|
|
||||||
.loadAttractionsFromLocation(curLatLang))
|
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(this::populatePlaces);
|
|
||||||
} else if (locationChangeType
|
|
||||||
.equals(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
|
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(Uri.class, new UriSerializer())
|
.registerTypeAdapter(Uri.class, new UriSerializer())
|
||||||
.create();
|
.create();
|
||||||
String gsonCurLatLng = gson.toJson(curLatLang);
|
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||||
|
bundle.clear();
|
||||||
|
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||||
|
|
||||||
|
placesDisposable = Observable.fromCallable(() -> nearbyController
|
||||||
|
.loadAttractionsFromLocation(curLatLng))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(this::populatePlaces,
|
||||||
|
throwable -> {
|
||||||
|
Timber.d(throwable);
|
||||||
|
showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
} else if (locationChangeType
|
||||||
|
.equals(LOCATION_SLIGHTLY_CHANGED)) {
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriSerializer())
|
||||||
|
.create();
|
||||||
|
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||||
bundle.putString("CurLatLng", gsonCurLatLng);
|
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||||
updateMapFragment(true);
|
updateMapFragment(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method first checks if the location permissions has been granted and then register the location manager for updates.
|
||||||
|
*/
|
||||||
|
private void registerLocationUpdates() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
if (locationManager.isLocationPermissionGranted()) {
|
||||||
|
locationManager.registerLocationManager();
|
||||||
|
} else {
|
||||||
|
// Should we show an explanation?
|
||||||
|
if (locationManager.isPermissionExplanationRequired(this)) {
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setMessage(getString(R.string.location_permission_rationale_nearby))
|
||||||
|
.setPositiveButton("OK", (dialog, which) -> {
|
||||||
|
requestLocationPermissions();
|
||||||
|
dialog.dismiss();
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", (dialog, id) -> {
|
||||||
|
showLocationPermissionDeniedErrorDialog();
|
||||||
|
dialog.cancel();
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// No explanation needed, we can request the permission.
|
||||||
|
requestLocationPermissions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
locationManager.registerLocationManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
private void populatePlaces(NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||||
List<Place> placeList = nearbyPlacesInfo.placeList;
|
List<Place> placeList = nearbyPlacesInfo.placeList;
|
||||||
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
|
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
|
||||||
|
|
@ -378,20 +494,20 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
.registerTypeAdapter(Uri.class, new UriSerializer())
|
.registerTypeAdapter(Uri.class, new UriSerializer())
|
||||||
.create();
|
.create();
|
||||||
String gsonPlaceList = gson.toJson(placeList);
|
String gsonPlaceList = gson.toJson(placeList);
|
||||||
String gsonCurLatLng = gson.toJson(curLatLang);
|
String gsonCurLatLng = gson.toJson(curLatLng);
|
||||||
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
|
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
|
||||||
|
|
||||||
if (placeList.size() == 0) {
|
if (placeList.size() == 0) {
|
||||||
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
|
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle.clear();
|
|
||||||
bundle.putString("PlaceList", gsonPlaceList);
|
bundle.putString("PlaceList", gsonPlaceList);
|
||||||
bundle.putString("CurLatLng", gsonCurLatLng);
|
//bundle.putString("CurLatLng", gsonCurLatLng);
|
||||||
bundle.putString("BoundaryCoord", gsonBoundaryCoordinates);
|
bundle.putString("BoundaryCoord", gsonBoundaryCoordinates);
|
||||||
|
|
||||||
// First time to init fragments
|
// First time to init fragments
|
||||||
if (nearbyMapFragment == null) {
|
if (nearbyMapFragment == null) {
|
||||||
|
Timber.d("Init map fragment for the first time");
|
||||||
lockNearbyView(true);
|
lockNearbyView(true);
|
||||||
setMapFragment();
|
setMapFragment();
|
||||||
setListFragment();
|
setListFragment();
|
||||||
|
|
@ -399,9 +515,49 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
lockNearbyView(false);
|
lockNearbyView(false);
|
||||||
} else {
|
} else {
|
||||||
// There are fragments, just update the map and list
|
// There are fragments, just update the map and list
|
||||||
|
Timber.d("Map fragment already exists, just update the map and list");
|
||||||
updateMapFragment(false);
|
updateMapFragment(false);
|
||||||
updateListFragment();
|
updateListFragment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMapShowCaseAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMapViewTutorialShowCase() {
|
||||||
|
/*
|
||||||
|
*This showcase view will be the first step of our nearbyMaterialShowcaseSequence. The reason we use a
|
||||||
|
* single item instead of adding another step to nearbyMaterialShowcaseSequence is that we are not able to
|
||||||
|
* call withoutShape() method on steps. For mapView we need an showcase view without
|
||||||
|
* any circle on it, it should cover the whole page.
|
||||||
|
* */
|
||||||
|
MaterialShowcaseView firstSingleShowCaseView = new MaterialShowcaseView.Builder(this)
|
||||||
|
.setTarget(nearbyMapFragment.mapView)
|
||||||
|
.setDismissText(getString(R.string.showcase_view_got_it_button))
|
||||||
|
.setContentText(getString(R.string.showcase_view_whole_nearby_activity))
|
||||||
|
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
|
||||||
|
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_2) // provide a unique ID used to ensure it is only shown once
|
||||||
|
.withoutShape() // no shape on map view since there are no view to focus on
|
||||||
|
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
|
||||||
|
.setListener(new IShowcaseListener() {
|
||||||
|
@Override
|
||||||
|
public void onShowcaseDisplayed(MaterialShowcaseView materialShowcaseView) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShowcaseDismissed(MaterialShowcaseView materialShowcaseView) {
|
||||||
|
/* Add other nearbyMaterialShowcaseSequence here, it will make the user feel as they are a
|
||||||
|
* nearbyMaterialShowcaseSequence whole together.
|
||||||
|
* */
|
||||||
|
secondSingleShowCaseView.show(NearbyActivity.this);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if (applicationPrefs.getBoolean("firstRunNearby", true)) {
|
||||||
|
applicationPrefs.edit().putBoolean("firstRunNearby", false).apply();
|
||||||
|
firstSingleShowCaseView.show(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lockNearbyView(boolean lock) {
|
private void lockNearbyView(boolean lock) {
|
||||||
|
|
@ -411,7 +567,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
locationManager.removeLocationListener(this);
|
locationManager.removeLocationListener(this);
|
||||||
} else {
|
} else {
|
||||||
lockNearbyView = false;
|
lockNearbyView = false;
|
||||||
locationManager.registerLocationManager();
|
registerLocationUpdates();
|
||||||
locationManager.addLocationListener(this);
|
locationManager.addLocationListener(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -457,34 +613,39 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
|
|
||||||
NearbyMapFragment nearbyMapFragment = getMapFragment();
|
NearbyMapFragment nearbyMapFragment = getMapFragment();
|
||||||
|
|
||||||
if (nearbyMapFragment != null && curLatLang != null) {
|
if (nearbyMapFragment != null && curLatLng != null) {
|
||||||
hideProgressBar(); // In case it is visible (this happens, not an impossible case)
|
hideProgressBar(); // In case it is visible (this happens, not an impossible case)
|
||||||
/*
|
/*
|
||||||
* If we are close to nearby places boundaries, we need a significant update to
|
* If we are close to nearby places boundaries, we need a significant update to
|
||||||
* get new nearby places. Check order is south, north, west, east
|
* get new nearby places. Check order is south, north, west, east
|
||||||
* */
|
* */
|
||||||
if (nearbyMapFragment.boundaryCoordinates != null
|
if (nearbyMapFragment.boundaryCoordinates != null
|
||||||
&& (curLatLang.getLatitude() <= nearbyMapFragment.boundaryCoordinates[0].getLatitude()
|
&& (curLatLng.getLatitude() <= nearbyMapFragment.boundaryCoordinates[0].getLatitude()
|
||||||
|| curLatLang.getLatitude() >= nearbyMapFragment.boundaryCoordinates[1].getLatitude()
|
|| curLatLng.getLatitude() >= nearbyMapFragment.boundaryCoordinates[1].getLatitude()
|
||||||
|| curLatLang.getLongitude() <= nearbyMapFragment.boundaryCoordinates[2].getLongitude()
|
|| curLatLng.getLongitude() <= nearbyMapFragment.boundaryCoordinates[2].getLongitude()
|
||||||
|| curLatLang.getLongitude() >= nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
|
|| curLatLng.getLongitude() >= nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
|
||||||
// populate places
|
// populate places
|
||||||
placesDisposable = Observable.fromCallable(() -> nearbyController
|
placesDisposable = Observable.fromCallable(() -> nearbyController
|
||||||
.loadAttractionsFromLocation(curLatLang))
|
.loadAttractionsFromLocation(curLatLng))
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(this::populatePlaces);
|
.subscribe(this::populatePlaces,
|
||||||
nearbyMapFragment.setArguments(bundle);
|
throwable -> {
|
||||||
|
Timber.d(throwable);
|
||||||
|
showErrorMessage(getString(R.string.error_fetching_nearby_places));
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
});
|
||||||
|
nearbyMapFragment.setBundleForUpdtes(bundle);
|
||||||
nearbyMapFragment.updateMapSignificantly();
|
nearbyMapFragment.updateMapSignificantly();
|
||||||
updateListFragment();
|
updateListFragment();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSlightUpdate) {
|
if (isSlightUpdate) {
|
||||||
nearbyMapFragment.setArguments(bundle);
|
nearbyMapFragment.setBundleForUpdtes(bundle);
|
||||||
nearbyMapFragment.updateMapSlightly();
|
nearbyMapFragment.updateMapSlightly();
|
||||||
} else {
|
} else {
|
||||||
nearbyMapFragment.setArguments(bundle);
|
nearbyMapFragment.setBundleForUpdtes(bundle);
|
||||||
nearbyMapFragment.updateMapSignificantly();
|
nearbyMapFragment.updateMapSignificantly();
|
||||||
updateListFragment();
|
updateListFragment();
|
||||||
}
|
}
|
||||||
|
|
@ -498,7 +659,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateListFragment() {
|
private void updateListFragment() {
|
||||||
nearbyListFragment.setArguments(bundle);
|
nearbyListFragment.setBundleForUpdates(bundle);
|
||||||
nearbyListFragment.updateNearbyListSignificantly();
|
nearbyListFragment.updateNearbyListSignificantly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -528,15 +689,24 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED);
|
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChangedSlightly(LatLng latLng) {
|
public void onLocationChangedSlightly(LatLng latLng) {
|
||||||
refreshView(LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED);
|
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showErrorMessage(String message) {
|
||||||
|
ViewUtil.showLongToast(NearbyActivity.this, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onWikidataEditSuccessful() {
|
||||||
|
refreshView(MAP_UPDATED);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
@ -44,7 +45,7 @@ public class NearbyController {
|
||||||
* @return NearbyPlacesInfo a variable holds Place list without distance information
|
* @return NearbyPlacesInfo a variable holds Place list without distance information
|
||||||
* and boundary coordinates of current Place List
|
* and boundary coordinates of current Place List
|
||||||
*/
|
*/
|
||||||
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) {
|
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) throws IOException {
|
||||||
|
|
||||||
Timber.d("Loading attractions near %s", curLatLng);
|
Timber.d("Loading attractions near %s", curLatLng);
|
||||||
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
|
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -21,6 +22,9 @@ import java.lang.reflect.Type;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
import dagger.android.support.AndroidSupportInjection;
|
import dagger.android.support.AndroidSupportInjection;
|
||||||
import dagger.android.support.DaggerFragment;
|
import dagger.android.support.DaggerFragment;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -33,6 +37,8 @@ import static android.app.Activity.RESULT_OK;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
public class NearbyListFragment extends DaggerFragment {
|
public class NearbyListFragment extends DaggerFragment {
|
||||||
|
private Bundle bundleForUpdates; // Carry information from activity about changed nearby places and current location
|
||||||
|
|
||||||
private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
|
private static final Type LIST_TYPE = new TypeToken<List<Place>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() {
|
private static final Type CUR_LAT_LNG_TYPE = new TypeToken<LatLng>() {
|
||||||
|
|
@ -45,6 +51,11 @@ public class NearbyListFragment extends DaggerFragment {
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private ContributionController controller;
|
private ContributionController controller;
|
||||||
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@Named("direct_nearby_upload_prefs")
|
||||||
|
SharedPreferences directPrefs;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
@ -80,9 +91,11 @@ public class NearbyListFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateNearbyListSignificantly() {
|
public void updateNearbyListSignificantly() {
|
||||||
Bundle bundle = this.getArguments();
|
try {
|
||||||
adapterFactory.updateAdapterData(getPlaceListFromBundle(bundle),
|
adapterFactory.updateAdapterData(getPlaceListFromBundle(bundleForUpdates), (RVRendererAdapter<Place>) recyclerView.getAdapter());
|
||||||
(RVRendererAdapter<Place>) recyclerView.getAdapter());
|
} catch (NullPointerException e) {
|
||||||
|
Timber.e("Null pointer exception from calling recyclerView.getAdapter()");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Place> getPlaceListFromBundle(Bundle bundle) {
|
private List<Place> getPlaceListFromBundle(Bundle bundle) {
|
||||||
|
|
@ -133,11 +146,15 @@ public class NearbyListFragment extends DaggerFragment {
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
controller.handleImagePicked(requestCode, data, true);
|
controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
|
||||||
} else {
|
} else {
|
||||||
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBundleForUpdates(Bundle bundleForUpdates) {
|
||||||
|
this.bundleForUpdates = bundleForUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Typeface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
@ -58,13 +59,14 @@ import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.utils.UriDeserializer;
|
import fr.free.nrw.commons.utils.UriDeserializer;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseView;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||||
|
|
||||||
public class NearbyMapFragment extends DaggerFragment {
|
public class NearbyMapFragment extends DaggerFragment {
|
||||||
|
|
||||||
private MapView mapView;
|
public MapView mapView;
|
||||||
private List<NearbyBaseMarker> baseMarkerOptions;
|
private List<NearbyBaseMarker> baseMarkerOptions;
|
||||||
private fr.free.nrw.commons.location.LatLng curLatLng;
|
private fr.free.nrw.commons.location.LatLng curLatLng;
|
||||||
public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates;
|
public fr.free.nrw.commons.location.LatLng[] boundaryCoordinates;
|
||||||
|
|
@ -111,6 +113,12 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
|
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
|
||||||
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04;
|
private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.04;
|
||||||
|
|
||||||
|
private boolean isSecondMaterialShowcaseDismissed;
|
||||||
|
private boolean isMapReady;
|
||||||
|
private MaterialShowcaseView thirdSingleShowCaseView;
|
||||||
|
|
||||||
|
private Bundle bundleForUpdtes;// Carry information from activity about changed nearby places and current location
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("prefs")
|
@Named("prefs")
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
|
@ -124,6 +132,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
Timber.d("Nearby map fragment created");
|
||||||
|
|
||||||
controller = new ContributionController(this);
|
controller = new ContributionController(this);
|
||||||
directUpload = new DirectUpload(this, controller);
|
directUpload = new DirectUpload(this, controller);
|
||||||
|
|
@ -149,17 +158,20 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
getActivity());
|
getActivity());
|
||||||
boundaryCoordinates = gson.fromJson(gsonBoundaryCoordinates, gsonBoundaryCoordinatesType);
|
boundaryCoordinates = gson.fromJson(gsonBoundaryCoordinates, gsonBoundaryCoordinatesType);
|
||||||
}
|
}
|
||||||
Mapbox.getInstance(getActivity(),
|
if (curLatLng != null) {
|
||||||
getString(R.string.mapbox_commons_app_token));
|
Mapbox.getInstance(getActivity(),
|
||||||
MapboxTelemetry.getInstance().setTelemetryEnabled(false);
|
getString(R.string.mapbox_commons_app_token));
|
||||||
|
MapboxTelemetry.getInstance().setTelemetryEnabled(false);
|
||||||
|
}
|
||||||
setRetainInstance(true);
|
setRetainInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
Timber.d("onCreateView called");
|
||||||
if (curLatLng != null) {
|
if (curLatLng != null) {
|
||||||
|
Timber.d("curLatLng found, setting up map view...");
|
||||||
setupMapView(savedInstanceState);
|
setupMapView(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,14 +204,12 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMapSlightly() {
|
public void updateMapSlightly() {
|
||||||
// Get arguments from bundle for new location
|
|
||||||
Bundle bundle = this.getArguments();
|
|
||||||
if (mapboxMap != null) {
|
if (mapboxMap != null) {
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||||
.create();
|
.create();
|
||||||
if (bundle != null) {
|
if (bundleForUpdtes != null) {
|
||||||
String gsonLatLng = bundle.getString("CurLatLng");
|
String gsonLatLng = bundleForUpdtes.getString("CurLatLng");
|
||||||
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
|
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
|
||||||
curLatLng = gson.fromJson(gsonLatLng, curLatLngType);
|
curLatLng = gson.fromJson(gsonLatLng, curLatLngType);
|
||||||
}
|
}
|
||||||
|
|
@ -209,17 +219,15 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateMapSignificantly() {
|
public void updateMapSignificantly() {
|
||||||
|
|
||||||
Bundle bundle = this.getArguments();
|
|
||||||
if (mapboxMap != null) {
|
if (mapboxMap != null) {
|
||||||
if (bundle != null) {
|
if (bundleForUpdtes != null) {
|
||||||
Gson gson = new GsonBuilder()
|
Gson gson = new GsonBuilder()
|
||||||
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
String gsonPlaceList = bundle.getString("PlaceList");
|
String gsonPlaceList = bundleForUpdtes.getString("PlaceList");
|
||||||
String gsonLatLng = bundle.getString("CurLatLng");
|
String gsonLatLng = bundleForUpdtes.getString("CurLatLng");
|
||||||
String gsonBoundaryCoordinates = bundle.getString("BoundaryCoord");
|
String gsonBoundaryCoordinates = bundleForUpdtes.getString("BoundaryCoord");
|
||||||
Type listType = new TypeToken<List<Place>>() {}.getType();
|
Type listType = new TypeToken<List<Place>>() {}.getType();
|
||||||
List<Place> placeList = gson.fromJson(gsonPlaceList, listType);
|
List<Place> placeList = gson.fromJson(gsonPlaceList, listType);
|
||||||
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
|
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
|
||||||
|
|
@ -457,6 +465,8 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
|
|
||||||
private void setupMapView(Bundle savedInstanceState) {
|
private void setupMapView(Bundle savedInstanceState) {
|
||||||
MapboxMapOptions options = new MapboxMapOptions()
|
MapboxMapOptions options = new MapboxMapOptions()
|
||||||
|
.compassGravity(Gravity.BOTTOM | Gravity.LEFT)
|
||||||
|
.compassMargins(new int[]{12, 0, 0, 24})
|
||||||
.styleUrl(Style.OUTDOORS)
|
.styleUrl(Style.OUTDOORS)
|
||||||
.logoEnabled(false)
|
.logoEnabled(false)
|
||||||
.attributionEnabled(false)
|
.attributionEnabled(false)
|
||||||
|
|
@ -471,6 +481,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
mapView.getMapAsync(new OnMapReadyCallback() {
|
mapView.getMapAsync(new OnMapReadyCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onMapReady(MapboxMap mapboxMap) {
|
public void onMapReady(MapboxMap mapboxMap) {
|
||||||
|
((NearbyActivity)getActivity()).setMapViewTutorialShowCase();
|
||||||
NearbyMapFragment.this.mapboxMap = mapboxMap;
|
NearbyMapFragment.this.mapboxMap = mapboxMap;
|
||||||
updateMapSignificantly();
|
updateMapSignificantly();
|
||||||
}
|
}
|
||||||
|
|
@ -514,6 +525,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
private void addNearbyMarkerstoMapBoxMap() {
|
private void addNearbyMarkerstoMapBoxMap() {
|
||||||
|
|
||||||
mapboxMap.addMarkers(baseMarkerOptions);
|
mapboxMap.addMarkers(baseMarkerOptions);
|
||||||
|
|
||||||
mapboxMap.setOnInfoWindowCloseListener(marker -> {
|
mapboxMap.setOnInfoWindowCloseListener(marker -> {
|
||||||
if (marker == selected) {
|
if (marker == selected) {
|
||||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
|
|
@ -529,6 +541,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
});
|
});
|
||||||
|
|
||||||
mapboxMap.setOnMarkerClickListener(marker -> {
|
mapboxMap.setOnMarkerClickListener(marker -> {
|
||||||
|
|
||||||
if (marker instanceof NearbyMarker) {
|
if (marker instanceof NearbyMarker) {
|
||||||
this.selected = marker;
|
this.selected = marker;
|
||||||
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
||||||
|
|
@ -536,6 +549,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
passInfoToSheet(place);
|
passInfoToSheet(place);
|
||||||
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||||
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
@ -629,7 +643,19 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
|
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
|
||||||
|
|
||||||
addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId());
|
addAnchorToSmallFABs(fabCamera, getActivity().findViewById(R.id.empty_view1).getId());
|
||||||
|
thirdSingleShowCaseView = new MaterialShowcaseView.Builder(this.getActivity())
|
||||||
|
.setTarget(fabPlus)
|
||||||
|
.setDismissText(getString(R.string.showcase_view_got_it_button))
|
||||||
|
.setContentText(getString(R.string.showcase_view_plus_fab))
|
||||||
|
.setDelay(500) // optional but starting animations immediately in onCreate can make them choppy
|
||||||
|
.singleUse(ViewUtil.SHOWCASE_VIEW_ID_3) // provide a unique ID used to ensure it is only shown once
|
||||||
|
.setDismissStyle(Typeface.defaultFromStyle(Typeface.BOLD))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
isMapReady = true;
|
||||||
|
if (isSecondMaterialShowcaseDismissed) {
|
||||||
|
thirdSingleShowCaseView.show(getActivity());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -666,7 +692,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
|
|
||||||
directionsButton.setOnClickListener(view -> {
|
directionsButton.setOnClickListener(view -> {
|
||||||
//Open map app at given position
|
//Open map app at given position
|
||||||
Intent mapIntent = new Intent(Intent.ACTION_VIEW, place.location.getGmmIntentUri());
|
Intent mapIntent = new Intent(Intent.ACTION_VIEW, this.place.location.getGmmIntentUri());
|
||||||
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
||||||
startActivity(mapIntent);
|
startActivity(mapIntent);
|
||||||
}
|
}
|
||||||
|
|
@ -705,6 +731,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
editor.putString("Title", place.getName());
|
editor.putString("Title", place.getName());
|
||||||
editor.putString("Desc", place.getLongDescription());
|
editor.putString("Desc", place.getLongDescription());
|
||||||
editor.putString("Category", place.getCategory());
|
editor.putString("Category", place.getCategory());
|
||||||
|
editor.putString("WikiDataEntityId", place.getWikiDataEntityId());
|
||||||
editor.apply();
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -740,7 +767,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
controller.handleImagePicked(requestCode, data, true);
|
controller.handleImagePicked(requestCode, data, true, directPrefs.getString("WikiDataEntityId", null));
|
||||||
} else {
|
} else {
|
||||||
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
requestCode, resultCode, data);
|
requestCode, resultCode, data);
|
||||||
|
|
@ -771,7 +798,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeFabs ( boolean isFabOpen){
|
private void closeFabs ( boolean isFabOpen){
|
||||||
if (isFabOpen) {
|
if (isFabOpen) {
|
||||||
fabPlus.startAnimation(rotate_backward);
|
fabPlus.startAnimation(rotate_backward);
|
||||||
fabCamera.startAnimation(fab_close);
|
fabCamera.startAnimation(fab_close);
|
||||||
|
|
@ -782,6 +809,18 @@ public class NearbyMapFragment extends DaggerFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBundleForUpdtes(Bundle bundleForUpdtes) {
|
||||||
|
this.bundleForUpdtes = bundleForUpdtes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNearbyMaterialShowcaseDismissed() {
|
||||||
|
isSecondMaterialShowcaseDismissed = true;
|
||||||
|
if (isMapReady) {
|
||||||
|
thirdSingleShowCaseView.show(getActivity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
if (mapView != null) {
|
if (mapView != null) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
|
import uk.co.deanwild.materialshowcaseview.MaterialShowcaseSequence;
|
||||||
|
import uk.co.deanwild.materialshowcaseview.ShowcaseConfig;
|
||||||
|
|
||||||
|
|
||||||
|
public class NearbyMaterialShowcaseSequence extends MaterialShowcaseSequence {
|
||||||
|
|
||||||
|
public NearbyMaterialShowcaseSequence(Activity activity, String sequenceID) {
|
||||||
|
super(activity, sequenceID);
|
||||||
|
ShowcaseConfig config = new ShowcaseConfig();
|
||||||
|
config.setDelay(500); // half second between each showcase view
|
||||||
|
this.setConfig(config);
|
||||||
|
this.singleUse(sequenceID); // Display tutorial only once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,7 +17,7 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.utils.FileUtils;
|
import fr.free.nrw.commons.upload.FileUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class NearbyPlaces {
|
public class NearbyPlaces {
|
||||||
|
|
@ -40,10 +40,9 @@ public class NearbyPlaces {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
|
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) throws IOException {
|
||||||
List<Place> places = Collections.emptyList();
|
List<Place> places = Collections.emptyList();
|
||||||
|
|
||||||
try {
|
|
||||||
// increase the radius gradually to find a satisfactory number of nearby places
|
// increase the radius gradually to find a satisfactory number of nearby places
|
||||||
while (radius <= MAX_RADIUS) {
|
while (radius <= MAX_RADIUS) {
|
||||||
places = getFromWikidataQuery(curLatLng, lang, radius);
|
places = getFromWikidataQuery(curLatLng, lang, radius);
|
||||||
|
|
@ -54,13 +53,6 @@ public class NearbyPlaces {
|
||||||
radius *= RADIUS_MULTIPLIER;
|
radius *= RADIUS_MULTIPLIER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.d(e.toString());
|
|
||||||
// errors tend to be caused by too many results (and time out)
|
|
||||||
// try a small radius next time
|
|
||||||
Timber.d("back to initial radius: %f", radius);
|
|
||||||
radius = INITIAL_RADIUS;
|
|
||||||
}
|
|
||||||
// make sure we will be able to send at least one request next time
|
// make sure we will be able to send at least one request next time
|
||||||
if (radius > MAX_RADIUS) {
|
if (radius > MAX_RADIUS) {
|
||||||
radius = MAX_RADIUS;
|
radius = MAX_RADIUS;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.nearby;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.support.annotation.DrawableRes;
|
import android.support.annotation.DrawableRes;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -50,6 +51,20 @@ public class Place {
|
||||||
this.distance = distance;
|
this.distance = distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the entity id from the wikidata link
|
||||||
|
* @return returns the entity id if wikidata link exists
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getWikiDataEntityId() {
|
||||||
|
if (!hasWikidataLink()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String wikiDataLink = siteLinks.getWikidataLink().toString();
|
||||||
|
return wikiDataLink.replace("http://www.wikidata.org/entity/", "");
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasWikipediaLink() {
|
public boolean hasWikipediaLink() {
|
||||||
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
|
return !(siteLinks == null || Uri.EMPTY.equals(siteLinks.getWikipediaLink()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import javax.inject.Named;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import android.widget.RelativeLayout;
|
||||||
|
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -26,6 +25,7 @@ import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
|
@ -46,6 +46,8 @@ public class NotificationActivity extends NavigationBaseActivity {
|
||||||
@BindView(R.id.container) RelativeLayout relativeLayout;
|
@BindView(R.id.container) RelativeLayout relativeLayout;
|
||||||
|
|
||||||
@Inject NotificationController controller;
|
@Inject NotificationController controller;
|
||||||
|
@Inject
|
||||||
|
MediaWikiApi mediaWikiApi;
|
||||||
|
|
||||||
private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment";
|
private static final String TAG_NOTIFICATION_WORKER_FRAGMENT = "NotificationWorkerFragment";
|
||||||
private NotificationWorkerFragment mNotificationWorkerFragment;
|
private NotificationWorkerFragment mNotificationWorkerFragment;
|
||||||
|
|
@ -81,7 +83,6 @@ public class NotificationActivity extends NavigationBaseActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
private void addNotifications() {
|
private void addNotifications() {
|
||||||
Timber.d("Add notifications");
|
Timber.d("Add notifications");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.notification;
|
package fr.free.nrw.commons.notification;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
import android.text.Html;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -8,17 +9,23 @@ import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.borjabravo.readmoretextview.ReadMoreTextView;
|
import com.borjabravo.readmoretextview.ReadMoreTextView;
|
||||||
|
import com.bumptech.glide.RequestBuilder;
|
||||||
import com.pedrogomez.renderers.Renderer;
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.glide.SvgSoftwareLayerSetter;
|
||||||
|
|
||||||
|
import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by root on 19.12.2017.
|
* Created by root on 19.12.2017.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class NotificationRenderer extends Renderer<Notification> {
|
public class NotificationRenderer extends Renderer<Notification> {
|
||||||
|
private RequestBuilder<PictureDrawable> requestBuilder;
|
||||||
|
|
||||||
@BindView(R.id.title) ReadMoreTextView title;
|
@BindView(R.id.title) ReadMoreTextView title;
|
||||||
@BindView(R.id.time) TextView time;
|
@BindView(R.id.time) TextView time;
|
||||||
@BindView(R.id.icon) ImageView icon;
|
@BindView(R.id.icon) ImageView icon;
|
||||||
|
|
@ -41,23 +48,32 @@ public class NotificationRenderer extends Renderer<Notification> {
|
||||||
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
||||||
View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
|
View inflatedView = layoutInflater.inflate(R.layout.item_notification, viewGroup, false);
|
||||||
ButterKnife.bind(this, inflatedView);
|
ButterKnife.bind(this, inflatedView);
|
||||||
|
requestBuilder = GlideApp.with(inflatedView.getContext())
|
||||||
|
.as(PictureDrawable.class)
|
||||||
|
.error(R.drawable.round_icon_unknown)
|
||||||
|
.transition(withCrossFade())
|
||||||
|
.listener(new SvgSoftwareLayerSetter());
|
||||||
return inflatedView;
|
return inflatedView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render() {
|
public void render() {
|
||||||
Notification notification = getContent();
|
Notification notification = getContent();
|
||||||
String str = notification.notificationText.trim();
|
setTitle(notification.notificationText);
|
||||||
str = str.concat(" ");
|
|
||||||
title.setText(str);
|
|
||||||
time.setText(notification.date);
|
time.setText(notification.date);
|
||||||
switch (notification.notificationType) {
|
requestBuilder.load(notification.iconUrl).into(icon);
|
||||||
case THANK_YOU_EDIT:
|
}
|
||||||
icon.setImageResource(R.drawable.ic_edit_black_24dp);
|
|
||||||
break;
|
/**
|
||||||
default:
|
* Cleans up the notification text and sets it as the title
|
||||||
icon.setImageResource(R.drawable.round_icon_unknown);
|
* Clean up is required to fix escaped HTML string and extra white spaces at the beginning of the notification
|
||||||
}
|
* @param notificationText
|
||||||
|
*/
|
||||||
|
private void setTitle(String notificationText) {
|
||||||
|
notificationText = notificationText.trim().replaceAll("(^\\h*)|(\\h*$)", "");
|
||||||
|
notificationText = Html.fromHtml(notificationText).toString();
|
||||||
|
notificationText = notificationText.concat(" ");
|
||||||
|
title.setText(notificationText);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface NotificationClicked{
|
public interface NotificationClicked{
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ import javax.annotation.Nullable;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.notification.NotificationType.THANK_YOU_EDIT;
|
|
||||||
import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN;
|
import static fr.free.nrw.commons.notification.NotificationType.UNKNOWN;
|
||||||
|
|
||||||
public class NotificationUtils {
|
public class NotificationUtils {
|
||||||
|
|
||||||
private static final String COMMONS_WIKI = "commonswiki";
|
private static final String COMMONS_WIKI = "commonswiki";
|
||||||
|
private static final String WIKIDATA_WIKI = "wikidatawiki";
|
||||||
|
private static final String WIKIPEDIA_WIKI = "enwiki";
|
||||||
|
|
||||||
public static boolean isCommonsNotification(Node document) {
|
public static boolean isCommonsNotification(Node document) {
|
||||||
if (document == null || !document.hasAttributes()) {
|
if (document == null || !document.hasAttributes()) {
|
||||||
|
|
@ -31,6 +32,32 @@ public class NotificationUtils {
|
||||||
return COMMONS_WIKI.equals(element.getAttribute("wiki"));
|
return COMMONS_WIKI.equals(element.getAttribute("wiki"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the wiki attribute corresponds to wikidatawiki
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isWikidataNotification(Node document) {
|
||||||
|
if (document == null || !document.hasAttributes()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Element element = (Element) document;
|
||||||
|
return WIKIDATA_WIKI.equals(element.getAttribute("wiki"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the wiki attribute corresponds to enwiki
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isWikipediaNotification(Node document) {
|
||||||
|
if (document == null || !document.hasAttributes()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Element element = (Element) document;
|
||||||
|
return WIKIPEDIA_WIKI.equals(element.getAttribute("wiki"));
|
||||||
|
}
|
||||||
|
|
||||||
public static NotificationType getNotificationType(Node document) {
|
public static NotificationType getNotificationType(Node document) {
|
||||||
Element element = (Element) document;
|
Element element = (Element) document;
|
||||||
String type = element.getAttribute("type");
|
String type = element.getAttribute("type");
|
||||||
|
|
@ -68,10 +95,17 @@ public class NotificationUtils {
|
||||||
return notifications;
|
return notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently the app is interested in showing notifications just from the following three wikis: commons, wikidata, wikipedia
|
||||||
|
* This function returns true only if the notification belongs to any of the above wikis and is of a known notification type
|
||||||
|
* @param node
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private static boolean isUsefulNotification(Node node) {
|
private static boolean isUsefulNotification(Node node) {
|
||||||
return isCommonsNotification(node)
|
return (isCommonsNotification(node)
|
||||||
&& !getNotificationType(node).equals(UNKNOWN)
|
|| isWikidataNotification(node)
|
||||||
&& !getNotificationType(node).equals(THANK_YOU_EDIT);
|
|| isWikipediaNotification(node))
|
||||||
|
&& !getNotificationType(node).equals(UNKNOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isBundledNotification(Node document) {
|
public static boolean isBundledNotification(Node document) {
|
||||||
|
|
@ -97,7 +131,7 @@ public class NotificationUtils {
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case THANK_YOU_EDIT:
|
case THANK_YOU_EDIT:
|
||||||
notificationText = context.getString(R.string.notifications_thank_you_edit);
|
notificationText = getThankYouEditDescription(document);
|
||||||
break;
|
break;
|
||||||
case EDIT_USER_TALK:
|
case EDIT_USER_TALK:
|
||||||
notificationText = getNotificationText(document);
|
notificationText = getNotificationText(document);
|
||||||
|
|
@ -146,6 +180,16 @@ public class NotificationUtils {
|
||||||
return body != null ? body.getTextContent() : "";
|
return body != null ? body.getTextContent() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the header node returned in the XML document to form the description for thank you edits
|
||||||
|
* @param document
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private static String getThankYouEditDescription(Node document) {
|
||||||
|
Node body = getNode(getModel(document), "header");
|
||||||
|
return body != null ? body.getTextContent() : "";
|
||||||
|
}
|
||||||
|
|
||||||
private static String getNotificationIconUrl(Node document) {
|
private static String getNotificationIconUrl(Node document) {
|
||||||
String format = "%s%s";
|
String format = "%s%s";
|
||||||
Node iconUrl = getNode(getModel(document), "iconUrl");
|
Node iconUrl = getNode(getModel(document), "iconUrl");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package fr.free.nrw.commons.notification;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.PictureDrawable;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.Registry;
|
||||||
|
import com.bumptech.glide.annotation.GlideModule;
|
||||||
|
import com.bumptech.glide.module.AppGlideModule;
|
||||||
|
import com.caverock.androidsvg.SVG;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.glide.SvgDecoder;
|
||||||
|
import fr.free.nrw.commons.glide.SvgDrawableTranscoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module for the SVG sample app.
|
||||||
|
*/
|
||||||
|
@GlideModule
|
||||||
|
public class SvgModule extends AppGlideModule {
|
||||||
|
@Override
|
||||||
|
public void registerComponents(@NonNull Context context, @NonNull Glide glide,
|
||||||
|
@NonNull Registry registry) {
|
||||||
|
registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
|
||||||
|
.append(InputStream.class, SVG.class, new SvgDecoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable manifest parsing to avoid adding similar modules twice.
|
||||||
|
@Override
|
||||||
|
public boolean isManifestParsingEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,10 @@ package fr.free.nrw.commons.settings;
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
@ -24,8 +21,6 @@ import android.support.v4.content.FileProvider;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
@ -35,7 +30,7 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.utils.FileUtils;
|
import fr.free.nrw.commons.upload.FileUtils;
|
||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragment {
|
public class SettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
|
@ -102,6 +97,11 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Preference betaTesterPreference = findPreference("becomeBetaTester");
|
||||||
|
betaTesterPreference.setOnPreferenceClickListener(preference -> {
|
||||||
|
Utils.handleWebUrl(getActivity(),Uri.parse(getResources().getString(R.string.beta_opt_in_link)));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
Preference sendLogsPreference = findPreference("sendLogFile");
|
Preference sendLogsPreference = findPreference("sendLogFile");
|
||||||
sendLogsPreference.setOnPreferenceClickListener(preference -> {
|
sendLogsPreference.setOnPreferenceClickListener(preference -> {
|
||||||
//first we need to check if we have the necessary permissions
|
//first we need to check if we have the necessary permissions
|
||||||
|
|
@ -128,8 +128,8 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE) {
|
if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
{
|
||||||
sendAppLogsViaEmail();
|
sendAppLogsViaEmail();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,11 @@ import fr.free.nrw.commons.AboutActivity;
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.WelcomeActivity;
|
import fr.free.nrw.commons.WelcomeActivity;
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
@ -36,6 +36,8 @@ import timber.log.Timber;
|
||||||
public abstract class NavigationBaseActivity extends BaseActivity
|
public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
implements NavigationView.OnNavigationItemSelectedListener {
|
implements NavigationView.OnNavigationItemSelectedListener {
|
||||||
|
|
||||||
|
private static final String FEATURED_IMAGES_CATEGORY = "Category:Featured_pictures_on_Wikimedia_Commons";
|
||||||
|
|
||||||
@BindView(R.id.toolbar)
|
@BindView(R.id.toolbar)
|
||||||
Toolbar toolbar;
|
Toolbar toolbar;
|
||||||
@BindView(R.id.navigation_view)
|
@BindView(R.id.navigation_view)
|
||||||
|
|
@ -154,6 +156,10 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
||||||
drawerLayout.closeDrawer(navigationView);
|
drawerLayout.closeDrawer(navigationView);
|
||||||
NotificationActivity.startYourself(this);
|
NotificationActivity.startYourself(this);
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_featured_images:
|
||||||
|
drawerLayout.closeDrawer(navigationView);
|
||||||
|
CategoryImagesActivity.startYourself(this, getString(R.string.title_activity_featured_images), FEATURED_IMAGES_CATEGORY);
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
Timber.e("Unknown option [%s] selected from the navigation menu", itemId);
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapRegionDecoder;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
|
|
||||||
263
app/src/main/java/fr/free/nrw/commons/upload/FileProcessor.java
Normal file
|
|
@ -0,0 +1,263 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.caching.CacheController;
|
||||||
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
|
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processing of the image file that is about to be uploaded via ShareActivity is done here
|
||||||
|
*/
|
||||||
|
public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CacheController cacheController;
|
||||||
|
@Inject
|
||||||
|
GpsCategoryModel gpsCategoryModel;
|
||||||
|
@Inject
|
||||||
|
CategoryApi apiCall;
|
||||||
|
@Inject
|
||||||
|
@Named("default_preferences")
|
||||||
|
SharedPreferences prefs;
|
||||||
|
private Uri mediaUri;
|
||||||
|
private ContentResolver contentResolver;
|
||||||
|
private GPSExtractor imageObj;
|
||||||
|
private Context context;
|
||||||
|
private String decimalCoords;
|
||||||
|
private boolean haveCheckedForOtherImages = false;
|
||||||
|
private String filePath;
|
||||||
|
private boolean useExtStorage;
|
||||||
|
private boolean cacheFound;
|
||||||
|
private GPSExtractor tempImageObj;
|
||||||
|
|
||||||
|
FileProcessor(Uri mediaUri, ContentResolver contentResolver, Context context) {
|
||||||
|
this.mediaUri = mediaUri;
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
this.context = context;
|
||||||
|
ApplicationlessInjection.getInstance(context.getApplicationContext()).getCommonsApplicationComponent().inject(this);
|
||||||
|
useExtStorage = prefs.getBoolean("useExternalStorage", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets file path from media URI.
|
||||||
|
* In older devices getPath() may fail depending on the source URI, creating and using a copy of the file seems to work instead.
|
||||||
|
*
|
||||||
|
* @return file path of media
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private String getPathOfMediaOrCopy() {
|
||||||
|
filePath = FileUtils.getPath(context, mediaUri);
|
||||||
|
Timber.d("Filepath: " + filePath);
|
||||||
|
if (filePath == null) {
|
||||||
|
String copyPath = null;
|
||||||
|
try {
|
||||||
|
ParcelFileDescriptor descriptor = contentResolver.openFileDescriptor(mediaUri, "r");
|
||||||
|
if (descriptor != null) {
|
||||||
|
if (useExtStorage) {
|
||||||
|
copyPath = FileUtils.createCopyPath(descriptor);
|
||||||
|
return copyPath;
|
||||||
|
}
|
||||||
|
copyPath = getApplicationContext().getCacheDir().getAbsolutePath() + "/" + new Date().getTime() + ".jpg";
|
||||||
|
FileUtils.copy(descriptor.getFileDescriptor(), copyPath);
|
||||||
|
Timber.d("Filepath (copied): %s", copyPath);
|
||||||
|
return copyPath;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.w(e, "Error in file " + copyPath);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes file coordinates, either from EXIF data or user location
|
||||||
|
*
|
||||||
|
* @param gpsEnabled if true use GPS
|
||||||
|
*/
|
||||||
|
GPSExtractor processFileCoordinates(boolean gpsEnabled) {
|
||||||
|
Timber.d("Calling GPSExtractor");
|
||||||
|
try {
|
||||||
|
ParcelFileDescriptor descriptor = contentResolver.openFileDescriptor(mediaUri, "r");
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
if (descriptor != null) {
|
||||||
|
imageObj = new GPSExtractor(descriptor.getFileDescriptor(), context, prefs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String filePath = getPathOfMediaOrCopy();
|
||||||
|
if (filePath != null) {
|
||||||
|
imageObj = new GPSExtractor(filePath, context, prefs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
decimalCoords = imageObj.getCoords(gpsEnabled);
|
||||||
|
if (decimalCoords == null || !imageObj.imageCoordsExists) {
|
||||||
|
//Find other photos taken around the same time which has gps coordinates
|
||||||
|
if (!haveCheckedForOtherImages)
|
||||||
|
findOtherImages(gpsEnabled);// Do not do repeat the process
|
||||||
|
} else {
|
||||||
|
useImageCoords();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
Timber.w("File not found: " + mediaUri, e);
|
||||||
|
}
|
||||||
|
return imageObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getDecimalCoords() {
|
||||||
|
return decimalCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find other images around the same location that were taken within the last 20 sec
|
||||||
|
*
|
||||||
|
* @param gpsEnabled True if GPS is enabled
|
||||||
|
*/
|
||||||
|
private void findOtherImages(boolean gpsEnabled) {
|
||||||
|
Timber.d("filePath" + getPathOfMediaOrCopy());
|
||||||
|
|
||||||
|
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
|
||||||
|
File folder = new File(filePath.substring(0, filePath.lastIndexOf('/')));
|
||||||
|
File[] files = folder.listFiles();
|
||||||
|
Timber.d("folderTime Number:" + files.length);
|
||||||
|
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.lastModified() - timeOfCreation <= (120 * 1000) && file.lastModified() - timeOfCreation >= -(120 * 1000)) {
|
||||||
|
//Make sure the photos were taken within 20seconds
|
||||||
|
Timber.d("fild date:" + file.lastModified() + " time of creation" + timeOfCreation);
|
||||||
|
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
||||||
|
ParcelFileDescriptor descriptor = null;
|
||||||
|
try {
|
||||||
|
descriptor = contentResolver.openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
if (descriptor != null) {
|
||||||
|
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(), context, prefs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (filePath != null) {
|
||||||
|
tempImageObj = new GPSExtractor(file.getAbsolutePath(), context, prefs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempImageObj != null) {
|
||||||
|
Timber.d("not null fild EXIF" + tempImageObj.imageCoordsExists + " coords" + tempImageObj.getCoords(gpsEnabled));
|
||||||
|
if (tempImageObj.getCoords(gpsEnabled) != null && tempImageObj.imageCoordsExists) {
|
||||||
|
// Current image has gps coordinates and it's not current gps locaiton
|
||||||
|
Timber.d("This file has image coords:" + file.getAbsolutePath());
|
||||||
|
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("originalImagePath", filePath);
|
||||||
|
args.putString("possibleImagePath", file.getAbsolutePath());
|
||||||
|
newFragment.setArguments(args);
|
||||||
|
newFragment.show(((AppCompatActivity) context).getSupportFragmentManager(), "dialog");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
haveCheckedForOtherImages = true; //Finished checking for other images
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
||||||
|
* Then initiates the calls to MediaWiki API through an instance of CategoryApi.
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
public void useImageCoords() {
|
||||||
|
if (decimalCoords != null) {
|
||||||
|
Timber.d("Decimal coords of image: %s", decimalCoords);
|
||||||
|
Timber.d("is EXIF data present:" + imageObj.imageCoordsExists + " from findOther image");
|
||||||
|
|
||||||
|
// Only set cache for this point if image has coords
|
||||||
|
if (imageObj.imageCoordsExists) {
|
||||||
|
double decLongitude = imageObj.getDecLongitude();
|
||||||
|
double decLatitude = imageObj.getDecLatitude();
|
||||||
|
cacheController.setQtPoint(decLongitude, decLatitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> displayCatList = cacheController.findCategory();
|
||||||
|
boolean catListEmpty = displayCatList.isEmpty();
|
||||||
|
|
||||||
|
|
||||||
|
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||||
|
if (catListEmpty) {
|
||||||
|
cacheFound = false;
|
||||||
|
apiCall.request(decimalCoords)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.subscribe(
|
||||||
|
gpsCategoryModel::setCategoryList,
|
||||||
|
throwable -> {
|
||||||
|
Timber.e(throwable);
|
||||||
|
gpsCategoryModel.clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
||||||
|
} else {
|
||||||
|
cacheFound = true;
|
||||||
|
Timber.d("Cache found, setting categoryList in model to %s", displayCatList);
|
||||||
|
gpsCategoryModel.setCategoryList(displayCatList);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.d("EXIF: no coords");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isCacheFound() {
|
||||||
|
return cacheFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the async task that detects if image is fuzzy, too dark, etc
|
||||||
|
*/
|
||||||
|
void detectUnwantedPictures() {
|
||||||
|
String imageMediaFilePath = FileUtils.getPath(context, mediaUri);
|
||||||
|
DetectUnwantedPicturesAsync detectUnwantedPicturesAsync
|
||||||
|
= new DetectUnwantedPicturesAsync(new WeakReference<Activity>((Activity) context), imageMediaFilePath);
|
||||||
|
detectUnwantedPicturesAsync.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositiveResponse() {
|
||||||
|
imageObj = tempImageObj;
|
||||||
|
decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
|
||||||
|
Timber.d("EXIF from tempImageObj");
|
||||||
|
useImageCoords();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNegativeResponse() {
|
||||||
|
Timber.d("EXIF from imageObj");
|
||||||
|
useImageCoords();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,18 +15,84 @@ import android.provider.MediaStore;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get SHA1 of file from input stream
|
||||||
|
*/
|
||||||
|
static String getSHA1(InputStream is) {
|
||||||
|
|
||||||
|
MessageDigest digest;
|
||||||
|
try {
|
||||||
|
digest = MessageDigest.getInstance("SHA1");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
Timber.e(e, "Exception while getting Digest");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int read;
|
||||||
|
try {
|
||||||
|
while ((read = is.read(buffer)) > 0) {
|
||||||
|
digest.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
byte[] md5sum = digest.digest();
|
||||||
|
BigInteger bigInt = new BigInteger(1, md5sum);
|
||||||
|
String output = bigInt.toString(16);
|
||||||
|
// Fill to 40 chars
|
||||||
|
output = String.format("%40s", output).replace(' ', '0');
|
||||||
|
Timber.i("File SHA1: %s", output);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, "IO Exception");
|
||||||
|
return "";
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
is.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, "Exception on closing MD5 input stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In older devices getPath() may fail depending on the source URI. Creating and using a copy of the file seems to work instead.
|
||||||
|
* @return path of copy
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
static String createCopyPath(ParcelFileDescriptor descriptor) {
|
||||||
|
try {
|
||||||
|
String copyPath = Environment.getExternalStorageDirectory().toString() + "/CommonsApp/" + new Date().getTime() + ".jpg";
|
||||||
|
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||||
|
newFile.mkdir();
|
||||||
|
FileUtils.copy(descriptor.getFileDescriptor(), copyPath);
|
||||||
|
Timber.d("Filepath (copied): %s", copyPath);
|
||||||
|
return copyPath;
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a file path from a Uri. This will get the the path for Storage Access
|
* Get a file path from a Uri. This will get the the path for Storage Access
|
||||||
* Framework Documents, as well as the _data field for the MediaStore and
|
* Framework Documents, as well as the _data field for the MediaStore and
|
||||||
|
|
@ -59,7 +125,7 @@ public class FileUtils {
|
||||||
|
|
||||||
final String id = DocumentsContract.getDocumentId(uri);
|
final String id = DocumentsContract.getDocumentId(uri);
|
||||||
final Uri contentUri = ContentUris.withAppendedId(
|
final Uri contentUri = ContentUris.withAppendedId(
|
||||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
Uri.parse("content://downloads/document"), Long.valueOf(id));
|
||||||
|
|
||||||
returnPath = getDataColumn(context, contentUri, null, null);
|
returnPath = getDataColumn(context, contentUri, null, null);
|
||||||
} else if (isMediaDocument(uri)) { // MediaProvider
|
} else if (isMediaDocument(uri)) { // MediaProvider
|
||||||
|
|
@ -235,4 +301,80 @@ public class FileUtils {
|
||||||
copy(new FileInputStream(source), new FileOutputStream(destination));
|
copy(new FileInputStream(source), new FileOutputStream(destination));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and return the content of a resource file as string.
|
||||||
|
* @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
|
||||||
|
* @return the content of the file
|
||||||
|
*/
|
||||||
|
public static String readFromResource(String fileName) throws IOException {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
InputStream inputStream = FileUtils.class.getResourceAsStream(fileName);
|
||||||
|
if (inputStream == null) {
|
||||||
|
throw new FileNotFoundException(fileName);
|
||||||
|
}
|
||||||
|
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
buffer.append(line).append("\n");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes files.
|
||||||
|
* @param file context
|
||||||
|
*/
|
||||||
|
public static boolean deleteFile(File file) {
|
||||||
|
boolean deletedAll = true;
|
||||||
|
if (file != null) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
String[] children = file.list();
|
||||||
|
for (String child : children) {
|
||||||
|
deletedAll = deleteFile(new File(file, child)) && deletedAll;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deletedAll = file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletedAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File createAndGetAppLogsFile(String logs) {
|
||||||
|
try {
|
||||||
|
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||||
|
if (!commonsAppDirectory.exists()) {
|
||||||
|
commonsAppDirectory.mkdir();
|
||||||
|
}
|
||||||
|
|
||||||
|
File logsFile = new File(commonsAppDirectory,"logs.txt");
|
||||||
|
if (logsFile.exists()) {
|
||||||
|
//old logs file is useless
|
||||||
|
logsFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
logsFile.createNewFile();
|
||||||
|
|
||||||
|
FileOutputStream outputStream = new FileOutputStream(logsFile);
|
||||||
|
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
||||||
|
outputStreamWriter.append(logs);
|
||||||
|
outputStreamWriter.close();
|
||||||
|
outputStream.flush();
|
||||||
|
outputStream.close();
|
||||||
|
|
||||||
|
return logsFile;
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Timber.e(ioe);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class GpsCategoryModel {
|
||||||
|
private Set<String> categorySet;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public GpsCategoryModel() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
categorySet = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getGpsCatExists() {
|
||||||
|
return !categorySet.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getCategoryList() {
|
||||||
|
return new ArrayList<>(categorySet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryList(List<String> categoryList) {
|
||||||
|
clear();
|
||||||
|
categorySet.addAll(categoryList != null ? categoryList : new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String categoryString) {
|
||||||
|
categorySet.add(categoryString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -47,6 +47,8 @@ import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
//TODO: We should use this class to see how multiple uploads are handled, and then REMOVE it.
|
||||||
|
|
||||||
public class MultipleShareActivity extends AuthenticatedActivity
|
public class MultipleShareActivity extends AuthenticatedActivity
|
||||||
implements MediaDetailPagerFragment.MediaDetailProvider,
|
implements MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
AdapterView.OnItemClickListener,
|
AdapterView.OnItemClickListener,
|
||||||
|
|
@ -166,7 +168,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
||||||
View target = getCurrentFocus();
|
View target = getCurrentFocus();
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
if (imm != null)
|
||||||
|
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager().beginTransaction()
|
||||||
.add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization")
|
.add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization")
|
||||||
|
|
@ -221,8 +224,8 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
||||||
|
|
||||||
//TODO: 15/10/17 should location permission be explicitly requested if not provided?
|
//TODO: 15/10/17 should location permission be explicitly requested if not provided?
|
||||||
//check if location permission is enabled
|
//check if location permission is enabled
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
{
|
||||||
locationPermitted = true;
|
locationPermitted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,7 +240,7 @@ public class MultipleShareActivity extends AuthenticatedActivity
|
||||||
|
|
||||||
private void showDetail(int i) {
|
private void showDetail(int i) {
|
||||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||||
mediaDetails = new MediaDetailPagerFragment(true);
|
mediaDetails = new MediaDetailPagerFragment(true, false);
|
||||||
getSupportFragmentManager()
|
getSupportFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(R.id.uploadsFragmentContainer, mediaDetails)
|
.replace(R.id.uploadsFragmentContainer, mediaDetails)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
|
@ -11,14 +10,12 @@ import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.BaseAdapter;
|
import android.widget.BaseAdapter;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
@ -27,6 +24,8 @@ import android.widget.GridView;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
|
|
@ -34,6 +33,7 @@ import dagger.android.support.AndroidSupportInjection;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
|
||||||
public class MultipleUploadListFragment extends Fragment {
|
public class MultipleUploadListFragment extends Fragment {
|
||||||
|
|
||||||
|
|
@ -41,9 +41,13 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
void OnMultipleUploadInitiated();
|
void OnMultipleUploadInitiated();
|
||||||
}
|
}
|
||||||
|
|
||||||
private GridView photosGrid;
|
@BindView(R.id.multipleShareBackground)
|
||||||
|
GridView photosGrid;
|
||||||
|
|
||||||
|
@BindView(R.id.multipleBaseTitle)
|
||||||
|
EditText baseTitle;
|
||||||
|
|
||||||
private PhotoDisplayAdapter photosAdapter;
|
private PhotoDisplayAdapter photosAdapter;
|
||||||
private EditText baseTitle;
|
|
||||||
private TitleTextWatcher textWatcher = new TitleTextWatcher();
|
private TitleTextWatcher textWatcher = new TitleTextWatcher();
|
||||||
|
|
||||||
private Point photoSize;
|
private Point photoSize;
|
||||||
|
|
@ -89,9 +93,9 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
view = LayoutInflater.from(getContext()).inflate(R.layout.layout_upload_item, viewGroup, false);
|
view = LayoutInflater.from(getContext()).inflate(R.layout.layout_upload_item, viewGroup, false);
|
||||||
holder = new UploadHolderView();
|
holder = new UploadHolderView();
|
||||||
holder.image = (SimpleDraweeView) view.findViewById(R.id.uploadImage);
|
holder.image = view.findViewById(R.id.uploadImage);
|
||||||
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
|
holder.title = view.findViewById(R.id.uploadTitle);
|
||||||
holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
|
holder.overlay = view.findViewById(R.id.uploadOverlay);
|
||||||
|
|
||||||
holder.image.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, photoSize.y));
|
holder.image.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, photoSize.y));
|
||||||
holder.image.setHierarchy(GenericDraweeHierarchyBuilder
|
holder.image.setHierarchy(GenericDraweeHierarchyBuilder
|
||||||
|
|
@ -129,11 +133,8 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||||
View target = getView().findFocus();
|
View target = getActivity().getCurrentFocus();
|
||||||
if (target != null) {
|
ViewUtil.hideKeyboard(target);
|
||||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Wrong result type
|
// FIXME: Wrong result type
|
||||||
|
|
@ -169,9 +170,7 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
|
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
|
||||||
photosGrid = (GridView) view.findViewById(R.id.multipleShareBackground);
|
ButterKnife.bind(this,view);
|
||||||
baseTitle = (EditText) view.findViewById(R.id.multipleBaseTitle);
|
|
||||||
|
|
||||||
photosAdapter = new PhotoDisplayAdapter();
|
photosAdapter = new PhotoDisplayAdapter();
|
||||||
photosGrid.setAdapter(photosAdapter);
|
photosGrid.setAdapter(photosAdapter);
|
||||||
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||||
|
|
@ -182,18 +181,13 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
|
|
||||||
baseTitle.setOnFocusChangeListener((v, hasFocus) -> {
|
baseTitle.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideKeyboard(View view) {
|
|
||||||
InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
baseTitle.removeTextChangedListener(textWatcher);
|
baseTitle.removeTextChangedListener(textWatcher);
|
||||||
|
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.net.Uri;
|
|
||||||
|
|
||||||
import com.android.volley.Cache;
|
|
||||||
import com.android.volley.NetworkResponse;
|
|
||||||
import com.android.volley.Request;
|
|
||||||
import com.android.volley.RequestQueue;
|
|
||||||
import com.android.volley.Response;
|
|
||||||
import com.android.volley.VolleyError;
|
|
||||||
import com.android.volley.toolbox.HttpHeaderParser;
|
|
||||||
import com.android.volley.toolbox.JsonRequest;
|
|
||||||
import com.android.volley.toolbox.Volley;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
|
|
||||||
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
|
|
||||||
* of relevant categories.
|
|
||||||
*/
|
|
||||||
public class MwVolleyApi {
|
|
||||||
|
|
||||||
private static RequestQueue REQUEST_QUEUE;
|
|
||||||
private static final Gson GSON = new GsonBuilder().create();
|
|
||||||
|
|
||||||
private static Set<String> categorySet;
|
|
||||||
private static List<String> categoryList;
|
|
||||||
|
|
||||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
public MwVolleyApi(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
categorySet = new HashSet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> getGpsCat() {
|
|
||||||
return categoryList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setGpsCat(List<String> cachedList) {
|
|
||||||
categoryList = new ArrayList<>();
|
|
||||||
categoryList.addAll(cachedList);
|
|
||||||
Timber.d("Setting GPS cats from cache: %s", categoryList);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void request(String coords) {
|
|
||||||
String apiUrl = buildUrl(coords);
|
|
||||||
Timber.d("URL: %s", apiUrl);
|
|
||||||
|
|
||||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
|
||||||
new LogResponseListener<>(), new LogResponseErrorListener());
|
|
||||||
getQueue().add(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds URL with image coords for MediaWiki API calls
|
|
||||||
* Example URL: https://commons.wikimedia.org/w/api.php?action=query&prop=categories|coordinates|pageprops&format=json&clshow=!hidden&coprop=type|name|dim|country|region|globe&codistancefrompoint=38.11386944444445|13.356263888888888&generator=geosearch&redirects=&ggscoord=38.11386944444445|1.356263888888888&ggsradius=100&ggslimit=10&ggsnamespace=6&ggsprop=type|name|dim|country|region|globe&ggsprimary=all&formatversion=2
|
|
||||||
* @param coords Coordinates to build query with
|
|
||||||
* @return URL for API query
|
|
||||||
*/
|
|
||||||
private String buildUrl(String coords) {
|
|
||||||
|
|
||||||
Uri.Builder builder = Uri.parse(MWURL).buildUpon();
|
|
||||||
|
|
||||||
builder.appendPath("w")
|
|
||||||
.appendPath("api.php")
|
|
||||||
.appendQueryParameter("action", "query")
|
|
||||||
.appendQueryParameter("prop", "categories|coordinates|pageprops")
|
|
||||||
.appendQueryParameter("format", "json")
|
|
||||||
.appendQueryParameter("clshow", "!hidden")
|
|
||||||
.appendQueryParameter("coprop", "type|name|dim|country|region|globe")
|
|
||||||
.appendQueryParameter("codistancefrompoint", coords)
|
|
||||||
.appendQueryParameter("generator", "geosearch")
|
|
||||||
.appendQueryParameter("ggscoord", coords)
|
|
||||||
.appendQueryParameter("ggsradius", "10000")
|
|
||||||
.appendQueryParameter("ggslimit", "10")
|
|
||||||
.appendQueryParameter("ggsnamespace", "6")
|
|
||||||
.appendQueryParameter("ggsprop", "type|name|dim|country|region|globe")
|
|
||||||
.appendQueryParameter("ggsprimary", "all")
|
|
||||||
.appendQueryParameter("formatversion", "2");
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized RequestQueue getQueue() {
|
|
||||||
if (REQUEST_QUEUE == null) {
|
|
||||||
REQUEST_QUEUE = Volley.newRequestQueue(context);
|
|
||||||
}
|
|
||||||
return REQUEST_QUEUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogResponseListener<T> implements Response.Listener<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(T response) {
|
|
||||||
Timber.d(response.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogResponseErrorListener implements Response.ErrorListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(VolleyError error) {
|
|
||||||
Timber.e(error.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
|
||||||
|
|
||||||
public QueryRequest(String url,
|
|
||||||
Response.Listener<QueryResponse> listener,
|
|
||||||
Response.ErrorListener errorListener) {
|
|
||||||
super(Request.Method.GET, url, null, listener, errorListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Response<QueryResponse> parseNetworkResponse(NetworkResponse response) {
|
|
||||||
String json = parseString(response);
|
|
||||||
QueryResponse queryResponse = GSON.fromJson(json, QueryResponse.class);
|
|
||||||
return Response.success(queryResponse, cacheEntry(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cache.Entry cacheEntry(NetworkResponse response) {
|
|
||||||
return HttpHeaderParser.parseCacheHeaders(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseString(NetworkResponse response) {
|
|
||||||
try {
|
|
||||||
return new String(response.data, HttpHeaderParser.parseCharset(response.headers));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
return new String(response.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class GpsCatExists {
|
|
||||||
private static boolean gpsCatExists;
|
|
||||||
|
|
||||||
public static void setGpsCatExists(boolean gpsCat) {
|
|
||||||
gpsCatExists = gpsCat;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean getGpsCatExists() {
|
|
||||||
return gpsCatExists;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class QueryResponse {
|
|
||||||
private Query query = new Query();
|
|
||||||
|
|
||||||
private String printSet() {
|
|
||||||
if (categorySet == null || categorySet.isEmpty()) {
|
|
||||||
GpsCatExists.setGpsCatExists(false);
|
|
||||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
|
||||||
return "No collection of categories";
|
|
||||||
} else {
|
|
||||||
GpsCatExists.setGpsCatExists(true);
|
|
||||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
|
||||||
return "CATEGORIES FOUND" + categorySet.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (query != null) {
|
|
||||||
return "query=" + query.toString() + "\n" + printSet();
|
|
||||||
} else {
|
|
||||||
return "No pages found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Query {
|
|
||||||
private Page [] pages;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder builder = new StringBuilder("pages=" + "\n");
|
|
||||||
if (pages != null) {
|
|
||||||
for (Page page : pages) {
|
|
||||||
builder.append(page.toString());
|
|
||||||
builder.append("\n");
|
|
||||||
}
|
|
||||||
builder.replace(builder.length() - 1, builder.length(), "");
|
|
||||||
return builder.toString();
|
|
||||||
} else {
|
|
||||||
return "No pages found";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Page {
|
|
||||||
private int pageid;
|
|
||||||
private int ns;
|
|
||||||
private String title;
|
|
||||||
private Category[] categories;
|
|
||||||
private Category category;
|
|
||||||
|
|
||||||
public Page() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder("PAGEID=" + pageid + " ns=" + ns + " title=" + title + "\n" + " CATEGORIES= ");
|
|
||||||
|
|
||||||
if (categories == null || categories.length == 0) {
|
|
||||||
builder.append("no categories exist\n");
|
|
||||||
} else {
|
|
||||||
for (Category category : categories) {
|
|
||||||
builder.append(category.toString());
|
|
||||||
builder.append("\n");
|
|
||||||
if (category != null) {
|
|
||||||
String categoryString = category.toString().replace("Category:", "");
|
|
||||||
categorySet.add(categoryString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
categoryList = new ArrayList<>(categorySet);
|
|
||||||
builder.replace(builder.length() - 1, builder.length(), "");
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class Category {
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,50 +1,53 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.animation.Animator;
|
||||||
|
import android.animation.AnimatorListenerAdapter;
|
||||||
|
import android.animation.AnimatorSet;
|
||||||
|
import android.animation.ObjectAnimator;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.support.annotation.RequiresApi;
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.design.widget.FloatingActionButton;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.view.KeyEvent;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.view.animation.DecelerateInterpolator;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.MessageDigest;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
|
|
@ -52,19 +55,19 @@ import fr.free.nrw.commons.caching.CacheController;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
|
||||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
|
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
|
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
|
||||||
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
|
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
|
||||||
|
import static fr.free.nrw.commons.upload.FileUtils.getSHA1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for the title/desc screen after image is selected. Also starts processing image
|
* Activity for the title/desc screen after image is selected. Also starts processing image
|
||||||
|
|
@ -73,14 +76,13 @@ import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
|
||||||
public class ShareActivity
|
public class ShareActivity
|
||||||
extends AuthenticatedActivity
|
extends AuthenticatedActivity
|
||||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||||
OnCategoriesSaveHandler,SimilarImageDialogFragment.onResponse {
|
OnCategoriesSaveHandler {
|
||||||
|
|
||||||
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
|
|
||||||
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
|
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
|
||||||
private static final int REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION = 3;
|
|
||||||
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
|
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
|
||||||
private CategorizationFragment categorizationFragment;
|
//Had to make them class variables, to extract out the click listeners, also I see no harm in this
|
||||||
|
final Rect startBounds = new Rect();
|
||||||
|
final Rect finalBounds = new Rect();
|
||||||
|
final Point globalOffset = new Point();
|
||||||
@Inject
|
@Inject
|
||||||
MediaWikiApi mwApi;
|
MediaWikiApi mwApi;
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -92,36 +94,55 @@ public class ShareActivity
|
||||||
@Inject
|
@Inject
|
||||||
ModifierSequenceDao modifierSequenceDao;
|
ModifierSequenceDao modifierSequenceDao;
|
||||||
@Inject
|
@Inject
|
||||||
|
CategoryApi apiCall;
|
||||||
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
|
@Inject
|
||||||
|
GpsCategoryModel gpsCategoryModel;
|
||||||
|
|
||||||
|
@BindView(R.id.container)
|
||||||
|
FrameLayout flContainer;
|
||||||
|
@BindView(R.id.backgroundImage)
|
||||||
|
SimpleDraweeView backgroundImageView;
|
||||||
|
@BindView(R.id.media_map)
|
||||||
|
FloatingActionButton mapButton;
|
||||||
|
@BindView(R.id.media_upload_zoom_in)
|
||||||
|
FloatingActionButton zoomInButton;
|
||||||
|
@BindView(R.id.media_upload_zoom_out)
|
||||||
|
FloatingActionButton zoomOutButton;
|
||||||
|
@BindView(R.id.main_fab)
|
||||||
|
FloatingActionButton mainFab;
|
||||||
|
@BindView(R.id.expanded_image)
|
||||||
|
PhotoView expandedImageView;
|
||||||
|
|
||||||
private String source;
|
private String source;
|
||||||
private String mimeType;
|
private String mimeType;
|
||||||
|
private CategorizationFragment categorizationFragment;
|
||||||
private Uri mediaUri;
|
private Uri mediaUri;
|
||||||
private Contribution contribution;
|
private Contribution contribution;
|
||||||
private SimpleDraweeView backgroundImageView;
|
private GPSExtractor gpsObj;
|
||||||
|
|
||||||
private boolean cacheFound;
|
|
||||||
|
|
||||||
private GPSExtractor imageObj;
|
|
||||||
private GPSExtractor tempImageObj;
|
|
||||||
private String decimalCoords;
|
private String decimalCoords;
|
||||||
|
private FileProcessor fileObj;
|
||||||
private boolean useNewPermissions = false;
|
private boolean useNewPermissions = false;
|
||||||
private boolean storagePermitted = false;
|
private boolean storagePermitted = false;
|
||||||
private boolean locationPermitted = false;
|
private boolean locationPermitted = false;
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
private String description;
|
private String description;
|
||||||
|
private String wikiDataEntityId;
|
||||||
private Snackbar snackbar;
|
private Snackbar snackbar;
|
||||||
private boolean duplicateCheckPassed = false;
|
private boolean duplicateCheckPassed = false;
|
||||||
|
|
||||||
private boolean haveCheckedForOtherImages = false;
|
|
||||||
private boolean isNearbyUpload = false;
|
private boolean isNearbyUpload = false;
|
||||||
|
private Animator CurrentAnimator;
|
||||||
|
private long ShortAnimationDuration;
|
||||||
|
private boolean isFABOpen = false;
|
||||||
|
private float startScaleFinal;
|
||||||
|
private boolean isZoom = false;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when user taps the submit button.
|
* Called when user taps the submit button.
|
||||||
|
* Requests Storage permission, if needed.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void uploadActionInitiated(String title, String description) {
|
public void uploadActionInitiated(String title, String description) {
|
||||||
|
|
@ -130,8 +151,6 @@ public class ShareActivity
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
// Check for Storage permission that is required for upload.
|
|
||||||
// Do not allow user to proceed without permission, otherwise will crash
|
|
||||||
if (needsToRequestStoragePermission()) {
|
if (needsToRequestStoragePermission()) {
|
||||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
REQUEST_PERM_ON_SUBMIT_STORAGE);
|
REQUEST_PERM_ON_SUBMIT_STORAGE);
|
||||||
|
|
@ -143,34 +162,44 @@ public class ShareActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether storage permissions need to be requested.
|
||||||
|
* Permissions are needed if the file is not owned by this application, (e.g. shared from the Gallery)
|
||||||
|
*
|
||||||
|
* @return true if file is not owned by this application and permission hasn't been granted beforehand
|
||||||
|
*/
|
||||||
@RequiresApi(16)
|
@RequiresApi(16)
|
||||||
private boolean needsToRequestStoragePermission() {
|
private boolean needsToRequestStoragePermission() {
|
||||||
// We need to ask storage permission when
|
|
||||||
// the file is not owned by this application, (e.g. shared from the Gallery)
|
|
||||||
// and permission is not obtained.
|
|
||||||
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
|
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
|
||||||
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
!= PackageManager.PERMISSION_GRANTED);
|
!= PackageManager.PERMISSION_GRANTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after permission checks are done.
|
||||||
|
* Gets file metadata for category suggestions, displays toast, caches categories found, calls uploadController
|
||||||
|
*/
|
||||||
private void uploadBegins() {
|
private void uploadBegins() {
|
||||||
getFileMetadata(locationPermitted);
|
fileObj.processFileCoordinates(locationPermitted);
|
||||||
|
|
||||||
Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
|
Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
|
||||||
startingToast.show();
|
startingToast.show();
|
||||||
|
|
||||||
if (!cacheFound) {
|
if (!fileObj.isCacheFound()) {
|
||||||
//Has to be called after apiCall.request()
|
//Has to be called after apiCall.request()
|
||||||
cacheController.cacheCategory();
|
cacheController.cacheCategory();
|
||||||
Timber.d("Cache the categories found");
|
Timber.d("Cache the categories found");
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, c -> {
|
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, wikiDataEntityId, c -> {
|
||||||
ShareActivity.this.contribution = c;
|
ShareActivity.this.contribution = c;
|
||||||
showPostUpload();
|
showPostUpload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts CategorizationFragment after uploadBegins.
|
||||||
|
*/
|
||||||
private void showPostUpload() {
|
private void showPostUpload() {
|
||||||
if (categorizationFragment == null) {
|
if (categorizationFragment == null) {
|
||||||
categorizationFragment = new CategorizationFragment();
|
categorizationFragment = new CategorizationFragment();
|
||||||
|
|
@ -180,6 +209,11 @@ public class ShareActivity
|
||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send categories to modifications queue after they are selected
|
||||||
|
*
|
||||||
|
* @param categories categories selected
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCategoriesSave(List<String> categories) {
|
public void onCategoriesSave(List<String> categories) {
|
||||||
if (categories.size() > 0) {
|
if (categories.size() > 0) {
|
||||||
|
|
@ -217,9 +251,6 @@ public class ShareActivity
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isNearbyUpload() {
|
|
||||||
return isNearbyUpload;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
@ -228,7 +259,6 @@ public class ShareActivity
|
||||||
setContentView(R.layout.activity_share);
|
setContentView(R.layout.activity_share);
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
initBack();
|
initBack();
|
||||||
backgroundImageView = (SimpleDraweeView) findViewById(R.id.backgroundImage);
|
|
||||||
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
|
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
|
||||||
.newInstance(getResources())
|
.newInstance(getResources())
|
||||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||||
|
|
@ -237,7 +267,54 @@ public class ShareActivity
|
||||||
R.drawable.ic_error_outline_black_24dp, getTheme()))
|
R.drawable.ic_error_outline_black_24dp, getTheme()))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
//Receive intent from ContributionController.java when user selects picture to upload
|
receiveImageIntent();
|
||||||
|
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
contribution = savedInstanceState.getParcelable("contribution");
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAuthToken();
|
||||||
|
Timber.d("Uri: %s", mediaUri.toString());
|
||||||
|
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
|
||||||
|
|
||||||
|
useNewPermissions = false;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
useNewPermissions = true;
|
||||||
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
locationPermitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check location permissions if M or newer for category suggestions, request via snackbar if not present
|
||||||
|
if (!locationPermitted) {
|
||||||
|
requestPermissionUsingSnackBar(
|
||||||
|
getString(R.string.location_permission_rationale),
|
||||||
|
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
||||||
|
REQUEST_PERM_ON_CREATE_LOCATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||||
|
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||||
|
if (shareView == null && categorizationFragment == null) {
|
||||||
|
shareView = new SingleUploadFragment();
|
||||||
|
getSupportFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.add(R.id.single_upload_fragment_container, shareView, "shareView")
|
||||||
|
.commitAllowingStateLoss();
|
||||||
|
}
|
||||||
|
uploadController.prepareService();
|
||||||
|
|
||||||
|
ContentResolver contentResolver = this.getContentResolver();
|
||||||
|
fileObj = new FileProcessor(mediaUri, contentResolver, this);
|
||||||
|
checkIfFileExists();
|
||||||
|
gpsObj = fileObj.processFileCoordinates(locationPermitted);
|
||||||
|
decimalCoords = fileObj.getDecimalCoords();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receive intent from ContributionController.java when user selects picture to upload
|
||||||
|
*/
|
||||||
|
private void receiveImageIntent() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|
||||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||||
|
|
@ -257,174 +334,100 @@ public class ShareActivity
|
||||||
if (mediaUri != null) {
|
if (mediaUri != null) {
|
||||||
backgroundImageView.setImageURI(mediaUri);
|
backgroundImageView.setImageURI(mediaUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
|
||||||
contribution = savedInstanceState.getParcelable("contribution");
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAuthToken();
|
|
||||||
|
|
||||||
Timber.d("Uri: %s", mediaUri.toString());
|
|
||||||
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
|
|
||||||
|
|
||||||
useNewPermissions = false;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
useNewPermissions = true;
|
|
||||||
|
|
||||||
if (!needsToRequestStoragePermission()) {
|
|
||||||
storagePermitted = true;
|
|
||||||
}
|
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
locationPermitted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check storage permissions if marshmallow or newer
|
|
||||||
if (useNewPermissions && (!storagePermitted || !locationPermitted)) {
|
|
||||||
if (!storagePermitted && !locationPermitted) {
|
|
||||||
String permissionRationales =
|
|
||||||
getResources().getString(R.string.read_storage_permission_rationale) + "\n"
|
|
||||||
+ getResources().getString(R.string.location_permission_rationale);
|
|
||||||
snackbar = requestPermissionUsingSnackBar(
|
|
||||||
permissionRationales,
|
|
||||||
new String[]{
|
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION},
|
|
||||||
REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION);
|
|
||||||
View snackbarView = snackbar.getView();
|
|
||||||
TextView textView = (TextView) snackbarView.findViewById(android.support.design.R.id.snackbar_text);
|
|
||||||
textView.setMaxLines(3);
|
|
||||||
} else if (!storagePermitted) {
|
|
||||||
requestPermissionUsingSnackBar(
|
|
||||||
getString(R.string.read_storage_permission_rationale),
|
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
|
||||||
REQUEST_PERM_ON_CREATE_STORAGE);
|
|
||||||
} else if (!locationPermitted) {
|
|
||||||
requestPermissionUsingSnackBar(
|
|
||||||
getString(R.string.location_permission_rationale),
|
|
||||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
|
||||||
REQUEST_PERM_ON_CREATE_LOCATION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
performPreUploadProcessingOfFile();
|
|
||||||
|
|
||||||
|
|
||||||
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
|
||||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
|
||||||
if (shareView == null && categorizationFragment == null) {
|
|
||||||
shareView = new SingleUploadFragment();
|
|
||||||
getSupportFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.add(R.id.single_upload_fragment_container, shareView, "shareView")
|
|
||||||
.commitAllowingStateLoss();
|
|
||||||
}
|
|
||||||
uploadController.prepareService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to display the zoom and map FAB
|
||||||
|
*/
|
||||||
|
private void showFABMenu() {
|
||||||
|
isFABOpen = true;
|
||||||
|
|
||||||
|
if (gpsObj != null && gpsObj.imageCoordsExists)
|
||||||
|
mapButton.setVisibility(View.VISIBLE);
|
||||||
|
zoomInButton.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
mainFab.animate().rotationBy(180);
|
||||||
|
mapButton.animate().translationY(-getResources().getDimension(R.dimen.second_fab));
|
||||||
|
zoomInButton.animate().translationY(-getResources().getDimension(R.dimen.first_fab));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to close the zoom and map FAB
|
||||||
|
*/
|
||||||
|
private void closeFABMenu() {
|
||||||
|
isFABOpen = false;
|
||||||
|
mainFab.animate().rotationBy(-180);
|
||||||
|
mapButton.animate().translationY(0);
|
||||||
|
zoomInButton.animate().translationY(0).setListener(new Animator.AnimatorListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animator animator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animator) {
|
||||||
|
if (!isFABOpen) {
|
||||||
|
mapButton.setVisibility(View.GONE);
|
||||||
|
zoomInButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animator animator) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if upload was initiated via Nearby
|
||||||
|
*
|
||||||
|
* @return true if upload was initiated via Nearby
|
||||||
|
*/
|
||||||
|
protected boolean isNearbyUpload() {
|
||||||
|
return isNearbyUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles BOTH snackbar permission request (for location) and submit button permission request (for storage)
|
||||||
|
*
|
||||||
|
* @param requestCode type of request
|
||||||
|
* @param permissions permissions requested
|
||||||
|
* @param grantResults grant results
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode,
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
@NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case REQUEST_PERM_ON_CREATE_STORAGE: {
|
|
||||||
if (grantResults.length >= 1
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
backgroundImageView.setImageURI(mediaUri);
|
|
||||||
storagePermitted = true;
|
|
||||||
performPreUploadProcessingOfFile();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case REQUEST_PERM_ON_CREATE_LOCATION: {
|
case REQUEST_PERM_ON_CREATE_LOCATION: {
|
||||||
if (grantResults.length >= 1
|
if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
locationPermitted = true;
|
locationPermitted = true;
|
||||||
performPreUploadProcessingOfFile();
|
checkIfFileExists();
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
case REQUEST_PERM_ON_CREATE_STORAGE_AND_LOCATION: {
|
|
||||||
if (grantResults.length >= 2
|
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
backgroundImageView.setImageURI(mediaUri);
|
|
||||||
storagePermitted = true;
|
|
||||||
performPreUploadProcessingOfFile();
|
|
||||||
}
|
|
||||||
if (grantResults.length >= 2
|
|
||||||
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
locationPermitted = true;
|
|
||||||
performPreUploadProcessingOfFile();
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storage (from submit button) - this needs to be separate from (1) because only the
|
// Storage (from submit button) - this needs to be separate from (1) because only the
|
||||||
// submit button should bring user to next screen
|
// submit button should bring user to next screen
|
||||||
case REQUEST_PERM_ON_SUBMIT_STORAGE: {
|
case REQUEST_PERM_ON_SUBMIT_STORAGE: {
|
||||||
if (grantResults.length >= 1
|
if (grantResults.length >= 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
//It is OK to call this at both (1) and (4) because if perm had been granted at
|
//It is OK to call this at both (1) and (4) because if perm had been granted at
|
||||||
//snackbar, user should not be prompted at submit button
|
//snackbar, user should not be prompted at submit button
|
||||||
performPreUploadProcessingOfFile();
|
checkIfFileExists();
|
||||||
|
|
||||||
//Uploading only begins if storage permission granted from arrow icon
|
//Uploading only begins if storage permission granted from arrow icon
|
||||||
uploadBegins();
|
uploadBegins();
|
||||||
snackbar.dismiss();
|
snackbar.dismiss();
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performPreUploadProcessingOfFile() {
|
/**
|
||||||
if (!useNewPermissions || storagePermitted) {
|
* Displays Snackbar to ask for location permissions
|
||||||
if (!duplicateCheckPassed) {
|
*/
|
||||||
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
private Snackbar requestPermissionUsingSnackBar(String rationale, final String[] perms, final int code) {
|
||||||
try {
|
|
||||||
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
|
|
||||||
Timber.d("Input stream created from %s", mediaUri.toString());
|
|
||||||
String fileSHA1 = getSHA1(inputStream);
|
|
||||||
Timber.d("File SHA1 is: %s", fileSHA1);
|
|
||||||
|
|
||||||
ExistingFileAsync fileAsyncTask =
|
|
||||||
new ExistingFileAsync(new WeakReference<Activity>(this), fileSHA1, new WeakReference<Context>(this), result -> {
|
|
||||||
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
|
||||||
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|
|
||||||
|| result == NO_DUPLICATE);
|
|
||||||
/*
|
|
||||||
TODO: 16/9/17 should we run DetectUnwantedPicturesAsync if DUPLICATE_PROCEED is returned? Since that means
|
|
||||||
we are processing images that are already on server???...
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (duplicateCheckPassed) {
|
|
||||||
//image can be uploaded, so now check if its a useless picture or not
|
|
||||||
performUnwantedPictureDetectionProcess();
|
|
||||||
}
|
|
||||||
|
|
||||||
},mwApi);
|
|
||||||
fileAsyncTask.execute();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.d(e, "IO Exception: ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getFileMetadata(locationPermitted);
|
|
||||||
} else {
|
|
||||||
Timber.w("not ready for preprocessing: useNewPermissions=%s storage=%s location=%s",
|
|
||||||
useNewPermissions, storagePermitted, locationPermitted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void performUnwantedPictureDetectionProcess() {
|
|
||||||
String imageMediaFilePath = FileUtils.getPath(this,mediaUri);
|
|
||||||
DetectUnwantedPicturesAsync detectUnwantedPicturesAsync
|
|
||||||
= new DetectUnwantedPicturesAsync(new WeakReference<Activity>(this)
|
|
||||||
, imageMediaFilePath);
|
|
||||||
|
|
||||||
detectUnwantedPicturesAsync.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Snackbar requestPermissionUsingSnackBar(String rationale,
|
|
||||||
final String[] perms,
|
|
||||||
final int code) {
|
|
||||||
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), rationale,
|
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), rationale,
|
||||||
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
|
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
|
||||||
view -> ActivityCompat.requestPermissions(ShareActivity.this, perms, code));
|
view -> ActivityCompat.requestPermissions(ShareActivity.this, perms, code));
|
||||||
|
|
@ -432,202 +435,44 @@ public class ShareActivity
|
||||||
return snackbar;
|
return snackbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getPathOfMediaOrCopy() {
|
|
||||||
String filePath = FileUtils.getPath(getApplicationContext(), mediaUri);
|
|
||||||
Timber.d("Filepath: " + filePath);
|
|
||||||
if (filePath == null) {
|
|
||||||
// in older devices getPath() may fail depending on the source URI
|
|
||||||
// creating and using a copy of the file seems to work instead.
|
|
||||||
// TODO: there might be a more proper solution than this
|
|
||||||
String copyPath = null;
|
|
||||||
try {
|
|
||||||
ParcelFileDescriptor descriptor
|
|
||||||
= getContentResolver().openFileDescriptor(mediaUri, "r");
|
|
||||||
if (descriptor != null) {
|
|
||||||
boolean useExtStorage = prefs.getBoolean("useExternalStorage", true);
|
|
||||||
if (useExtStorage) {
|
|
||||||
copyPath = Environment.getExternalStorageDirectory().toString()
|
|
||||||
+ "/CommonsApp/" + new Date().getTime() + ".jpg";
|
|
||||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
|
||||||
newFile.mkdir();
|
|
||||||
FileUtils.copy(
|
|
||||||
descriptor.getFileDescriptor(),
|
|
||||||
copyPath);
|
|
||||||
Timber.d("Filepath (copied): %s", copyPath);
|
|
||||||
return copyPath;
|
|
||||||
}
|
|
||||||
copyPath = getApplicationContext().getCacheDir().getAbsolutePath()
|
|
||||||
+ "/" + new Date().getTime() + ".jpg";
|
|
||||||
FileUtils.copy(
|
|
||||||
descriptor.getFileDescriptor(),
|
|
||||||
copyPath);
|
|
||||||
Timber.d("Filepath (copied): %s", copyPath);
|
|
||||||
return copyPath;
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Timber.w(e, "Error in file " + copyPath);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets coordinates for category suggestions, either from EXIF data or user location
|
* Check if file user wants to upload already exists on Commons
|
||||||
*
|
|
||||||
* @param gpsEnabled if true use GPS
|
|
||||||
*/
|
*/
|
||||||
private void getFileMetadata(boolean gpsEnabled) {
|
private void checkIfFileExists() {
|
||||||
Timber.d("Calling GPSExtractor");
|
if (!useNewPermissions || storagePermitted) {
|
||||||
try {
|
if (!duplicateCheckPassed) {
|
||||||
if (imageObj == null) {
|
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
||||||
ParcelFileDescriptor descriptor
|
|
||||||
= getContentResolver().openFileDescriptor(mediaUri, "r");
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
if (descriptor != null) {
|
|
||||||
imageObj = new GPSExtractor(descriptor.getFileDescriptor(), this, prefs);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String filePath = getPathOfMediaOrCopy();
|
|
||||||
if (filePath != null) {
|
|
||||||
imageObj = new GPSExtractor(filePath, this, prefs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageObj != null) {
|
|
||||||
// Gets image coords from exif data or user location
|
|
||||||
decimalCoords = imageObj.getCoords(gpsEnabled);
|
|
||||||
if(decimalCoords==null || !imageObj.imageCoordsExists){
|
|
||||||
// Check if the location is from GPS or EXIF
|
|
||||||
// Find other photos taken around the same time which has gps coordinates
|
|
||||||
Timber.d("EXIF:false");
|
|
||||||
Timber.d("EXIF call"+(imageObj==tempImageObj));
|
|
||||||
if(!haveCheckedForOtherImages)
|
|
||||||
findOtherImages(gpsEnabled);// Do not do repeat the process
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// As the selected image has GPS data in EXIF go ahead with the same.
|
|
||||||
useImageCoords();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Timber.w("File not found: " + mediaUri, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findOtherImages(boolean gpsEnabled) {
|
|
||||||
Timber.d("filePath"+getPathOfMediaOrCopy());
|
|
||||||
String filePath = getPathOfMediaOrCopy();
|
|
||||||
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
|
|
||||||
File folder = new File(filePath.substring(0,filePath.lastIndexOf('/')));
|
|
||||||
File[] files = folder.listFiles();
|
|
||||||
Timber.d("folderTime Number:"+files.length);
|
|
||||||
|
|
||||||
for(File file : files){
|
|
||||||
if(file.lastModified()-timeOfCreation<=(120*1000) && file.lastModified()-timeOfCreation>=-(120*1000)){
|
|
||||||
//Make sure the photos were taken within 20seconds
|
|
||||||
Timber.d("fild date:"+file.lastModified()+ " time of creation"+timeOfCreation);
|
|
||||||
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
|
||||||
ParcelFileDescriptor descriptor
|
|
||||||
= null;
|
|
||||||
try {
|
try {
|
||||||
descriptor = getContentResolver().openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
|
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
|
||||||
} catch (FileNotFoundException e) {
|
String fileSHA1 = getSHA1(inputStream);
|
||||||
e.printStackTrace();
|
Timber.d("Input stream created from %s", mediaUri.toString());
|
||||||
}
|
Timber.d("File SHA1 is: %s", fileSHA1);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
if (descriptor != null) {
|
|
||||||
tempImageObj = new GPSExtractor(descriptor.getFileDescriptor(),this, prefs);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (filePath != null) {
|
|
||||||
tempImageObj = new GPSExtractor(file.getAbsolutePath(), this, prefs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tempImageObj!=null){
|
|
||||||
Timber.d("not null fild EXIF"+tempImageObj.imageCoordsExists +" coords"+tempImageObj.getCoords(gpsEnabled));
|
|
||||||
if(tempImageObj.getCoords(gpsEnabled)!=null && tempImageObj.imageCoordsExists){
|
|
||||||
// Current image has gps coordinates and it's not current gps locaiton
|
|
||||||
Timber.d("This fild has image coords:"+ file.getAbsolutePath());
|
|
||||||
// Create a dialog fragment for the suggestion
|
|
||||||
FragmentManager fragmentManager = getSupportFragmentManager();
|
|
||||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
|
||||||
Bundle args = new Bundle();
|
|
||||||
args.putString("originalImagePath",filePath);
|
|
||||||
args.putString("possibleImagePath",file.getAbsolutePath());
|
|
||||||
newFragment.setArguments(args);
|
|
||||||
newFragment.show(fragmentManager, "dialog");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ExistingFileAsync fileAsyncTask =
|
||||||
|
new ExistingFileAsync(new WeakReference<Activity>(this), fileSHA1, new WeakReference<Context>(this), result -> {
|
||||||
|
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
||||||
|
duplicateCheckPassed = (result == DUPLICATE_PROCEED || result == NO_DUPLICATE);
|
||||||
|
if (duplicateCheckPassed) {
|
||||||
|
//image is not a duplicate, so now check if its a unwanted picture or not
|
||||||
|
fileObj.detectUnwantedPictures();
|
||||||
|
}
|
||||||
|
}, mwApi);
|
||||||
|
fileAsyncTask.execute();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e, "IO Exception: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Timber.w("not ready for preprocessing: useNewPermissions=%s storage=%s location=%s",
|
||||||
|
useNewPermissions, storagePermitted, locationPermitted);
|
||||||
}
|
}
|
||||||
haveCheckedForOtherImages = true; //Finished checking for other images
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPostiveResponse() {
|
|
||||||
imageObj = tempImageObj;
|
|
||||||
decimalCoords = imageObj.getCoords(false);// Not necessary to use gps as image already ha EXIF data
|
|
||||||
Timber.d("EXIF from tempImageObj");
|
|
||||||
useImageCoords();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNegativeResponse() {
|
|
||||||
Timber.d("EXIF from imageObj");
|
|
||||||
useImageCoords();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates retrieval of image coordinates or user coordinates, and caching of coordinates.
|
|
||||||
* Then initiates the calls to MediaWiki API through an instance of MwVolleyApi.
|
|
||||||
*/
|
|
||||||
public void useImageCoords() {
|
|
||||||
if (decimalCoords != null) {
|
|
||||||
Timber.d("Decimal coords of image: %s", decimalCoords);
|
|
||||||
Timber.d("is EXIF data present:"+imageObj.imageCoordsExists+" from findOther image:"+(imageObj==tempImageObj));
|
|
||||||
|
|
||||||
// Only set cache for this point if image has coords
|
|
||||||
if (imageObj.imageCoordsExists) {
|
|
||||||
double decLongitude = imageObj.getDecLongitude();
|
|
||||||
double decLatitude = imageObj.getDecLatitude();
|
|
||||||
cacheController.setQtPoint(decLongitude, decLatitude);
|
|
||||||
}
|
|
||||||
|
|
||||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
|
||||||
|
|
||||||
List<String> displayCatList = cacheController.findCategory();
|
|
||||||
boolean catListEmpty = displayCatList.isEmpty();
|
|
||||||
|
|
||||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
|
||||||
if (catListEmpty) {
|
|
||||||
cacheFound = false;
|
|
||||||
apiCall.request(decimalCoords);
|
|
||||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
|
||||||
} else {
|
|
||||||
cacheFound = true;
|
|
||||||
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
|
|
||||||
MwVolleyApi.setGpsCat(displayCatList);
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
Timber.d("EXIF: no coords");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
try {
|
try {
|
||||||
imageObj.unregisterLocationManager();
|
gpsObj.unregisterLocationManager();
|
||||||
Timber.d("Unregistered locationManager");
|
Timber.d("Unregistered locationManager");
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
Timber.d("locationManager does not exist, not unregistered");
|
Timber.d("locationManager does not exist, not unregistered");
|
||||||
|
|
@ -654,40 +499,157 @@ public class ShareActivity
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get SHA1 of file from input stream
|
/**
|
||||||
private String getSHA1(InputStream is) {
|
* Allows zooming in to the image about to be uploaded. Called when zoom FAB is tapped
|
||||||
|
*/
|
||||||
|
private void zoomImageFromThumb(final View thumbView, Uri imageuri) {
|
||||||
|
// If there's an animation in progress, cancel it immediately and proceed with this one.
|
||||||
|
if (CurrentAnimator != null) {
|
||||||
|
CurrentAnimator.cancel();
|
||||||
|
}
|
||||||
|
isZoom = true;
|
||||||
|
ViewUtil.hideKeyboard(ShareActivity.this.findViewById(R.id.titleEdit | R.id.descEdit));
|
||||||
|
closeFABMenu();
|
||||||
|
mainFab.setVisibility(View.GONE);
|
||||||
|
|
||||||
MessageDigest digest;
|
InputStream input = null;
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA1");
|
input = this.getContentResolver().openInputStream(imageuri);
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (FileNotFoundException e) {
|
||||||
Timber.e(e, "Exception while getting Digest");
|
e.printStackTrace();
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] buffer = new byte[8192];
|
Zoom zoomObj = new Zoom(thumbView, flContainer, this.getContentResolver());
|
||||||
int read;
|
Bitmap scaledImage = zoomObj.createScaledImage(input, imageuri);
|
||||||
try {
|
|
||||||
while ((read = is.read(buffer)) > 0) {
|
|
||||||
digest.update(buffer, 0, read);
|
|
||||||
}
|
|
||||||
byte[] md5sum = digest.digest();
|
|
||||||
BigInteger bigInt = new BigInteger(1, md5sum);
|
|
||||||
String output = bigInt.toString(16);
|
|
||||||
// Fill to 40 chars
|
|
||||||
output = String.format("%40s", output).replace(' ', '0');
|
|
||||||
Timber.i("File SHA1: %s", output);
|
|
||||||
|
|
||||||
return output;
|
// Load the high-resolution "zoomed-in" image.
|
||||||
} catch (IOException e) {
|
expandedImageView.setImageBitmap(scaledImage);
|
||||||
Timber.e(e, "IO Exception");
|
float startScale = zoomObj.adjustStartEndBounds(startBounds, finalBounds, globalOffset);
|
||||||
return "";
|
|
||||||
} finally {
|
// Hide the thumbnail and show the zoomed-in view. When the animation
|
||||||
try {
|
// begins, it will position the zoomed-in view in the place of the
|
||||||
is.close();
|
// thumbnail.
|
||||||
} catch (IOException e) {
|
thumbView.setAlpha(0f);
|
||||||
Timber.e(e, "Exception on closing MD5 input stream");
|
expandedImageView.setVisibility(View.VISIBLE);
|
||||||
|
zoomOutButton.setVisibility(View.VISIBLE);
|
||||||
|
zoomInButton.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
// Set the pivot point for SCALE_X and SCALE_Y transformations
|
||||||
|
// to the top-left corner of the zoomed-in view (the default
|
||||||
|
// is the center of the view).
|
||||||
|
expandedImageView.setPivotX(0f);
|
||||||
|
expandedImageView.setPivotY(0f);
|
||||||
|
|
||||||
|
// Construct and run the parallel animation of the four translation and
|
||||||
|
// scale properties (X, Y, SCALE_X, and SCALE_Y).
|
||||||
|
AnimatorSet set = new AnimatorSet();
|
||||||
|
set.play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left, finalBounds.left))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top, finalBounds.top))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScale, 1f))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScale, 1f));
|
||||||
|
set.setDuration(ShortAnimationDuration);
|
||||||
|
set.setInterpolator(new DecelerateInterpolator());
|
||||||
|
set.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
CurrentAnimator = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
CurrentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set.start();
|
||||||
|
CurrentAnimator = set;
|
||||||
|
|
||||||
|
// Upon clicking the zoomed-in image, it should zoom back down
|
||||||
|
// to the original bounds and show the thumbnail instead of
|
||||||
|
// the expanded image.
|
||||||
|
startScaleFinal = startScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when user taps the ^ FAB button, expands to show Zoom and Map
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.main_fab)
|
||||||
|
public void onMainFabClicked() {
|
||||||
|
if (!isFABOpen) {
|
||||||
|
showFABMenu();
|
||||||
|
} else {
|
||||||
|
closeFABMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.media_upload_zoom_in)
|
||||||
|
public void onZoomInFabClicked() {
|
||||||
|
try {
|
||||||
|
zoomImageFromThumb(backgroundImageView, mediaUri);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Timber.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.media_upload_zoom_out)
|
||||||
|
public void onZoomOutFabClicked() {
|
||||||
|
if (CurrentAnimator != null) {
|
||||||
|
CurrentAnimator.cancel();
|
||||||
|
}
|
||||||
|
isZoom = false;
|
||||||
|
zoomOutButton.setVisibility(View.GONE);
|
||||||
|
mainFab.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Animate the four positioning/sizing properties in parallel,
|
||||||
|
// back to their original values.
|
||||||
|
AnimatorSet set = new AnimatorSet();
|
||||||
|
set.play(ObjectAnimator.ofFloat(expandedImageView, View.X, startBounds.left))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y, startBounds.top))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X, startScaleFinal))
|
||||||
|
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y, startScaleFinal));
|
||||||
|
|
||||||
|
set.setDuration(ShortAnimationDuration);
|
||||||
|
set.setInterpolator(new DecelerateInterpolator());
|
||||||
|
set.addListener(new AnimatorListenerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animator animation) {
|
||||||
|
//background image view is thumbView
|
||||||
|
backgroundImageView.setAlpha(1f);
|
||||||
|
expandedImageView.setVisibility(View.GONE);
|
||||||
|
CurrentAnimator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationCancel(Animator animation) {
|
||||||
|
//background image view is thumbView
|
||||||
|
backgroundImageView.setAlpha(1f);
|
||||||
|
expandedImageView.setVisibility(View.GONE);
|
||||||
|
CurrentAnimator = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
set.start();
|
||||||
|
CurrentAnimator = set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.media_map)
|
||||||
|
public void onFabShowMapsClicked() {
|
||||||
|
if (gpsObj != null && gpsObj.imageCoordsExists) {
|
||||||
|
Uri gmmIntentUri = Uri.parse("google.streetview:cbll=" + gpsObj.getDecLatitude() + "," + gpsObj.getDecLongitude());
|
||||||
|
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
|
||||||
|
mapIntent.setPackage("com.google.android.apps.maps");
|
||||||
|
startActivity(mapIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
|
switch (keyCode) {
|
||||||
|
case KeyEvent.KEYCODE_BACK:
|
||||||
|
if(isZoom) {
|
||||||
|
onZoomOutFabClicked();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode,event);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@ import android.view.ViewGroup;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.facebook.imagepipeline.listener.RequestListener;
|
import com.facebook.imagepipeline.listener.RequestListener;
|
||||||
|
|
@ -29,29 +32,33 @@ import fr.free.nrw.commons.R;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class SimilarImageDialogFragment extends DialogFragment {
|
public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
|
@BindView(R.id.orginalImage)
|
||||||
SimpleDraweeView originalImage;
|
SimpleDraweeView originalImage;
|
||||||
|
@BindView(R.id.possibleImage)
|
||||||
SimpleDraweeView possibleImage;
|
SimpleDraweeView possibleImage;
|
||||||
|
@BindView(R.id.postive_button)
|
||||||
Button positiveButton;
|
Button positiveButton;
|
||||||
|
@BindView(R.id.negative_button)
|
||||||
Button negativeButton;
|
Button negativeButton;
|
||||||
onResponse mOnResponse;//Implemented interface from shareActivity
|
onResponse mOnResponse;//Implemented interface from shareActivity
|
||||||
Boolean gotResponse = false;
|
Boolean gotResponse = false;
|
||||||
|
|
||||||
public SimilarImageDialogFragment() {
|
public SimilarImageDialogFragment() {
|
||||||
}
|
}
|
||||||
public interface onResponse{
|
public interface onResponse{
|
||||||
public void onPostiveResponse();
|
public void onPositiveResponse();
|
||||||
|
|
||||||
public void onNegativeResponse();
|
public void onNegativeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
|
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
|
||||||
|
ButterKnife.bind(this,view);
|
||||||
Set<RequestListener> requestListeners = new HashSet<>();
|
Set<RequestListener> requestListeners = new HashSet<>();
|
||||||
requestListeners.add(new RequestLoggingListener());
|
requestListeners.add(new RequestLoggingListener());
|
||||||
|
|
||||||
originalImage =(SimpleDraweeView) view.findViewById(R.id.orginalImage);
|
|
||||||
possibleImage =(SimpleDraweeView) view.findViewById(R.id.possibleImage);
|
|
||||||
positiveButton = (Button) view.findViewById(R.id.postive_button);
|
|
||||||
negativeButton = (Button) view.findViewById(R.id.negative_button);
|
|
||||||
|
|
||||||
originalImage.setHierarchy(GenericDraweeHierarchyBuilder
|
originalImage.setHierarchy(GenericDraweeHierarchyBuilder
|
||||||
.newInstance(getResources())
|
.newInstance(getResources())
|
||||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||||
|
|
@ -70,22 +77,6 @@ public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
|
originalImage.setImageURI(Uri.fromFile(new File(getArguments().getString("originalImagePath"))));
|
||||||
possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
|
possibleImage.setImageURI(Uri.fromFile(new File(getArguments().getString("possibleImagePath"))));
|
||||||
|
|
||||||
negativeButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
mOnResponse.onNegativeResponse();
|
|
||||||
gotResponse = true;
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
positiveButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
mOnResponse.onPostiveResponse();
|
|
||||||
gotResponse = true;
|
|
||||||
dismiss();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,8 +96,23 @@ public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
// I user dismisses dialog by pressing outside the dialog.
|
// I user dismisses dialog by pressing outside the dialog.
|
||||||
if(!gotResponse)
|
if (!gotResponse) {
|
||||||
mOnResponse.onNegativeResponse();
|
mOnResponse.onNegativeResponse();
|
||||||
|
}
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.negative_button)
|
||||||
|
public void onNegativeButtonClicked() {
|
||||||
|
mOnResponse.onNegativeResponse();
|
||||||
|
gotResponse = true;
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.postive_button)
|
||||||
|
public void onPositiveButtonClicked() {
|
||||||
|
mOnResponse.onPositiveResponse();
|
||||||
|
gotResponse = true;
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,18 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.view.ViewCompat;
|
import android.support.v4.view.ViewCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.Html;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
|
@ -23,7 +20,6 @@ import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
@ -46,9 +42,9 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.MotionEvent.ACTION_DOWN;
|
|
||||||
import static android.view.MotionEvent.ACTION_UP;
|
import static android.view.MotionEvent.ACTION_UP;
|
||||||
|
|
||||||
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
@ -59,6 +55,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
|
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
|
||||||
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
|
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
|
||||||
|
|
||||||
|
|
||||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||||
|
|
||||||
|
|
@ -166,13 +163,13 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if (!hasFocus) {
|
if (!hasFocus) {
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
descEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
descEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
if(!hasFocus){
|
if(!hasFocus){
|
||||||
hideKeyboard(v);
|
ViewUtil.hideKeyboard(v);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -181,12 +178,6 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
return rootView;
|
return rootView;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hideKeyboard(View view) {
|
|
||||||
Log.i("hide", "hideKeyboard: ");
|
|
||||||
InputMethodManager inputMethodManager =(InputMethodManager)getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
|
|
||||||
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
titleEdit.removeTextChangedListener(textWatcher);
|
titleEdit.removeTextChangedListener(textWatcher);
|
||||||
|
|
@ -222,21 +213,9 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
setLicenseSummary(license);
|
setLicenseSummary(license);
|
||||||
prefs.edit()
|
prefs.edit()
|
||||||
.putString(Prefs.DEFAULT_LICENSE, license)
|
.putString(Prefs.DEFAULT_LICENSE, license)
|
||||||
.commit();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnTouch(R.id.share_license_summary)
|
|
||||||
boolean showLicence(View view, MotionEvent motionEvent) {
|
|
||||||
if (motionEvent.getActionMasked() == ACTION_DOWN) {
|
|
||||||
Intent intent = new Intent();
|
|
||||||
intent.setAction(Intent.ACTION_VIEW);
|
|
||||||
intent.setData(Uri.parse(licenseUrlFor(license)));
|
|
||||||
startActivity(intent);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.titleDescButton)
|
@OnClick(R.id.titleDescButton)
|
||||||
void setTitleDescButton() {
|
void setTitleDescButton() {
|
||||||
|
|
@ -294,8 +273,10 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
@SuppressLint("StringFormatInvalid")
|
@SuppressLint("StringFormatInvalid")
|
||||||
private void setLicenseSummary(String license) {
|
private void setLicenseSummary(String license) {
|
||||||
licenseSummaryView.setText(getString(R.string.share_license_summary, getString(Utils.licenseNameFor(license))));
|
String licenseHyperLink = "<a href='" + licenseUrlFor(license)+"'>"+ getString(Utils.licenseNameFor(license)) + "</a><br>";
|
||||||
}
|
licenseSummaryView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
licenseSummaryView.setText(Html.fromHtml(getString(R.string.share_license_summary, licenseHyperLink)));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
|
@ -309,11 +290,8 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
||||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||||
View target = getView().findFocus();
|
View target = getActivity().getCurrentFocus();
|
||||||
if (target != null) {
|
ViewUtil.hideKeyboard(target);
|
||||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
@ -354,6 +332,7 @@ public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void showInfoAlert (int titleStringID, int messageStringID){
|
private void showInfoAlert (int titleStringID, int messageStringID){
|
||||||
new AlertDialog.Builder(getContext())
|
new AlertDialog.Builder(getContext())
|
||||||
.setTitle(titleStringID)
|
.setTitle(titleStringID)
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ public class UploadController {
|
||||||
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
||||||
* @param onComplete the progress tracker
|
* @param onComplete the progress tracker
|
||||||
*/
|
*/
|
||||||
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, ContributionUploadProgress onComplete) {
|
public void startUpload(String title, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
|
||||||
Contribution contribution;
|
Contribution contribution;
|
||||||
|
|
||||||
//TODO: Modify this to include coords
|
//TODO: Modify this to include coords
|
||||||
|
|
@ -101,6 +101,7 @@ public class UploadController {
|
||||||
|
|
||||||
contribution.setTag("mimeType", mimeType);
|
contribution.setTag("mimeType", mimeType);
|
||||||
contribution.setSource(source);
|
contribution.setSource(source);
|
||||||
|
contribution.setWikiDataEntityId(wikiDataEntityId);
|
||||||
|
|
||||||
//Calls the next overloaded method
|
//Calls the next overloaded method
|
||||||
startUpload(contribution, onComplete);
|
startUpload(contribution, onComplete);
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.app.PendingIntent;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
@ -23,7 +22,6 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
@ -36,6 +34,7 @@ import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.mwapi.UploadResult;
|
import fr.free.nrw.commons.mwapi.UploadResult;
|
||||||
|
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadService extends HandlerService<Contribution> {
|
public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
@ -49,8 +48,8 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
|
public static final String EXTRA_CAMPAIGN = EXTRA_PREFIX + ".campaign";
|
||||||
|
|
||||||
@Inject MediaWikiApi mwApi;
|
@Inject MediaWikiApi mwApi;
|
||||||
|
@Inject WikidataEditService wikidataEditService;
|
||||||
@Inject SessionManager sessionManager;
|
@Inject SessionManager sessionManager;
|
||||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
|
||||||
@Inject ContributionDao contributionDao;
|
@Inject ContributionDao contributionDao;
|
||||||
|
|
||||||
private NotificationManager notificationManager;
|
private NotificationManager notificationManager;
|
||||||
|
|
@ -137,6 +136,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queue(int what, Contribution contribution) {
|
public void queue(int what, Contribution contribution) {
|
||||||
|
Timber.d("Upload service queue has contribution with wiki data entity id as %s", contribution.getWikiDataEntityId());
|
||||||
switch (what) {
|
switch (what) {
|
||||||
case ACTION_UPLOAD_FILE:
|
case ACTION_UPLOAD_FILE:
|
||||||
|
|
||||||
|
|
@ -231,10 +231,10 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
Timber.d("Successfully revalidated token!");
|
Timber.d("Successfully revalidated token!");
|
||||||
} else {
|
} else {
|
||||||
Timber.d("Unable to revalidate :(");
|
Timber.d("Unable to revalidate :(");
|
||||||
// TODO: Put up a new notification, ask them to re-login
|
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||||
failureToast.show();
|
failureToast.show();
|
||||||
|
sessionManager.forceLogin(this);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -253,6 +253,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
if (!resultStatus.equals("Success")) {
|
if (!resultStatus.equals("Success")) {
|
||||||
showFailedNotification(contribution);
|
showFailedNotification(contribution);
|
||||||
} else {
|
} else {
|
||||||
|
wikidataEditService.createClaimWithLogging(contribution.getWikiDataEntityId(), filename);
|
||||||
contribution.setFilename(uploadResult.getCanonicalFilename());
|
contribution.setFilename(uploadResult.getCanonicalFilename());
|
||||||
contribution.setImageUrl(uploadResult.getImageUrl());
|
contribution.setImageUrl(uploadResult.getImageUrl());
|
||||||
contribution.setState(Contribution.STATE_COMPLETED);
|
contribution.setState(Contribution.STATE_COMPLETED);
|
||||||
|
|
|
||||||
115
app/src/main/java/fr/free/nrw/commons/upload/Zoom.java
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapRegionDecoder;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import android.support.v4.graphics.BitmapCompat;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains utility methods for the Zoom function in ShareActivity.
|
||||||
|
*/
|
||||||
|
public class Zoom {
|
||||||
|
|
||||||
|
private View thumbView;
|
||||||
|
private ContentResolver contentResolver;
|
||||||
|
private FrameLayout flContainer;
|
||||||
|
|
||||||
|
Zoom(View thumbView, FrameLayout flContainer, ContentResolver contentResolver) {
|
||||||
|
this.thumbView = thumbView;
|
||||||
|
this.contentResolver = contentResolver;
|
||||||
|
this.flContainer = flContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a scaled bitmap to display the zoomed-in image
|
||||||
|
* @param input the input stream corresponding to the uploaded image
|
||||||
|
* @param imageUri the uploaded image's URI
|
||||||
|
* @return a zoomable bitmap
|
||||||
|
*/
|
||||||
|
Bitmap createScaledImage(InputStream input, Uri imageUri) {
|
||||||
|
|
||||||
|
Bitmap scaled = null;
|
||||||
|
BitmapRegionDecoder decoder = null;
|
||||||
|
Bitmap bitmap = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
decoder = BitmapRegionDecoder.newInstance(input, false);
|
||||||
|
bitmap = decoder.decodeRegion(new Rect(10, 10, 50, 50), null);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
//Compress the Image
|
||||||
|
System.gc();
|
||||||
|
Runtime rt = Runtime.getRuntime();
|
||||||
|
long maxMemory = rt.freeMemory();
|
||||||
|
bitmap = MediaStore.Images.Media.getBitmap(contentResolver, imageUri);
|
||||||
|
int bitmapByteCount = BitmapCompat.getAllocationByteCount(bitmap);
|
||||||
|
long height = bitmap.getHeight();
|
||||||
|
long width = bitmap.getWidth();
|
||||||
|
long calHeight = (long) ((height * maxMemory) / (bitmapByteCount * 1.1));
|
||||||
|
long calWidth = (long) ((width * maxMemory) / (bitmapByteCount * 1.1));
|
||||||
|
scaled = Bitmap.createScaledBitmap(bitmap, (int) Math.min(width, calWidth), (int) Math.min(height, calHeight), true);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
scaled = bitmap;
|
||||||
|
}
|
||||||
|
return scaled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the starting and ending bounds for the zoomed-in image.
|
||||||
|
* Also set the container view's offset as the origin for the
|
||||||
|
* bounds, since that's the origin for the positioning animation
|
||||||
|
* properties (X, Y).
|
||||||
|
* @param startBounds the global visible rectangle of the thumbnail
|
||||||
|
* @param finalBounds the global visible rectangle of the container view
|
||||||
|
* @param globalOffset the container view's offset
|
||||||
|
* @return scaled start bounds
|
||||||
|
*/
|
||||||
|
float adjustStartEndBounds(Rect startBounds, Rect finalBounds, Point globalOffset) {
|
||||||
|
|
||||||
|
thumbView.getGlobalVisibleRect(startBounds);
|
||||||
|
flContainer.getGlobalVisibleRect(finalBounds, globalOffset);
|
||||||
|
startBounds.offset(-globalOffset.x, -globalOffset.y);
|
||||||
|
finalBounds.offset(-globalOffset.x, -globalOffset.y);
|
||||||
|
|
||||||
|
// Adjust the start bounds to be the same aspect ratio as the final
|
||||||
|
// bounds using the "center crop" technique. This prevents undesirable
|
||||||
|
// stretching during the animation. Also calculate the start scaling
|
||||||
|
// factor (the end scaling factor is always 1.0).
|
||||||
|
float startScale;
|
||||||
|
if ((float) finalBounds.width() / finalBounds.height()
|
||||||
|
> (float) startBounds.width() / startBounds.height()) {
|
||||||
|
// Extend start bounds horizontally
|
||||||
|
startScale = (float) startBounds.height() / finalBounds.height();
|
||||||
|
float startWidth = startScale * finalBounds.width();
|
||||||
|
float deltaWidth = (startWidth - startBounds.width()) / 2;
|
||||||
|
startBounds.left -= deltaWidth;
|
||||||
|
startBounds.right += deltaWidth;
|
||||||
|
} else {
|
||||||
|
// Extend start bounds vertically
|
||||||
|
startScale = (float) startBounds.width() / finalBounds.width();
|
||||||
|
float startHeight = startScale * finalBounds.height();
|
||||||
|
float deltaHeight = (startHeight - startBounds.height()) / 2;
|
||||||
|
startBounds.top -= deltaHeight;
|
||||||
|
startBounds.bottom += deltaHeight;
|
||||||
|
}
|
||||||
|
return startScale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.category.QueryContinue;
|
||||||
|
|
||||||
|
public class ContinueUtils {
|
||||||
|
|
||||||
|
public static QueryContinue getQueryContinue(Node document) {
|
||||||
|
Element continueElement = (Element) document;
|
||||||
|
return new QueryContinue(continueElement.getAttribute("continue"),
|
||||||
|
continueElement.getAttribute("gcmcontinue"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package fr.free.nrw.commons.utils;
|
|
||||||
|
|
||||||
import android.os.Environment;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class FileUtils {
|
|
||||||
/**
|
|
||||||
* Read and return the content of a resource file as string.
|
|
||||||
*
|
|
||||||
* @param fileName asset file's path (e.g. "/queries/nearby_query.rq")
|
|
||||||
* @return the content of the file
|
|
||||||
*/
|
|
||||||
public static String readFromResource(String fileName) throws IOException {
|
|
||||||
StringBuilder buffer = new StringBuilder();
|
|
||||||
BufferedReader reader = null;
|
|
||||||
try {
|
|
||||||
InputStream inputStream = FileUtils.class.getResourceAsStream(fileName);
|
|
||||||
if (inputStream == null) {
|
|
||||||
throw new FileNotFoundException(fileName);
|
|
||||||
}
|
|
||||||
reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
buffer.append(line).append("\n");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
if (reader != null) {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes files.
|
|
||||||
* @param file context
|
|
||||||
*/
|
|
||||||
public static boolean deleteFile(File file) {
|
|
||||||
boolean deletedAll = true;
|
|
||||||
if (file != null) {
|
|
||||||
if (file.isDirectory()) {
|
|
||||||
String[] children = file.list();
|
|
||||||
for (String child : children) {
|
|
||||||
deletedAll = deleteFile(new File(file, child)) && deletedAll;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
deletedAll = file.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return deletedAll;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static File createAndGetAppLogsFile(String logs) {
|
|
||||||
try {
|
|
||||||
File commonsAppDirectory = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
|
||||||
if (!commonsAppDirectory.exists()) {
|
|
||||||
commonsAppDirectory.mkdir();
|
|
||||||
}
|
|
||||||
|
|
||||||
File logsFile = new File(commonsAppDirectory,"logs.txt");
|
|
||||||
if (logsFile.exists()) {
|
|
||||||
//old logs file is useless
|
|
||||||
logsFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
logsFile.createNewFile();
|
|
||||||
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(logsFile);
|
|
||||||
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
|
|
||||||
outputStreamWriter.append(logs);
|
|
||||||
outputStreamWriter.close();
|
|
||||||
outputStream.flush();
|
|
||||||
outputStream.close();
|
|
||||||
|
|
||||||
return logsFile;
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
Timber.e(ioe);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +1,27 @@
|
||||||
package fr.free.nrw.commons.utils;
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.app.WallpaperManager;
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapRegionDecoder;
|
import android.graphics.BitmapRegionDecoder;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.facebook.common.executors.CallerThreadExecutor;
|
||||||
|
import com.facebook.common.references.CloseableReference;
|
||||||
|
import com.facebook.datasource.DataSource;
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipeline;
|
||||||
|
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
|
||||||
|
import com.facebook.imagepipeline.image.CloseableImage;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -132,4 +149,52 @@ public class ImageUtils {
|
||||||
|
|
||||||
return isImageDark;
|
return isImageDark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads the image from the URL and sets it as the phone's wallpaper
|
||||||
|
* Fails silently if download or setting wallpaper fails.
|
||||||
|
* @param context
|
||||||
|
* @param imageUrl
|
||||||
|
*/
|
||||||
|
public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) {
|
||||||
|
Timber.d("Trying to set wallpaper from url %s", imageUrl.toString());
|
||||||
|
ImageRequest imageRequest = ImageRequestBuilder
|
||||||
|
.newBuilderWithSource(imageUrl)
|
||||||
|
.setAutoRotateEnabled(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ImagePipeline imagePipeline = Fresco.getImagePipeline();
|
||||||
|
final DataSource<CloseableReference<CloseableImage>>
|
||||||
|
dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
|
||||||
|
|
||||||
|
dataSource.subscribe(new BaseBitmapDataSubscriber() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||||
|
if (dataSource.isFinished() && bitmap != null){
|
||||||
|
Timber.d("Bitmap loaded from url %s", imageUrl.toString());
|
||||||
|
setWallpaper(context, Bitmap.createBitmap(bitmap));
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailureImpl(DataSource dataSource) {
|
||||||
|
Timber.d("Error getting bitmap from image url %s", imageUrl.toString());
|
||||||
|
if (dataSource != null) {
|
||||||
|
dataSource.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, CallerThreadExecutor.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setWallpaper(Context context, Bitmap bitmap) {
|
||||||
|
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
|
||||||
|
try {
|
||||||
|
wallpaperManager.setBitmap(bitmap);
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wallpaper_set_successfully));
|
||||||
|
} catch (IOException e) {
|
||||||
|
Timber.e(e,"Error setting wallpaper");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ public class NetworkUtils {
|
||||||
|
|
||||||
public static boolean isInternetConnectionEstablished(Context context) {
|
public static boolean isInternetConnectionEstablished(Context context) {
|
||||||
ConnectivityManager cm =
|
ConnectivityManager cm =
|
||||||
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
(ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
|
||||||
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
|
||||||
return activeNetwork != null &&
|
return activeNetwork != null &&
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.utils;
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import info.debatty.java.stringsimilarity.Levenshtein;
|
import info.debatty.java.stringsimilarity.Levenshtein;
|
||||||
|
|
||||||
|
|
@ -28,8 +29,8 @@ public class StringSortingUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double calculateSimilarity(String firstString, String secondString) {
|
private static double calculateSimilarity(String firstString, String secondString) {
|
||||||
String longer = firstString.toLowerCase();
|
String longer = firstString.toLowerCase(Locale.getDefault());
|
||||||
String shorter = secondString.toLowerCase();
|
String shorter = secondString.toLowerCase(Locale.getDefault());
|
||||||
|
|
||||||
if (firstString.length() < secondString.length()) {
|
if (firstString.length() < secondString.length()) {
|
||||||
longer = secondString;
|
longer = secondString;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,15 @@ import android.content.Context;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
public class ViewUtil {
|
public class ViewUtil {
|
||||||
|
|
||||||
|
public static final String SHOWCASE_VIEW_ID_1 = "SHOWCASE_VIEW_ID_1";
|
||||||
|
public static final String SHOWCASE_VIEW_ID_2 = "SHOWCASE_VIEW_ID_2";
|
||||||
|
public static final String SHOWCASE_VIEW_ID_3 = "SHOWCASE_VIEW_ID_3";
|
||||||
|
|
||||||
public static void showSnackbar(View view, int messageResourceId) {
|
public static void showSnackbar(View view, int messageResourceId) {
|
||||||
Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show();
|
Snackbar.make(view, messageResourceId, Snackbar.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|
@ -27,4 +32,14 @@ public class ViewUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void hideKeyboard(View view){
|
||||||
|
if (view != null) {
|
||||||
|
InputMethodManager manager = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
view.clearFocus();
|
||||||
|
if (manager != null) {
|
||||||
|
manager.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.widget;
|
||||||
|
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
|
import com.prof.rssparser.Article;
|
||||||
|
import com.prof.rssparser.Parser;
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of App Widget functionality.
|
||||||
|
*/
|
||||||
|
public class PicOfDayAppWidget extends AppWidgetProvider {
|
||||||
|
|
||||||
|
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
||||||
|
int appWidgetId) {
|
||||||
|
|
||||||
|
// Construct the RemoteViews object
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.pic_of_day_app_widget);
|
||||||
|
|
||||||
|
String urlString = BuildConfig.WIKIMEDIA_API_POTD;
|
||||||
|
Parser parser = new Parser();
|
||||||
|
parser.execute(urlString);
|
||||||
|
parser.onFinish(new Parser.OnTaskCompleted() {
|
||||||
|
@Override
|
||||||
|
public void onTaskCompleted(ArrayList<Article> list) {
|
||||||
|
String desc = list.get(list.size() - 1).getDescription();
|
||||||
|
if (desc != null) {
|
||||||
|
Document document = Jsoup.parse(desc);
|
||||||
|
Elements elements = document.select("img");
|
||||||
|
String imageUrl = elements.get(0).attr("src");
|
||||||
|
if (imageUrl != null && imageUrl.length() > 0) {
|
||||||
|
Picasso.get().load(imageUrl).into(views, R.id.appwidget_image, new int[]{appWidgetId});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError() {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
// There may be multiple widgets active, so update all of them
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Context context) {
|
||||||
|
// Enter relevant functionality for when the first widget is created
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled(Context context) {
|
||||||
|
// Enter relevant functionality for when the last widget is disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fr.free.nrw.commons.wikidata;
|
||||||
|
|
||||||
|
public abstract class WikidataEditListener {
|
||||||
|
|
||||||
|
protected WikidataP18EditListener wikidataP18EditListener;
|
||||||
|
|
||||||
|
public abstract void onSuccessfulWikidataEdit();
|
||||||
|
|
||||||
|
public void setAuthenticationStateListener(WikidataP18EditListener wikidataP18EditListener) {
|
||||||
|
this.wikidataP18EditListener = wikidataP18EditListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface WikidataP18EditListener {
|
||||||
|
void onWikidataEditSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.free.nrw.commons.wikidata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for wikidata edits
|
||||||
|
*/
|
||||||
|
public class WikidataEditListenerImpl extends WikidataEditListener {
|
||||||
|
|
||||||
|
public WikidataEditListenerImpl() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when wikidata P18 edit is successful. If there's an active listener, then it is fired
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onSuccessfulWikidataEdit() {
|
||||||
|
if (wikidataP18EditListener != null) {
|
||||||
|
wikidataP18EditListener.onWikidataEditSuccessful();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
package fr.free.nrw.commons.wikidata;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is meant to handle the Wikidata edits made through the app
|
||||||
|
* It will talk with MediaWikiApi to make necessary API calls, log the edits and fire listeners
|
||||||
|
* on successful edits
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class WikidataEditService {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final MediaWikiApi mediaWikiApi;
|
||||||
|
private final WikidataEditListener wikidataEditListener;
|
||||||
|
private final SharedPreferences directPrefs;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public WikidataEditService(Context context,
|
||||||
|
MediaWikiApi mediaWikiApi,
|
||||||
|
WikidataEditListener wikidataEditListener,
|
||||||
|
@Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) {
|
||||||
|
this.context = context;
|
||||||
|
this.mediaWikiApi = mediaWikiApi;
|
||||||
|
this.wikidataEditListener = wikidataEditListener;
|
||||||
|
this.directPrefs = directPrefs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a P18 claim and log the edit with custom tag
|
||||||
|
* @param wikidataEntityId
|
||||||
|
* @param fileName
|
||||||
|
*/
|
||||||
|
public void createClaimWithLogging(String wikidataEntityId, String fileName) {
|
||||||
|
if(wikidataEntityId == null
|
||||||
|
|| fileName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editWikidataProperty(wikidataEntityId, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edits the wikidata entity by adding the P18 property to it.
|
||||||
|
* Adding the P18 edit requires calling the wikidata API to create a claim against the entity
|
||||||
|
*
|
||||||
|
* @param wikidataEntityId
|
||||||
|
* @param fileName
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void editWikidataProperty(String wikidataEntityId, String fileName) {
|
||||||
|
Timber.d("Upload successful with wiki data entity id as %s", wikidataEntityId);
|
||||||
|
Timber.d("Attempting to edit Wikidata property %s", wikidataEntityId);
|
||||||
|
Observable.fromCallable(() -> {
|
||||||
|
String propertyValue = getFileName(fileName);
|
||||||
|
return mediaWikiApi.wikidatCreateClaim(wikidataEntityId, "P18", "value", propertyValue);
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(revisionId -> handleClaimResult(wikidataEntityId, revisionId), throwable -> {
|
||||||
|
Timber.e(throwable, "Error occurred while making claim");
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleClaimResult(String wikidataEntityId, String revisionId) {
|
||||||
|
if (revisionId != null) {
|
||||||
|
wikidataEditListener.onSuccessfulWikidataEdit();
|
||||||
|
showSuccessToast();
|
||||||
|
logEdit(revisionId);
|
||||||
|
} else {
|
||||||
|
Timber.d("Unable to make wiki data edit for entity %s", wikidataEntityId);
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the Wikidata edit by adding Wikimedia Commons App tag to the edit
|
||||||
|
* @param revisionId
|
||||||
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private void logEdit(String revisionId) {
|
||||||
|
Observable.fromCallable(() -> mediaWikiApi.addWikidataEditTag(revisionId))
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(result -> {
|
||||||
|
if (result) {
|
||||||
|
Timber.d("Wikidata edit was tagged successfully");
|
||||||
|
} else {
|
||||||
|
Timber.d("Wikidata edit couldn't be tagged");
|
||||||
|
}
|
||||||
|
}, throwable -> {
|
||||||
|
Timber.e(throwable, "Error occurred while adding tag to the edit");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a success toast when the edit is made successfully
|
||||||
|
*/
|
||||||
|
private void showSuccessToast() {
|
||||||
|
String title = directPrefs.getString("Title", "");
|
||||||
|
String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
|
||||||
|
String successMessage = String.format(Locale.getDefault(), successStringTemplate, title);
|
||||||
|
ViewUtil.showLongToast(context, successMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats and returns the filename as accepted by the wiki base API
|
||||||
|
* https://www.mediawiki.org/wiki/Wikibase/API#wbcreateclaim
|
||||||
|
*
|
||||||
|
* @param fileName
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private String getFileName(String fileName) {
|
||||||
|
fileName = String.format("\"%s\"", fileName.replace("File:", ""));
|
||||||
|
Timber.d("Wikidata property name is %s", fileName);
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_zoom_in_white_24dp.png
Normal file
|
After Width: | Height: | Size: 422 B |
BIN
app/src/main/res/drawable-hdpi/ic_zoom_out_white_24dp.png
Normal file
|
After Width: | Height: | Size: 412 B |
BIN
app/src/main/res/drawable-mdpi/ic_zoom_in_white_24dp.png
Normal file
|
After Width: | Height: | Size: 257 B |
BIN
app/src/main/res/drawable-mdpi/ic_zoom_out_white_24dp.png
Normal file
|
After Width: | Height: | Size: 249 B |
BIN
app/src/main/res/drawable-xhdpi/ic_zoom_in_white_24dp.png
Normal file
|
After Width: | Height: | Size: 486 B |
BIN
app/src/main/res/drawable-xhdpi/ic_zoom_out_white_24dp.png
Normal file
|
After Width: | Height: | Size: 470 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_zoom_in_white_24dp.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_zoom_out_white_24dp.png
Normal file
|
After Width: | Height: | Size: 731 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_zoom_in_white_24dp.png
Normal file
|
After Width: | Height: | Size: 954 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_zoom_out_white_24dp.png
Normal file
|
After Width: | Height: | Size: 925 B |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||||
|
</vector>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<vector android:alpha="0.84" android:height="32dp"
|
<vector android:alpha="0.84" android:height="24dp"
|
||||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||||
android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="#ffffffff" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
<path android:fillColor="#ffffffff" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/ic_star_black_24dp.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24.0"
|
||||||
|
android:viewportHeight="24.0">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
||||||
|
</vector>
|
||||||