mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge branch 'master' into addedReasonList
This commit is contained in:
commit
2bcc8c10b6
248 changed files with 7414 additions and 4619 deletions
|
|
@ -2,24 +2,28 @@
|
|||
|
||||
Summarize your issue in one sentence (what goes wrong, what did you expect to happen)
|
||||
|
||||
_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby `) or just by typing keywords into the search filter._
|
||||
_Before creating an issue, please search the existing issues to see if a similar one has already been created. You can search issues by specific labels (e.g. `label:nearby`) or just by typing keywords into the search filter._
|
||||
|
||||
**Steps to reproduce:**
|
||||
|
||||
How can we reproduce the issue?
|
||||
What did you expect the app to do, and what did you see instead?
|
||||
|
||||
**Add System logs:**
|
||||
**System logs:**
|
||||
|
||||
```
|
||||
Add logcat files here (if possible).
|
||||
|
||||
Need help? See https://github.com/commons-app/apps-android-commons/wiki/Getting-app-logs-from-Android-Studio
|
||||
```
|
||||
|
||||
**Device and Android version:**
|
||||
|
||||
What make and model device (e.g., Samsung J7) did you encounter this on? What Android
|
||||
version (e.g., Android 4.0 Ice Cream Sandwich or Android 6.0 Marshmallow) are you running? Is it
|
||||
the stock version from the manufacturer or a custom ROM ?
|
||||
What make and model device (e.g., Samsung J7) did you encounter this on?
|
||||
What Android version (e.g., Android 4.0 Ice Cream Sandwich or Android 6.0 Marshmallow) are you running?
|
||||
Is it the stock version from the manufacturer or a custom ROM ?
|
||||
|
||||
**Commons app version:**
|
||||
**Commons app version:**
|
||||
|
||||
You can find this information by going to the navigation drawer in the app and tapping 'About'. If you are building from our codebase instead of downloading the app, please also mention the branch and build variant (e.g. master and prodDebug).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
## Title (required)
|
||||
**Description (required)**
|
||||
|
||||
Fixes #{GitHub issue number and title (Please do not forget adding title) }
|
||||
Fixes #{GitHub issue number} {Github issue title}
|
||||
|
||||
## Description (required)
|
||||
What changes did you make and why?
|
||||
|
||||
Fixes #{GitHub issue number and title}
|
||||
**Tests performed (required)**
|
||||
|
||||
{Describe the changes made and why they were made.}
|
||||
Tested {build variant, e.g. ProdDebug} on {name of device or emulator} with API level {API level}.
|
||||
|
||||
## Tests performed (required)
|
||||
**Screenshots showing what changed (optional - for UI changes)**
|
||||
|
||||
Tested on {API level & name of device/emulator}, with {build variant, e.g. ProdDebug}.
|
||||
Need help? See https://support.google.com/android/answer/9075928
|
||||
|
||||
## Screenshots showing what changed (optional)
|
||||
|
||||
{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._
|
||||
73
README.md
73
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# Wikimedia Commons Android app [](https://travis-ci.org/commons-app/apps-android-commons)
|
||||
# Wikimedia Commons Android app [](https://travis-ci.org/commons-app/apps-android-commons)
|
||||
|
||||
The Wikimedia Commons Android app allows users to upload pictures from their Android phone/tablet to Wikimedia Commons. Download the app [here][1], or view our [website][2].
|
||||
|
||||
|
|
@ -11,37 +11,13 @@ Initially started by the Wikimedia Foundation, this app is now maintained by gra
|
|||
|
||||
## Documentation
|
||||
|
||||
We try to have an extensive documentation at [our wiki here at Github][5]:
|
||||
We try to have an extensive documentation at [our wiki here at Github][4]:
|
||||
|
||||
* [User Documentation][6]
|
||||
* [Contributor Documentation][7]
|
||||
* [Volunteers Welcome!][9]
|
||||
* [User Documentation][5]
|
||||
* [Contributor Documentation][6]
|
||||
* [Volunteers Welcome!][7]
|
||||
* [Developer Documentation][8]
|
||||
|
||||
## Libraries Used ##
|
||||
|
||||
* [Picasso][11]
|
||||
* [RSS-Parser][12]
|
||||
* [ViewPagerIndicator][13]
|
||||
* [PhotoView][14]
|
||||
* [Acra][15]
|
||||
* [Renderers][16]
|
||||
* [Gson][17]
|
||||
* [Timber][18]
|
||||
* [Java-String-Similarity][19]
|
||||
* [ReadMoreTextView][20]
|
||||
* [MaterialShowcaseView][21]
|
||||
* [Butterknife][22]
|
||||
* [OKHttp][23]
|
||||
* [Okio][24]
|
||||
* [RxJava][25]
|
||||
* [JSoup][26]
|
||||
* [Fresco][27]
|
||||
* [Stetho][28]
|
||||
* [Dagger][29]
|
||||
* [Java-HTTP-Fluent][30]
|
||||
* [CircleProgressBar][31]
|
||||
* [Leak Canary][32]
|
||||
* [Libraries Used][9]
|
||||
|
||||
## Contributors ##
|
||||
|
||||
|
|
@ -60,37 +36,18 @@ Thank you all for your work!
|
|||
|
||||
## License ##
|
||||
|
||||
This software is open source, licensed under the [Apache License 2.0][4].
|
||||
|
||||
This software is open source, licensed under the [Apache License 2.0][10].
|
||||
|
||||
|
||||
[1]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons
|
||||
[2]: https://commons-app.github.io/
|
||||
[3]: https://github.com/commons-app/apps-android-commons/issues
|
||||
[4]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
[5]: https://github.com/commons-app/apps-android-commons/wiki
|
||||
[6]: https://github.com/commons-app/apps-android-commons/wiki#user-documentation
|
||||
[7]: https://github.com/commons-app/apps-android-commons/wiki#contributor-documentation
|
||||
|
||||
[4]: https://github.com/commons-app/apps-android-commons/wiki
|
||||
[5]: https://github.com/commons-app/apps-android-commons/wiki#user-documentation
|
||||
[6]: https://github.com/commons-app/apps-android-commons/wiki#contributor-documentation
|
||||
[7]: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
|
||||
[8]: https://github.com/commons-app/apps-android-commons/wiki#developer-documentation
|
||||
[9]: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
|
||||
[10]: https://meta.wikimedia.org/wiki/Grants:Project/Improve_%27Upload_to_Commons%27_Android_App/Renewal
|
||||
[11]: https://github.com/square/picasso
|
||||
[13]: https://github.com/avianey/Android-ViewPagerIndicator
|
||||
[14]: https://github.com/chrisbanes/PhotoView
|
||||
[15]: https://github.com/ACRA/acra
|
||||
[16]: https://github.com/pedrovgs/Renderers
|
||||
[17]: https://github.com/google/gson
|
||||
[18]: https://github.com/JakeWharton/timber
|
||||
[19]: https://github.com/tdebatty/java-string-similarity
|
||||
[20]: https://github.com/bravoborja/ReadMoreTextView
|
||||
[21]: https://github.com/deano2390/MaterialShowcaseView
|
||||
[22]: https://github.com/JakeWharton/butterknife
|
||||
[23]: https://github.com/square/okhttp
|
||||
[24]: https://github.com/square/okio
|
||||
[25]: https://github.com/ReactiveX/RxJava
|
||||
[27]: https://github.com/facebook/fresco
|
||||
[28]: https://github.com/facebook/stetho
|
||||
[29]: https://github.com/google/dagger
|
||||
[30]: https://github.com/yuvipanda/java-http-fluent/blob/master/src/main/java/in/yuvi/http/fluent/Http.java
|
||||
[31]: https://github.com/dinuscxj/CircleProgressBar
|
||||
[32]: https://github.com/square/leakcanary
|
||||
[9]: https://github.com/commons-app/apps-android-commons/wiki/Libraries-used
|
||||
|
||||
[10]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ dependencies {
|
|||
|
||||
implementation 'ch.acra:acra:4.9.2'
|
||||
|
||||
implementation 'org.mediawiki:api:1.3'
|
||||
implementation 'commons-codec:commons-codec:1.10'
|
||||
implementation 'com.github.pedrovgs:renderers:3.3.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
|
|
@ -32,6 +31,7 @@ dependencies {
|
|||
transitive = true
|
||||
}
|
||||
implementation 'com.github.deano2390:MaterialShowcaseView:1.2.0'
|
||||
|
||||
//noinspection GradleCompatible
|
||||
implementation "com.android.support:support-v4:$SUPPORT_LIB_VERSION"
|
||||
implementation "com.android.support:appcompat-v7:$SUPPORT_LIB_VERSION"
|
||||
|
|
@ -44,6 +44,7 @@ dependencies {
|
|||
implementation 'com.squareup.okio:okio:1.14.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
// 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.
|
||||
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.1.1'
|
||||
|
|
@ -153,8 +154,8 @@ android {
|
|||
buildConfigField "String", "MODIFICATION_AUTHORITY", "\"fr.free.nrw.commons.modifications.contentprovider\""
|
||||
buildConfigField "String", "CATEGORY_AUTHORITY", "\"fr.free.nrw.commons.categories.contentprovider\""
|
||||
buildConfigField "String", "RECENT_SEARCH_AUTHORITY", "\"fr.free.nrw.commons.explore.recentsearches.contentprovider\""
|
||||
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.contentprovider\""
|
||||
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.beta.bookmarks.locations.contentprovider\""
|
||||
buildConfigField "String", "BOOKMARK_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.contentprovider\""
|
||||
buildConfigField "String", "BOOKMARK_LOCATIONS_AUTHORITY", "\"fr.free.nrw.commons.bookmarks.locations.contentprovider\""
|
||||
|
||||
dimension 'tier'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class FileUtilsTest {
|
||||
@Test
|
||||
public void isSelfOwned() throws Exception {
|
||||
Uri uri = Uri.parse("content://" + BuildConfig.APPLICATION_ID + ".provider/document/1");
|
||||
boolean selfOwned = FileUtils.isSelfOwned(InstrumentationRegistry.getTargetContext(), uri);
|
||||
assertThat(selfOwned, is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isNotSelfOwned() throws Exception {
|
||||
Uri uri = Uri.parse("content://com.android.providers.media.documents/document/1");
|
||||
boolean selfOwned = FileUtils.isSelfOwned(InstrumentationRegistry.getTargetContext(), uri);
|
||||
assertThat(selfOwned, is(false));
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
|
||||
<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"/>
|
||||
|
||||
|
||||
|
|
@ -41,56 +41,44 @@
|
|||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".WelcomeActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".upload.ShareActivity"
|
||||
<activity android:name=".upload.UploadActivity"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter android:label="@string/intent_share_upload_label">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="audio/ogg" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".upload.MultipleShareActivity"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter android:label="@string/intent_share_upload_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="audio/ogg" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".contributions.ContributionsActivity"
|
||||
android:name=".contributions.MainActivity"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name" />
|
||||
|
||||
<activity
|
||||
android:name=".settings.SettingsActivity"
|
||||
android:label="@string/title_activity_settings" />
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/title_activity_about"
|
||||
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".auth.SignupActivity"
|
||||
android:label="@string/title_activity_signup" />
|
||||
|
||||
<activity
|
||||
android:name=".nearby.NearbyActivity"
|
||||
android:label="@string/title_activity_nearby"
|
||||
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".notification.NotificationActivity"
|
||||
android:label="@string/navigation_item_notification" />
|
||||
|
|
@ -104,18 +92,18 @@
|
|||
<activity
|
||||
android:name=".category.CategoryImagesActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".category.CategoryDetailsActivity"
|
||||
android:label="@string/title_activity_featured_images"
|
||||
android:parentActivityName=".contributions.ContributionsActivity" />
|
||||
android:parentActivityName=".contributions.MainActivity" />
|
||||
|
||||
<activity
|
||||
android:name=".explore.SearchActivity"
|
||||
android:label="@string/title_activity_search"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:parentActivityName=".contributions.ContributionsActivity"
|
||||
android:parentActivityName=".contributions.MainActivity"
|
||||
/>
|
||||
|
||||
<activity
|
||||
|
|
@ -140,24 +128,24 @@
|
|||
android:name="android.accounts.AccountAuthenticator"
|
||||
android:resource="@xml/authenticator" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".contributions.ContributionsSyncService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/contributions_sync_adapter" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".modifications.ModificationsSyncService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.content.SyncAdapter" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.content.SyncAdapter"
|
||||
android:resource="@xml/modifications_sync_adapter" />
|
||||
|
|
@ -177,21 +165,18 @@
|
|||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name=".contributions.ContributionsContentProvider"
|
||||
android:authorities="${applicationId}.contributions.contentprovider"
|
||||
android:exported="false"
|
||||
android:label="@string/provider_contributions"
|
||||
android:syncable="true" />
|
||||
|
||||
<provider
|
||||
android:name=".modifications.ModificationsContentProvider"
|
||||
android:authorities="${applicationId}.modifications.contentprovider"
|
||||
android:exported="false"
|
||||
android:label="@string/provider_modifications"
|
||||
android:syncable="true" />
|
||||
|
||||
<provider
|
||||
android:name=".category.CategoryContentProvider"
|
||||
android:authorities="${applicationId}.categories.contentprovider"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
|
|
@ -14,12 +15,11 @@ import android.widget.Toast;
|
|||
import org.apache.commons.codec.binary.Hex;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
|
@ -110,6 +110,31 @@ public class Utils {
|
|||
throw new RuntimeException("Unrecognized license value: " + license);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates license url with given ID
|
||||
* @param license License ID
|
||||
* @return Url of license
|
||||
*/
|
||||
|
||||
|
||||
@NonNull
|
||||
public static String licenseUrlFor(String license) {
|
||||
switch (license) {
|
||||
case Prefs.Licenses.CC_BY_3:
|
||||
return "https://creativecommons.org/licenses/by/3.0/";
|
||||
case Prefs.Licenses.CC_BY_4:
|
||||
return "https://creativecommons.org/licenses/by/4.0/";
|
||||
case Prefs.Licenses.CC_BY_SA_3:
|
||||
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
||||
case Prefs.Licenses.CC_BY_SA_4:
|
||||
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
||||
case Prefs.Licenses.CC0:
|
||||
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||
default:
|
||||
throw new RuntimeException("Unrecognized license value: " + license);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds extension to filename. Converts to .jpg if system provides .jpeg, adds .jpg if no extension detected
|
||||
* @param title File name
|
||||
|
|
@ -176,6 +201,18 @@ public class Utils {
|
|||
customTabsIntent.launchUrl(context, url);
|
||||
}
|
||||
|
||||
public static void handleGeoCoordinates(Context context, String coords) {
|
||||
try {
|
||||
Uri gmmIntentUri = Uri.parse("google.streetview:cbll=" + coords);
|
||||
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
|
||||
mapIntent.setPackage("com.google.android.apps.maps");
|
||||
context.startActivity(mapIntent);
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
Toast toast = Toast.makeText(context, context.getString(R.string.map_application_missing), LENGTH_SHORT);
|
||||
toast.show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* To take screenshot of the screen and return it in Bitmap format
|
||||
*
|
||||
|
|
@ -190,4 +227,14 @@ public class Utils {
|
|||
return bitmap;
|
||||
}
|
||||
|
||||
public static <K,V> Map<K,V> arraysToMap(K[] kArray, V[] vArray){
|
||||
if(kArray.length!=vArray.length)
|
||||
throw new RuntimeException("arraysToMap array sizes don't match");
|
||||
Map<K,V> map=new LinkedHashMap<>();
|
||||
for (int i=0;i<vArray.length;i++){
|
||||
map.put(kArray[i], vArray[i]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class WelcomeActivity extends BaseActivity {
|
|||
|
||||
moreInformation = this.getString(R.string.welcome_help_button_text);
|
||||
|
||||
if(getIntent() != null) {
|
||||
if (getIntent() != null) {
|
||||
Bundle bundle = getIntent().getExtras();
|
||||
if (bundle != null) {
|
||||
isQuiz = bundle.getBoolean("isQuiz");
|
||||
|
|
@ -54,7 +54,7 @@ public class WelcomeActivity extends BaseActivity {
|
|||
*/
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if(isQuiz){
|
||||
if (isQuiz){
|
||||
Intent i = new Intent(WelcomeActivity.this, QuizActivity.class);
|
||||
startActivity(i);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,15 +60,15 @@ public class WelcomePagerAdapter extends PagerAdapter {
|
|||
this.container=container;
|
||||
LayoutInflater inflater = LayoutInflater.from(container.getContext());
|
||||
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
|
||||
if( BuildConfig.FLAVOR == "beta"){
|
||||
TextView textView = (TextView) layout.findViewById(R.id.welcomeYesButton);
|
||||
if( textView.getVisibility() != View.VISIBLE){
|
||||
if (BuildConfig.FLAVOR == "beta") {
|
||||
TextView textView = layout.findViewById(R.id.welcomeYesButton);
|
||||
if (textView.getVisibility() != View.VISIBLE) {
|
||||
textView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
ViewHolder holder = new ViewHolder(layout);
|
||||
layout.setTag(holder);
|
||||
|
||||
if(position == PAGE_FINAL){
|
||||
if (position == PAGE_FINAL){
|
||||
TextView moreInfo = layout.findViewById(R.id.welcomeInfo);
|
||||
moreInfo.setText(Html.fromHtml(WelcomeActivity.moreInformation));
|
||||
ViewHolder holder1 = new ViewHolder(layout);
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ public class AchievementsActivity extends NavigationBaseActivity {
|
|||
* which then calls parseJson when results are fetched
|
||||
*/
|
||||
private void setAchievements() {
|
||||
if(checkAccount()) {
|
||||
if (checkAccount()) {
|
||||
compositeDisposable.add(mediaWikiApi
|
||||
.getAchievements(Objects.requireNonNull(sessionManager.getCurrentAccount()).name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -218,7 +218,7 @@ public class AchievementsActivity extends NavigationBaseActivity {
|
|||
* used to the count of images uploaded by user
|
||||
*/
|
||||
private void setUploadCount(Achievements achievements) {
|
||||
if(checkAccount()) {
|
||||
if (checkAccount()) {
|
||||
compositeDisposable.add(mediaWikiApi
|
||||
.getUploadCount(Objects.requireNonNull(sessionManager.getCurrentAccount()).name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -338,9 +338,9 @@ public class AchievementsActivity extends NavigationBaseActivity {
|
|||
AlertDialog.Builder alertadd = new AlertDialog.Builder(AchievementsActivity.this);
|
||||
LayoutInflater factory = LayoutInflater.from(AchievementsActivity.this);
|
||||
final View view = factory.inflate(R.layout.image_alert_layout, null);
|
||||
ImageView screenShotImage = (ImageView) view.findViewById(R.id.alert_image);
|
||||
ImageView screenShotImage = view.findViewById(R.id.alert_image);
|
||||
screenShotImage.setImageBitmap(screenshot);
|
||||
TextView shareMessage = (TextView) view.findViewById(R.id.alert_text);
|
||||
TextView shareMessage = view.findViewById(R.id.alert_text);
|
||||
shareMessage.setText(R.string.achievements_share_message);
|
||||
alertadd.setView(view);
|
||||
alertadd.setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> shareScreen(screenshot));
|
||||
|
|
@ -387,7 +387,7 @@ public class AchievementsActivity extends NavigationBaseActivity {
|
|||
*/
|
||||
private boolean checkAccount(){
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if(currentAccount == null) {
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(this, getResources().getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(this);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ public class BitmapUtils {
|
|||
*/
|
||||
public static BitmapDrawable writeOnDrawable(Bitmap bm, String text, Context context){
|
||||
Bitmap.Config config = bm.getConfig();
|
||||
if(config == null){
|
||||
if (config == null){
|
||||
config = Bitmap.Config.ARGB_8888;
|
||||
}
|
||||
Bitmap bitmap = Bitmap.createBitmap(bm.getWidth(),bm.getHeight(),config);
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import static fr.free.nrw.commons.auth.AccountUtil.AUTH_COOKIE;
|
|||
|
||||
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
|
||||
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject
|
||||
protected SessionManager sessionManager;
|
||||
@Inject
|
||||
MediaWikiApi mediaWikiApi;
|
||||
private String authCookie;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ import fr.free.nrw.commons.R;
|
|||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.WelcomeActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
|
|
@ -139,7 +139,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
|
||||
.show());
|
||||
|
||||
if(BuildConfig.FLAVOR.equals("beta")){
|
||||
if (BuildConfig.FLAVOR.equals("beta")){
|
||||
loginCredentials.setText(getString(R.string.login_credential));
|
||||
} else {
|
||||
loginCredentials.setVisibility(View.GONE);
|
||||
|
|
@ -381,10 +381,10 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
super.onRestoreInstanceState(savedInstanceState);
|
||||
loginCurrentlyInProgress = savedInstanceState.getBoolean(LOGING_IN, false);
|
||||
errorMessageShown = savedInstanceState.getBoolean(ERROR_MESSAGE_SHOWN, false);
|
||||
if(loginCurrentlyInProgress){
|
||||
if (loginCurrentlyInProgress){
|
||||
performLogin();
|
||||
}
|
||||
if(errorMessageShown){
|
||||
if (errorMessageShown){
|
||||
resultantError = savedInstanceState.getString(RESULTANT_ERROR);
|
||||
handleOtherResults(resultantError);
|
||||
}
|
||||
|
|
@ -399,7 +399,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
|
||||
public void showMessageAndCancelDialog(@StringRes int resId) {
|
||||
showMessage(resId, R.color.secondaryDarkColor);
|
||||
if(progressDialog != null){
|
||||
if (progressDialog != null){
|
||||
progressDialog.cancel();
|
||||
}
|
||||
}
|
||||
|
|
@ -415,7 +415,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
|
||||
public void startMainActivity() {
|
||||
NavigationBaseActivity.startActivityWithFlags(this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavigationBaseActivity.startActivityWithFlags(this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
|
|||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void initList() {
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -179,7 +179,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
|
|||
* @param collection List of new Media to be displayed
|
||||
*/
|
||||
private void handleSuccess(List<Media> collection) {
|
||||
if(collection == null) {
|
||||
if (collection == null) {
|
||||
initErrorView();
|
||||
return;
|
||||
}
|
||||
|
|
@ -188,7 +188,7 @@ public class BookmarkPicturesFragment extends DaggerFragment {
|
|||
return;
|
||||
}
|
||||
|
||||
if(gridAdapter == null) {
|
||||
if (gridAdapter == null) {
|
||||
setAdapter(collection);
|
||||
} else {
|
||||
if (gridAdapter.containsAll(collection)) {
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
import com.pedrogomez.renderers.RendererBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class CategoriesAdapterFactory {
|
||||
private final CategoriesRenderer.CategoryClickedListener listener;
|
||||
public class CategoriesAdapterFactory {
|
||||
private final CategoryClickedListener listener;
|
||||
|
||||
CategoriesAdapterFactory(CategoriesRenderer.CategoryClickedListener listener) {
|
||||
public CategoriesAdapterFactory(CategoryClickedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public RVRendererAdapter<CategoryItem> create(List<CategoryItem> placeList) {
|
||||
public CategoryRendererAdapter create(List<CategoryItem> placeList) {
|
||||
RendererBuilder<CategoryItem> builder = new RendererBuilder<CategoryItem>()
|
||||
.bind(CategoryItem.class, new CategoriesRenderer(listener));
|
||||
ListAdapteeCollection<CategoryItem> collection = new ListAdapteeCollection<>(
|
||||
placeList != null ? placeList : Collections.<CategoryItem>emptyList());
|
||||
return new RVRendererAdapter<>(builder, collection);
|
||||
return new CategoryRendererAdapter(builder, collection);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||
import io.reactivex.Observable;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class CategoriesModel implements CategoryClickedListener {
|
||||
private static final int SEARCH_CATS_LIMIT = 25;
|
||||
|
||||
private final MediaWikiApi mwApi;
|
||||
private final CategoryDao categoryDao;
|
||||
private final SharedPreferences prefs;
|
||||
private final SharedPreferences directPrefs;
|
||||
|
||||
private HashMap<String, ArrayList<String>> categoriesCache;
|
||||
private List<CategoryItem> selectedCategories;
|
||||
|
||||
@Inject GpsCategoryModel gpsCategoryModel;
|
||||
@Inject
|
||||
public CategoriesModel(MediaWikiApi mwApi,
|
||||
CategoryDao categoryDao,
|
||||
@Named("default_preferences") SharedPreferences prefs,
|
||||
@Named("direct_nearby_upload_prefs") SharedPreferences directPrefs) {
|
||||
this.mwApi = mwApi;
|
||||
this.categoryDao = categoryDao;
|
||||
this.prefs = prefs;
|
||||
this.directPrefs = directPrefs;
|
||||
this.categoriesCache = new HashMap<>();
|
||||
this.selectedCategories = new ArrayList<>();
|
||||
}
|
||||
|
||||
//region Misc. utility methods
|
||||
public Comparator<CategoryItem> sortBySimilarity(final String filter) {
|
||||
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
|
||||
return (firstItem, secondItem) -> stringSimilarityComparator
|
||||
.compare(firstItem.getName(), secondItem.getName());
|
||||
}
|
||||
|
||||
public boolean containsYear(String item) {
|
||||
//Check for current and previous year to exclude these categories from removal
|
||||
Calendar now = Calendar.getInstance();
|
||||
int year = now.get(Calendar.YEAR);
|
||||
String yearInString = String.valueOf(year);
|
||||
|
||||
int prevYear = year - 1;
|
||||
String prevYearInString = String.valueOf(prevYear);
|
||||
Timber.d("Previous year: %s", prevYearInString);
|
||||
|
||||
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||
//And that item does not equal the current year or previous year
|
||||
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
|
||||
//Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
|
||||
return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString))
|
||||
|| item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
|
||||
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
|
||||
}
|
||||
|
||||
public void updateCategoryCount(CategoryItem item) {
|
||||
Category category = categoryDao.find(item.getName());
|
||||
|
||||
// Newly used category...
|
||||
if (category == null) {
|
||||
category = new Category(null, item.getName(), new Date(), 0);
|
||||
}
|
||||
|
||||
category.incTimesUsed();
|
||||
categoryDao.save(category);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Category Caching
|
||||
public void cacheAll(HashMap<String, ArrayList<String>> categories) {
|
||||
categoriesCache.putAll(categories);
|
||||
}
|
||||
|
||||
public HashMap<String, ArrayList<String>> getCategoriesCache() {
|
||||
return categoriesCache;
|
||||
}
|
||||
|
||||
boolean cacheContainsKey(String term) {
|
||||
return categoriesCache.containsKey(term);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Category searching
|
||||
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
||||
//If user hasn't typed anything in yet, get GPS and recent items
|
||||
if (TextUtils.isEmpty(term)) {
|
||||
return gpsCategories()
|
||||
.concatWith(titleCategories(imageTitleList))
|
||||
.concatWith(recentCategories());
|
||||
}
|
||||
|
||||
//if user types in something that is in cache, return cached category
|
||||
if (cacheContainsKey(term)) {
|
||||
return Observable.fromIterable(getCachedCategories(term))
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
//otherwise, search API for matching categories
|
||||
return mwApi
|
||||
.allCategories(term, SEARCH_CATS_LIMIT)
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
public Observable<CategoryItem> searchCategories(String term, List<String> imageTitleList) {
|
||||
//If user hasn't typed anything in yet, get GPS and recent items
|
||||
if (TextUtils.isEmpty(term)) {
|
||||
return gpsCategories()
|
||||
.concatWith(titleCategories(imageTitleList))
|
||||
.concatWith(recentCategories());
|
||||
}
|
||||
|
||||
return mwApi
|
||||
.searchCategories(term, SEARCH_CATS_LIMIT)
|
||||
.map(s -> new CategoryItem(s, false));
|
||||
}
|
||||
|
||||
private ArrayList<String> getCachedCategories(String term) {
|
||||
return categoriesCache.get(term);
|
||||
}
|
||||
|
||||
public Observable<CategoryItem> defaultCategories(List<String> titleList) {
|
||||
Observable<CategoryItem> directCat = directCategories();
|
||||
if (hasDirectCategories()) {
|
||||
Timber.d("Image has direct Cat");
|
||||
return directCat
|
||||
.concatWith(gpsCategories())
|
||||
.concatWith(titleCategories(titleList))
|
||||
.concatWith(recentCategories());
|
||||
} else {
|
||||
Timber.d("Image has no direct Cat");
|
||||
return gpsCategories()
|
||||
.concatWith(titleCategories(titleList))
|
||||
.concatWith(recentCategories());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDirectCategories() {
|
||||
return !directPrefs.getString("Category", "").equals("");
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> directCategories() {
|
||||
String directCategory = directPrefs.getString("Category", "");
|
||||
List<String> categoryList = new ArrayList<>();
|
||||
Timber.d("Direct category found: " + directCategory);
|
||||
|
||||
if (!directCategory.equals("")) {
|
||||
categoryList.add(directCategory);
|
||||
Timber.d("DirectCat does not equal emptyString. Direct Cat list has " + categoryList);
|
||||
}
|
||||
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
Observable<CategoryItem> gpsCategories() {
|
||||
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> titleCategories(List<String> titleList) {
|
||||
return Observable.fromIterable(titleList)
|
||||
.concatMap(this::getTitleCategories);
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> getTitleCategories(String title) {
|
||||
return mwApi.searchTitles(title, SEARCH_CATS_LIMIT)
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> recentCategories() {
|
||||
return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
|
||||
.map(s -> new CategoryItem(s, false));
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Category Selection
|
||||
@Override
|
||||
public void categoryClicked(CategoryItem item) {
|
||||
if (item.isSelected()) {
|
||||
selectCategory(item);
|
||||
updateCategoryCount(item);
|
||||
} else {
|
||||
unselectCategory(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void selectCategory(CategoryItem item) {
|
||||
selectedCategories.add(item);
|
||||
}
|
||||
|
||||
public void unselectCategory(CategoryItem item) {
|
||||
selectedCategories.remove(item);
|
||||
}
|
||||
|
||||
public int selectedCategoriesCount() {
|
||||
return selectedCategories.size();
|
||||
}
|
||||
|
||||
public List<CategoryItem> getSelectedCategories() {
|
||||
return selectedCategories;
|
||||
}
|
||||
|
||||
public List<String> getCategoryStringList() {
|
||||
List<String> output = new ArrayList<>();
|
||||
for (CategoryItem item : selectedCategories) {
|
||||
output.add(item.getName());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
//endregion
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -11,7 +12,7 @@ import butterknife.BindView;
|
|||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
|
||||
class CategoriesRenderer extends Renderer<CategoryItem> {
|
||||
public class CategoriesRenderer extends Renderer<CategoryItem> {
|
||||
@BindView(R.id.tvName) CheckedTextView checkedView;
|
||||
private final CategoryClickedListener listener;
|
||||
|
||||
|
|
@ -44,11 +45,8 @@ class CategoriesRenderer extends Renderer<CategoryItem> {
|
|||
@Override
|
||||
public void render() {
|
||||
CategoryItem item = getContent();
|
||||
Log.e("Commons", "Rendering: "+item);
|
||||
checkedView.setChecked(item.isSelected());
|
||||
checkedView.setText(item.getName());
|
||||
}
|
||||
|
||||
interface CategoryClickedListener {
|
||||
void categoryClicked(CategoryItem item);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,421 +0,0 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel;
|
||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
||||
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.KeyEvent.ACTION_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
|
||||
/**
|
||||
* Displays the category suggestion and selection screen. Category search is initiated here.
|
||||
*/
|
||||
public class CategorizationFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
public static final int SEARCH_CATS_LIMIT = 25;
|
||||
|
||||
@BindView(R.id.categoriesListBox)
|
||||
RecyclerView categoriesList;
|
||||
@BindView(R.id.categoriesSearchBox)
|
||||
EditText categoriesFilter;
|
||||
@BindView(R.id.categoriesSearchInProgress)
|
||||
ProgressBar categoriesSearchInProgress;
|
||||
@BindView(R.id.categoriesNotFound)
|
||||
TextView categoriesNotFoundView;
|
||||
@BindView(R.id.categoriesExplanation)
|
||||
TextView categoriesSkip;
|
||||
|
||||
@Inject MediaWikiApi mwApi;
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject @Named("prefs") SharedPreferences prefsPrefs;
|
||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||
@Inject CategoryDao categoryDao;
|
||||
@Inject GpsCategoryModel gpsCategoryModel;
|
||||
|
||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
||||
private HashMap<String, ArrayList<String>> categoriesCache;
|
||||
private List<CategoryItem> selectedCategories = new ArrayList<>();
|
||||
private TitleTextWatcher textWatcher = new TitleTextWatcher();
|
||||
private boolean hasDirectCategories = false;
|
||||
|
||||
private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(item -> {
|
||||
if (item.isSelected()) {
|
||||
selectedCategories.add(item);
|
||||
updateCategoryCount(item);
|
||||
} else {
|
||||
selectedCategories.remove(item);
|
||||
}
|
||||
});
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_categorization, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
|
||||
categoriesList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
||||
ArrayList<CategoryItem> items = new ArrayList<>();
|
||||
categoriesCache = new HashMap<>();
|
||||
if (savedInstanceState != null) {
|
||||
items.addAll(savedInstanceState.getParcelableArrayList("currentCategories"));
|
||||
//noinspection unchecked
|
||||
categoriesCache.putAll((HashMap<String, ArrayList<String>>) savedInstanceState
|
||||
.getSerializable("categoriesCache"));
|
||||
}
|
||||
|
||||
categoriesAdapter = adapterFactory.create(items);
|
||||
categoriesList.setAdapter(categoriesAdapter);
|
||||
|
||||
|
||||
categoriesFilter.addTextChangedListener(textWatcher);
|
||||
|
||||
categoriesFilter.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
RxTextView.textChanges(categoriesFilter)
|
||||
.takeUntil(RxView.detaches(categoriesFilter))
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(filter -> updateCategoryList(filter.toString()));
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
categoriesFilter.removeTextChangedListener(textWatcher);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
menu.clear();
|
||||
inflater.inflate(R.menu.fragment_categorization, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
View rootView = getView();
|
||||
if (rootView != null) {
|
||||
rootView.setFocusableInTouchMode(true);
|
||||
rootView.requestFocus();
|
||||
rootView.setOnKeyListener((v, keyCode, event) -> {
|
||||
if (event.getAction() == ACTION_UP && keyCode == KEYCODE_BACK) {
|
||||
showBackButtonDialog();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
int itemCount = categoriesAdapter.getItemCount();
|
||||
ArrayList<CategoryItem> items = new ArrayList<>(itemCount);
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
items.add(categoriesAdapter.getItem(i));
|
||||
}
|
||||
outState.putParcelableArrayList("currentCategories", items);
|
||||
outState.putSerializable("categoriesCache", categoriesCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_save_categories:
|
||||
if (selectedCategories.size() > 0) {
|
||||
//Some categories selected, proceed to submission
|
||||
onCategoriesSaveHandler.onCategoriesSave(getStringList(selectedCategories));
|
||||
} else {
|
||||
//No categories selected, prompt the user to select some
|
||||
showConfirmationDialog();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(menuItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity();
|
||||
getActivity().setTitle(R.string.categories_activity_title);
|
||||
}
|
||||
|
||||
private void updateCategoryList(String filter) {
|
||||
Observable.fromIterable(selectedCategories)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSubscribe(disposable -> {
|
||||
categoriesSearchInProgress.setVisibility(View.VISIBLE);
|
||||
categoriesNotFoundView.setVisibility(View.GONE);
|
||||
categoriesSkip.setVisibility(View.GONE);
|
||||
categoriesAdapter.clear();
|
||||
})
|
||||
.observeOn(Schedulers.io())
|
||||
.concatWith(
|
||||
searchAll(filter)
|
||||
.mergeWith(searchCategories(filter))
|
||||
.concatWith(TextUtils.isEmpty(filter)
|
||||
? defaultCategories() : Observable.empty())
|
||||
)
|
||||
.filter(categoryItem -> !containsYear(categoryItem.getName()))
|
||||
.distinct()
|
||||
.sorted(sortBySimilarity(filter))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
s -> categoriesAdapter.add(s),
|
||||
Timber::e,
|
||||
() -> {
|
||||
categoriesAdapter.notifyDataSetChanged();
|
||||
categoriesSearchInProgress.setVisibility(View.GONE);
|
||||
|
||||
if (categoriesAdapter.getItemCount() == selectedCategories.size()) {
|
||||
// There are no suggestions
|
||||
if (TextUtils.isEmpty(filter)) {
|
||||
// Allow to send image with no categories
|
||||
categoriesSkip.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
// Inform the user that the searched term matches no category
|
||||
categoriesNotFoundView.setText(getString(R.string.categories_not_found, filter));
|
||||
categoriesNotFoundView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private Comparator<CategoryItem> sortBySimilarity(final String filter) {
|
||||
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
|
||||
return (firstItem, secondItem) -> stringSimilarityComparator
|
||||
.compare(firstItem.getName(), secondItem.getName());
|
||||
}
|
||||
|
||||
private List<String> getStringList(List<CategoryItem> input) {
|
||||
List<String> output = new ArrayList<>();
|
||||
for (CategoryItem item : input) {
|
||||
output.add(item.getName());
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> defaultCategories() {
|
||||
Observable<CategoryItem> directCat = directCategories();
|
||||
if (hasDirectCategories) {
|
||||
Timber.d("Image has direct Cat");
|
||||
return directCat
|
||||
.concatWith(gpsCategories())
|
||||
.concatWith(titleCategories())
|
||||
.concatWith(recentCategories());
|
||||
}
|
||||
else {
|
||||
Timber.d("Image has no direct Cat");
|
||||
return gpsCategories()
|
||||
.concatWith(titleCategories())
|
||||
.concatWith(recentCategories());
|
||||
}
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> directCategories() {
|
||||
String directCategory = directPrefs.getString("Category", "");
|
||||
// Strip newlines to prevent blank categories, and to tidy existing categories
|
||||
directCategory = directCategory.replace("\n", "");
|
||||
|
||||
List<String> categoryList = new ArrayList<>();
|
||||
Timber.d("Direct category found: " + "'" + directCategory + "'");
|
||||
|
||||
if (!directCategory.equals("")) {
|
||||
hasDirectCategories = true;
|
||||
categoryList.add(directCategory);
|
||||
Timber.d("DirectCat does not equal emptyString. Direct Cat list has " + categoryList);
|
||||
}
|
||||
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> gpsCategories() {
|
||||
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> titleCategories() {
|
||||
//Retrieve the title that was saved when user tapped submit icon
|
||||
String title = prefs.getString("Title", "");
|
||||
|
||||
return mwApi
|
||||
.searchTitles(title, SEARCH_CATS_LIMIT)
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> recentCategories() {
|
||||
return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
|
||||
.map(s -> new CategoryItem(s, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> searchAll(String term) {
|
||||
//If user hasn't typed anything in yet, get GPS and recent items
|
||||
if (TextUtils.isEmpty(term)) {
|
||||
return Observable.empty();
|
||||
}
|
||||
|
||||
//if user types in something that is in cache, return cached category
|
||||
if (categoriesCache.containsKey(term)) {
|
||||
return Observable.fromIterable(categoriesCache.get(term))
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
//otherwise, search API for matching categories
|
||||
return mwApi
|
||||
.allCategories(term, SEARCH_CATS_LIMIT)
|
||||
.map(name -> new CategoryItem(name, false));
|
||||
}
|
||||
|
||||
private Observable<CategoryItem> searchCategories(String term) {
|
||||
//If user hasn't typed anything in yet, get GPS and recent items
|
||||
if (TextUtils.isEmpty(term)) {
|
||||
return Observable.empty();
|
||||
}
|
||||
|
||||
return mwApi
|
||||
.searchCategories(term, SEARCH_CATS_LIMIT)
|
||||
.map(s -> new CategoryItem(s, false));
|
||||
}
|
||||
|
||||
private boolean containsYear(String item) {
|
||||
//Check for current and previous year to exclude these categories from removal
|
||||
Calendar now = Calendar.getInstance();
|
||||
int year = now.get(Calendar.YEAR);
|
||||
String yearInString = String.valueOf(year);
|
||||
|
||||
int prevYear = year - 1;
|
||||
String prevYearInString = String.valueOf(prevYear);
|
||||
Timber.d("Previous year: %s", prevYearInString);
|
||||
|
||||
//Check if item contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||
//And that item does not equal the current year or previous year
|
||||
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
|
||||
//Check if the year in the form of XX(X)0s is relevant, i.e. in the 2000s or 2010s as stated in Issue #1029
|
||||
return ((item.matches(".*(19|20)\\d{2}.*") && !item.contains(yearInString) && !item.contains(prevYearInString))
|
||||
|| item.matches("(.*)needing(.*)") || item.matches("(.*)taken on(.*)")
|
||||
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
|
||||
}
|
||||
|
||||
private void updateCategoryCount(CategoryItem item) {
|
||||
Category category = categoryDao.find(item.getName());
|
||||
|
||||
// Newly used category...
|
||||
if (category == null) {
|
||||
category = new Category(null, item.getName(), new Date(), 0);
|
||||
}
|
||||
|
||||
category.incTimesUsed();
|
||||
categoryDao.save(category);
|
||||
}
|
||||
|
||||
public int getCurrentSelectedCount() {
|
||||
return selectedCategories.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog asking for confirmation to leave without saving categories.
|
||||
*/
|
||||
public void showBackButtonDialog() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage("Are you sure you want to go back? The image will not "
|
||||
+ "have any categories saved.")
|
||||
.setTitle("Warning")
|
||||
.setPositiveButton(android.R.string.no, (dialog, id) -> {
|
||||
//No need to do anything, user remains on categorization screen
|
||||
})
|
||||
.setNegativeButton(android.R.string.yes, (dialog, id) -> getActivity().finish())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showConfirmationDialog() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage("Images without categories are rarely usable. "
|
||||
+ "Are you sure you want to submit without selecting "
|
||||
+ "categories?")
|
||||
.setTitle("No Categories Selected")
|
||||
.setPositiveButton(android.R.string.no, (dialog, id) -> {
|
||||
//Exit menuItem so user can select their categories
|
||||
})
|
||||
.setNegativeButton(android.R.string.yes, (dialog, id) -> {
|
||||
//Proceed to submission
|
||||
onCategoriesSaveHandler.onCategoriesSave(getStringList(selectedCategories));
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private class TitleTextWatcher implements TextWatcher {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
public interface CategoryClickedListener {
|
||||
void categoryClicked(CategoryItem item);
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
|||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void initList() {
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -208,7 +208,7 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
|||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void fetchMoreImages() {
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -227,13 +227,13 @@ public class CategoryImagesListFragment extends DaggerFragment {
|
|||
* @param collection List of new Media to be displayed
|
||||
*/
|
||||
private void handleSuccess(List<Media> collection) {
|
||||
if(collection == null || collection.isEmpty()) {
|
||||
if (collection == null || collection.isEmpty()) {
|
||||
initErrorView();
|
||||
hasMoreImages = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(gridAdapter == null) {
|
||||
if (gridAdapter == null) {
|
||||
setAdapter(collection);
|
||||
} else {
|
||||
if (gridAdapter.containsAll(collection)) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package fr.free.nrw.commons.category;
|
|||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
class CategoryItem implements Parcelable {
|
||||
public class CategoryItem implements Parcelable {
|
||||
private final String name;
|
||||
private boolean selected;
|
||||
|
||||
|
|
@ -71,4 +71,9 @@ class CategoryItem implements Parcelable {
|
|||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CategoryItem: '" + name + '\'';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import com.pedrogomez.renderers.AdapteeCollection;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
import com.pedrogomez.renderers.RendererBuilder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class CategoryRendererAdapter extends RVRendererAdapter<CategoryItem> {
|
||||
CategoryRendererAdapter(RendererBuilder<CategoryItem> rendererBuilder, AdapteeCollection<CategoryItem> collection) {
|
||||
super(rendererBuilder, collection);
|
||||
}
|
||||
|
||||
protected ArrayList<CategoryItem> allItems() {
|
||||
int itemCount = getItemCount();
|
||||
ArrayList<CategoryItem> items = new ArrayList<>(itemCount);
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
items.add(getItem(i));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
|
|||
categoryName = getArguments().getString("categoryName");
|
||||
isParentCategory = getArguments().getBoolean("isParentCategory");
|
||||
initSubCategoryList();
|
||||
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
else{
|
||||
|
|
@ -91,7 +91,7 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
|
|||
*/
|
||||
public void initSubCategoryList() {
|
||||
categoriesNotFoundView.setVisibility(GONE);
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ public class SubCategoryListFragment extends CommonsDaggerSupportFragment {
|
|||
* @param subCategoryList
|
||||
*/
|
||||
private void handleSuccess(List<String> subCategoryList) {
|
||||
if(subCategoryList == null || subCategoryList.isEmpty()) {
|
||||
if (subCategoryList == null || subCategoryList.isEmpty()) {
|
||||
initEmptyView();
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@ package fr.free.nrw.commons.contributions;
|
|||
|
||||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.StringDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
|
@ -13,6 +16,8 @@ import fr.free.nrw.commons.CommonsApplication;
|
|||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
public class Contribution extends Media {
|
||||
|
||||
public static Creator<Contribution> CREATOR = new Creator<Contribution>() {
|
||||
|
|
@ -33,6 +38,10 @@ public class Contribution extends Media {
|
|||
public static final int STATE_QUEUED = 2;
|
||||
public static final int STATE_IN_PROGRESS = 3;
|
||||
|
||||
@Retention(SOURCE)
|
||||
@StringDef({SOURCE_CAMERA, SOURCE_GALLERY, SOURCE_EXTERNAL})
|
||||
public @interface FileSource {}
|
||||
|
||||
public static final String SOURCE_CAMERA = "camera";
|
||||
public static final String SOURCE_GALLERY = "gallery";
|
||||
public static final String SOURCE_EXTERNAL = "external";
|
||||
|
|
@ -40,7 +49,6 @@ public class Contribution extends Media {
|
|||
private Uri contentUri;
|
||||
private String source;
|
||||
private String editSummary;
|
||||
private Date timestamp;
|
||||
private int state;
|
||||
private long transferred;
|
||||
private String decimalCoords;
|
||||
|
|
@ -48,14 +56,13 @@ public class Contribution extends Media {
|
|||
private String wikiDataEntityId;
|
||||
private Uri contentProviderUri;
|
||||
|
||||
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date timestamp,
|
||||
public Contribution(Uri contentUri, String filename, Uri localUri, String imageUrl, Date dateCreated,
|
||||
int state, long dataLength, Date dateUploaded, long transferred,
|
||||
String source, String description, String creator, boolean isMultiple,
|
||||
int width, int height, String license) {
|
||||
super(localUri, imageUrl, filename, description, dataLength, timestamp, dateUploaded, creator);
|
||||
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
||||
this.contentUri = contentUri;
|
||||
this.state = state;
|
||||
this.timestamp = timestamp;
|
||||
this.transferred = transferred;
|
||||
this.source = source;
|
||||
this.isMultiple = isMultiple;
|
||||
|
|
@ -69,14 +76,12 @@ public class Contribution extends Media {
|
|||
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
||||
this.decimalCoords = decimalCoords;
|
||||
this.editSummary = editSummary;
|
||||
timestamp = new Date(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Contribution(Parcel in) {
|
||||
super(in);
|
||||
contentUri = in.readParcelable(Uri.class.getClassLoader());
|
||||
source = in.readString();
|
||||
timestamp = (Date) in.readSerializable();
|
||||
state = in.readInt();
|
||||
transferred = in.readLong();
|
||||
isMultiple = in.readInt() == 1;
|
||||
|
|
@ -87,12 +92,13 @@ public class Contribution extends Media {
|
|||
super.writeToParcel(parcel, flags);
|
||||
parcel.writeParcelable(contentUri, flags);
|
||||
parcel.writeString(source);
|
||||
parcel.writeSerializable(timestamp);
|
||||
parcel.writeInt(state);
|
||||
parcel.writeLong(transferred);
|
||||
parcel.writeInt(isMultiple ? 1 : 0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean getMultiple() {
|
||||
return isMultiple;
|
||||
}
|
||||
|
|
@ -121,14 +127,6 @@ public class Contribution extends Media {
|
|||
this.contentUri = contentUri;
|
||||
}
|
||||
|
||||
public Date getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(Date timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
|
@ -141,10 +139,6 @@ public class Contribution extends Media {
|
|||
this.dateUploaded = date;
|
||||
}
|
||||
|
||||
public String getTrackingTemplates() {
|
||||
return "{{subst:unc}}"; // Remove when we have categorization
|
||||
}
|
||||
|
||||
public String getPageContents() {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
||||
|
|
@ -169,8 +163,15 @@ public class Contribution extends Media {
|
|||
|
||||
buffer.append("== {{int:license-header}} ==\n")
|
||||
.append(licenseTemplateFor(getLicense())).append("\n\n")
|
||||
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
|
||||
.append(getTrackingTemplates());
|
||||
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n");
|
||||
if(categories!=null&&categories.size()!=0) {
|
||||
for (int i = 0; i < categories.size(); i++) {
|
||||
String category = categories.get(i);
|
||||
buffer.append("\n[[Category:").append(category).append("]]");
|
||||
}
|
||||
}
|
||||
else
|
||||
buffer.append("{{subst:unc}}");
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
|
|
@ -184,7 +185,7 @@ public class Contribution extends Media {
|
|||
}
|
||||
|
||||
public Contribution() {
|
||||
timestamp = new Date(System.currentTimeMillis());
|
||||
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
|
|
@ -232,7 +233,7 @@ public class Contribution extends Media {
|
|||
/**
|
||||
* When the corresponding wikidata entity is known as in case of nearby uploads, it can be set
|
||||
* using the setter method
|
||||
* @param wikiDataEntityId
|
||||
* @param wikiDataEntityId wikiDataEntityId
|
||||
*/
|
||||
public void setWikiDataEntityId(String wikiDataEntityId) {
|
||||
this.wikiDataEntityId = wikiDataEntityId;
|
||||
|
|
|
|||
|
|
@ -5,22 +5,26 @@ import android.content.Intent;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.upload.ShareActivity;
|
||||
import fr.free.nrw.commons.upload.UploadActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.content.Intent.ACTION_GET_CONTENT;
|
||||
import static android.content.Intent.ACTION_SEND;
|
||||
import static android.content.Intent.ACTION_SEND_MULTIPLE;
|
||||
import static android.content.Intent.EXTRA_STREAM;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_CAMERA;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_GALLERY;
|
||||
|
|
@ -31,6 +35,7 @@ public class ContributionController {
|
|||
|
||||
public static final int SELECT_FROM_GALLERY = 1;
|
||||
public static final int SELECT_FROM_CAMERA = 2;
|
||||
public static final int PICK_IMAGE_MULTIPLE = 3;
|
||||
|
||||
private Fragment fragment;
|
||||
|
||||
|
|
@ -79,6 +84,14 @@ public class ContributionController {
|
|||
}
|
||||
|
||||
public void startGalleryPick() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
startMultipleGalleryPick();
|
||||
} else {
|
||||
startSingleGalleryPick();
|
||||
}
|
||||
}
|
||||
|
||||
public void startSingleGalleryPick() {
|
||||
//FIXME: Starts gallery (opens Google Photos)
|
||||
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
|
||||
pickImageIntent.setType("image/*");
|
||||
|
|
@ -87,15 +100,41 @@ public class ContributionController {
|
|||
Timber.d("Fragment is not added, startActivityForResult cannot be called");
|
||||
return;
|
||||
}
|
||||
Timber.d("startGalleryPick() called with pickImageIntent");
|
||||
Timber.d("startSingleGalleryPick() called with pickImageIntent");
|
||||
|
||||
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
public void startMultipleGalleryPick() {
|
||||
Intent pickImageIntent = new Intent(ACTION_GET_CONTENT);
|
||||
pickImageIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
|
||||
pickImageIntent.setType("image/*");
|
||||
if (!fragment.isAdded()) {
|
||||
Timber.d("Fragment is not added, startActivityForResult cannot be called");
|
||||
return;
|
||||
}
|
||||
Timber.d("startMultipleGalleryPick() called with pickImageIntent");
|
||||
|
||||
fragment.startActivityForResult(pickImageIntent, PICK_IMAGE_MULTIPLE);
|
||||
}
|
||||
|
||||
public void handleImagesPicked(int requestCode, @Nullable ArrayList<Uri> uri) {
|
||||
FragmentActivity activity = fragment.getActivity();
|
||||
Intent shareIntent = new Intent(activity, UploadActivity.class);
|
||||
shareIntent.setAction(ACTION_SEND_MULTIPLE);
|
||||
shareIntent.putExtra(EXTRA_SOURCE, SOURCE_GALLERY);
|
||||
shareIntent.putExtra(EXTRA_STREAM, uri);
|
||||
shareIntent.setType("image/jpeg");
|
||||
if (activity != null) {
|
||||
activity.startActivity(shareIntent);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleImagePicked(int requestCode, @Nullable Uri uri, boolean isDirectUpload, String wikiDataEntityId) {
|
||||
FragmentActivity activity = fragment.getActivity();
|
||||
Timber.d("handleImagePicked() called with onActivityResult(). Boolean isDirectUpload: " + isDirectUpload + "String wikiDataEntityId: " + wikiDataEntityId);
|
||||
Intent shareIntent = new Intent(activity, ShareActivity.class);
|
||||
Intent shareIntent = new Intent(activity, UploadActivity.class);
|
||||
shareIntent.setAction(ACTION_SEND);
|
||||
switch (requestCode) {
|
||||
case SELECT_FROM_GALLERY:
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ public class ContributionDao {
|
|||
cv.put(Table.COLUMN_UPLOADED, contribution.getDateUploaded().getTime());
|
||||
}
|
||||
cv.put(Table.COLUMN_LENGTH, contribution.getDataLength());
|
||||
cv.put(Table.COLUMN_TIMESTAMP, contribution.getTimestamp().getTime());
|
||||
//This was always meant to store the date created..If somehow date created is not fetched while actually saving the contribution, lets save today's date
|
||||
cv.put(Table.COLUMN_TIMESTAMP, contribution.getDateCreated()==null?System.currentTimeMillis():contribution.getDateCreated().getTime());
|
||||
cv.put(Table.COLUMN_STATE, contribution.getState());
|
||||
cv.put(Table.COLUMN_TRANSFERRED, contribution.getTransferred());
|
||||
cv.put(Table.COLUMN_SOURCE, contribution.getSource());
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ class ContributionViewHolder {
|
|||
final ProgressBar progressView;
|
||||
|
||||
ContributionViewHolder(View parent) {
|
||||
imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage);
|
||||
titleView = (TextView)parent.findViewById(R.id.contributionTitle);
|
||||
stateView = (TextView)parent.findViewById(R.id.contributionState);
|
||||
seqNumView = (TextView)parent.findViewById(R.id.contributionSequenceNumber);
|
||||
progressView = (ProgressBar)parent.findViewById(R.id.contributionProgress);
|
||||
imageView = parent.findViewById(R.id.contributionImage);
|
||||
titleView = parent.findViewById(R.id.contributionTitle);
|
||||
stateView = parent.findViewById(R.id.contributionState);
|
||||
seqNumView = parent.findViewById(R.id.contributionSequenceNumber);
|
||||
progressView = parent.findViewById(R.id.contributionProgress);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,382 +0,0 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.quiz.QuizChecker;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ContributionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.content.ContentResolver.requestSync;
|
||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
|
||||
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
|
||||
|
||||
public class ContributionsActivity
|
||||
extends AuthenticatedActivity
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>,
|
||||
AdapterView.OnItemClickListener,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
ContributionsListFragment.SourceRefresher {
|
||||
|
||||
@Inject MediaWikiApi mediaWikiApi;
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject ContributionDao contributionDao;
|
||||
|
||||
private Cursor allContributions;
|
||||
private ContributionsListFragment contributionsList;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private UploadService uploadService;
|
||||
private boolean isUploadServiceConnected;
|
||||
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
|
||||
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
|
||||
.getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
// this should never happen
|
||||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
compositeDisposable.clear();
|
||||
getSupportFragmentManager().removeOnBackStackChangedListener(this);
|
||||
super.onDestroy();
|
||||
if (isUploadServiceConnected) {
|
||||
unbindService(uploadServiceConnection);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
|
||||
prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
|
||||
if (isSettingsChanged) {
|
||||
refreshSource();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
// Do a sync everytime we get here!
|
||||
requestSync(sessionManager.getCurrentAccount(), BuildConfig.CONTRIBUTION_AUTHORITY, new Bundle());
|
||||
Intent uploadServiceIntent = new Intent(this, UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
startService(uploadServiceIntent);
|
||||
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
|
||||
allContributions = contributionDao.loadAllContributions();
|
||||
|
||||
getSupportLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_contributions);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
// Activity can call methods in the fragment by acquiring a
|
||||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
contributionsList = (ContributionsListFragment)supportFragmentManager
|
||||
.findFragmentById(R.id.contributionsListFragment);
|
||||
|
||||
supportFragmentManager.addOnBackStackChangedListener(this);
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetails = (MediaDetailPagerFragment)supportFragmentManager
|
||||
.findFragmentById(R.id.contributionsFragmentContainer);
|
||||
|
||||
getSupportLoaderManager().initLoader(0, null, this);
|
||||
}
|
||||
|
||||
requestAuthToken();
|
||||
initDrawer();
|
||||
setTitle(getString(R.string.title_activity_contributions));
|
||||
|
||||
|
||||
if(checkAccount()) {
|
||||
new QuizChecker(this,
|
||||
sessionManager.getCurrentAccount().name,
|
||||
mediaWikiApi);
|
||||
}
|
||||
if(!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
||||
setUploadCount();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
boolean mediaDetailsVisible = mediaDetails != null && mediaDetails.isVisible();
|
||||
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace whatever is in the current contributionsFragmentContainer view with
|
||||
* mediaDetailPagerFragment, and preserve previous state in back stack.
|
||||
* Called when user selects a contribution.
|
||||
*/
|
||||
private void showDetail(int i) {
|
||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||
mediaDetails = new MediaDetailPagerFragment();
|
||||
FragmentManager supportFragmentManager = getSupportFragmentManager();
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.contributionsFragmentContainer, mediaDetails)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
supportFragmentManager.executePendingTransactions();
|
||||
}
|
||||
mediaDetails.showImage(i);
|
||||
}
|
||||
|
||||
public void retryUpload(int i) {
|
||||
allContributions.moveToPosition(i);
|
||||
Contribution c = contributionDao.fromCursor(allContributions);
|
||||
if (c.getState() == STATE_FAILED) {
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
|
||||
Timber.d("Restarting for %s", c.toString());
|
||||
} else {
|
||||
Timber.d("Skipping re-upload for non-failed %s", c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteUpload(int i) {
|
||||
allContributions.moveToPosition(i);
|
||||
Contribution c = contributionDao.fromCursor(allContributions);
|
||||
if (c.getState() == STATE_FAILED) {
|
||||
Timber.d("Deleting failed contrib %s", c.toString());
|
||||
// If upload fails and then user decides to cancel upload at all, which means contribution
|
||||
// object will be deleted. So we have to delete temp file for that contribution.
|
||||
ContributionUtils.removeTemporaryFile(c.getLocalUri());
|
||||
contributionDao.delete(c);
|
||||
} else {
|
||||
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (mediaDetails.isVisible()) {
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
finish(); // If authentication failed, we just exit
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int position, long item) {
|
||||
showDetail(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
|
||||
return new CursorLoader(this, BASE_URI,
|
||||
ALL_FIELDS, "", null,
|
||||
ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
contributionsList.changeProgressBarVisibility(false);
|
||||
|
||||
if (contributionsList.getAdapter() == null) {
|
||||
contributionsList.setAdapter(new ContributionsListAdapter(getApplicationContext(),
|
||||
cursor, 0, contributionDao));
|
||||
} else {
|
||||
((CursorAdapter) contributionsList.getAdapter()).swapCursor(cursor);
|
||||
}
|
||||
|
||||
if(contributionsList.getAdapter().getCount()>0){
|
||||
contributionsList.changeEmptyScreen(false);
|
||||
}
|
||||
contributionsList.clearSyncMessage();
|
||||
notifyAndMigrateDataSetObservers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
((CursorAdapter) contributionsList.getAdapter()).swapCursor(null);
|
||||
}
|
||||
|
||||
//FIXME: Potential cause of wrong image display bug
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
if (contributionsList.getAdapter() == null) {
|
||||
// not yet ready to return data
|
||||
return null;
|
||||
} else {
|
||||
return contributionDao.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
if (contributionsList.getAdapter() == null) {
|
||||
return 0;
|
||||
}
|
||||
return contributionsList.getAdapter().getCount();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void setUploadCount() {
|
||||
compositeDisposable.add(mediaWikiApi
|
||||
.getUploadCount(sessionManager.getCurrentAccount().name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::displayUploadCount,
|
||||
t -> Timber.e(t, "Fetching upload count failed")
|
||||
));
|
||||
}
|
||||
|
||||
private void displayUploadCount(Integer uploadCount) {
|
||||
if (isFinishing()
|
||||
|| getSupportActionBar() == null
|
||||
|| getResources() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
getSupportActionBar().setSubtitle(getResources()
|
||||
.getQuantityString(R.plurals.contributions_subtitle,
|
||||
uploadCount, uploadCount));
|
||||
}
|
||||
|
||||
public void betaSetUploadCount(int betaUploadCount) {
|
||||
displayUploadCount(betaUploadCount);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void notifyDatasetChanged() {
|
||||
// Do nothing for now
|
||||
}
|
||||
|
||||
private void notifyAndMigrateDataSetObservers() {
|
||||
Adapter adapter = contributionsList.getAdapter();
|
||||
|
||||
// First, move the observers over to the adapter now that we have it.
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
observersWaitingForLoad.clear();
|
||||
|
||||
// Now fire off a first notification...
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
observer.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsList.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.add(observer);
|
||||
} else {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsList.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.remove(observer);
|
||||
} else {
|
||||
adapter.unregisterDataSetObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* to ensure user is logged in
|
||||
* @return
|
||||
*/
|
||||
private boolean checkAccount() {
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(this, getResources().getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(this);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
initBackButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSource() {
|
||||
getSupportLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,625 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.location.LocationUpdateListener;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
import fr.free.nrw.commons.nearby.NearbyNoificationCardView;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.notification.NotificationController;
|
||||
import fr.free.nrw.commons.notification.UnreadNotificationsCheckAsync;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
|
||||
import static fr.free.nrw.commons.contributions.ContributionDao.Table.ALL_FIELDS;
|
||||
import static fr.free.nrw.commons.contributions.ContributionsContentProvider.BASE_URI;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
|
||||
import static fr.free.nrw.commons.settings.Prefs.UPLOADS_SHOWING;
|
||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||
|
||||
public class ContributionsFragment
|
||||
extends CommonsDaggerSupportFragment
|
||||
implements LoaderManager.LoaderCallbacks<Cursor>,
|
||||
AdapterView.OnItemClickListener,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
ContributionsListFragment.SourceRefresher,
|
||||
LocationUpdateListener
|
||||
{
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
@Inject
|
||||
ContributionDao contributionDao;
|
||||
@Inject
|
||||
MediaWikiApi mediaWikiApi;
|
||||
@Inject
|
||||
NotificationController notificationController;
|
||||
@Inject
|
||||
NearbyController nearbyController;
|
||||
|
||||
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
|
||||
private Cursor allContributions;
|
||||
private UploadService uploadService;
|
||||
private boolean isUploadServiceConnected;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
CountDownLatch waitForContributionsListFragment = new CountDownLatch(1);
|
||||
|
||||
private ContributionsListFragment contributionsListFragment;
|
||||
private MediaDetailPagerFragment mediaDetailPagerFragment;
|
||||
public static final String CONTRIBUTION_LIST_FRAGMENT_TAG = "ContributionListFragmentTag";
|
||||
public static final String MEDIA_DETAIL_PAGER_FRAGMENT_TAG = "MediaDetailFragmentTag";
|
||||
|
||||
public NearbyNoificationCardView nearbyNoificationCardView;
|
||||
private Disposable placesDisposable;
|
||||
private LatLng curLatLng;
|
||||
|
||||
private boolean firstLocationUpdate = true;
|
||||
private LocationServiceManager locationManager;
|
||||
|
||||
private boolean isFragmentAttachedBefore = false;
|
||||
|
||||
|
||||
/**
|
||||
* Since we will need to use parent activity on onAuthCookieAcquired, we have to wait
|
||||
* fragment to be attached. Latch will be responsible for this sync.
|
||||
*/
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder) binder)
|
||||
.getService();
|
||||
isUploadServiceConnected = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
// this should never happen
|
||||
Timber.e(new RuntimeException("UploadService died but the rest of the process did not!"));
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_contributions, container, false);
|
||||
nearbyNoificationCardView = view.findViewById(R.id.card_view_nearby);
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetailPagerFragment = (MediaDetailPagerFragment)getChildFragmentManager().findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
contributionsListFragment = (ContributionsListFragment) getChildFragmentManager().findFragmentByTag(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
|
||||
if (savedInstanceState.getBoolean("mediaDetailsVisible")) {
|
||||
setMediaDetailPagerFragment();
|
||||
} else {
|
||||
setContributionsListFragment();
|
||||
}
|
||||
} else {
|
||||
setContributionsListFragment();
|
||||
}
|
||||
|
||||
if(!BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
||||
setUploadCount();
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
/*
|
||||
- There are some operations we need auth, so we need to make sure isAuthCookieAcquired.
|
||||
- And since we use same retained fragment doesn't want to make all network operations
|
||||
all over again on same fragment attached to recreated activity, we do this network
|
||||
operations on first time fragment atached to an activity. Then they will be retained
|
||||
until fragment life time ends.
|
||||
*/
|
||||
if (((MainActivity)getActivity()).isAuthCookieAcquired && !isFragmentAttachedBefore) {
|
||||
onAuthCookieAcquired(((MainActivity)getActivity()).uploadServiceIntent);
|
||||
isFragmentAttachedBefore = true;
|
||||
new UnreadNotificationsCheckAsync((MainActivity) getActivity(), notificationController).execute();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace FrameLayout with ContributionsListFragment, user will see contributions list.
|
||||
* Creates new one if null.
|
||||
*/
|
||||
public void setContributionsListFragment() {
|
||||
// show tabs on contribution list is visible
|
||||
((MainActivity)getActivity()).showTabs();
|
||||
// show nearby card view on contributions list is visible
|
||||
if (nearbyNoificationCardView != null) {
|
||||
if (prefs.getBoolean("displayNearbyCardView", true)) {
|
||||
nearbyNoificationCardView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
nearbyNoificationCardView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// Create if null
|
||||
if (getContributionsListFragment() == null) {
|
||||
contributionsListFragment = new ContributionsListFragment();
|
||||
}
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
// When this container fragment is created, we fill it with our ContributionsListFragment
|
||||
transaction.replace(R.id.root_frame, contributionsListFragment, CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.addToBackStack(CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace FrameLayout with MediaDetailPagerFragment, user will see details of selected media.
|
||||
* Creates new one if null.
|
||||
*/
|
||||
public void setMediaDetailPagerFragment() {
|
||||
// hide tabs on media detail view is visible
|
||||
((MainActivity)getActivity()).hideTabs();
|
||||
// hide nearby card view on media detail is visible
|
||||
nearbyNoificationCardView.setVisibility(View.GONE);
|
||||
|
||||
// Create if null
|
||||
if (getMediaDetailPagerFragment() == null) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
}
|
||||
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
|
||||
// When this container fragment is created, we fill it with our MediaDetailPagerFragment
|
||||
//transaction.addToBackStack(null);
|
||||
transaction.add(R.id.root_frame, mediaDetailPagerFragment, MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
transaction.addToBackStack(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
transaction.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Just getter method of ContributionsListFragment child of ContributionsFragment
|
||||
* @return contributionsListFragment, if any created
|
||||
*/
|
||||
public ContributionsListFragment getContributionsListFragment() {
|
||||
return contributionsListFragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Just getter method of MediaDetailPagerFragment child of ContributionsFragment
|
||||
* @return mediaDetailsFragment, if any created
|
||||
*/
|
||||
public MediaDetailPagerFragment getMediaDetailPagerFragment() {
|
||||
return mediaDetailPagerFragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
((MainActivity)getActivity()).initBackButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||
int uploads = prefs.getInt(UPLOADS_SHOWING, 100);
|
||||
return new CursorLoader(getActivity(), BASE_URI, //TODO find out the reason we pass activity here
|
||||
ALL_FIELDS, "", null,
|
||||
ContributionDao.CONTRIBUTION_SORT + "LIMIT " + uploads);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
|
||||
if (contributionsListFragment != null) {
|
||||
contributionsListFragment.changeProgressBarVisibility(false);
|
||||
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
contributionsListFragment.setAdapter(new ContributionsListAdapter(getActivity().getApplicationContext(),
|
||||
cursor, 0, contributionDao));
|
||||
} else {
|
||||
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(cursor);
|
||||
}
|
||||
|
||||
contributionsListFragment.clearSyncMessage();
|
||||
notifyAndMigrateDataSetObservers();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(Loader<Cursor> cursorLoader) {
|
||||
((CursorAdapter) contributionsListFragment.getAdapter()).swapCursor(null);
|
||||
}
|
||||
|
||||
private void notifyAndMigrateDataSetObservers() {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
|
||||
// First, move the observers over to the adapter now that we have it.
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
observersWaitingForLoad.clear();
|
||||
|
||||
// Now fire off a first notification...
|
||||
for (DataSetObserver observer : observersWaitingForLoad) {
|
||||
observer.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when onAuthCookieAcquired is called on authenticated parent activity
|
||||
* @param uploadServiceIntent
|
||||
*/
|
||||
public void onAuthCookieAcquired(Intent uploadServiceIntent) {
|
||||
// Since we call onAuthCookieAcquired method from onAttach, isAdded is still false. So don't use it
|
||||
|
||||
if (getActivity() != null) { // If fragment is attached to parent activity
|
||||
getActivity().bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
isUploadServiceConnected = true;
|
||||
allContributions = contributionDao.loadAllContributions();
|
||||
getActivity().getSupportLoaderManager().initLoader(0, null, ContributionsFragment.this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case LOCATION_REQUEST: {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Timber.d("Location permission granted, refreshing view");
|
||||
// No need to display permission request button anymore
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(false);
|
||||
locationManager.registerLocationManager();
|
||||
} else {
|
||||
// Still ask for permission
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is needed to allow the request codes from the Fragments to be routed appropriately
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
// show detail at a position
|
||||
showDetail(i);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace whatever is in the current contributionsFragmentContainer view with
|
||||
* mediaDetailPagerFragment, and preserve previous state in back stack.
|
||||
* Called when user selects a contribution.
|
||||
*/
|
||||
private void showDetail(int i) {
|
||||
if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) {
|
||||
mediaDetailPagerFragment = new MediaDetailPagerFragment();
|
||||
setMediaDetailPagerFragment();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry upload when it is failed
|
||||
* @param i position of upload which will be retried
|
||||
*/
|
||||
public void retryUpload(int i) {
|
||||
allContributions.moveToPosition(i);
|
||||
Contribution c = contributionDao.fromCursor(allContributions);
|
||||
if (c.getState() == STATE_FAILED) {
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
|
||||
Timber.d("Restarting for %s", c.toString());
|
||||
} else {
|
||||
Timber.d("Skipping re-upload for non-failed %s", c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a failed upload attempt
|
||||
* @param i position of upload attempt which will be deteled
|
||||
*/
|
||||
public void deleteUpload(int i) {
|
||||
allContributions.moveToPosition(i);
|
||||
Contribution c = contributionDao.fromCursor(allContributions);
|
||||
if (c.getState() == STATE_FAILED) {
|
||||
Timber.d("Deleting failed contrib %s", c.toString());
|
||||
contributionDao.delete(c);
|
||||
} else {
|
||||
Timber.d("Skipping deletion for non-failed contrib %s", c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSource() {
|
||||
getActivity().getSupportLoaderManager().restartLoader(0, null, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
// not yet ready to return data
|
||||
return null;
|
||||
} else {
|
||||
return contributionDao.fromCursor((Cursor) contributionsListFragment.getAdapter().getItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
if (contributionsListFragment.getAdapter() == null) {
|
||||
return 0;
|
||||
}
|
||||
return contributionsListFragment.getAdapter().getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDatasetChanged() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.add(observer);
|
||||
} else {
|
||||
adapter.registerDataSetObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
Adapter adapter = contributionsListFragment.getAdapter();
|
||||
if (adapter == null) {
|
||||
observersWaitingForLoad.remove(observer);
|
||||
} else {
|
||||
adapter.unregisterDataSetObserver(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private void setUploadCount() {
|
||||
|
||||
compositeDisposable.add(mediaWikiApi
|
||||
.getUploadCount(((MainActivity)getActivity()).sessionManager.getCurrentAccount().name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::displayUploadCount,
|
||||
t -> Timber.e(t, "Fetching upload count failed")
|
||||
));
|
||||
}
|
||||
|
||||
private void displayUploadCount(Integer uploadCount) {
|
||||
if (getActivity().isFinishing()
|
||||
|| getResources() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
((MainActivity)getActivity()).setNumOfUploads(uploadCount);
|
||||
|
||||
}
|
||||
|
||||
public void betaSetUploadCount(int betaUploadCount) {
|
||||
displayUploadCount(betaUploadCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates notification indicator on toolbar to indicate there are unread notifications
|
||||
* @param isThereUnreadNotifications true if user checked notifications before last notification date
|
||||
*/
|
||||
public void updateNotificationsNotification(boolean isThereUnreadNotifications) {
|
||||
((MainActivity)getActivity()).updateNotificationIcon(isThereUnreadNotifications);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
locationManager.removeLocationListener(this);
|
||||
locationManager.unregisterLocationManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
boolean mediaDetailsVisible = mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible();
|
||||
outState.putBoolean("mediaDetailsVisible", mediaDetailsVisible);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
locationManager = new LocationServiceManager(getActivity());
|
||||
|
||||
firstLocationUpdate = true;
|
||||
locationManager.addLocationListener(this);
|
||||
|
||||
boolean isSettingsChanged = prefs.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false);
|
||||
prefs.edit().putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED, false).apply();
|
||||
if (isSettingsChanged) {
|
||||
refreshSource();
|
||||
}
|
||||
|
||||
|
||||
if (prefs.getBoolean("displayNearbyCardView", true)) {
|
||||
nearbyNoificationCardView.cardViewVisibilityState = NearbyNoificationCardView.CardViewVisibilityState.LOADING;
|
||||
nearbyNoificationCardView.setVisibility(View.VISIBLE);
|
||||
checkGPS();
|
||||
|
||||
} else {
|
||||
// Hide nearby notification card view if related shared preferences is false
|
||||
nearbyNoificationCardView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check GPS to decide displaying request permission button or not.
|
||||
*/
|
||||
private void checkGPS() {
|
||||
if (!locationManager.isProviderEnabled()) {
|
||||
Timber.d("GPS is not enabled");
|
||||
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.ENABLE_GPS;
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(true);
|
||||
} else {
|
||||
Timber.d("GPS is enabled");
|
||||
checkLocationPermission();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLocationPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (locationManager.isLocationPermissionGranted()) {
|
||||
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.NO_PERMISSION_NEEDED;
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(false);
|
||||
locationManager.registerLocationManager();
|
||||
} else {
|
||||
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.ENABLE_LOCATION_PERMISSON;
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(true);
|
||||
}
|
||||
} else {
|
||||
// If device is under Marshmallow, we already checked for GPS
|
||||
nearbyNoificationCardView.permissionType = NearbyNoificationCardView.PermissionType.NO_PERMISSION_NEEDED;
|
||||
nearbyNoificationCardView.displayPermissionRequestButton(false);
|
||||
locationManager.registerLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void updateClosestNearbyCardViewInfo() {
|
||||
curLatLng = locationManager.getLastLocation();
|
||||
|
||||
placesDisposable = Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng, true)) // thanks to boolean, it will only return closest result
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::updateNearbyNotification,
|
||||
throwable -> {
|
||||
Timber.d(throwable);
|
||||
updateNearbyNotification(null);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateNearbyNotification(@Nullable NearbyController.NearbyPlacesInfo nearbyPlacesInfo) {
|
||||
|
||||
if (nearbyPlacesInfo != null && nearbyPlacesInfo.placeList != null && nearbyPlacesInfo.placeList.size() > 0) {
|
||||
Place closestNearbyPlace = nearbyPlacesInfo.placeList.get(0);
|
||||
String distance = formatDistanceBetween(curLatLng, closestNearbyPlace.location);
|
||||
closestNearbyPlace.setDistance(distance);
|
||||
nearbyNoificationCardView.updateContent (true, closestNearbyPlace);
|
||||
} else {
|
||||
// Means that no close nearby place is found
|
||||
nearbyNoificationCardView.updateContent (false, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
compositeDisposable.clear();
|
||||
getChildFragmentManager().removeOnBackStackChangedListener(this);
|
||||
locationManager.unregisterLocationManager();
|
||||
locationManager.removeLocationListener(this);
|
||||
// Try to prevent a possible NPE
|
||||
locationManager.context = null;
|
||||
super.onDestroy();
|
||||
|
||||
if (isUploadServiceConnected) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().unbindService(uploadServiceConnection);
|
||||
isUploadServiceConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (placesDisposable != null) {
|
||||
placesDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
// Will be called if location changed more than 1000 meter
|
||||
// Do nothing on slight changes for using network efficiently
|
||||
firstLocationUpdate = false;
|
||||
updateClosestNearbyCardViewInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
/* Update closest nearby notification card onLocationChangedSlightly
|
||||
If first time to update location after onResume, then no need to wait for significant
|
||||
location change. Any closest location is better than no location
|
||||
*/
|
||||
if (firstLocationUpdate) {
|
||||
updateClosestNearbyCardViewInfo();
|
||||
// Turn it to false, since it is not first location update anymore. To change closest location
|
||||
// notifiction, we need to wait for a significant location change.
|
||||
firstLocationUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
// Update closest nearby card view if location changed more than 500 meters
|
||||
updateClosestNearbyCardViewInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,25 +1,31 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -30,7 +36,7 @@ import butterknife.ButterKnife;
|
|||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
|
|
@ -38,6 +44,11 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
|||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
|
||||
import static android.view.View.GONE;
|
||||
import static fr.free.nrw.commons.contributions.ContributionController.SELECT_FROM_GALLERY;
|
||||
|
||||
/**
|
||||
* Created by root on 01.06.2018.
|
||||
*/
|
||||
|
||||
public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
|
|
@ -47,100 +58,121 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
TextView waitingMessage;
|
||||
@BindView(R.id.loadingContributionsProgressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.fab_plus)
|
||||
FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera)
|
||||
FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_galery)
|
||||
FloatingActionButton fabGalery;
|
||||
@BindView(R.id.noDataYet)
|
||||
TextView noDataYet;
|
||||
|
||||
@Inject
|
||||
@Named("prefs")
|
||||
SharedPreferences prefs;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences defaultPrefs;
|
||||
|
||||
private ContributionController controller;
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
private Animation rotate_backward;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View v = inflater.inflate(R.layout.fragment_contributions, container, false);
|
||||
ButterKnife.bind(this, v);
|
||||
private boolean isFabOpen = false;
|
||||
public ContributionController controller;
|
||||
|
||||
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||
if (savedInstanceState != null) {
|
||||
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
|
||||
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
|
||||
}
|
||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
//TODO: Should this be in onResume?
|
||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||
Timber.d("Last Sync Timestamp: %s", lastModified);
|
||||
|
||||
if (lastModified.equals("")) {
|
||||
waitingMessage.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
waitingMessage.setVisibility(GONE);
|
||||
}
|
||||
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener) getParentFragment());
|
||||
|
||||
changeEmptyScreen(true);
|
||||
changeProgressBarVisibility(true);
|
||||
return v;
|
||||
return view;
|
||||
}
|
||||
|
||||
public ListAdapter getAdapter() {
|
||||
return contributionsList.getAdapter();
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
if (controller == null) {
|
||||
controller = new ContributionController(this);
|
||||
}
|
||||
controller.loadState(savedInstanceState);
|
||||
}
|
||||
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
this.contributionsList.setAdapter(adapter);
|
||||
|
||||
if(BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
||||
((ContributionsActivity) getActivity()).betaSetUploadCount(adapter.getCount());
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (controller != null) {
|
||||
controller.saveState(outState);
|
||||
} else {
|
||||
controller = new ContributionController(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
initializeAnimations();
|
||||
setListeners();
|
||||
}
|
||||
|
||||
public void changeEmptyScreen(boolean isEmpty){
|
||||
this.noDataYet.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
public void changeProgressBarVisibility(boolean isVisible) {
|
||||
this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
|
||||
private void initializeAnimations() {
|
||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||
}
|
||||
|
||||
private void setListeners() {
|
||||
|
||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||
fabCamera.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if (outState == null) {
|
||||
outState = new Bundle();
|
||||
}
|
||||
super.onSaveInstanceState(outState);
|
||||
controller.saveState(outState);
|
||||
outState.putInt("grid-position", contributionsList.getFirstVisiblePosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
//FIXME: must get the file data for Google Photos when receive the intent answer, in the onActivityResult method
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
if (resultCode == RESULT_OK) {
|
||||
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||
requestCode, resultCode, data);
|
||||
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
|
||||
// If coming from camera, pass null as uri. Because camera photos get saved to a
|
||||
// fixed directory
|
||||
controller.handleImagePicked(requestCode, null, false, null);
|
||||
public void onClick(View view) {
|
||||
boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
|
||||
// Here, thisActivity is the current activity
|
||||
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
if (shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
new AlertDialog.Builder(getParentFragment().getActivity())
|
||||
.setMessage(getString(R.string.write_storage_permission_rationale))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
getActivity().requestPermissions
|
||||
(new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
controller.handleImagePicked(requestCode, data.getData(), false, null);
|
||||
// No explanation needed, we can request the permission.
|
||||
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
|
||||
3);
|
||||
// MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE is an
|
||||
// app-defined int constant. The callback method gets the
|
||||
// result of the request.
|
||||
}
|
||||
} else {
|
||||
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||
requestCode, resultCode, data);
|
||||
controller.startCameraCapture();
|
||||
}
|
||||
} else {
|
||||
controller.startCameraCapture();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
fabGalery.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_from_gallery:
|
||||
public void onClick(View view) {
|
||||
//Gallery crashes before reach ShareActivity screen so must implement permissions check here
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
|
||||
|
|
@ -156,10 +188,11 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
|
||||
new AlertDialog.Builder(getActivity())
|
||||
new AlertDialog.Builder(getParentFragment().getActivity())
|
||||
.setMessage(getString(R.string.read_storage_permission_rationale))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
|
||||
getActivity().requestPermissions
|
||||
(new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
|
|
@ -179,59 +212,64 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
} else {
|
||||
controller.startGalleryPick();
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
controller.startGalleryPick();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
case R.id.menu_from_camera:
|
||||
boolean useExtStorage = defaultPrefs.getBoolean("useExternalStorage", true);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && useExtStorage) {
|
||||
// Here, thisActivity is the current activity
|
||||
if (ContextCompat.checkSelfPermission(getActivity(), WRITE_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
if (shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(getString(R.string.write_storage_permission_rationale))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 3);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
private void animateFAB(boolean isFabOpen) {
|
||||
this.isFabOpen = !isFabOpen;
|
||||
if (fabPlus.isShown()){
|
||||
if (isFabOpen) {
|
||||
fabPlus.startAnimation(rotate_backward);
|
||||
fabCamera.startAnimation(fab_close);
|
||||
fabGalery.startAnimation(fab_close);
|
||||
fabCamera.hide();
|
||||
fabGalery.hide();
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
|
||||
3);
|
||||
// MY_PERMISSIONS_WRITE_EXTERNAL_STORAGE is an
|
||||
// app-defined int constant. The callback method gets the
|
||||
// result of the request.
|
||||
fabPlus.startAnimation(rotate_forward);
|
||||
fabCamera.startAnimation(fab_open);
|
||||
fabGalery.startAnimation(fab_open);
|
||||
fabCamera.show();
|
||||
fabGalery.show();
|
||||
}
|
||||
} else {
|
||||
controller.startCameraCapture();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
controller.startCameraCapture();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
this.isFabOpen=!isFabOpen;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
|
||||
@NonNull int[] grantResults) {
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
ContributionsFragment parentFragment = (ContributionsFragment)getParentFragment();
|
||||
parentFragment.waitForContributionsListFragment.countDown();
|
||||
}
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||
requestCode, resultCode, data);
|
||||
if (requestCode == ContributionController.SELECT_FROM_CAMERA) {
|
||||
// If coming from camera, pass null as uri. Because camera photos get saved to a
|
||||
// fixed directory
|
||||
controller.handleImagePicked(requestCode, null, false, null);
|
||||
} else if (requestCode == ContributionController.PICK_IMAGE_MULTIPLE) {
|
||||
handleMultipleImages(requestCode, data);
|
||||
} else if (requestCode == ContributionController.SELECT_FROM_GALLERY){
|
||||
controller.handleImagePicked(requestCode, data.getData(), false, null);
|
||||
}
|
||||
} else {
|
||||
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||
requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
Timber.d("onRequestPermissionsResult: req code = " + " perm = "
|
||||
+ Arrays.toString(permissions) + " grant =" + Arrays.toString(grantResults));
|
||||
|
||||
|
|
@ -246,11 +284,12 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
break;
|
||||
// 2 = Location allowed when 'nearby places' selected
|
||||
case 2: {
|
||||
if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
|
||||
// TODO: understand and fix
|
||||
/*if (grantResults.length > 0 && grantResults[0] == PERMISSION_GRANTED) {
|
||||
Timber.d("Location permission granted");
|
||||
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
||||
Intent nearbyIntent = new Intent(getActivity(), MainActivity.class);
|
||||
startActivity(nearbyIntent);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
break;
|
||||
case 3: {
|
||||
|
|
@ -262,42 +301,60 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
|
||||
inflater.inflate(R.menu.fragment_contributions_list, menu);
|
||||
private void handleMultipleImages(int requestCode, Intent data) {
|
||||
if (getContext() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deviceHasCamera()) {
|
||||
menu.findItem(R.id.menu_from_camera).setEnabled(false);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
|
||||
&& data.getClipData() != null) {
|
||||
ClipData mClipData = data.getClipData();
|
||||
ArrayList<Uri> mArrayUri = new ArrayList<Uri>();
|
||||
for (int i = 0; i < mClipData.getItemCount(); i++) {
|
||||
|
||||
ClipData.Item item = mClipData.getItemAt(i);
|
||||
Uri uri = item.getUri();
|
||||
mArrayUri.add(uri);
|
||||
}
|
||||
Log.v("LOG_TAG", "Selected Images" + mArrayUri.size());
|
||||
controller.handleImagesPicked(requestCode, mArrayUri);
|
||||
} else if(data.getData() != null) {
|
||||
controller.handleImagePicked(SELECT_FROM_GALLERY, data.getData(), false, null);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean deviceHasCamera() {
|
||||
PackageManager pm = getContext().getPackageManager();
|
||||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
|
||||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
controller = new ContributionController(this);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
controller.loadState(savedInstanceState);
|
||||
|
||||
/**
|
||||
* Responsible to set progress bar invisible and visible
|
||||
* @param isVisible True when contributions list should be hidden.
|
||||
*/
|
||||
public void changeProgressBarVisibility(boolean isVisible) {
|
||||
this.progressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears sync message displayed with progress bar before contributions list became visible
|
||||
*/
|
||||
protected void clearSyncMessage() {
|
||||
waitingMessage.setVisibility(GONE);
|
||||
noDataYet.setVisibility(GONE);
|
||||
}
|
||||
|
||||
public ListAdapter getAdapter() {
|
||||
return contributionsList.getAdapter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets adapter to contributions list. If beta mode, sets upload count for beta explicitly.
|
||||
* @param adapter List adapter for uploads of contributor
|
||||
*/
|
||||
public void setAdapter(ListAdapter adapter) {
|
||||
this.contributionsList.setAdapter(adapter);
|
||||
|
||||
if(BuildConfig.FLAVOR.equalsIgnoreCase("beta")){
|
||||
//TODO: add betaSetUploadCount method
|
||||
((ContributionsFragment) getParentFragment()).betaSetUploadCount(adapter.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
public interface SourceRefresher {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,550 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.TabLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentPagerAdapter;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.nearby.NearbyFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.content.ContentResolver.requestSync;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LOCATION_REQUEST;
|
||||
|
||||
public class MainActivity extends AuthenticatedActivity implements FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.pager)
|
||||
public UnswipableViewPager viewPager;
|
||||
@Inject
|
||||
public LocationServiceManager locationManager;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public SharedPreferences prefs;
|
||||
|
||||
|
||||
public Intent uploadServiceIntent;
|
||||
public boolean isAuthCookieAcquired = false;
|
||||
|
||||
public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter;
|
||||
public final int CONTRIBUTIONS_TAB_POSITION = 0;
|
||||
public final int NEARBY_TAB_POSITION = 1;
|
||||
|
||||
public boolean isContributionsFragmentVisible = true; // False means nearby fragment is visible
|
||||
private Menu menu;
|
||||
private boolean isThereUnreadNotifications = false;
|
||||
|
||||
private boolean onOrientationChanged = false;
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_contributions);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
requestAuthToken();
|
||||
initDrawer();
|
||||
setTitle(getString(R.string.navigation_item_home)); // Should I create a new string variable with another name instead?
|
||||
|
||||
if (savedInstanceState != null ) {
|
||||
onOrientationChanged = true; // Will be used in nearby fragment to determine significant update of map
|
||||
|
||||
//If nearby map was visible, call on Tab Selected to call all nearby operations
|
||||
if (savedInstanceState.getInt("viewPagerCurrentItem") == 1) {
|
||||
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).onTabSelected(onOrientationChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putInt("viewPagerCurrentItem", viewPager.getCurrentItem());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
// Do a sync everytime we get here!
|
||||
requestSync(sessionManager.getCurrentAccount(), BuildConfig.CONTRIBUTION_AUTHORITY, new Bundle());
|
||||
uploadServiceIntent = new Intent(this, UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
startService(uploadServiceIntent);
|
||||
|
||||
addTabsAndFragments();
|
||||
isAuthCookieAcquired = true;
|
||||
if (contributionsActivityPagerAdapter.getItem(0) != null) {
|
||||
((ContributionsFragment)contributionsActivityPagerAdapter.getItem(0)).onAuthCookieAcquired(uploadServiceIntent);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTabsAndFragments() {
|
||||
contributionsActivityPagerAdapter = new ContributionsActivityPagerAdapter(getSupportFragmentManager());
|
||||
viewPager.setAdapter(contributionsActivityPagerAdapter);
|
||||
|
||||
tabLayout.addTab(tabLayout.newTab().setText(getResources().getString(R.string.contributions_fragment)));
|
||||
tabLayout.addTab(tabLayout.newTab().setText(getResources().getString(R.string.nearby_fragment)));
|
||||
|
||||
// Set custom view to add nearby info icon next to text
|
||||
View nearbyTabLinearLayout = LayoutInflater.from(this).inflate(R.layout.custom_nearby_tab_layout, null);
|
||||
View nearbyInfoPopupWindowLayout = LayoutInflater.from(this).inflate(R.layout.nearby_info_popup_layout, null);
|
||||
ImageView nearbyInfo = nearbyTabLinearLayout.findViewById(R.id.nearby_info_image);
|
||||
tabLayout.getTabAt(1).setCustomView(nearbyTabLinearLayout);
|
||||
|
||||
nearbyInfo.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
/*new AlertDialog.Builder(MainActivity.this)
|
||||
.setTitle(R.string.title_activity_nearby)
|
||||
.setMessage(R.string.showcase_view_whole_nearby_activity)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();*/
|
||||
String popupText = getResources().getString(R.string.showcase_view_whole_nearby_activity);
|
||||
ViewUtil.displayPopupWindow(nearbyInfo, MainActivity.this, nearbyInfoPopupWindowLayout, popupText);
|
||||
}
|
||||
});
|
||||
|
||||
if (uploadServiceIntent != null) {
|
||||
// If auth cookie already acquired notify contrib fragmnet so that it san operate auth required actions
|
||||
((ContributionsFragment)contributionsActivityPagerAdapter.getItem(CONTRIBUTIONS_TAB_POSITION)).onAuthCookieAcquired(uploadServiceIntent);
|
||||
}
|
||||
setTabAndViewPagerSynchronisation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds number of uploads next to tab text "Contributions" then it will look like
|
||||
* "Contributions (NUMBER)"
|
||||
* @param uploadCount
|
||||
*/
|
||||
public void setNumOfUploads(int uploadCount) {
|
||||
tabLayout.getTabAt(0).setText(getResources().getString(R.string.contributions_fragment) +" "+ getResources()
|
||||
.getQuantityString(R.plurals.contributions_subtitle,
|
||||
uploadCount, uploadCount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally tab layout and view pager has no relation, which means when you swipe view pager
|
||||
* tab won't change and vice versa. So we have to notify each of them.
|
||||
*/
|
||||
private void setTabAndViewPagerSynchronisation() {
|
||||
//viewPager.canScrollHorizontally(false);
|
||||
viewPager.setFocusableInTouchMode(true);
|
||||
|
||||
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
switch (position) {
|
||||
case CONTRIBUTIONS_TAB_POSITION:
|
||||
Timber.d("Contributions tab selected");
|
||||
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
|
||||
isContributionsFragmentVisible = true;
|
||||
updateMenuItem();
|
||||
|
||||
break;
|
||||
case NEARBY_TAB_POSITION:
|
||||
Timber.d("Nearby tab selected");
|
||||
tabLayout.getTabAt(NEARBY_TAB_POSITION).select();
|
||||
isContributionsFragmentVisible = false;
|
||||
updateMenuItem();
|
||||
// Do all permission and GPS related tasks on tab selected, not on create
|
||||
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).onTabSelected(onOrientationChanged);
|
||||
|
||||
break;
|
||||
default:
|
||||
tabLayout.getTabAt(CONTRIBUTIONS_TAB_POSITION).select();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
viewPager.setCurrentItem(tab.getPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void hideTabs() {
|
||||
changeDrawerIconToBakcButton();
|
||||
if (tabLayout != null) {
|
||||
tabLayout.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void showTabs() {
|
||||
changeDrawerIconToDefault();
|
||||
if (tabLayout != null) {
|
||||
tabLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
String contributionsFragmentTag = ((ContributionsActivityPagerAdapter) viewPager.getAdapter()).makeFragmentName(R.id.pager, 0);
|
||||
String nearbyFragmentTag = ((ContributionsActivityPagerAdapter) viewPager.getAdapter()).makeFragmentName(R.id.pager, 1);
|
||||
if (getSupportFragmentManager().findFragmentByTag(contributionsFragmentTag) != null && isContributionsFragmentVisible) {
|
||||
// Meas that contribution fragment is visible (not nearby fragment)
|
||||
ContributionsFragment contributionsFragment = (ContributionsFragment) getSupportFragmentManager().findFragmentByTag(contributionsFragmentTag);
|
||||
|
||||
if (contributionsFragment.getChildFragmentManager().findFragmentByTag(ContributionsFragment.MEDIA_DETAIL_PAGER_FRAGMENT_TAG) != null) {
|
||||
// Means that media details fragment is visible to uer instead of contributions list fragment (As chils fragment)
|
||||
// Then we want to go back to contributions list fragment on backbutton pressed from media detail fragment
|
||||
contributionsFragment.getChildFragmentManager().popBackStack();
|
||||
// Tabs were invisible when Media Details Fragment is active, make them visible again on Contrib List Fragment active
|
||||
showTabs();
|
||||
// Nearby Notification Card View was invisible when Media Details Fragment is active, make it visible again on Contrib List Fragment active, according to preferences
|
||||
if (prefs.getBoolean("displayNearbyCardView", true)) {
|
||||
contributionsFragment.nearbyNoificationCardView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
contributionsFragment.nearbyNoificationCardView.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
} else if (getSupportFragmentManager().findFragmentByTag(nearbyFragmentTag) != null && !isContributionsFragmentVisible) {
|
||||
// Meas that nearby fragment is visible (not contributions fragment)
|
||||
// Set current item to contributions activity instead of closing the activity
|
||||
viewPager.setCurrentItem(0);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
initBackButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.contribution_activity_notification_menu, menu);
|
||||
|
||||
if (!isThereUnreadNotifications) {
|
||||
// TODO: used vectors are not compatible with API 19 and below, change them
|
||||
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art));
|
||||
} else {
|
||||
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot));
|
||||
}
|
||||
|
||||
this.menu = menu;
|
||||
|
||||
updateMenuItem();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible with displaying required menu items according to displayed fragment.
|
||||
* Notifications icon when contributions list is visible, list sheet icon when nearby is visible
|
||||
*/
|
||||
private void updateMenuItem() {
|
||||
if (menu != null) {
|
||||
if (isContributionsFragmentVisible) {
|
||||
// Display notifications menu item
|
||||
menu.findItem(R.id.notifications).setVisible(true);
|
||||
menu.findItem(R.id.list_sheet).setVisible(false);
|
||||
Timber.d("Contributions activity notifications menu item is visible");
|
||||
} else {
|
||||
// Display bottom list menu item
|
||||
menu.findItem(R.id.notifications).setVisible(false);
|
||||
menu.findItem(R.id.list_sheet).setVisible(true);
|
||||
Timber.d("Contributions activity list sheet menu item is visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.notifications:
|
||||
// Starts notification activity on click to notification icon
|
||||
NavigationBaseActivity.startActivityWithFlags(this, NotificationActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
finish();
|
||||
return true;
|
||||
|
||||
case R.id.list_sheet:
|
||||
if (contributionsActivityPagerAdapter.getItem(1) != null) {
|
||||
((NearbyFragment)contributionsActivityPagerAdapter.getItem(1)).listOptionMenuIteClicked();
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deviceHasCamera() {
|
||||
PackageManager pm = getPackageManager();
|
||||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
|
||||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updte notification icon if there is an unread notification
|
||||
* @param isThereUnreadNotifications true if user didn't visit notifications activity since
|
||||
* latest notification came to account
|
||||
*/
|
||||
public void updateNotificationIcon(boolean isThereUnreadNotifications) {
|
||||
if (!isThereUnreadNotifications) {
|
||||
this.isThereUnreadNotifications = false;
|
||||
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art));
|
||||
} else {
|
||||
this.isThereUnreadNotifications = true;
|
||||
menu.findItem(R.id.notifications).setIcon(ContextCompat.getDrawable(this, R.drawable.ic_notification_white_clip_art_dot));
|
||||
}
|
||||
}
|
||||
|
||||
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
|
||||
FragmentManager fragmentManager;
|
||||
private boolean isContributionsListFragment = true; // to know what to put in first tab, Contributions of Media Details
|
||||
|
||||
|
||||
public ContributionsActivityPagerAdapter(FragmentManager fragmentManager) {
|
||||
super(fragmentManager);
|
||||
this.fragmentManager = fragmentManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not use getItem method to access fragments on pager adapter. User reference vairables
|
||||
* instead.
|
||||
* */
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
switch (position){
|
||||
case 0:
|
||||
ContributionsFragment retainedContributionsFragment = getContributionsFragment(0);
|
||||
if (retainedContributionsFragment != null) {
|
||||
/**
|
||||
* ContributionsFragment is parent of ContributionsListFragment and
|
||||
* MediaDetailsFragment. If below decides which child will be visible.
|
||||
*/
|
||||
if (isContributionsListFragment) {
|
||||
retainedContributionsFragment.setContributionsListFragment();
|
||||
} else {
|
||||
retainedContributionsFragment.setMediaDetailPagerFragment();
|
||||
}
|
||||
return retainedContributionsFragment;
|
||||
} else {
|
||||
// If we reach here, retainedContributionsFragment is null
|
||||
return new ContributionsFragment();
|
||||
|
||||
}
|
||||
|
||||
case 1:
|
||||
NearbyFragment retainedNearbyFragment = getNearbyFragment(1);
|
||||
if (retainedNearbyFragment != null) {
|
||||
return retainedNearbyFragment;
|
||||
} else {
|
||||
// If we reach here, retainedNearbyFragment is null
|
||||
return new NearbyFragment();
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fragment tag with makeFragmentName method to get retained contributions fragment
|
||||
* @param position index of tabs, in our case 0 or 1
|
||||
* @return
|
||||
*/
|
||||
private ContributionsFragment getContributionsFragment(int position) {
|
||||
String tag = makeFragmentName(R.id.pager, position);
|
||||
return (ContributionsFragment)fragmentManager.findFragmentByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates fragment tag with makeFragmentName method to get retained nearby fragment
|
||||
* @param position index of tabs, in our case 0 or 1
|
||||
* @return
|
||||
*/
|
||||
private NearbyFragment getNearbyFragment(int position) {
|
||||
String tag = makeFragmentName(R.id.pager, position);
|
||||
return (NearbyFragment)fragmentManager.findFragmentByTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple hack to use retained fragment when getID is called explicitly, if we don't use
|
||||
* this method, a new fragment will be initialized on each explicit calls of getID
|
||||
* @param viewId id of view pager
|
||||
* @param index index of tabs, in our case 0 or 1
|
||||
* @return
|
||||
*/
|
||||
public String makeFragmentName(int viewId, int index) {
|
||||
return "android:switcher:" + viewId + ":" + index;
|
||||
}
|
||||
|
||||
/**
|
||||
* In first tab we can have ContributionsFragment or Media details fragment. This method
|
||||
* is responsible to update related boolean
|
||||
* @param isContributionsListFragment true when contribution fragment should be visible, false
|
||||
* means user clicked to MediaDetails
|
||||
*/
|
||||
private void updateContributionFragmentTabContent(boolean isContributionsListFragment) {
|
||||
this.isContributionsListFragment = isContributionsListFragment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
ContributionsListFragment contributionsListFragment =
|
||||
(ContributionsListFragment) contributionsActivityPagerAdapter
|
||||
.getItem(0).getChildFragmentManager()
|
||||
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
contributionsListFragment.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String permissions[], int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case LOCATION_REQUEST: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Timber.d("Location permission given");
|
||||
} else {
|
||||
// If nearby fragment is visible and location permission is not given, send user back to contrib fragment
|
||||
if (!isContributionsFragmentVisible) {
|
||||
viewPager.setCurrentItem(CONTRIBUTIONS_TAB_POSITION);
|
||||
|
||||
// TODO: If contrib fragment is visible and location permission is not given, display permission request button
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Storage permission for gallery
|
||||
case PermissionUtils.GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Storage permission given
|
||||
ContributionsListFragment contributionsListFragment =
|
||||
(ContributionsListFragment) contributionsActivityPagerAdapter
|
||||
.getItem(0).getChildFragmentManager()
|
||||
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
contributionsListFragment.controller.startGalleryPick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case PermissionUtils.CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Storage permission given
|
||||
ContributionsListFragment contributionsListFragment =
|
||||
(ContributionsListFragment) contributionsActivityPagerAdapter
|
||||
.getItem(0).getChildFragmentManager()
|
||||
.findFragmentByTag(ContributionsFragment.CONTRIBUTION_LIST_FRAGMENT_TAG);
|
||||
contributionsListFragment.controller.startCameraCapture();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Storage permission given
|
||||
NearbyMapFragment nearbyMapFragment =
|
||||
((NearbyFragment) contributionsActivityPagerAdapter
|
||||
.getItem(1)).nearbyMapFragment;
|
||||
nearbyMapFragment.controller.startCameraCapture();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP: {
|
||||
// If request is cancelled, the result arrays are empty.
|
||||
if (grantResults.length > 0
|
||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Storage permission given
|
||||
NearbyMapFragment nearbyMapFragment =
|
||||
((NearbyFragment) contributionsActivityPagerAdapter
|
||||
.getItem(1)).nearbyMapFragment;
|
||||
nearbyMapFragment.controller.startGalleryPick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
locationManager.unregisterLocationManager();
|
||||
// Remove ourself from hashmap to prevent memory leaks
|
||||
locationManager = null;
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
public class UnswipableViewPager extends ViewPager{
|
||||
public UnswipableViewPager(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public UnswipableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
// Unswipable
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Unswipable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -9,14 +9,13 @@ import fr.free.nrw.commons.auth.LoginActivity;
|
|||
import fr.free.nrw.commons.auth.SignupActivity;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.explore.SearchActivity;
|
||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
import fr.free.nrw.commons.upload.MultipleShareActivity;
|
||||
import fr.free.nrw.commons.upload.ShareActivity;
|
||||
import fr.free.nrw.commons.upload.UploadActivity;
|
||||
|
||||
@Module
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
|
|
@ -29,13 +28,7 @@ public abstract class ActivityBuilderModule {
|
|||
abstract WelcomeActivity bindWelcomeActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ShareActivity bindShareActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract MultipleShareActivity bindMultipleShareActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ContributionsActivity bindContributionsActivity();
|
||||
abstract MainActivity bindContributionsActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SettingsActivity bindSettingsActivity();
|
||||
|
|
@ -46,15 +39,15 @@ public abstract class ActivityBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract SignupActivity bindSignupActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NearbyActivity bindNearbyActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NotificationActivity bindNotificationActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract CategoryImagesActivity bindFeaturedImagesActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract UploadActivity bindUploadActivity();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SearchActivity bindSearchActivity();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,17 @@
|
|||
package fr.free.nrw.commons.di;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ContentProviderClient;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
|
|
@ -12,12 +19,14 @@ import javax.inject.Singleton;
|
|||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AccountUtil;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadController;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListener;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||
|
|
@ -38,6 +47,35 @@ public class CommonsApplicationModule {
|
|||
return this.applicationContext;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public InputMethodManager provideInputMethodManager() {
|
||||
return (InputMethodManager) applicationContext.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("licenses")
|
||||
public List<String> provideLicenses(Context context) {
|
||||
List<String> licenseItems = new ArrayList<>();
|
||||
licenseItems.add(context.getString(R.string.license_name_cc0));
|
||||
licenseItems.add(context.getString(R.string.license_name_cc_by));
|
||||
licenseItems.add(context.getString(R.string.license_name_cc_by_sa));
|
||||
licenseItems.add(context.getString(R.string.license_name_cc_by_four));
|
||||
licenseItems.add(context.getString(R.string.license_name_cc_by_sa_four));
|
||||
return licenseItems;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Named("licenses_by_name")
|
||||
public Map<String, String> provideLicensesByName(Context context) {
|
||||
Map<String, String> byName = new HashMap<>();
|
||||
byName.put(context.getString(R.string.license_name_cc0), Prefs.Licenses.CC0);
|
||||
byName.put(context.getString(R.string.license_name_cc_by), Prefs.Licenses.CC_BY_3);
|
||||
byName.put(context.getString(R.string.license_name_cc_by_sa), Prefs.Licenses.CC_BY_SA_3);
|
||||
byName.put(context.getString(R.string.license_name_cc_by_four), Prefs.Licenses.CC_BY_4);
|
||||
byName.put(context.getString(R.string.license_name_cc_by_sa_four), Prefs.Licenses.CC_BY_SA_4);
|
||||
return byName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
public AccountUtil providesAccountUtil(Context context) {
|
||||
return new AccountUtil(context);
|
||||
|
|
@ -120,6 +158,17 @@ public class CommonsApplicationModule {
|
|||
return context.getSharedPreferences("direct_nearby_upload_prefs", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is used to determine when user is viewed notifications activity last
|
||||
* @param context
|
||||
* @return date of lastReadNotificationDate
|
||||
*/
|
||||
@Provides
|
||||
@Named("last_read_notification_date")
|
||||
public SharedPreferences providesLastReadNotificationDatePreferences(Context context) {
|
||||
return context.getSharedPreferences("last_read_notification_date", MODE_PRIVATE);
|
||||
}
|
||||
|
||||
@Provides
|
||||
public UploadController providesUploadController(SessionManager sessionManager, @Named("default_preferences") SharedPreferences sharedPreferences, Context context) {
|
||||
return new UploadController(sessionManager, context, sharedPreferences);
|
||||
|
|
|
|||
|
|
@ -4,29 +4,25 @@ import dagger.Module;
|
|||
import dagger.android.ContributesAndroidInjector;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsFragment;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesFragment;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.category.CategoryImagesListFragment;
|
||||
import fr.free.nrw.commons.category.SubCategoryListFragment;
|
||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||
import fr.free.nrw.commons.contributions.ContributionsListFragment;
|
||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||
import fr.free.nrw.commons.explore.images.SearchImageFragment;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailFragment;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
||||
import fr.free.nrw.commons.nearby.NoPermissionsFragment;
|
||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||
import fr.free.nrw.commons.upload.MultipleUploadListFragment;
|
||||
import fr.free.nrw.commons.upload.SingleUploadFragment;
|
||||
|
||||
@Module
|
||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||
public abstract class FragmentBuilderModule {
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract CategorizationFragment bindCategorizationFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ContributionsListFragment bindContributionsListFragment();
|
||||
|
||||
|
|
@ -48,12 +44,6 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract SettingsFragment bindSettingsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract MultipleUploadListFragment bindMultipleUploadListFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract SingleUploadFragment bindSingleUploadFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract CategoryImagesListFragment bindFeaturedImagesListFragment();
|
||||
|
||||
|
|
@ -69,6 +59,12 @@ public abstract class FragmentBuilderModule {
|
|||
@ContributesAndroidInjector
|
||||
abstract RecentSearchesFragment bindRecentSearchesFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract ContributionsFragment bindContributionsFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract NearbyFragment bindNearbyFragment();
|
||||
|
||||
@ContributesAndroidInjector
|
||||
abstract BookmarkPicturesFragment bindBookmarkPictureListFragment();
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
categoriesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
else{
|
||||
|
|
@ -124,7 +124,7 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
|||
public void updateCategoryList(String query) {
|
||||
this.query = query;
|
||||
categoriesNotFoundView.setVisibility(GONE);
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -173,7 +173,7 @@ public class SearchCategoryFragment extends CommonsDaggerSupportFragment {
|
|||
*/
|
||||
private void handleSuccess(List<String> mediaList) {
|
||||
queryList = mediaList;
|
||||
if(mediaList == null || mediaList.isEmpty()) {
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
initErrorView();
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.explore.images;
|
||||
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
|
|
@ -97,7 +98,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_browse_image, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
if(getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
if (getActivity().getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
imagesRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
}
|
||||
else{
|
||||
|
|
@ -123,12 +124,13 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|||
* Checks for internet connection and then initializes the recycler view with 25 images of the searched query
|
||||
* Clearing imageAdapter every time new keyword is searched so that user can see only new results
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
public void updateImageList(String query) {
|
||||
this.query = query;
|
||||
if(imagesNotFoundView != null) {
|
||||
if (imagesNotFoundView != null) {
|
||||
imagesNotFoundView.setVisibility(GONE);
|
||||
}
|
||||
if(!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
return;
|
||||
}
|
||||
|
|
@ -146,6 +148,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|||
/**
|
||||
* Adds more results to existing search results
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
public void addImagesToList(String query) {
|
||||
this.query = query;
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
|
@ -163,13 +166,11 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|||
*/
|
||||
private void handlePaginationSuccess(List<Media> mediaList) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if (mediaList.size()!=0){
|
||||
if (!queryList.get(queryList.size()-1).getFilename().equals(mediaList.get(mediaList.size()-1).getFilename())) {
|
||||
if (mediaList.size() != 0 || !queryList.get(queryList.size() - 1).getFilename().equals(mediaList.get(mediaList.size() - 1).getFilename())) {
|
||||
queryList.addAll(mediaList);
|
||||
imagesAdapter.addAll(mediaList);
|
||||
imagesAdapter.notifyDataSetChanged();
|
||||
((SearchActivity)getContext()).viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
((SearchActivity) getContext()).viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,7 +183,7 @@ public class SearchImageFragment extends CommonsDaggerSupportFragment {
|
|||
*/
|
||||
private void handleSuccess(List<Media> mediaList) {
|
||||
queryList = mediaList;
|
||||
if(mediaList == null || mediaList.isEmpty()) {
|
||||
if (mediaList == null || mediaList.isEmpty()) {
|
||||
initErrorView();
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -26,12 +26,13 @@ public class LocationServiceManager implements LocationListener {
|
|||
private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 2 * 60 * 100;
|
||||
private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 10;
|
||||
|
||||
private Context context;
|
||||
public Context context;
|
||||
private LocationManager locationManager;
|
||||
private Location lastLocation;
|
||||
//private Location lastLocationDuplicate; // Will be used for nearby card view on contributions activity
|
||||
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
|
||||
private boolean isLocationManagerRegistered = false;
|
||||
private Set<Activity> locationExplanationDisplayed = new HashSet<>();
|
||||
public Set<Activity> locationExplanationDisplayed = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Constructs a new instance of LocationServiceManager.
|
||||
|
|
@ -253,15 +254,25 @@ public class LocationServiceManager implements LocationListener {
|
|||
|
||||
@Override
|
||||
public void onLocationChanged(Location location) {
|
||||
Timber.d("on location changed");
|
||||
if (isBetterLocation(location, lastLocation)
|
||||
.equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) {
|
||||
lastLocation = location;
|
||||
//lastLocationDuplicate = location;
|
||||
for (LocationUpdateListener listener : locationListeners) {
|
||||
listener.onLocationChangedSignificantly(LatLng.from(lastLocation));
|
||||
}
|
||||
} else if (isBetterLocation(location, lastLocation)
|
||||
} else if (location.distanceTo(lastLocation) >= 500) {
|
||||
// Update nearby notification card at every 500 meters.
|
||||
for (LocationUpdateListener listener : locationListeners) {
|
||||
listener.onLocationChangedMedium(LatLng.from(lastLocation));
|
||||
}
|
||||
}
|
||||
|
||||
else if (isBetterLocation(location, lastLocation)
|
||||
.equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
|
||||
lastLocation = location;
|
||||
//lastLocationDuplicate = location;
|
||||
for (LocationUpdateListener listener : locationListeners) {
|
||||
listener.onLocationChangedSlightly(LatLng.from(lastLocation));
|
||||
}
|
||||
|
|
@ -286,6 +297,7 @@ public class LocationServiceManager implements LocationListener {
|
|||
public enum LocationChangeType{
|
||||
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
|
||||
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
|
||||
LOCATION_MEDIUM_CHANGED, //Between slight and significant changes, will be used for nearby card view updates.
|
||||
LOCATION_NOT_CHANGED,
|
||||
PERMISSION_JUST_GRANTED,
|
||||
MAP_UPDATED
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.location;
|
||||
|
||||
public interface LocationUpdateListener {
|
||||
void onLocationChangedSignificantly(LatLng latLng);
|
||||
void onLocationChangedSlightly(LatLng latLng);
|
||||
void onLocationChangedSignificantly(LatLng latLng); // Will be used to update all nearby markers on the map
|
||||
void onLocationChangedSlightly(LatLng latLng); // Will be used to track users motion
|
||||
void onLocationChangedMedium(LatLng latLng); // Will be used updating nearby card view notification
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public final class LogUtils {
|
|||
* @return
|
||||
*/
|
||||
public static String getLogDirectory(boolean isBeta) {
|
||||
if(isBeta) {
|
||||
if (isBeta) {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/beta";
|
||||
} else {
|
||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/logs/prod";
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import fr.free.nrw.commons.MediaWikiImageView;
|
|||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||
import fr.free.nrw.commons.delete.DeleteTask;
|
||||
import fr.free.nrw.commons.delete.ReasonBuilder;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
|
|
@ -156,7 +157,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
|
||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) (getParentFragment().getParentFragment());
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
editable = savedInstanceState.getBoolean("editable");
|
||||
|
|
@ -222,12 +223,14 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
};
|
||||
view.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
|
||||
locale = getResources().getConfiguration().locale;
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
((ContributionsFragment)(getParentFragment().getParentFragment())).nearbyNoificationCardView.setVisibility(View.GONE);
|
||||
media = detailProvider.getMediaAtPosition(index);
|
||||
if (media == null) {
|
||||
// Ask the detail provider to ping us when we're ready
|
||||
|
|
@ -271,7 +274,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
// Local files have no filename yet
|
||||
if(media.getFilename() == null) {
|
||||
if (media.getFilename() == null) {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
try {
|
||||
|
|
@ -343,7 +346,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
rebuildCatList();
|
||||
|
||||
if(media.getCreator() == null || media.getCreator().equals("")) {
|
||||
if (media.getCreator() == null || media.getCreator().equals("")) {
|
||||
authorLayout.setVisibility(GONE);
|
||||
} else {
|
||||
author.setText(media.getCreator());
|
||||
|
|
@ -357,7 +360,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
if (!TextUtils.isEmpty(licenseLink(media))) {
|
||||
openWebBrowser(licenseLink(media));
|
||||
} else {
|
||||
if(isCategoryImage) {
|
||||
if (isCategoryImage) {
|
||||
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);
|
||||
|
|
@ -427,14 +430,14 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@OnClick(R.id.seeMore)
|
||||
public void onSeeMoreClicked(){
|
||||
if(nominatedForDeletion.getVisibility()== VISIBLE) {
|
||||
if (nominatedForDeletion.getVisibility()== VISIBLE) {
|
||||
openWebBrowser(media.getFilePageTitle().getMobileUri().toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void enableDeleteButton(boolean visibility) {
|
||||
delete.setEnabled(visibility);
|
||||
if(visibility) {
|
||||
if (visibility) {
|
||||
delete.setTextColor(getResources().getColor(R.color.primaryTextColor));
|
||||
} else {
|
||||
delete.setTextColor(getResources().getColor(R.color.deleteButtonLight));
|
||||
|
|
@ -444,15 +447,26 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
|||
private void rebuildCatList() {
|
||||
categoryContainer.removeAllViews();
|
||||
// @fixme add the category items
|
||||
for (String cat : categoryNames) {
|
||||
View catLabel = buildCatLabel(cat, categoryContainer);
|
||||
|
||||
//As per issue #1826(see https://github.com/commons-app/apps-android-commons/issues/1826), some categories come suffixed with strings prefixed with |. As per the discussion
|
||||
//that was meant for alphabetical sorting of the categories and can be safely removed.
|
||||
for (int i = 0; i < categoryNames.size(); i++) {
|
||||
String categoryName = categoryNames.get(i);
|
||||
//Removed everything after '|'
|
||||
int indexOfPipe = categoryName.indexOf('|');
|
||||
if (indexOfPipe != -1) {
|
||||
categoryName = categoryName.substring(0, indexOfPipe);
|
||||
//Set the updated category to the list as well
|
||||
categoryNames.set(i, categoryName);
|
||||
}
|
||||
View catLabel = buildCatLabel(categoryName, categoryContainer);
|
||||
categoryContainer.addView(catLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private View buildCatLabel(final String catName, ViewGroup categoryContainer) {
|
||||
final View item = LayoutInflater.from(getContext()).inflate(R.layout.detail_category_item, categoryContainer, false);
|
||||
final CompatTextView textView = (CompatTextView) item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||
final CompatTextView textView = item.findViewById(R.id.mediaDetailCategoryItemText);
|
||||
|
||||
textView.setText(catName);
|
||||
if (categoriesLoaded && categoriesPresent) {
|
||||
|
|
|
|||
|
|
@ -38,7 +38,6 @@ import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
|||
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.explore.SearchActivity;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
|
|
@ -99,7 +98,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
pager.setAdapter(adapter);
|
||||
pager.setCurrentItem(pageNumber, false);
|
||||
|
||||
if(getActivity() == null) {
|
||||
if (getActivity() == null) {
|
||||
Timber.d("Returning as activity is destroyed!");
|
||||
return;
|
||||
}
|
||||
|
|
@ -133,11 +132,11 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(getActivity() == null) {
|
||||
if (getActivity() == null) {
|
||||
Timber.d("Returning as activity is destroyed!");
|
||||
return true;
|
||||
}
|
||||
MediaDetailProvider provider = (MediaDetailProvider) getActivity();
|
||||
MediaDetailProvider provider = (MediaDetailProvider) getParentFragment();
|
||||
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_bookmark_current_image:
|
||||
|
|
@ -156,7 +155,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
viewIntent.setAction(ACTION_VIEW);
|
||||
viewIntent.setData(m.getFilePageTitle().getMobileUri());
|
||||
//check if web browser available
|
||||
if(viewIntent.resolveActivity(getActivity().getPackageManager()) != null){
|
||||
if (viewIntent.resolveActivity(getActivity().getPackageManager()) != null){
|
||||
startActivity(viewIntent);
|
||||
} else {
|
||||
Toast toast = Toast.makeText(getContext(), getString(R.string.no_web_browser), LENGTH_SHORT);
|
||||
|
|
@ -174,12 +173,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
return true;
|
||||
case R.id.menu_retry_current_image:
|
||||
// Retry
|
||||
((ContributionsActivity) getActivity()).retryUpload(pager.getCurrentItem());
|
||||
//((MainActivity) getActivity()).retryUpload(pager.getCurrentItem());
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
return true;
|
||||
case R.id.menu_cancel_current_image:
|
||||
// todo: delete image
|
||||
((ContributionsActivity) getActivity()).deleteUpload(pager.getCurrentItem());
|
||||
//((MainActivity) getActivity()).deleteUpload(pager.getCurrentItem());
|
||||
getActivity().getSupportFragmentManager().popBackStack();
|
||||
return true;
|
||||
default:
|
||||
|
|
@ -193,7 +192,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
* @param media
|
||||
*/
|
||||
private void setWallpaper(Media media) {
|
||||
if(media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
|
||||
if (media.getImageUrl() == null || media.getImageUrl().isEmpty()) {
|
||||
Timber.d("Media URL not present");
|
||||
return;
|
||||
}
|
||||
|
|
@ -254,7 +253,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
menu.clear(); // see http://stackoverflow.com/a/8495697/17865
|
||||
inflater.inflate(R.menu.fragment_image_detail, menu);
|
||||
if (pager != null) {
|
||||
MediaDetailProvider provider = (MediaDetailProvider) getActivity();
|
||||
MediaDetailProvider provider = (MediaDetailProvider) getParentFragment();
|
||||
if(provider == null) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -326,7 +325,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
|
||||
@Override
|
||||
public void onPageScrolled(int i, float v, int i2) {
|
||||
if(getActivity() == null) {
|
||||
if(getParentFragment().getActivity() == null) {
|
||||
Timber.d("Returning as activity is destroyed!");
|
||||
return;
|
||||
}
|
||||
|
|
@ -347,7 +346,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
getActivity().supportInvalidateOptionsMenu();
|
||||
getParentFragment().getActivity().supportInvalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -381,22 +380,22 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
public Fragment getItem(int i) {
|
||||
if (i == 0) {
|
||||
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
||||
if(getActivity() == null) {
|
||||
if(getParentFragment().getActivity() == null) {
|
||||
Timber.d("Skipping getItem. Returning as activity is destroyed!");
|
||||
return null;
|
||||
}
|
||||
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
|
||||
pager.postDelayed(() -> getParentFragment().getActivity().supportInvalidateOptionsMenu(), 5);
|
||||
}
|
||||
return MediaDetailFragment.forMedia(i, editable, isFeaturedImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if(getActivity() == null) {
|
||||
if (getActivity() == null) {
|
||||
Timber.d("Skipping getCount. Returning as activity is destroyed!");
|
||||
return 0;
|
||||
}
|
||||
return ((MediaDetailProvider) getActivity()).getTotalMediaCount();
|
||||
return ((MediaDetailProvider) getParentFragment()).getTotalMediaCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -244,9 +244,9 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
Timber.d("Central auth token isn't valid. Trying to fetch a fresh token");
|
||||
api.removeAllCookies();
|
||||
String loginResultCode = login(AccountUtil.getUserName(context), AccountUtil.getPassword(context));
|
||||
if(loginResultCode.equals("PASS")) {
|
||||
if (loginResultCode.equals("PASS")) {
|
||||
return getCentralAuthToken();
|
||||
} else if(loginResultCode.equals("2FA")) {
|
||||
} else if (loginResultCode.equals("2FA")) {
|
||||
Timber.e("Cannot refresh session for 2FA enabled user. Login required");
|
||||
} else {
|
||||
Timber.e("Error occurred in refreshing session. Error code is %s", loginResultCode);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class CustomMwApi {
|
|||
}
|
||||
|
||||
public String getAuthCookie() {
|
||||
if(authCookie == null){
|
||||
if (authCookie == null){
|
||||
authCookie = "";
|
||||
List<Cookie> cookies = client.getCookieStore().getCookies();
|
||||
for(Cookie cookie: cookies) {
|
||||
|
|
@ -102,14 +102,14 @@ public class CustomMwApi {
|
|||
}
|
||||
|
||||
public String getUserID() throws IOException {
|
||||
if(this.userID == null || this.userID.equals("0")) {
|
||||
if (this.userID == null || this.userID.equals("0")) {
|
||||
this.validateLogin();
|
||||
}
|
||||
return userID;
|
||||
}
|
||||
|
||||
public String getUserName() throws IOException {
|
||||
if(this.userID == null || this.userID.equals("0")) {
|
||||
if (this.userID == null || this.userID.equals("0")) {
|
||||
this.validateLogin();
|
||||
}
|
||||
return userName;
|
||||
|
|
@ -122,7 +122,7 @@ public class CustomMwApi {
|
|||
String token = tokenData.getString("/api/login/@token");
|
||||
CustomApiResult confirmData = this.action("login").param("lgname", username).param("lgpassword", password).param("lgtoken", token).post();
|
||||
String finalResult = confirmData.getString("/api/login/@result");
|
||||
if(finalResult.equals("Success")) {
|
||||
if (finalResult.equals("Success")) {
|
||||
isLoggedIn = true;
|
||||
}
|
||||
return finalResult;
|
||||
|
|
@ -149,7 +149,7 @@ public class CustomMwApi {
|
|||
.data("comment", comment)
|
||||
.data("filename", filename)
|
||||
.sendProgressListener(uploadProgressListener);
|
||||
if(length != -1) {
|
||||
if (length != -1) {
|
||||
builder.file("file", filename, file, length);
|
||||
} else {
|
||||
builder.file("file", filename, file);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,11 @@ import android.os.Build;
|
|||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionController;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||
|
|
@ -24,8 +26,9 @@ class DirectUpload {
|
|||
}
|
||||
|
||||
// These permission requests will be handled by the Fragments.
|
||||
// Do not use requestCode 1 as it will conflict with NearbyActivity's requestCodes
|
||||
// Do not use requestCode 1 as it will conflict with NearbyFragment's requestCodes
|
||||
void initiateGalleryUpload() {
|
||||
Log.d("deneme7","initiateGalleryUpload");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(fragment.getActivity(), READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
|
||||
if (fragment.shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
|
||||
|
|
@ -33,40 +36,42 @@ class DirectUpload {
|
|||
.setMessage(fragment.getActivity().getString(R.string.read_storage_permission_rationale))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Timber.d("Requesting permissions for read external storage");
|
||||
fragment.requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 4);
|
||||
fragment.getActivity().requestPermissions
|
||||
(new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
fragment.requestPermissions(new String[]{READ_EXTERNAL_STORAGE},
|
||||
4);
|
||||
fragment.getActivity().requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, PermissionUtils.GALLERY_PERMISSION_FROM_NEARBY_MAP);
|
||||
}
|
||||
} else {
|
||||
controller.startGalleryPick();
|
||||
controller.startSingleGalleryPick();
|
||||
}
|
||||
}
|
||||
else {
|
||||
controller.startGalleryPick();
|
||||
controller.startSingleGalleryPick();
|
||||
}
|
||||
}
|
||||
|
||||
void initiateCameraUpload() {
|
||||
Log.d("deneme7","initiateCameraUpload");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (ContextCompat.checkSelfPermission(fragment.getActivity(), WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
|
||||
if (fragment.shouldShowRequestPermissionRationale(WRITE_EXTERNAL_STORAGE)) {
|
||||
new AlertDialog.Builder(fragment.getActivity())
|
||||
.setMessage(fragment.getActivity().getString(R.string.write_storage_permission_rationale))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
fragment.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 5);
|
||||
fragment.getActivity().requestPermissions
|
||||
(new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP);
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
fragment.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, 5);
|
||||
fragment.getActivity().requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE}, PermissionUtils.CAMERA_PERMISSION_FROM_NEARBY_MAP);
|
||||
}
|
||||
} else {
|
||||
controller.startCameraCapture();
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ public class NearbyController {
|
|||
this.prefs = prefs;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepares Place list to make their distance information update later.
|
||||
*
|
||||
|
|
@ -46,15 +47,15 @@ public class NearbyController {
|
|||
* @return NearbyPlacesInfo a variable holds Place list without distance information
|
||||
* and boundary coordinates of current Place List
|
||||
*/
|
||||
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng) throws IOException {
|
||||
|
||||
public NearbyPlacesInfo loadAttractionsFromLocation(LatLng curLatLng, boolean returnClosestResult) throws IOException {
|
||||
Timber.d("Loading attractions near %s", curLatLng);
|
||||
NearbyPlacesInfo nearbyPlacesInfo = new NearbyPlacesInfo();
|
||||
|
||||
if (curLatLng == null) {
|
||||
Timber.d("Loading attractions neari, but curLatLng is null");
|
||||
return null;
|
||||
}
|
||||
List<Place> places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage());
|
||||
List<Place> places = nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage(), returnClosestResult);
|
||||
|
||||
if (null != places && places.size() > 0) {
|
||||
LatLng[] boundaryCoordinates = {places.get(0).location, // south
|
||||
|
|
@ -168,7 +169,7 @@ public class NearbyController {
|
|||
}
|
||||
|
||||
public class NearbyPlacesInfo {
|
||||
List<Place> placeList; // List of nearby places
|
||||
LatLng[] boundaryCoordinates; // Corners of nearby area
|
||||
public List<Place> placeList; // List of nearby places
|
||||
public LatLng[] boundaryCoordinates; // Corners of nearby area
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,20 +5,19 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.BottomSheetBehavior;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
|
|
@ -33,11 +32,11 @@ import javax.inject.Named;
|
|||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
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.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.UriSerializer;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
|
|
@ -47,23 +46,18 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
|||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
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.LOCATION_SIGNIFICANTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED;
|
||||
import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.PERMISSION_JUST_GRANTED;
|
||||
|
||||
|
||||
public class NearbyActivity extends NavigationBaseActivity implements LocationUpdateListener,
|
||||
public class NearbyFragment extends CommonsDaggerSupportFragment
|
||||
implements LocationUpdateListener,
|
||||
WikidataEditListener.WikidataP18EditListener {
|
||||
|
||||
private static final int LOCATION_REQUEST = 1;
|
||||
|
||||
@BindView(R.id.progressBar)
|
||||
ProgressBar progressBar;
|
||||
|
||||
@BindView(R.id.bottom_sheet)
|
||||
LinearLayout bottomSheet;
|
||||
@BindView(R.id.bottom_sheet_details)
|
||||
|
|
@ -77,59 +71,127 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
LocationServiceManager locationManager;
|
||||
@Inject
|
||||
NearbyController nearbyController;
|
||||
@Inject WikidataEditListener wikidataEditListener;
|
||||
|
||||
@Inject
|
||||
@Named("application_preferences") SharedPreferences applicationPrefs;
|
||||
private LatLng curLatLng;
|
||||
private Bundle bundle;
|
||||
private Disposable placesDisposable;
|
||||
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
|
||||
private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
|
||||
private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
|
||||
WikidataEditListener wikidataEditListener;
|
||||
@Inject
|
||||
@Named("application_preferences")
|
||||
SharedPreferences applicationPrefs;
|
||||
|
||||
public NearbyMapFragment nearbyMapFragment;
|
||||
private NearbyListFragment nearbyListFragment;
|
||||
private static final String TAG_RETAINED_MAP_FRAGMENT = NearbyMapFragment.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 Bundle bundle;
|
||||
private BottomSheetBehavior bottomSheetBehavior; // Behavior for list bottom sheet
|
||||
private BottomSheetBehavior bottomSheetBehaviorForDetails; // Behavior for details bottom sheet
|
||||
|
||||
private LatLng curLatLng;
|
||||
private Disposable placesDisposable;
|
||||
private boolean lockNearbyView; //Determines if the nearby places needs to be refreshed
|
||||
public View view;
|
||||
private Snackbar snackbar;
|
||||
|
||||
private LatLng lastKnownLocation;
|
||||
|
||||
private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
|
||||
private BroadcastReceiver broadcastReceiver;
|
||||
|
||||
private boolean isListShowcaseAdded = false;
|
||||
private boolean isMapShowCaseAdded = false;
|
||||
|
||||
private LatLng lastKnownLocation;
|
||||
|
||||
private MaterialShowcaseView secondSingleShowCaseView;
|
||||
private boolean onOrientationChanged = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_nearby);
|
||||
ButterKnife.bind(this);
|
||||
resumeFragment();
|
||||
bundle = new Bundle();
|
||||
|
||||
initBottomSheetBehaviour();
|
||||
initDrawer();
|
||||
wikidataEditListener.setAuthenticationStateListener(this);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
|
||||
/*// Resume the fragment if exist
|
||||
resumeFragment();*/
|
||||
bundle = new Bundle();
|
||||
initBottomSheetBehaviour();
|
||||
wikidataEditListener.setAuthenticationStateListener(this);
|
||||
this.view = view;
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
onOrientationChanged = true;
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide or expand bottom sheet according to states of all sheets
|
||||
*/
|
||||
public void listOptionMenuIteClicked() {
|
||||
if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
|
||||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}else if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume fragments if they exists
|
||||
*/
|
||||
private void resumeFragment() {
|
||||
// Find the retained fragment on activity restarts
|
||||
nearbyMapFragment = getMapFragment();
|
||||
nearbyListFragment = getListFragment();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyMapFragment getMapFragment() {
|
||||
return (NearbyMapFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeMapFragment() {
|
||||
if (nearbyMapFragment != null) {
|
||||
android.support.v4.app.FragmentManager fm = getFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyMapFragment).commit();
|
||||
nearbyMapFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list fragment added to child fragment manager previously, if exists.
|
||||
*/
|
||||
private NearbyListFragment getListFragment() {
|
||||
return (NearbyListFragment) getChildFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeListFragment() {
|
||||
if (nearbyListFragment != null) {
|
||||
android.support.v4.app.FragmentManager fm = getFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyListFragment).commit();
|
||||
nearbyListFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize bottom sheet behaviour (sheet for map list.) Set height 9/16 of all window.
|
||||
* Add callback for bottom sheet changes, so that we can sync it with bottom sheet for details
|
||||
* (sheet for nearby details)
|
||||
*/
|
||||
private void initBottomSheetBehaviour() {
|
||||
|
||||
transparentView.setAlpha(0);
|
||||
|
||||
bottomSheet.getLayoutParams().height = getWindowManager()
|
||||
bottomSheet.getLayoutParams().height = getActivity().getWindowManager()
|
||||
.getDefaultDisplay().getHeight() / 16 * 9;
|
||||
bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
|
||||
// TODO initProperBottomSheetBehavior();
|
||||
bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
|
||||
|
||||
@Override
|
||||
|
|
@ -148,257 +210,44 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
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);
|
||||
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
|
||||
// Handle item selection
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_display_list:
|
||||
if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_COLLAPSED || bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_HIDDEN){
|
||||
bottomSheetBehaviorForDetails.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
|
||||
}else if(bottomSheetBehavior.getState()==BottomSheetBehavior.STATE_EXPANDED){
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestLocationPermissions() {
|
||||
if (!isFinishing()) {
|
||||
locationManager.requestPermissions(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case LOCATION_REQUEST: {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Timber.d("Location permission granted, refreshing view");
|
||||
//Still need to check if GPS is enabled
|
||||
checkGps();
|
||||
lastKnownLocation = locationManager.getLKL();
|
||||
refreshView(PERMISSION_JUST_GRANTED);
|
||||
} else {
|
||||
//If permission not granted, go to page that says Nearby Places cannot be displayed
|
||||
hideProgressBar();
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// This is needed to allow the request codes from the Fragments to be routed appropriately
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
private void showLocationPermissionDeniedErrorDialog() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.nearby_needs_permissions)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
|
||||
//will ask for the location permission again
|
||||
checkGps();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
//dismiss dialog and finish activity
|
||||
dialog.cancel();
|
||||
finish();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
private void checkGps() {
|
||||
if (!locationManager.isProviderEnabled()) {
|
||||
Timber.d("GPS is not enabled");
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(R.string.gps_disabled)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.enable_gps,
|
||||
(dialog, id) -> {
|
||||
Intent callGPSSettingIntent = new Intent(
|
||||
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
Timber.d("Loaded settings page");
|
||||
startActivityForResult(callGPSSettingIntent, 1);
|
||||
})
|
||||
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
Timber.d("GPS is enabled");
|
||||
checkLocationPermission();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLocationPermission() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (locationManager.isLocationPermissionGranted()) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
// Should we show an explanation?
|
||||
if (locationManager.isPermissionExplanationRequired(this)) {
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
new AlertDialog.Builder(this)
|
||||
.setMessage(getString(R.string.location_permission_rationale_nearby))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
requestLocationPermissions();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestLocationPermissions();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onLocationChangedMedium(LatLng latLng) {
|
||||
// For nearby map actions, there are no differences between 500 meter location change (aka medium change) and slight change
|
||||
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == 1) {
|
||||
Timber.d("User is back from Settings page");
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
public void onWikidataEditSuccessful() {
|
||||
refreshView(MAP_UPDATED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
locationManager.addLocationListener(this);
|
||||
registerLocationUpdates();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (placesDisposable != null) {
|
||||
placesDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
lockNearbyView = false;
|
||||
checkGps();
|
||||
addNetworkBroadcastReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// this means that this activity will not be recreated now, user is leaving it
|
||||
// or the activity is otherwise finishing
|
||||
if(isFinishing()) {
|
||||
// we will not need this fragment anymore, this may also be a good place to signal
|
||||
// to the retained fragment object to perform its own cleanup.
|
||||
removeMapFragment();
|
||||
removeListFragment();
|
||||
|
||||
}
|
||||
unregisterReceiver(broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
locationManager.removeLocationListener(this);
|
||||
locationManager.unregisterLocationManager();
|
||||
|
||||
}
|
||||
|
||||
private void addNetworkBroadcastReceiver() {
|
||||
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
|
||||
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(NearbyActivity.this)) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
ViewUtil.showLongToast(NearbyActivity.this, getString(R.string.no_internet));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.registerReceiver(broadcastReceiver, intentFilter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method should be the single point to load/refresh nearby places
|
||||
*
|
||||
* @param locationChangeType defines if location shanged significantly or slightly
|
||||
*/
|
||||
private void refreshView(LocationChangeType locationChangeType) {
|
||||
private void refreshView(LocationServiceManager.LocationChangeType locationChangeType) {
|
||||
Timber.d("Refreshing nearby places");
|
||||
if (lockNearbyView) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(this)) {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
hideProgressBar();
|
||||
return;
|
||||
}
|
||||
|
|
@ -408,8 +257,10 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
|
||||
if (curLatLng != null && curLatLng.equals(lastLocation)
|
||||
&& !locationChangeType.equals(MAP_UPDATED)) { //refresh view only if location has changed
|
||||
if (!onOrientationChanged) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
curLatLng = lastLocation;
|
||||
|
||||
if (locationChangeType.equals(PERMISSION_JUST_GRANTED)) {
|
||||
|
|
@ -421,9 +272,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
onOrientation changed is true whenever activities orientation changes. After orientation
|
||||
change we want to refresh map significantly, doesn't matter if location changed significantly
|
||||
or not. Thus, we included onOrientatinChanged boolean to if clause
|
||||
*/
|
||||
if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
|| locationChangeType.equals(PERMISSION_JUST_GRANTED)
|
||||
|| locationChangeType.equals(MAP_UPDATED)) {
|
||||
|| locationChangeType.equals(MAP_UPDATED)
|
||||
|| onOrientationChanged) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
//TODO: This hack inserts curLatLng before populatePlaces is called (see #1440). Ideally a proper fix should be found
|
||||
|
|
@ -435,7 +292,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||
|
||||
placesDisposable = Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng))
|
||||
.loadAttractionsFromLocation(curLatLng, false))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::populatePlaces,
|
||||
|
|
@ -455,40 +312,8 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(android.R.string.ok, (dialog, which) -> {
|
||||
requestLocationPermissions();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.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) {
|
||||
Timber.d("Populating nearby places");
|
||||
List<Place> placeList = nearbyPlacesInfo.placeList;
|
||||
LatLng[] boundaryCoordinates = nearbyPlacesInfo.boundaryCoordinates;
|
||||
Gson gson = new GsonBuilder()
|
||||
|
|
@ -499,7 +324,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
String gsonBoundaryCoordinates = gson.toJson(boundaryCoordinates);
|
||||
|
||||
if (placeList.size() == 0) {
|
||||
ViewUtil.showSnackbar(findViewById(R.id.container), R.string.no_nearby);
|
||||
ViewUtil.showSnackbar(view.findViewById(R.id.container), R.string.no_nearby);
|
||||
}
|
||||
|
||||
bundle.putString("PlaceList", gsonPlaceList);
|
||||
|
|
@ -520,47 +345,13 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
updateMapFragment(false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock nearby view updates while updating map or list. Because we don't want new update calls
|
||||
* when we already updating for old location update.
|
||||
* @param lock
|
||||
*/
|
||||
private void lockNearbyView(boolean lock) {
|
||||
if (lock) {
|
||||
lockNearbyView = true;
|
||||
|
|
@ -573,45 +364,14 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
}
|
||||
}
|
||||
|
||||
private void hideProgressBar() {
|
||||
if (progressBar != null) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private NearbyMapFragment getMapFragment() {
|
||||
return (NearbyMapFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_MAP_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeMapFragment() {
|
||||
if (nearbyMapFragment != null) {
|
||||
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyMapFragment).commit();
|
||||
nearbyMapFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
private NearbyListFragment getListFragment() {
|
||||
return (NearbyListFragment) getSupportFragmentManager().findFragmentByTag(TAG_RETAINED_LIST_FRAGMENT);
|
||||
}
|
||||
|
||||
private void removeListFragment() {
|
||||
if (nearbyListFragment != null) {
|
||||
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
|
||||
fm.beginTransaction().remove(nearbyListFragment).commit();
|
||||
nearbyListFragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMapFragment(boolean isSlightUpdate) {
|
||||
/*
|
||||
* Significant update means updating nearby place markers. Slightly update means only
|
||||
* updating current location marker and camera target.
|
||||
* We update our map Significantly on each 1000 meter change, but we can't never know
|
||||
* the frequency of nearby places. Thus we check if we are close to the boundaries of
|
||||
* our nearby markers, we update our map Significantly.
|
||||
* */
|
||||
|
||||
Significant update means updating nearby place markers. Slightly update means only
|
||||
updating current location marker and camera target.
|
||||
We update our map Significantly on each 1000 meter change, but we can't never know
|
||||
the frequency of nearby places. Thus we check if we are close to the boundaries of
|
||||
our nearby markers, we update our map Significantly.
|
||||
*/
|
||||
NearbyMapFragment nearbyMapFragment = getMapFragment();
|
||||
|
||||
if (nearbyMapFragment != null && curLatLng != null) {
|
||||
|
|
@ -627,7 +387,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
|| curLatLng.getLongitude() >= nearbyMapFragment.boundaryCoordinates[3].getLongitude())) {
|
||||
// populate places
|
||||
placesDisposable = Observable.fromCallable(() -> nearbyController
|
||||
.loadAttractionsFromLocation(curLatLng))
|
||||
.loadAttractionsFromLocation(curLatLng, false))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(this::populatePlaces,
|
||||
|
|
@ -642,6 +402,15 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
If this is the map update just after orientation change, then it is not a slight update
|
||||
anymore. We want to significantly update map after each orientation change
|
||||
*/
|
||||
if (onOrientationChanged) {
|
||||
isSlightUpdate = false;
|
||||
onOrientationChanged = false;
|
||||
}
|
||||
|
||||
if (isSlightUpdate) {
|
||||
nearbyMapFragment.setBundleForUpdtes(bundle);
|
||||
nearbyMapFragment.updateMapSlightly();
|
||||
|
|
@ -668,7 +437,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
* Calls fragment for map view.
|
||||
*/
|
||||
private void setMapFragment() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
|
||||
nearbyMapFragment = new NearbyMapFragment();
|
||||
nearbyMapFragment.setArguments(bundle);
|
||||
fragmentTransaction.replace(R.id.container, nearbyMapFragment, TAG_RETAINED_MAP_FRAGMENT);
|
||||
|
|
@ -679,7 +448,7 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
* Calls fragment for list view.
|
||||
*/
|
||||
private void setListFragment() {
|
||||
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
|
||||
nearbyListFragment = new NearbyListFragment();
|
||||
nearbyListFragment.setArguments(bundle);
|
||||
fragmentTransaction.replace(R.id.container_sheet, nearbyListFragment, TAG_RETAINED_LIST_FRAGMENT);
|
||||
|
|
@ -688,26 +457,235 @@ public class NearbyActivity extends NavigationBaseActivity implements LocationUp
|
|||
fragmentTransaction.commitAllowingStateLoss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSignificantly(LatLng latLng) {
|
||||
private void hideProgressBar() {
|
||||
if (progressBar != null) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(getActivity())) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(getString(R.string.location_permission_rationale_nearby))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
requestLocationPermissions();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestLocationPermissions();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
locationManager.registerLocationManager();
|
||||
}
|
||||
}
|
||||
|
||||
private void requestLocationPermissions() {
|
||||
if (!getActivity().isFinishing()) {
|
||||
locationManager.requestPermissions(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
private void showLocationPermissionDeniedErrorDialog() {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.nearby_needs_permissions)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.give_permission, (dialog, which) -> {
|
||||
//will ask for the location permission again
|
||||
checkGps();
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
//dismiss dialog and send user to contributions tab instead
|
||||
dialog.cancel();
|
||||
((MainActivity)getActivity()).viewPager.setCurrentItem(((MainActivity)getActivity()).CONTRIBUTIONS_TAB_POSITION);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks device GPS permission first for all API levels
|
||||
*/
|
||||
private void checkGps() {
|
||||
Timber.d("checking GPS");
|
||||
if (!locationManager.isProviderEnabled()) {
|
||||
Timber.d("GPS is not enabled");
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(R.string.gps_disabled)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.enable_gps,
|
||||
(dialog, id) -> {
|
||||
Intent callGPSSettingIntent = new Intent(
|
||||
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
Timber.d("Loaded settings page");
|
||||
startActivityForResult(callGPSSettingIntent, 1);
|
||||
})
|
||||
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
} else {
|
||||
Timber.d("GPS is enabled");
|
||||
checkLocationPermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method ideally should be called from inside of CheckGPS method. If device GPS is enabled
|
||||
* then we need to control app specific permissions for >=M devices. For other devices, enabled
|
||||
* GPS is enough for nearby, so directly call refresh view.
|
||||
*/
|
||||
private void checkLocationPermission() {
|
||||
Timber.d("Checking location permission");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (locationManager.isLocationPermissionGranted()) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
// Should we show an explanation?
|
||||
if (locationManager.isPermissionExplanationRequired(getActivity())) {
|
||||
// Show an explanation to the user *asynchronously* -- don't block
|
||||
// this thread waiting for the user's response! After the user
|
||||
// sees the explanation, try again to request the permission.
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(getString(R.string.location_permission_rationale_nearby))
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
requestLocationPermissions();
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||
showLocationPermissionDeniedErrorDialog();
|
||||
dialog.cancel();
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
|
||||
} else {
|
||||
// No explanation needed, we can request the permission.
|
||||
requestLocationPermissions();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocationChangedSlightly(LatLng latLng) {
|
||||
refreshView(LOCATION_SLIGHTLY_CHANGED);
|
||||
}
|
||||
|
||||
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private void showErrorMessage(String message) {
|
||||
ViewUtil.showLongToast(NearbyActivity.this, message);
|
||||
ViewUtil.showLongToast(getActivity(), message);
|
||||
}
|
||||
|
||||
private void addNetworkBroadcastReceiver() {
|
||||
IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION);
|
||||
snackbar = Snackbar.make(transparentView , R.string.no_internet, Snackbar.LENGTH_INDEFINITE);
|
||||
|
||||
broadcastReceiver = new BroadcastReceiver() {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (snackbar != null) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getActivity())) {
|
||||
refreshView(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
snackbar.dismiss();
|
||||
} else {
|
||||
snackbar.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
getActivity().registerReceiver(broadcastReceiver, intentFilter);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWikidataEditSuccessful() {
|
||||
refreshView(MAP_UPDATED);
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
// Resume the fragment if exist
|
||||
resumeFragment();
|
||||
}
|
||||
|
||||
public void onTabSelected(boolean onOrientationChanged) {
|
||||
Timber.d("On nearby tab selected");
|
||||
this.onOrientationChanged = onOrientationChanged;
|
||||
performNearbyOperations();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls nearby operations in required order.
|
||||
*/
|
||||
private void performNearbyOperations() {
|
||||
locationManager.addLocationListener(this);
|
||||
registerLocationUpdates();
|
||||
lockNearbyView = false;
|
||||
checkGps();
|
||||
addNetworkBroadcastReceiver();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (placesDisposable != null) {
|
||||
placesDisposable.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
snackbar = null;
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
// this means that this activity will not be recreated now, user is leaving it
|
||||
// or the activity is otherwise finishing
|
||||
if(getActivity().isFinishing()) {
|
||||
// we will not need this fragment anymore, this may also be a good place to signal
|
||||
// to the retained fragment object to perform its own cleanup.
|
||||
//removeMapFragment();
|
||||
removeListFragment();
|
||||
|
||||
}
|
||||
if (broadcastReceiver != null) {
|
||||
getActivity().unregisterReceiver(broadcastReceiver);
|
||||
broadcastReceiver = null;
|
||||
}
|
||||
|
||||
if (locationManager != null) {
|
||||
locationManager.removeLocationListener(this);
|
||||
locationManager.unregisterLocationManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ public class NearbyListFragment extends DaggerFragment {
|
|||
ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Timber.d("NearbyListFragment created");
|
||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||
View view = inflater.inflate(R.layout.fragment_nearby_list, container, false);
|
||||
recyclerView = view.findViewById(R.id.listView);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.content.Intent;
|
|||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
|
|
@ -16,6 +15,7 @@ import android.support.design.widget.BottomSheetBehavior;
|
|||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -109,7 +109,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
private ContributionController controller;
|
||||
public ContributionController controller;
|
||||
private DirectUpload directUpload;
|
||||
|
||||
private Place place;
|
||||
|
|
@ -122,9 +122,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.06;
|
||||
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
|
||||
|
||||
|
|
@ -177,6 +175,13 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
|
@ -214,7 +219,12 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map slightly means it doesn't updates all nearby markers around. It just updates
|
||||
* location tracker marker of user.
|
||||
*/
|
||||
public void updateMapSlightly() {
|
||||
Timber.d("updateMapSlightly called, bundle is:"+bundleForUpdtes);
|
||||
if (mapboxMap != null) {
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||
|
|
@ -229,7 +239,13 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map significantly means it updates nearby markers and location tracker marker. It is
|
||||
* called when user is out of boundaries (south, north, east or west) of markers drawn by
|
||||
* previous nearby call.
|
||||
*/
|
||||
public void updateMapSignificantly() {
|
||||
Timber.d("updateMapSignificantly called, bundle is:"+bundleForUpdtes);
|
||||
if (mapboxMap != null) {
|
||||
if (bundleForUpdtes != null) {
|
||||
Gson gson = new GsonBuilder()
|
||||
|
|
@ -281,7 +297,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
|
||||
// Make camera to follow user on location change
|
||||
CameraPosition position ;
|
||||
if(ViewUtil.isPortrait(getActivity())){
|
||||
if (ViewUtil.isPortrait(getActivity())){
|
||||
position = new CameraPosition.Builder()
|
||||
.target(isBottomListSheetExpanded ?
|
||||
new LatLng(curMapBoxLatLng.getLatitude()- CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT,
|
||||
|
|
@ -309,13 +325,19 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates camera position according to list sheet status. If list sheet is collapsed, camera
|
||||
* focus should be in the center. If list sheet is expanded, camera focus should be visible
|
||||
* on the gap between list sheet and tab layout.
|
||||
* @param isBottomListSheetExpanded
|
||||
*/
|
||||
private void updateMapCameraAccordingToBottomSheet(boolean isBottomListSheetExpanded) {
|
||||
CameraPosition position;
|
||||
this.isBottomListSheetExpanded = isBottomListSheetExpanded;
|
||||
if (mapboxMap != null && curLatLng != null) {
|
||||
if (isBottomListSheetExpanded) {
|
||||
// Make camera to follow user on location change
|
||||
if(ViewUtil.isPortrait(getActivity())) {
|
||||
if (ViewUtil.isPortrait(getActivity())) {
|
||||
position = new CameraPosition.Builder()
|
||||
.target(new LatLng(curLatLng.getLatitude() - CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT,
|
||||
curLatLng.getLongitude())) // Sets the new camera target above
|
||||
|
|
@ -345,39 +367,40 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
}
|
||||
|
||||
private void initViews() {
|
||||
bottomSheetList = getActivity().findViewById(R.id.bottom_sheet);
|
||||
Timber.d("initViews called");
|
||||
bottomSheetList = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet);
|
||||
bottomSheetListBehavior = BottomSheetBehavior.from(bottomSheetList);
|
||||
bottomSheetDetails = getActivity().findViewById(R.id.bottom_sheet_details);
|
||||
bottomSheetDetails = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet_details);
|
||||
bottomSheetDetailsBehavior = BottomSheetBehavior.from(bottomSheetDetails);
|
||||
bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
bottomSheetDetails.setVisibility(View.VISIBLE);
|
||||
|
||||
fabPlus = getActivity().findViewById(R.id.fab_plus);
|
||||
fabCamera = getActivity().findViewById(R.id.fab_camera);
|
||||
fabGallery = getActivity().findViewById(R.id.fab_galery);
|
||||
fabRecenter = getActivity().findViewById(R.id.fab_recenter);
|
||||
fabPlus = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_plus);
|
||||
fabCamera = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_camera);
|
||||
fabGallery = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_galery);
|
||||
fabRecenter = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.fab_recenter);
|
||||
|
||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||
fab_open = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.fab_open);
|
||||
fab_close = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.fab_close);
|
||||
rotate_forward = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.rotate_forward);
|
||||
rotate_backward = AnimationUtils.loadAnimation(getParentFragment().getActivity(), R.anim.rotate_backward);
|
||||
|
||||
transparentView = getActivity().findViewById(R.id.transparentView);
|
||||
transparentView = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.transparentView);
|
||||
|
||||
description = getActivity().findViewById(R.id.description);
|
||||
title = getActivity().findViewById(R.id.title);
|
||||
distance = getActivity().findViewById(R.id.category);
|
||||
icon = getActivity().findViewById(R.id.icon);
|
||||
description = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.description);
|
||||
title = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.title);
|
||||
distance = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.category);
|
||||
icon = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.icon);
|
||||
|
||||
wikidataButton = getActivity().findViewById(R.id.wikidataButton);
|
||||
wikipediaButton = getActivity().findViewById(R.id.wikipediaButton);
|
||||
directionsButton = getActivity().findViewById(R.id.directionsButton);
|
||||
commonsButton = getActivity().findViewById(R.id.commonsButton);
|
||||
wikidataButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikidataButton);
|
||||
wikipediaButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikipediaButton);
|
||||
directionsButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.directionsButton);
|
||||
commonsButton = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.commonsButton);
|
||||
|
||||
wikidataButtonText = getActivity().findViewById(R.id.wikidataButtonText);
|
||||
wikipediaButtonText = getActivity().findViewById(R.id.wikipediaButtonText);
|
||||
directionsButtonText = getActivity().findViewById(R.id.directionsButtonText);
|
||||
commonsButtonText = getActivity().findViewById(R.id.commonsButtonText);
|
||||
wikidataButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikidataButtonText);
|
||||
wikipediaButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.wikipediaButtonText);
|
||||
directionsButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.directionsButtonText);
|
||||
commonsButtonText = ((NearbyFragment)getParentFragment()).view.findViewById(R.id.commonsButtonText);
|
||||
|
||||
bookmarkButton = getActivity().findViewById(R.id.bookmarkButton);
|
||||
bookmarkButtonImage = getActivity().findViewById(R.id.bookmarkButtonImage);
|
||||
|
|
@ -416,7 +439,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
mapView.getMapAsync(mapboxMap -> {
|
||||
CameraPosition position;
|
||||
|
||||
if(ViewUtil.isPortrait(getActivity())){
|
||||
if (ViewUtil.isPortrait(getActivity())){
|
||||
position = new CameraPosition.Builder()
|
||||
.target(isBottomListSheetExpanded ?
|
||||
new LatLng(curLatLng.getLatitude()- CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT,
|
||||
|
|
@ -494,6 +517,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
}
|
||||
|
||||
private void setupMapView(Bundle savedInstanceState) {
|
||||
Timber.d("setupMapView called");
|
||||
MapboxMapOptions options = new MapboxMapOptions()
|
||||
.compassGravity(Gravity.BOTTOM | Gravity.LEFT)
|
||||
.compassMargins(new int[]{12, 0, 0, 24})
|
||||
|
|
@ -505,16 +529,20 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
.zoom(11)
|
||||
.build());
|
||||
|
||||
if (!getParentFragment().getActivity().isFinishing()) {
|
||||
mapView = new MapView(getParentFragment().getActivity(), options);
|
||||
// create map
|
||||
mapView = new MapView(getActivity(), options);
|
||||
mapView.onCreate(savedInstanceState);
|
||||
mapView.getMapAsync(mapboxMap -> {
|
||||
((NearbyActivity)getActivity()).setMapViewTutorialShowCase();
|
||||
mapView.getMapAsync(new OnMapReadyCallback() {
|
||||
@Override
|
||||
public void onMapReady(MapboxMap mapboxMap) {
|
||||
NearbyMapFragment.this.mapboxMap = mapboxMap;
|
||||
updateMapSignificantly();
|
||||
}
|
||||
});
|
||||
mapView.setStyleUrl("asset://mapstyle.json");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* onLogoutComplete is called after shared preferences and data stored in local database are cleared.
|
||||
|
|
@ -542,6 +570,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
* move.
|
||||
*/
|
||||
private void addCurrentLocationMarker(MapboxMap mapboxMap) {
|
||||
Timber.d("addCurrentLocationMarker is called");
|
||||
if (currentLocationMarker != null) {
|
||||
currentLocationMarker.remove(); // Remove previous marker, we are not Hansel and Gretel
|
||||
}
|
||||
|
|
@ -564,8 +593,11 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
mapboxMap.addPolygon(currentLocationPolygonOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds markers for nearby places to mapbox map
|
||||
*/
|
||||
private void addNearbyMarkerstoMapBoxMap() {
|
||||
|
||||
Timber.d("addNearbyMarkerstoMapBoxMap is called");
|
||||
mapboxMap.addMarkers(baseMarkerOptions);
|
||||
|
||||
mapboxMap.setOnInfoWindowCloseListener(marker -> {
|
||||
|
|
@ -624,6 +656,12 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
return circle;
|
||||
}
|
||||
|
||||
/**
|
||||
* If nearby details bottom sheet state is collapsed: show fab plus
|
||||
* If nearby details bottom sheet state is expanded: show fab plus
|
||||
* If nearby details bottom sheet state is hidden: hide all fabs
|
||||
* @param bottomSheetState
|
||||
*/
|
||||
public void prepareViewsForSheetPosition(int bottomSheetState) {
|
||||
|
||||
switch (bottomSheetState) {
|
||||
|
|
@ -648,6 +686,9 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides all fabs
|
||||
*/
|
||||
private void hideFAB() {
|
||||
|
||||
removeAnchorFromFABs(fabPlus);
|
||||
|
|
@ -679,25 +720,14 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
|
||||
private void showFAB() {
|
||||
|
||||
addAnchorToBigFABs(fabPlus, getActivity().findViewById(R.id.bottom_sheet_details).getId());
|
||||
addAnchorToBigFABs(fabPlus, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.bottom_sheet_details).getId());
|
||||
fabPlus.show();
|
||||
|
||||
addAnchorToSmallFABs(fabGallery, getActivity().findViewById(R.id.empty_view).getId());
|
||||
addAnchorToSmallFABs(fabGallery, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.empty_view).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();
|
||||
addAnchorToSmallFABs(fabCamera, ((NearbyFragment)getParentFragment()).view.findViewById(R.id.empty_view1).getId());
|
||||
|
||||
isMapReady = true;
|
||||
if (isSecondMaterialShowcaseDismissed) {
|
||||
thirdSingleShowCaseView.show(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -724,6 +754,11 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
floatingActionButton.setLayoutParams(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Same botom sheet carries information for all nearby places, so we need to pass information
|
||||
* (title, description, distance and links) to view on nearby marker click
|
||||
* @param place Place of clicked nearby marker
|
||||
*/
|
||||
private void passInfoToSheet(Place place) {
|
||||
this.place = place;
|
||||
|
||||
|
|
@ -795,7 +830,7 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Timber.d("onRequestPermissionsResult: req code = " + " perm = " + permissions + " grant =" + grantResults);
|
||||
|
||||
// Do not use requestCode 1 as it will conflict with NearbyActivity's requestCodes
|
||||
// Do not use requestCode 1 as it will conflict with NearbyFragment's requestCodes
|
||||
switch (requestCode) {
|
||||
// 4 = "Read external storage" allowed when gallery selected
|
||||
case 4: {
|
||||
|
|
@ -873,18 +908,13 @@ public class NearbyMapFragment extends DaggerFragment {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This bundle is sent whenever and updte for nearby map comes, not for recreation, for updates
|
||||
*/
|
||||
public void setBundleForUpdtes(Bundle bundleForUpdtes) {
|
||||
this.bundleForUpdtes = bundleForUpdtes;
|
||||
}
|
||||
|
||||
public void onNearbyMaterialShowcaseDismissed() {
|
||||
isSecondMaterialShowcaseDismissed = true;
|
||||
if (isMapReady) {
|
||||
thirdSingleShowCaseView.show(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
if (mapView != null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,283 @@
|
|||
package fr.free.nrw.commons.nearby;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.design.widget.SwipeDismissBehavior;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import android.widget.Toast;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Custom card view for nearby notification card view on main screen, above contributions list
|
||||
*/
|
||||
public class NearbyNoificationCardView extends CardView{
|
||||
|
||||
private static final float MINIMUM_THRESHOLD_FOR_SWIPE = 100;
|
||||
private Context context;
|
||||
|
||||
private Button permissionRequestButton;
|
||||
private RelativeLayout contentLayout;
|
||||
private TextView notificationTitle;
|
||||
private TextView notificationDistance;
|
||||
private ImageView notificationIcon;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
public CardViewVisibilityState cardViewVisibilityState;
|
||||
|
||||
public PermissionType permissionType;
|
||||
|
||||
float x1,x2;
|
||||
|
||||
public NearbyNoificationCardView(@NonNull Context context) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
|
||||
init();
|
||||
}
|
||||
|
||||
public NearbyNoificationCardView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
this.context = context;
|
||||
cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
|
||||
init();
|
||||
}
|
||||
|
||||
public NearbyNoificationCardView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
this.context = context;
|
||||
cardViewVisibilityState = CardViewVisibilityState.INVISIBLE;
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
View rootView = inflate(context, R.layout.nearby_card_view, this);
|
||||
|
||||
permissionRequestButton = rootView.findViewById(R.id.permission_request_button);
|
||||
contentLayout = rootView.findViewById(R.id.content_layout);
|
||||
|
||||
notificationTitle = rootView.findViewById(R.id.nearby_title);
|
||||
notificationDistance = rootView.findViewById(R.id.nearby_distance);
|
||||
|
||||
notificationIcon = rootView.findViewById(R.id.nearby_icon);
|
||||
|
||||
progressBar = rootView.findViewById(R.id.progressBar);
|
||||
|
||||
setActionListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
// If you don't setVisibility after getting layout params, then you will se an empty space in place of nerabyNotificationCardView
|
||||
if (((MainActivity)context).prefs.getBoolean("displayNearbyCardView", true)) {
|
||||
this.setVisibility(VISIBLE);
|
||||
} else {
|
||||
this.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setActionListeners() {
|
||||
this.setOnClickListener(view -> ((MainActivity)context).viewPager.setCurrentItem(1));
|
||||
|
||||
this.setOnTouchListener(
|
||||
(v, event) -> {
|
||||
boolean isSwipe = false;
|
||||
float deltaX=0.0f;
|
||||
switch (event.getAction()) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
x1 = event.getX();
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
x2 = event.getX();
|
||||
deltaX = x2 - x1;
|
||||
if (deltaX < 0) {
|
||||
//Right to left swipe
|
||||
isSwipe = true;
|
||||
} else if (deltaX > 0) {
|
||||
//Left to right swipe
|
||||
isSwipe = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (isSwipe && (pixelToDp(Math.abs(deltaX)) > MINIMUM_THRESHOLD_FOR_SWIPE)) {
|
||||
v.setVisibility(GONE);
|
||||
// Save shared preference for nearby card view accordingly
|
||||
((MainActivity) context).prefs.edit()
|
||||
.putBoolean("displayNearbyCardView", false).apply();
|
||||
ViewUtil.showLongToast(context, getResources().getString(R.string.nearby_notification_dismiss_message));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private float pixelToDp(float pixels) {
|
||||
return (pixels / Resources.getSystem().getDisplayMetrics().density);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets permission request button visible and content layout invisible, then adds correct
|
||||
* permission request actions to permission request button according to PermissionType enum
|
||||
* @param isPermissionRequestButtonNeeded true if permissions missing
|
||||
*/
|
||||
public void displayPermissionRequestButton(boolean isPermissionRequestButtonNeeded) {
|
||||
if (isPermissionRequestButtonNeeded) {
|
||||
cardViewVisibilityState = CardViewVisibilityState.ASK_PERMISSION;
|
||||
contentLayout.setVisibility(GONE);
|
||||
permissionRequestButton.setVisibility(VISIBLE);
|
||||
|
||||
if (permissionType == PermissionType.ENABLE_LOCATION_PERMISSON) {
|
||||
|
||||
permissionRequestButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!((MainActivity)context).isFinishing()) {
|
||||
((MainActivity) context).locationManager.requestPermissions((MainActivity) context);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} else if (permissionType == PermissionType.ENABLE_GPS) {
|
||||
|
||||
permissionRequestButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(R.string.gps_disabled)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.enable_gps,
|
||||
(dialog, id) -> {
|
||||
Intent callGPSSettingIntent = new Intent(
|
||||
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||
Timber.d("Loaded settings page");
|
||||
((MainActivity) context).startActivityForResult(callGPSSettingIntent, 1);
|
||||
})
|
||||
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> {
|
||||
dialog.cancel();
|
||||
displayPermissionRequestButton(true);
|
||||
})
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
cardViewVisibilityState = CardViewVisibilityState.LOADING;
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
contentLayout.setVisibility(VISIBLE);
|
||||
// Set visibility of elements in content layout once it become visible
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
notificationTitle.setVisibility(GONE);
|
||||
notificationDistance.setVisibility(GONE);
|
||||
notificationIcon.setVisibility(GONE);
|
||||
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass place information to views.
|
||||
* @param isClosestNearbyPlaceFound false if there are no close place
|
||||
* @param place Closes place where we will get information from
|
||||
*/
|
||||
public void updateContent(boolean isClosestNearbyPlaceFound, Place place) {
|
||||
Timber.d("Update nearby card notification content");
|
||||
cardViewVisibilityState = CardViewVisibilityState.READY;
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
contentLayout.setVisibility(VISIBLE);
|
||||
// Make progress bar invisible once data is ready
|
||||
progressBar.setVisibility(GONE);
|
||||
// And content views visible since they are ready
|
||||
notificationTitle.setVisibility(VISIBLE);
|
||||
notificationDistance.setVisibility(VISIBLE);
|
||||
notificationIcon.setVisibility(VISIBLE);
|
||||
|
||||
if (isClosestNearbyPlaceFound) {
|
||||
notificationTitle.setText(place.name);
|
||||
notificationDistance.setText(place.distance);
|
||||
} else {
|
||||
notificationDistance.setText("");
|
||||
notificationTitle.setText(R.string.no_close_nearby);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
|
||||
super.onVisibilityChanged(changedView, visibility);
|
||||
if (visibility == VISIBLE) {
|
||||
/**
|
||||
* Sometimes we need to preserve previous state of notification card view without getting
|
||||
* any data from user. Ie. wen user came back from Media Details fragment to Contrib List
|
||||
* fragment, we need to know what was the state of card view, and set it to exact same state.
|
||||
*/
|
||||
switch (cardViewVisibilityState) {
|
||||
case READY:
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
contentLayout.setVisibility(VISIBLE);
|
||||
// Make progress bar invisible once data is ready
|
||||
progressBar.setVisibility(GONE);
|
||||
// And content views visible since they are ready
|
||||
notificationTitle.setVisibility(VISIBLE);
|
||||
notificationDistance.setVisibility(VISIBLE);
|
||||
notificationIcon.setVisibility(VISIBLE);
|
||||
break;
|
||||
case LOADING:
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
contentLayout.setVisibility(VISIBLE);
|
||||
// Set visibility of elements in content layout once it become visible
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
notificationTitle.setVisibility(GONE);
|
||||
notificationDistance.setVisibility(GONE);
|
||||
notificationIcon.setVisibility(GONE);
|
||||
permissionRequestButton.setVisibility(GONE);
|
||||
break;
|
||||
case ASK_PERMISSION:
|
||||
contentLayout.setVisibility(GONE);
|
||||
permissionRequestButton.setVisibility(VISIBLE);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This states will help us to preserve progress bar and content layout states
|
||||
*/
|
||||
public enum CardViewVisibilityState {
|
||||
LOADING,
|
||||
READY,
|
||||
INVISIBLE,
|
||||
ASK_PERMISSION,
|
||||
}
|
||||
|
||||
/**
|
||||
* We need to know which kind of permission we need to request, then update permission request
|
||||
* button action accordingly
|
||||
*/
|
||||
public enum PermissionType {
|
||||
ENABLE_GPS,
|
||||
ENABLE_LOCATION_PERMISSON, // For only after Marsmallow
|
||||
NO_PERMISSION_NEEDED
|
||||
}
|
||||
}
|
||||
|
|
@ -23,9 +23,9 @@ import timber.log.Timber;
|
|||
|
||||
public class NearbyPlaces {
|
||||
|
||||
private static final int MIN_RESULTS = 40;
|
||||
private static int MIN_RESULTS = 40;
|
||||
private static final double INITIAL_RADIUS = 1.0; // in kilometers
|
||||
private static final double MAX_RADIUS = 300.0; // in kilometers
|
||||
private static double MAX_RADIUS = 300.0; // in kilometers
|
||||
private static final double RADIUS_MULTIPLIER = 1.618;
|
||||
private static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql");
|
||||
private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
|
||||
|
|
@ -41,9 +41,22 @@ public class NearbyPlaces {
|
|||
}
|
||||
}
|
||||
|
||||
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) throws IOException {
|
||||
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang, boolean returnClosestResult) throws IOException {
|
||||
List<Place> places = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* If returnClosestResult is true, then this means that we are trying to get closest point
|
||||
* to use in cardView above contributions list
|
||||
*/
|
||||
if (returnClosestResult) {
|
||||
MIN_RESULTS = 1; // Return closest nearby place
|
||||
MAX_RADIUS = 5; // Return places only in 5 km area
|
||||
radius = INITIAL_RADIUS; // refresh radius again, otherwise increased radius is grater than MAX_RADIUS, thus returns null
|
||||
} else {
|
||||
MIN_RESULTS = 40;
|
||||
MAX_RADIUS = 300.0; // in kilometers
|
||||
}
|
||||
|
||||
// increase the radius gradually to find a satisfactory number of nearby places
|
||||
while (radius <= MAX_RADIUS) {
|
||||
try {
|
||||
|
|
@ -103,12 +116,19 @@ public class NearbyPlaces {
|
|||
String point = fields[0];
|
||||
String wikiDataLink = Utils.stripLocalizedString(fields[1]);
|
||||
String name = Utils.stripLocalizedString(fields[2]);
|
||||
|
||||
//getting icon link here
|
||||
String identifier = Utils.stripLocalizedString(fields[3]);
|
||||
//getting the ID which is at the end of link
|
||||
identifier = identifier.split("/")[Utils.stripLocalizedString(fields[3]).split("/").length-1];
|
||||
//replaced the extra > char from fields
|
||||
identifier = identifier.replace(">","");
|
||||
|
||||
String type = Utils.stripLocalizedString(fields[4]);
|
||||
String icon = fields[5];
|
||||
String wikipediaSitelink = Utils.stripLocalizedString(fields[7]);
|
||||
String commonsSitelink = Utils.stripLocalizedString(fields[8]);
|
||||
String category = Utils.stripLocalizedString(fields[9]);
|
||||
|
||||
Timber.v("Name: " + name + ", type: " + type + ", category: " + category + ", wikipediaSitelink: " + wikipediaSitelink + ", commonsSitelink: " + commonsSitelink);
|
||||
|
||||
double latitude;
|
||||
|
|
@ -127,7 +147,7 @@ public class NearbyPlaces {
|
|||
|
||||
places.add(new Place(
|
||||
name,
|
||||
Place.Label.fromText(type), // list
|
||||
Place.Label.fromText(identifier), // list
|
||||
type, // details
|
||||
Uri.parse(icon),
|
||||
new LatLng(latitude, longitude, 0),
|
||||
|
|
@ -143,4 +163,5 @@ public class NearbyPlaces {
|
|||
|
||||
return places;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,27 +121,30 @@ public class Place {
|
|||
*/
|
||||
public enum Label {
|
||||
|
||||
BUILDING("building", R.drawable.round_icon_generic_building),
|
||||
HOUSE("house", R.drawable.round_icon_house),
|
||||
COTTAGE("cottage", R.drawable.round_icon_house),
|
||||
FARMHOUSE("farmhouse", R.drawable.round_icon_house),
|
||||
CHURCH("church", R.drawable.round_icon_church),
|
||||
RAILWAY_STATION("railway station", R.drawable.round_icon_railway_station),
|
||||
GATEHOUSE("gatehouse", R.drawable.round_icon_gatehouse),
|
||||
MILESTONE("milestone", R.drawable.round_icon_milestone),
|
||||
INN("inn", R.drawable.round_icon_house),
|
||||
CITY("city", R.drawable.round_icon_city),
|
||||
SECONDARY_SCHOOL("secondary school", R.drawable.round_icon_school),
|
||||
EDU("edu", R.drawable.round_icon_school),
|
||||
ISLE("isle", R.drawable.round_icon_island),
|
||||
MOUNTAIN("mountain", R.drawable.round_icon_mountain),
|
||||
AIRPORT("airport", R.drawable.round_icon_airport),
|
||||
BRIDGE("bridge", R.drawable.round_icon_bridge),
|
||||
ROAD("road", R.drawable.round_icon_road),
|
||||
FOREST("forest", R.drawable.round_icon_forest),
|
||||
PARK("park", R.drawable.round_icon_park),
|
||||
RIVER("river", R.drawable.round_icon_river),
|
||||
WATERFALL("waterfall", R.drawable.round_icon_waterfall),
|
||||
BUILDING("Q41176", R.drawable.round_icon_generic_building),
|
||||
HOUSE("Q3947", R.drawable.round_icon_house),
|
||||
COTTAGE("Q5783996", R.drawable.round_icon_house),
|
||||
FARMHOUSE("Q489357", R.drawable.round_icon_house),
|
||||
CHURCH("Q16970", R.drawable.round_icon_church), //changed from church to church building
|
||||
RAILWAY_STATION("Q55488", R.drawable.round_icon_railway_station),
|
||||
GATEHOUSE("Q277760", R.drawable.round_icon_gatehouse),
|
||||
MILESTONE("Q10145", R.drawable.round_icon_milestone),
|
||||
INN("Q256020", R.drawable.round_icon_house), //Q27686
|
||||
HOTEL("Q27686", R.drawable.round_icon_house),
|
||||
CITY("Q515", R.drawable.round_icon_city),
|
||||
UNIVERSITY("Q3918",R.drawable.round_icon_school), //added university
|
||||
SCHOOL("Q3914", R.drawable.round_icon_school), //changed from "secondary school" to school
|
||||
EDUCATION("Q8434", R.drawable.round_icon_school), //changed from edu to education, there is no id for "edu"
|
||||
ISLE("Q23442", R.drawable.round_icon_island),
|
||||
MOUNTAIN("Q8502", R.drawable.round_icon_mountain),
|
||||
AIRPORT("Q1248784", R.drawable.round_icon_airport),
|
||||
BRIDGE("Q12280", R.drawable.round_icon_bridge),
|
||||
ROAD("Q34442", R.drawable.round_icon_road),
|
||||
FOREST("Q4421", R.drawable.round_icon_forest),
|
||||
PARK("Q22698", R.drawable.round_icon_park),
|
||||
RIVER("Q4022", R.drawable.round_icon_river),
|
||||
WATERFALL("Q34038", R.drawable.round_icon_waterfall),
|
||||
TEMPLE("Q44539",R.drawable.round_icon_church),
|
||||
UNKNOWN("?", R.drawable.round_icon_unknown);
|
||||
|
||||
private static final Map<String, Label> TEXT_TO_DESCRIPTION
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ public class Notification {
|
|||
public String description;
|
||||
public String link;
|
||||
public String iconUrl;
|
||||
public String dateWithYear;
|
||||
|
||||
public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl) {
|
||||
public Notification(NotificationType notificationType, String notificationText, String date, String description, String link, String iconUrl, String dateWithYear) {
|
||||
this.notificationType = notificationType;
|
||||
this.notificationText = notificationText;
|
||||
this.date = date;
|
||||
this.description = description;
|
||||
this.link = link;
|
||||
this.iconUrl = iconUrl;
|
||||
this.dateWithYear = dateWithYear;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import android.widget.RelativeLayout;
|
|||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
|
@ -25,6 +26,7 @@ import butterknife.BindView;
|
|||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
|
|
@ -85,6 +87,11 @@ public class NotificationActivity extends NavigationBaseActivity {
|
|||
private void addNotifications() {
|
||||
Timber.d("Add notifications");
|
||||
|
||||
// Store when add notification is called last
|
||||
long currentDate = new Date(System.currentTimeMillis()).getTime();
|
||||
getSharedPreferences("prefs", MODE_PRIVATE).edit().putLong("last_read_notification_date", currentDate).apply();
|
||||
Timber.d("Set last notification read date to current date:"+ currentDate);
|
||||
|
||||
if(mNotificationWorkerFragment == null){
|
||||
Observable.fromCallable(() -> {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
|
@ -115,7 +122,7 @@ public class NotificationActivity extends NavigationBaseActivity {
|
|||
}
|
||||
|
||||
private void setAdapter(List<Notification> notificationList) {
|
||||
if(notificationList == null || notificationList.isEmpty()) {
|
||||
if (notificationList == null || notificationList.isEmpty()) {
|
||||
ViewUtil.showSnackbar(relativeLayout, R.string.no_notifications);
|
||||
return;
|
||||
}
|
||||
|
|
@ -140,4 +147,10 @@ public class NotificationActivity extends NavigationBaseActivity {
|
|||
.commit();
|
||||
mNotificationWorkerFragment.setNotificationList(notificationList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
startActivityWithFlags(
|
||||
this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package fr.free.nrw.commons.notification;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
|
@ -60,7 +65,25 @@ public class NotificationRenderer extends Renderer<Notification> {
|
|||
notificationText = notificationText.trim().replaceAll("(^\\s*)|(\\s*$)", "");
|
||||
notificationText = Html.fromHtml(notificationText).toString();
|
||||
notificationText = notificationText.concat(" ");
|
||||
title.setText(notificationText);
|
||||
|
||||
SpannableString ss = new SpannableString(notificationText);
|
||||
ClickableSpan clickableSpan = new ClickableSpan() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
listener.notificationClicked(getContent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDrawState(TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
ds.setColor(Color.BLACK);
|
||||
}
|
||||
};
|
||||
|
||||
// Attach a ClickableSpan to the range (start:0, end:notificationText.length()) of the String
|
||||
ss.setSpan(clickableSpan, 0, notificationText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
title.setText(ss, TextView.BufferType.SPANNABLE);
|
||||
}
|
||||
|
||||
public interface NotificationClicked{
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ public class NotificationUtils {
|
|||
notificationText = getWelcomeMessage(context, document);
|
||||
break;
|
||||
}
|
||||
return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl);
|
||||
return new Notification(type, notificationText, getTimestamp(document), description, link, iconUrl, getTimestampWithYear(document));
|
||||
}
|
||||
|
||||
private static String getNotificationText(Node document) {
|
||||
|
|
@ -193,7 +193,7 @@ public class NotificationUtils {
|
|||
private static String getNotificationIconUrl(Node document) {
|
||||
String format = "%s%s";
|
||||
Node iconUrl = getNode(getModel(document), "iconUrl");
|
||||
if(iconUrl == null) {
|
||||
if (iconUrl == null) {
|
||||
return null;
|
||||
} else {
|
||||
String url = iconUrl.getTextContent();
|
||||
|
|
@ -247,6 +247,14 @@ public class NotificationUtils {
|
|||
return "";
|
||||
}
|
||||
|
||||
private static String getTimestampWithYear(Node document) {
|
||||
Element timestampElement = (Element) getNode(document, "timestamp");
|
||||
if (timestampElement != null) {
|
||||
return timestampElement.getAttribute("utcunix");
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static String getNotificationDescription(Node document) {
|
||||
Element titleElement = (Element) getNode(document, "title");
|
||||
if (titleElement != null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
package fr.free.nrw.commons.notification;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsFragment;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* This asynctask will check unread notifications after a date (date user check notifications last)
|
||||
*/
|
||||
|
||||
public class UnreadNotificationsCheckAsync extends AsyncTask<Void, Void, Notification> {
|
||||
|
||||
WeakReference<MainActivity> context;
|
||||
NotificationController notificationController;
|
||||
|
||||
|
||||
public UnreadNotificationsCheckAsync(MainActivity context, NotificationController notificationController) {
|
||||
this.context = new WeakReference<>(context);
|
||||
this.notificationController = notificationController;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Notification doInBackground(Void... voids) {
|
||||
Notification lastNotification = null;
|
||||
|
||||
try {
|
||||
lastNotification = findLastNotification(notificationController.getNotifications());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return lastNotification;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Notification lastNotification) {
|
||||
super.onPostExecute(lastNotification);
|
||||
|
||||
if (lastNotification == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Date lastNotificationCheckDate = new Date(context.get()
|
||||
.getSharedPreferences("prefs",0)
|
||||
.getLong("last_read_notification_date", 0));
|
||||
Timber.d("You may have unread notifications since"+lastNotificationCheckDate);
|
||||
|
||||
boolean isThereUnreadNotifications;
|
||||
|
||||
Date lastReadNotificationDate = new java.util.Date(Long.parseLong(lastNotification.dateWithYear)*1000);
|
||||
|
||||
if (lastNotificationCheckDate.before(lastReadNotificationDate)) {
|
||||
isThereUnreadNotifications = true;
|
||||
} else {
|
||||
isThereUnreadNotifications = false;
|
||||
}
|
||||
|
||||
// Check if activity is still running
|
||||
if (context.get().getWindow().getDecorView().isShown() && !context.get().isFinishing()) {
|
||||
// Check if fragment is not null and visible
|
||||
if (context.get().isContributionsFragmentVisible && context.get().contributionsActivityPagerAdapter.getItem(0) != null) {
|
||||
((ContributionsFragment)(context.get().contributionsActivityPagerAdapter.getItem(0))).updateNotificationsNotification(isThereUnreadNotifications);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Notification findLastNotification(List<Notification> allNotifications) {
|
||||
if (allNotifications.size() > 0) {
|
||||
return allNotifications.get(allNotifications.size()-1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ public class QuizActivity extends AppCompatActivity {
|
|||
*/
|
||||
@OnClick(R.id.next_button)
|
||||
public void setNextQuestion(){
|
||||
if( questionIndex <= quiz.size() && (positiveAnswer.isChecked() || negativeAnswer.isChecked())) {
|
||||
if ( questionIndex <= quiz.size() && (positiveAnswer.isChecked() || negativeAnswer.isChecked())) {
|
||||
evaluateScore();
|
||||
} else if ( !positiveAnswer.isChecked() && !negativeAnswer.isChecked()){
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(this);
|
||||
|
|
@ -107,11 +107,11 @@ public class QuizActivity extends AppCompatActivity {
|
|||
* to evaluate score and check whether answer is correct or wrong
|
||||
*/
|
||||
public void evaluateScore() {
|
||||
if((quiz.get(questionIndex).isAnswer() && positiveAnswer.isChecked()) ||
|
||||
if ((quiz.get(questionIndex).isAnswer() && positiveAnswer.isChecked()) ||
|
||||
(!quiz.get(questionIndex).isAnswer() && negativeAnswer.isChecked()) ){
|
||||
customAlert(getResources().getString(R.string.correct),quiz.get(questionIndex).getAnswerMessage() );
|
||||
score++;
|
||||
} else{
|
||||
} else {
|
||||
customAlert(getResources().getString(R.string.wrong), quiz.get(questionIndex).getAnswerMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -127,12 +127,12 @@ public class QuizActivity extends AppCompatActivity {
|
|||
alert.setMessage(Message);
|
||||
alert.setPositiveButton(R.string.continue_message, (dialog, which) -> {
|
||||
questionIndex++;
|
||||
if(questionIndex == quiz.size()){
|
||||
if (questionIndex == quiz.size()) {
|
||||
Intent i = new Intent(QuizActivity.this, QuizResultActivity.class);
|
||||
dialog.dismiss();
|
||||
i.putExtra("QuizResult",score);
|
||||
startActivity(i);
|
||||
}else {
|
||||
} else {
|
||||
displayQuestion();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public class QuizChecker {
|
|||
*/
|
||||
private void setTotalUploadCount(int uploadCount) {
|
||||
totalUploadCount = uploadCount - countPref.getInt(UPLOAD_SHARED_PREFERENCE,0);
|
||||
if( totalUploadCount < 0){
|
||||
if ( totalUploadCount < 0){
|
||||
totalUploadCount = 0;
|
||||
countPref.edit().putInt(UPLOAD_SHARED_PREFERENCE,0).apply();
|
||||
}
|
||||
|
|
@ -104,7 +104,7 @@ public class QuizChecker {
|
|||
*/
|
||||
private void setRevertParameter(int revertCountFetched) {
|
||||
revertCount = revertCountFetched - revertPref.getInt(REVERT_SHARED_PREFERENCE,0);
|
||||
if(revertCount < 0){
|
||||
if (revertCount < 0){
|
||||
revertCount = 0;
|
||||
revertPref.edit().putInt(REVERT_SHARED_PREFERENCE, 0).apply();
|
||||
}
|
||||
|
|
@ -116,7 +116,7 @@ public class QuizChecker {
|
|||
* to check whether the criterion to call quiz is satisfied
|
||||
*/
|
||||
private void calculateRevertParameter() {
|
||||
if( revertCount < 0 || totalUploadCount < 0){
|
||||
if ( revertCount < 0 || totalUploadCount < 0){
|
||||
revertPref.edit().putInt(REVERT_SHARED_PREFERENCE, 0).apply();
|
||||
countPref.edit().putInt(UPLOAD_SHARED_PREFERENCE,0).apply();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import com.dinuscxj.progressbar.CircleProgressBar;
|
||||
|
||||
|
|
@ -16,7 +15,7 @@ import butterknife.BindView;
|
|||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.LayoutInflater;
|
||||
|
|
@ -26,16 +25,9 @@ import android.view.View;
|
|||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.dinuscxj.progressbar.CircleProgressBar;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
|
||||
/**
|
||||
* Displays the final score of quiz and congratulates the user
|
||||
|
|
@ -57,13 +49,13 @@ public class QuizResultActivity extends AppCompatActivity {
|
|||
ButterKnife.bind(this);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
if( getIntent() != null) {
|
||||
if ( getIntent() != null) {
|
||||
Bundle extras = getIntent().getExtras();
|
||||
int score = extras.getInt("QuizResult");
|
||||
setScore(score);
|
||||
}else{
|
||||
startActivityWithFlags(
|
||||
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
|
@ -87,14 +79,14 @@ public class QuizResultActivity extends AppCompatActivity {
|
|||
@OnClick(R.id.quiz_result_next)
|
||||
public void launchContributionActivity(){
|
||||
startActivityWithFlags(
|
||||
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
startActivityWithFlags(
|
||||
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
|
@ -186,9 +178,9 @@ public class QuizResultActivity extends AppCompatActivity {
|
|||
AlertDialog.Builder alertadd = new AlertDialog.Builder(QuizResultActivity.this);
|
||||
LayoutInflater factory = LayoutInflater.from(QuizResultActivity.this);
|
||||
final View view = factory.inflate(R.layout.image_alert_layout, null);
|
||||
ImageView screenShotImage = (ImageView) view.findViewById(R.id.alert_image);
|
||||
ImageView screenShotImage = view.findViewById(R.id.alert_image);
|
||||
screenShotImage.setImageBitmap(screenshot);
|
||||
TextView shareMessage = (TextView) view.findViewById(R.id.alert_text);
|
||||
TextView shareMessage = view.findViewById(R.id.alert_text);
|
||||
shareMessage.setText(R.string.quiz_result_share_message);
|
||||
alertadd.setView(view);
|
||||
alertadd.setPositiveButton(R.string.about_translate_proceed, (dialog, which) -> shareScreen(screenshot));
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ public class RadioGroupHelper {
|
|||
public RadioGroupHelper(View rootView, int... radiosIDs) {
|
||||
super();
|
||||
for (int radioButtonID : radiosIDs) {
|
||||
add((RadioButton)rootView.findViewById(radioButtonID));
|
||||
add(rootView.findViewById(radioButtonID));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ public class RadioGroupHelper {
|
|||
*/
|
||||
View.OnClickListener onClickListener = v -> {
|
||||
for (CompoundButton rb : radioButtons) {
|
||||
if(rb != v) rb.setChecked(false);
|
||||
if (rb != v) rb.setChecked(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import fr.free.nrw.commons.R;
|
|||
import fr.free.nrw.commons.di.CommonsDaggerAppCompatActivity;
|
||||
|
||||
public abstract class BaseActivity extends CommonsDaggerAppCompatActivity {
|
||||
boolean currentTheme;
|
||||
protected boolean currentTheme;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import android.support.v4.widget.DrawerLayout;
|
|||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
|
@ -32,11 +33,9 @@ import fr.free.nrw.commons.R;
|
|||
import fr.free.nrw.commons.WelcomeActivity;
|
||||
import fr.free.nrw.commons.achievements.AchievementsActivity;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.category.CategoryImagesActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||
import fr.free.nrw.commons.bookmarks.BookmarksActivity;
|
||||
import fr.free.nrw.commons.notification.NotificationActivity;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -91,6 +90,23 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
|||
}
|
||||
}
|
||||
|
||||
public void changeDrawerIconToBakcButton() {
|
||||
toggle.setDrawerIndicatorEnabled(false);
|
||||
toggle.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white);
|
||||
toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
onBackPressed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void changeDrawerIconToDefault() {
|
||||
if (toggle != null) {
|
||||
toggle.setDrawerIndicatorEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the username in navigationHeader.
|
||||
*/
|
||||
|
|
@ -156,13 +172,9 @@ public abstract class NavigationBaseActivity extends BaseActivity
|
|||
case R.id.action_home:
|
||||
drawerLayout.closeDrawer(navigationView);
|
||||
startActivityWithFlags(
|
||||
this, ContributionsActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
this, MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return true;
|
||||
case R.id.action_nearby:
|
||||
drawerLayout.closeDrawer(navigationView);
|
||||
startActivityWithFlags(this, NearbyActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
return true;
|
||||
case R.id.action_about:
|
||||
drawerLayout.closeDrawer(navigationView);
|
||||
startActivityWithFlags(this, AboutActivity.class, Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
|
||||
|
|
|
|||
|
|
@ -1,56 +1,72 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds a description of an item being uploaded by {@link UploadActivity}
|
||||
*/
|
||||
class Description {
|
||||
|
||||
private String languageId;
|
||||
private String languageDisplayText;
|
||||
private String languageCode;
|
||||
private String descriptionText;
|
||||
private boolean set;
|
||||
private int selectedLanguageIndex = -1;
|
||||
|
||||
public String getLanguageId() {
|
||||
return languageId;
|
||||
/**
|
||||
* @return The language code ie. "en" or "fr"
|
||||
*/
|
||||
String getLanguageCode() {
|
||||
return languageCode;
|
||||
}
|
||||
|
||||
public void setLanguageId(String languageId) {
|
||||
this.languageId = languageId;
|
||||
/**
|
||||
* @param languageCode The language code ie. "en" or "fr"
|
||||
*/
|
||||
void setLanguageCode(String languageCode) {
|
||||
this.languageCode = languageCode;
|
||||
}
|
||||
|
||||
public String getLanguageDisplayText() {
|
||||
return languageDisplayText;
|
||||
}
|
||||
|
||||
public void setLanguageDisplayText(String languageDisplayText) {
|
||||
this.languageDisplayText = languageDisplayText;
|
||||
}
|
||||
|
||||
public String getDescriptionText() {
|
||||
String getDescriptionText() {
|
||||
return descriptionText;
|
||||
}
|
||||
|
||||
public void setDescriptionText(String descriptionText) {
|
||||
void setDescriptionText(String descriptionText) {
|
||||
this.descriptionText = descriptionText;
|
||||
|
||||
if (!TextUtils.isEmpty(descriptionText)) {
|
||||
set = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return set;
|
||||
}
|
||||
|
||||
public void setSet(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public int getSelectedLanguageIndex() {
|
||||
/**
|
||||
* @return the index of the language selected in a spinner with {@link SpinnerLanguagesAdapter}
|
||||
*/
|
||||
int getSelectedLanguageIndex() {
|
||||
return selectedLanguageIndex;
|
||||
}
|
||||
|
||||
public void setSelectedLanguageIndex(int selectedLanguageIndex) {
|
||||
/**
|
||||
* @param selectedLanguageIndex the index of the language selected in a spinner with {@link SpinnerLanguagesAdapter}
|
||||
*/
|
||||
void setSelectedLanguageIndex(int selectedLanguageIndex) {
|
||||
this.selectedLanguageIndex = selectedLanguageIndex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats the list of descriptions into the format Commons requires for uploads.
|
||||
*
|
||||
* @param descriptions the list of descriptions, description is ignored if text is null.
|
||||
* @return a string with the pattern of {{en|1=descriptionText}}
|
||||
*/
|
||||
static String formatList(List<Description> descriptions) {
|
||||
StringBuilder descListString = new StringBuilder();
|
||||
for (Description description : descriptions) {
|
||||
if (!description.isEmpty()) {
|
||||
String individualDescription = String.format("{{%s|1=%s}}", description.getLanguageCode(),
|
||||
description.getDescriptionText());
|
||||
descListString.append(individualDescription);
|
||||
}
|
||||
}
|
||||
return descListString.toString();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return descriptionText == null || descriptionText.isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ package fr.free.nrw.commons.upload;
|
|||
|
||||
import android.content.Context;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.widget.AppCompatSpinner;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
|
@ -17,154 +17,196 @@ import android.widget.AdapterView.OnItemSelectedListener;
|
|||
import android.widget.EditText;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnTouch;
|
||||
import butterknife.Optional;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
||||
import fr.free.nrw.commons.utils.BiMap;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import io.reactivex.subjects.Subject;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
|
||||
|
||||
List<Description> descriptions;
|
||||
List<Language> languages;
|
||||
private Title title;
|
||||
private List<Description> descriptions;
|
||||
private Context context;
|
||||
private Callback callback;
|
||||
private Subject<String> titleChangedSubject;
|
||||
|
||||
public DescriptionsAdapter() {
|
||||
private BiMap<AdapterView, String> selectedLanguages;
|
||||
private UploadView uploadView;
|
||||
|
||||
DescriptionsAdapter(UploadView uploadView) {
|
||||
title = new Title();
|
||||
descriptions = new ArrayList<>();
|
||||
descriptions.add(new Description());
|
||||
languages = new ArrayList<>();
|
||||
titleChangedSubject = BehaviorSubject.create();
|
||||
selectedLanguages = new BiMap<>();
|
||||
this.uploadView = uploadView;
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback) {
|
||||
void setCallback(Callback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void setDescriptions(List<Description> descriptions) {
|
||||
void setItems(Title title, List<Description> descriptions) {
|
||||
this.descriptions = descriptions;
|
||||
this.title = title;
|
||||
selectedLanguages = new BiMap<>();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void setLanguages(List<Language> languages) {
|
||||
this.languages = languages;
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
if (position == 0) return 1;
|
||||
else return 2;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view;
|
||||
if (viewType == 1) {
|
||||
view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.row_item_title, parent, false);
|
||||
} else {
|
||||
view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.row_item_description, parent, false);
|
||||
}
|
||||
context = parent.getContext();
|
||||
ViewHolder viewHolder = new ViewHolder(view);
|
||||
return viewHolder;
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
holder.init(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return descriptions.size();
|
||||
return descriptions.size() + 1;
|
||||
}
|
||||
|
||||
public List<Description> getDescriptions() {
|
||||
List<Description> getDescriptions() {
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
public void addDescription(Description description) {
|
||||
void addDescription(Description description) {
|
||||
this.descriptions.add(description);
|
||||
notifyItemInserted(descriptions.size() - 1);
|
||||
notifyItemInserted(descriptions.size() + 1);
|
||||
}
|
||||
|
||||
public Title getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(Title title) {
|
||||
this.title = title;
|
||||
notifyItemInserted(0);
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@Nullable
|
||||
@BindView(R.id.spinner_description_languages)
|
||||
AppCompatSpinner spinnerDescriptionLanguages;
|
||||
@BindView(R.id.et_description_text)
|
||||
EditText etDescriptionText;
|
||||
private View view;
|
||||
|
||||
@BindView(R.id.description_item_edit_text)
|
||||
EditText descItemEditText;
|
||||
|
||||
private View view;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
ButterKnife.bind(this, itemView);
|
||||
this.view = itemView;
|
||||
Timber.i("descItemEditText:" + descItemEditText);
|
||||
}
|
||||
|
||||
public void init(int position) {
|
||||
Description description = descriptions.get(position);
|
||||
if (!TextUtils.isEmpty(description.getDescriptionText())) {
|
||||
etDescriptionText.setText(description.getDescriptionText());
|
||||
if (position == 0) {
|
||||
Timber.d("Title is " + title);
|
||||
if (!title.isEmpty()) {
|
||||
descItemEditText.setText(title.toString());
|
||||
} else {
|
||||
etDescriptionText.setText("");
|
||||
descItemEditText.setText("");
|
||||
}
|
||||
Drawable drawableRight = context.getResources()
|
||||
.getDrawable(R.drawable.mapbox_info_icon_default);
|
||||
if (position != 0) {
|
||||
etDescriptionText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||
|
||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(), null);
|
||||
|
||||
descItemEditText.addTextChangedListener(new AbstractTextWatcher(titleText ->{
|
||||
title.setTitleText(titleText);
|
||||
titleChangedSubject.onNext(titleText);
|
||||
}));
|
||||
|
||||
descItemEditText.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(v);
|
||||
} else {
|
||||
etDescriptionText.setCompoundDrawablesWithIntrinsicBounds(null, null, drawableRight, null);
|
||||
}
|
||||
|
||||
etDescriptionText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
description.setDescriptionText(editable.toString());
|
||||
uploadView.setTopCardState(false);
|
||||
}
|
||||
});
|
||||
|
||||
etDescriptionText.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
} else {
|
||||
Description description = descriptions.get(position - 1);
|
||||
Timber.d("Description is " + description);
|
||||
if (!TextUtils.isEmpty(description.getDescriptionText())) {
|
||||
descItemEditText.setText(description.getDescriptionText());
|
||||
} else {
|
||||
descItemEditText.setText("");
|
||||
}
|
||||
if (position == 1) {
|
||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(), null);
|
||||
} else {
|
||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||
}
|
||||
|
||||
descItemEditText.addTextChangedListener(new AbstractTextWatcher(descriptionText -> {
|
||||
description.setDescriptionText(descriptionText);
|
||||
}));
|
||||
descItemEditText.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(v);
|
||||
} else {
|
||||
uploadView.setTopCardState(false);
|
||||
}
|
||||
});
|
||||
|
||||
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context,
|
||||
R.layout.row_item_languages_spinner);
|
||||
Collections.sort(languages, (language, t1) -> language.getLocale().getDisplayLanguage()
|
||||
.compareTo(t1.getLocale().getDisplayLanguage().toString()));
|
||||
languagesAdapter.setLanguages(languages);
|
||||
R.layout.row_item_languages_spinner, selectedLanguages);
|
||||
languagesAdapter.notifyDataSetChanged();
|
||||
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
|
||||
|
||||
if (description.getSelectedLanguageIndex() == -1) {
|
||||
if (position == 0) {
|
||||
int defaultLocaleIndex = getIndexOfUserDefaultLocale();
|
||||
if (position == 1) {
|
||||
int defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(context);
|
||||
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex);
|
||||
} else {
|
||||
spinnerDescriptionLanguages.setSelection(0);
|
||||
}
|
||||
} else {
|
||||
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
|
||||
selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode());
|
||||
}
|
||||
|
||||
languages.get(spinnerDescriptionLanguages.getSelectedItemPosition()).setSet(true);
|
||||
|
||||
//TODO do it the butterknife way
|
||||
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
|
||||
long l) {
|
||||
//TODO handle case when user tries to select an already selected language
|
||||
updateDescriptionBasedOnSelectedLanguageIndex(description, position);
|
||||
description.setSelectedLanguageIndex(position);
|
||||
String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position);
|
||||
description.setLanguageCode(languageCode);
|
||||
selectedLanguages.remove(adapterView);
|
||||
selectedLanguages.put(adapterView, languageCode);
|
||||
((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -172,27 +214,44 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
|||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OnTouch(R.id.et_description_text)
|
||||
@Optional
|
||||
@OnTouch(R.id.description_item_edit_text)
|
||||
boolean descriptionInfo(View view, MotionEvent motionEvent) {
|
||||
|
||||
//Title info is visible only for the title
|
||||
if (getAdapterPosition() == 0) {
|
||||
//Description info is visible only for the first item
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
final int value = view.getRight() - descItemEditText
|
||||
.getCompoundDrawables()[2]
|
||||
.getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
callback.showAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
final int value = descItemEditText.getLeft() + descItemEditText
|
||||
.getCompoundDrawables()[0]
|
||||
.getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
|
||||
callback.showAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//Description info is visible only for the first description
|
||||
} else if (getAdapterPosition() == 1) {
|
||||
final int value;
|
||||
if (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
value = etDescriptionText.getRight() - etDescriptionText
|
||||
.getCompoundDrawables()[2]
|
||||
.getBounds().width() - etDescriptionText.getPaddingRight();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getX() >= value) {
|
||||
value = view.getRight() - descItemEditText.getCompoundDrawables()[2].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
callback.showAlert(R.string.media_detail_description,
|
||||
R.string.description_info);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
value = etDescriptionText.getLeft() + etDescriptionText
|
||||
value = descItemEditText.getLeft() + descItemEditText
|
||||
.getCompoundDrawables()[0]
|
||||
.getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
|
||||
|
|
@ -206,27 +265,12 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
|||
}
|
||||
}
|
||||
|
||||
private int getIndexOfUserDefaultLocale() {
|
||||
for (int i = 0; i < languages.size(); i++) {
|
||||
if (languages.get(i).getLocale()
|
||||
.equals(context.getResources().getConfiguration().locale)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void updateDescriptionBasedOnSelectedLanguageIndex(Description description,
|
||||
int position) {
|
||||
Language language = languages.get(position);
|
||||
Locale locale = language.getLocale();
|
||||
description.setSelectedLanguageIndex(position);
|
||||
description.setLanguageDisplayText(locale.getDisplayName());
|
||||
description.setLanguageId(locale.getLanguage());
|
||||
private Drawable getInfoIcon() {
|
||||
return context.getResources()
|
||||
.getDrawable(R.drawable.mapbox_info_icon_default);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,82 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Created by bluesir9 on 16/9/17.
|
||||
*
|
||||
* <p>Responsible for checking if the picture that the user is trying to upload is useful or not. Will attempt to filter
|
||||
* away completely black,fuzzy/blurry pictures(for now).
|
||||
*
|
||||
* <p>todo: Detect selfies?
|
||||
*/
|
||||
|
||||
public class DetectUnwantedPicturesAsync extends AsyncTask<Void, Void, ImageUtils.Result> {
|
||||
|
||||
private final String imageMediaFilePath;
|
||||
public final WeakReference<Activity> activityWeakReference;
|
||||
|
||||
DetectUnwantedPicturesAsync(WeakReference<Activity> activityWeakReference, String imageMediaFilePath) {
|
||||
//this.callback = callback;
|
||||
this.imageMediaFilePath = imageMediaFilePath;
|
||||
this.activityWeakReference = activityWeakReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImageUtils.Result doInBackground(Void... voids) {
|
||||
try {
|
||||
Timber.d("FilePath: " + imageMediaFilePath);
|
||||
if (imageMediaFilePath == null) {
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
|
||||
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(imageMediaFilePath,false);
|
||||
|
||||
return ImageUtils.checkIfImageIsTooDark(decoder);
|
||||
} catch (IOException ioe) {
|
||||
Timber.e(ioe, "IO Exception");
|
||||
return ImageUtils.Result.IMAGE_OK;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ImageUtils.Result result) {
|
||||
super.onPostExecute(result);
|
||||
Activity activity = activityWeakReference.get();
|
||||
|
||||
if (result != ImageUtils.Result.IMAGE_OK) {
|
||||
//show appropriate error message
|
||||
String errorMessage = result == ImageUtils.Result.IMAGE_DARK ? activity.getString(R.string.upload_image_too_dark) : activity.getString(R.string.upload_image_blurry);
|
||||
AlertDialog.Builder errorDialogBuilder = new AlertDialog.Builder(activity);
|
||||
errorDialogBuilder.setMessage(errorMessage);
|
||||
errorDialogBuilder.setTitle(activity.getString(R.string.warning));
|
||||
errorDialogBuilder.setPositiveButton(activity.getString(R.string.no), (dialogInterface, i) -> {
|
||||
//user does not wish to upload the picture, take them back to ContributionsActivity
|
||||
Intent intent = new Intent(activity, ContributionsActivity.class);
|
||||
dialogInterface.dismiss();
|
||||
activity.startActivity(intent);
|
||||
});
|
||||
errorDialogBuilder.setNegativeButton(activity.getString(R.string.yes), (dialogInterface, i) -> {
|
||||
//user wishes to go ahead with the upload of this picture, just dismiss this dialog
|
||||
dialogInterface.dismiss();
|
||||
});
|
||||
|
||||
AlertDialog errorDialog = errorDialogBuilder.create();
|
||||
if (!activity.isFinishing()) {
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import com.karumi.dexter.Dexter;
|
||||
import com.karumi.dexter.DexterBuilder;
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse;
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ExternalStorageUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.subjects.CompletableSubject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class DexterPermissionObtainer {
|
||||
private final String requestedPermission;
|
||||
private android.app.AlertDialog storagePermissionInfoDialog;
|
||||
private DexterBuilder dexterStoragePermissionBuilder;
|
||||
|
||||
private PermissionDeniedResponse permissionDeniedResponse;
|
||||
|
||||
private boolean storagePromptInProgress;
|
||||
|
||||
private final String rationaleTitle;
|
||||
private final String rationaleText;
|
||||
|
||||
private Activity activity;
|
||||
|
||||
private CompletableSubject storagePromptObservable;
|
||||
|
||||
/**
|
||||
* @param activity The activity that is requesting the permission
|
||||
* @param requestedPermission The permission being requested in the form of Manifest.permission.*
|
||||
* @param rationaleTitle The title of the rationale dialog
|
||||
* @param rationaleText The text inside the rationale dialog
|
||||
*/
|
||||
DexterPermissionObtainer(Activity activity, String requestedPermission, String rationaleTitle, String rationaleText) {
|
||||
this.activity = activity;
|
||||
this.rationaleTitle = rationaleTitle;
|
||||
this.rationaleText = rationaleText;
|
||||
this.requestedPermission = requestedPermission;
|
||||
this.storagePromptObservable = CompletableSubject.create();
|
||||
initPermissionsRationaleDialog();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if storage permissions are obtained, prompts the users to grant storage permissions if necessary.
|
||||
* When storage permission is present, onPermissionObtained is called.
|
||||
*/
|
||||
Completable confirmStoragePermissions() {
|
||||
if (ExternalStorageUtils.isStoragePermissionGranted(activity)) {
|
||||
Timber.i("Storage permissions already granted.");
|
||||
storagePromptObservable.onComplete();
|
||||
} else if (!storagePromptInProgress) {
|
||||
if (storagePromptObservable.hasComplete()) {
|
||||
storagePromptObservable = CompletableSubject.create();
|
||||
}
|
||||
//If permission is not there, ask for it
|
||||
storagePromptInProgress = true;
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
}
|
||||
return storagePromptObservable;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To be called when the user returns to the original activity after manually enabling storage permissions.
|
||||
*/
|
||||
void onManualPermissionReturned() {
|
||||
//OnActivity result, no matter what the result is, our function can handle that.
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method initialised the Dexter's permission builder (if not already initialised). Also makes sure that the builder is initialised
|
||||
* only once, otherwise we would'nt know on which instance of it, the user is working on. And after the builder is initialised, it checks
|
||||
* for the required permission and then handles the permission status, thanks to Dexter's appropriate callbacks.
|
||||
*/
|
||||
private void askDexterToHandleExternalStoragePermission() {
|
||||
Timber.d("External storage permission is being requested");
|
||||
if (null == dexterStoragePermissionBuilder) {
|
||||
dexterStoragePermissionBuilder = Dexter.withActivity(activity)
|
||||
.withPermission(requestedPermission)
|
||||
.withListener(new BasePermissionListener() {
|
||||
@Override
|
||||
public void onPermissionGranted(PermissionGrantedResponse response) {
|
||||
Timber.d("User has granted us the permission for writing the external storage");
|
||||
//If permission is granted, well and good
|
||||
storagePromptInProgress = false;
|
||||
storagePromptObservable.onComplete();
|
||||
//onPermissionObtained.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionDenied(PermissionDeniedResponse response) {
|
||||
Timber.d("User has granted us the permission for writing the external storage");
|
||||
//If permission is not granted in whatsoever scenario, we show him a dialog stating why we need the permission
|
||||
permissionDeniedResponse = response;
|
||||
if (null != storagePermissionInfoDialog && !storagePermissionInfoDialog
|
||||
.isShowing()) {
|
||||
storagePermissionInfoDialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
dexterStoragePermissionBuilder.check();
|
||||
}
|
||||
|
||||
/**
|
||||
* We have agreed to show a dialog showing why we need a particular permission.
|
||||
* This method is used to initialise the dialog which is going to show the permission's rationale.
|
||||
* The dialog is initialised along with a callback for positive and negative user actions.
|
||||
*/
|
||||
private void initPermissionsRationaleDialog() {
|
||||
if (storagePermissionInfoDialog == null) {
|
||||
storagePermissionInfoDialog = DialogUtil
|
||||
.getAlertDialogWithPositiveAndNegativeCallbacks(
|
||||
activity,
|
||||
rationaleTitle, rationaleText,
|
||||
R.drawable.ic_launcher, new DialogUtil.Callback() {
|
||||
@Override
|
||||
public void onPositiveButtonClicked() {
|
||||
//If the user is willing to give us the permission
|
||||
//But had somehow previously choose never ask again, we take him to app settings to manually enable permission
|
||||
if (null == permissionDeniedResponse) {
|
||||
//Dexter returned null, lets see if this ever happens
|
||||
Timber.w("Dexter returned null as permissionDeniedResponse");
|
||||
} else if (permissionDeniedResponse.isPermanentlyDenied()) {
|
||||
PermissionUtils.askUserToManuallyEnablePermissionFromSettings(activity);
|
||||
Timber.i("Permission permanently denied.");
|
||||
} else {
|
||||
//or if we still have chance to show runtime permission dialog, we show him that.
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
Timber.d("Asking via Dexter for permission.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeButtonClicked() {
|
||||
//This was the behaviour as of now, I was planning to maybe snack him with some message
|
||||
//and then call finish after some time, or may be it could be associated with some action
|
||||
// on the snack. If the user does not want us to give the permission, even after showing
|
||||
// rationale dialog, lets not trouble him any more.
|
||||
activity.finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,95 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Sends asynchronous queries to the Commons MediaWiki API to check that file doesn't already exist
|
||||
* Displays a warning to the user if the file already exists on Commons
|
||||
*/
|
||||
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
interface Callback {
|
||||
void onResult(Result result);
|
||||
}
|
||||
|
||||
public enum Result {
|
||||
NO_DUPLICATE,
|
||||
DUPLICATE_PROCEED,
|
||||
DUPLICATE_CANCELLED
|
||||
}
|
||||
|
||||
private final WeakReference<Activity> activity;
|
||||
private final MediaWikiApi api;
|
||||
private final String fileSha1;
|
||||
private final WeakReference<Context> context;
|
||||
private final Callback callback;
|
||||
|
||||
public ExistingFileAsync(WeakReference<Activity> activity, String fileSha1, WeakReference<Context> context, Callback callback, MediaWikiApi mwApi) {
|
||||
this.activity = activity;
|
||||
this.fileSha1 = fileSha1;
|
||||
this.context = context;
|
||||
this.callback = callback;
|
||||
this.api = mwApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
|
||||
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
||||
boolean fileExists;
|
||||
try {
|
||||
String fileSha1 = this.fileSha1;
|
||||
fileExists = api.existingFile(fileSha1);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
Timber.d("File already exists in Commons: %s", fileExists);
|
||||
return fileExists;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean fileExists) {
|
||||
super.onPostExecute(fileExists);
|
||||
|
||||
// If file exists, display warning to user.
|
||||
// Use soft warning for now (user able to choose to proceed) until have determined that implementation works without bugs
|
||||
if (fileExists) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context.get());
|
||||
builder.setMessage(R.string.file_exists)
|
||||
.setTitle(R.string.warning);
|
||||
builder.setPositiveButton(R.string.no, (dialog, id) -> {
|
||||
//Go back to ContributionsActivity
|
||||
Intent intent = new Intent(context.get(), ContributionsActivity.class);
|
||||
context.get().startActivity(intent);
|
||||
callback.onResult(Result.DUPLICATE_CANCELLED);
|
||||
});
|
||||
builder.setNegativeButton(R.string.yes, (dialog, id) -> callback.onResult(Result.DUPLICATE_PROCEED));
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
if (!activity.get().isFinishing()) {
|
||||
dialog.show();
|
||||
}
|
||||
} else {
|
||||
callback.onResult(Result.NO_DUPLICATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
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.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.NonNull;
|
||||
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;
|
||||
|
||||
|
|
@ -44,89 +44,44 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
private Uri mediaUri;
|
||||
private String filePath;
|
||||
private ContentResolver contentResolver;
|
||||
private GPSExtractor imageObj;
|
||||
private Context context;
|
||||
private String decimalCoords;
|
||||
private boolean haveCheckedForOtherImages = false;
|
||||
private String filePath;
|
||||
private ExifInterface exifInterface;
|
||||
private boolean useExtStorage;
|
||||
private boolean cacheFound;
|
||||
private boolean haveCheckedForOtherImages = false;
|
||||
private GPSExtractor tempImageObj;
|
||||
|
||||
FileProcessor(Uri mediaUri, ContentResolver contentResolver, Context context) {
|
||||
this.mediaUri = mediaUri;
|
||||
FileProcessor(@NonNull String filePath, ContentResolver contentResolver, Context context) {
|
||||
this.filePath = filePath;
|
||||
this.contentResolver = contentResolver;
|
||||
this.context = context;
|
||||
ApplicationlessInjection.getInstance(context.getApplicationContext()).getCommonsApplicationComponent().inject(this);
|
||||
try {
|
||||
exifInterface=new ExifInterface(filePath);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
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) {
|
||||
GPSExtractor processFileCoordinates(SimilarImageInterface similarImageInterface) {
|
||||
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());
|
||||
}
|
||||
} else {
|
||||
String filePath = getPathOfMediaOrCopy();
|
||||
if (filePath != null) {
|
||||
imageObj = new GPSExtractor(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
imageObj = new GPSExtractor(exifInterface);
|
||||
decimalCoords = imageObj.getCoords();
|
||||
if (decimalCoords == null || !imageObj.imageCoordsExists) {
|
||||
//Find other photos taken around the same time which has gps coordinates
|
||||
if (!haveCheckedForOtherImages)
|
||||
findOtherImages();// Do not do repeat the process
|
||||
findOtherImages(similarImageInterface);// Do not do repeat the process
|
||||
} else {
|
||||
useImageCoords();
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException e) {
|
||||
Timber.w("File not found: " + mediaUri, e);
|
||||
}
|
||||
return imageObj;
|
||||
}
|
||||
|
||||
|
|
@ -136,10 +91,10 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
|
||||
/**
|
||||
* Find other images around the same location that were taken within the last 20 sec
|
||||
*
|
||||
* @param similarImageInterface
|
||||
*/
|
||||
private void findOtherImages() {
|
||||
Timber.d("filePath" + getPathOfMediaOrCopy());
|
||||
private void findOtherImages(SimilarImageInterface similarImageInterface) {
|
||||
Timber.d("filePath" + filePath);
|
||||
|
||||
long timeOfCreation = new File(filePath).lastModified();//Time when the original image was created
|
||||
File folder = new File(filePath.substring(0, filePath.lastIndexOf('/')));
|
||||
|
|
@ -154,7 +109,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
tempImageObj = null;//Temporary GPSExtractor to extract coords from these photos
|
||||
ParcelFileDescriptor descriptor = null;
|
||||
try {
|
||||
descriptor = contentResolver.openFileDescriptor(Uri.parse(file.getAbsolutePath()), "r");
|
||||
descriptor = contentResolver.openFileDescriptor(Uri.fromFile(file), "r");
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -173,12 +128,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
if (tempImageObj.getCoords() != 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");
|
||||
similarImageInterface.showSimilarImageFragment(filePath, file.getAbsolutePath());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -210,7 +160,6 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
|
||||
// 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())
|
||||
|
|
@ -223,7 +172,6 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
);
|
||||
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);
|
||||
}
|
||||
|
|
@ -232,20 +180,6 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
|||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
|
@ -12,6 +13,7 @@ import android.os.ParcelFileDescriptor;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
|
@ -33,6 +35,8 @@ import java.util.Date;
|
|||
|
||||
import timber.log.Timber;
|
||||
|
||||
import static com.mapbox.mapboxsdk.Mapbox.getApplicationContext;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
/**
|
||||
|
|
@ -76,21 +80,32 @@ public class FileUtils {
|
|||
|
||||
/**
|
||||
* 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";
|
||||
@NonNull
|
||||
static String createExternalCopyPathAndCopy(Uri uri, ContentResolver contentResolver) throws IOException {
|
||||
FileDescriptor fileDescriptor = contentResolver.openFileDescriptor(uri, "r").getFileDescriptor();
|
||||
String copyPath = Environment.getExternalStorageDirectory().toString() + "/CommonsApp/" + new Date().getTime() + "." + getFileExt(uri, contentResolver);
|
||||
File newFile = new File(Environment.getExternalStorageDirectory().toString() + "/CommonsApp");
|
||||
newFile.mkdir();
|
||||
FileUtils.copy(descriptor.getFileDescriptor(), copyPath);
|
||||
FileUtils.copy(fileDescriptor, copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
} catch (IOException e) {
|
||||
Timber.e(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@NonNull
|
||||
static String createCopyPathAndCopy(Uri uri, Context context) throws IOException {
|
||||
FileDescriptor fileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r").getFileDescriptor();
|
||||
String copyPath = context.getCacheDir().getAbsolutePath() + "/" + new Date().getTime() + "." + getFileExt(uri, context.getContentResolver());
|
||||
FileUtils.copy(fileDescriptor, copyPath);
|
||||
Timber.d("Filepath (copied): %s", copyPath);
|
||||
return copyPath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -166,7 +181,7 @@ public class FileUtils {
|
|||
returnPath = uri.getPath();
|
||||
}
|
||||
|
||||
if(returnPath == null) {
|
||||
if (returnPath == null) {
|
||||
//fetching path may fail depending on the source URI and all hope is lost
|
||||
//so we will create and use a copy of the file, which seems to work
|
||||
String copyPath = null;
|
||||
|
|
@ -304,6 +319,7 @@ 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
|
||||
*/
|
||||
|
|
@ -330,6 +346,7 @@ public class FileUtils {
|
|||
|
||||
/**
|
||||
* Deletes files.
|
||||
*
|
||||
* @param file context
|
||||
*/
|
||||
public static boolean deleteFile(File file) {
|
||||
|
|
@ -355,7 +372,7 @@ public class FileUtils {
|
|||
commonsAppDirectory.mkdir();
|
||||
}
|
||||
|
||||
File logsFile = new File(commonsAppDirectory,"logs.txt");
|
||||
File logsFile = new File(commonsAppDirectory, "logs.txt");
|
||||
if (logsFile.exists()) {
|
||||
//old logs file is useless
|
||||
logsFile.delete();
|
||||
|
|
@ -377,4 +394,39 @@ public class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getFilename(Uri uri, ContentResolver contentResolver) {
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN)
|
||||
return "";
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = contentResolver.query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = result.lastIndexOf('/');
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String getFileExt(String fileName){
|
||||
//Default file extension
|
||||
String extension=".jpg";
|
||||
|
||||
int i = fileName.lastIndexOf('.');
|
||||
if (i > 0) {
|
||||
extension = fileName.substring(i+1);
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
public static String getFileExt(Uri uri, ContentResolver contentResolver) {
|
||||
return getFileExt(getFilename(uri, contentResolver));
|
||||
}
|
||||
}
|
||||
|
|
@ -16,11 +16,22 @@ import timber.log.Timber;
|
|||
*/
|
||||
public class GPSExtractor {
|
||||
|
||||
private ExifInterface exif;
|
||||
public static final GPSExtractor DUMMY= new GPSExtractor();
|
||||
private double decLatitude;
|
||||
private double decLongitude;
|
||||
public boolean imageCoordsExists;
|
||||
private String latitude;
|
||||
private String longitude;
|
||||
private String latitudeRef;
|
||||
private String longitudeRef;
|
||||
private String decimalCoords;
|
||||
|
||||
/**
|
||||
* Dummy constructor.
|
||||
*/
|
||||
private GPSExtractor(){
|
||||
|
||||
}
|
||||
/**
|
||||
* Construct from the file descriptor of the image (only for API 24 or newer).
|
||||
* @param fileDescriptor the file descriptor of the image
|
||||
|
|
@ -28,7 +39,8 @@ public class GPSExtractor {
|
|||
@RequiresApi(24)
|
||||
public GPSExtractor(@NonNull FileDescriptor fileDescriptor) {
|
||||
try {
|
||||
exif = new ExifInterface(fileDescriptor);
|
||||
ExifInterface exif = new ExifInterface(fileDescriptor);
|
||||
processCoords(exif);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Timber.w(e);
|
||||
}
|
||||
|
|
@ -41,39 +53,46 @@ public class GPSExtractor {
|
|||
*/
|
||||
public GPSExtractor(@NonNull String path) {
|
||||
try {
|
||||
exif = new ExifInterface(path);
|
||||
ExifInterface exif = new ExifInterface(path);
|
||||
processCoords(exif);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
Timber.w(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct from the file path of the image.
|
||||
* @param exif exif interface of the image
|
||||
*
|
||||
*/
|
||||
public GPSExtractor(@NonNull ExifInterface exif){
|
||||
processCoords(exif);
|
||||
}
|
||||
|
||||
private void processCoords(ExifInterface exif){
|
||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||
//Always return null as a temporary fix for #1599
|
||||
if (exif != null && exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) != null) {
|
||||
//If image has EXIF data, extract image coords
|
||||
imageCoordsExists = true;
|
||||
Timber.d("EXIF data has location info");
|
||||
|
||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts geolocation (either of image from EXIF data, or of user)
|
||||
* @return coordinates as string (needs to be passed as a String in API query)
|
||||
*/
|
||||
@Nullable
|
||||
public String getCoords() {
|
||||
String latitude;
|
||||
String longitude;
|
||||
String latitudeRef;
|
||||
String longitudeRef;
|
||||
String decimalCoords;
|
||||
|
||||
//If image has no EXIF data and user has enabled GPS setting, get user's location
|
||||
//TODO: Always return null as a temporary fix for #1599
|
||||
if (exif == null || exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE) == null) {
|
||||
return null;
|
||||
} else {
|
||||
//If image has EXIF data, extract image coords
|
||||
imageCoordsExists = true;
|
||||
Timber.d("EXIF data has location info");
|
||||
|
||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||
latitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
longitude = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE);
|
||||
longitudeRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
|
||||
if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
|
||||
if(decimalCoords!=null){
|
||||
return decimalCoords;
|
||||
}else if (latitude!=null && latitudeRef!=null && longitude!=null && longitudeRef!=null) {
|
||||
Timber.d("Latitude: %s %s", latitude, latitudeRef);
|
||||
Timber.d("Longitude: %s %s", longitude, longitudeRef);
|
||||
|
||||
|
|
@ -83,7 +102,6 @@ public class GPSExtractor {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getDecLatitude() {
|
||||
return decLatitude;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
|
||||
/**
|
||||
* Created by Ilgaz Er on 8/7/2018.
|
||||
*/
|
||||
public class HeightLimitedRecyclerView extends RecyclerView {
|
||||
|
||||
int height;
|
||||
|
||||
|
||||
public HeightLimitedRecyclerView(Context context) {
|
||||
super(context);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
((Activity) getContext()).getWindowManager()
|
||||
.getDefaultDisplay()
|
||||
.getMetrics(displayMetrics);
|
||||
height=displayMetrics.heightPixels;
|
||||
}
|
||||
|
||||
public HeightLimitedRecyclerView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
((Activity) getContext()).getWindowManager()
|
||||
.getDefaultDisplay()
|
||||
.getMetrics(displayMetrics);
|
||||
height=displayMetrics.heightPixels;
|
||||
}
|
||||
|
||||
public HeightLimitedRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
DisplayMetrics displayMetrics = new DisplayMetrics();
|
||||
((Activity) getContext()).getWindowManager()
|
||||
.getDefaultDisplay()
|
||||
.getMetrics(displayMetrics);
|
||||
height=displayMetrics.heightPixels;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthSpec, int heightSpec) {
|
||||
heightSpec = MeasureSpec.makeMeasureSpec((int) (height*0.3), MeasureSpec.AT_MOST);
|
||||
super.onMeasure(widthSpec, heightSpec);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.upload;
|
|||
import java.util.Locale;
|
||||
|
||||
class Language {
|
||||
|
||||
private Locale locale;
|
||||
private boolean isSet = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,493 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.Manifest;
|
||||
import android.Manifest.permission;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.DataSetObserver;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.karumi.dexter.Dexter;
|
||||
import com.karumi.dexter.DexterBuilder;
|
||||
import com.karumi.dexter.listener.PermissionDeniedResponse;
|
||||
import com.karumi.dexter.listener.PermissionGrantedResponse;
|
||||
import com.karumi.dexter.listener.single.BasePermissionListener;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.utils.ContributionUtils;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.DialogUtil.Callback;
|
||||
import fr.free.nrw.commons.utils.ExternalStorageUtils;
|
||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||
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
|
||||
implements MediaDetailPagerFragment.MediaDetailProvider,
|
||||
AdapterView.OnItemClickListener,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
|
||||
OnCategoriesSaveHandler,
|
||||
ActivityCompat.OnRequestPermissionsResultCallback{
|
||||
|
||||
@Inject
|
||||
MediaWikiApi mwApi;
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
UploadController uploadController;
|
||||
@Inject
|
||||
ModifierSequenceDao modifierSequenceDao;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
SharedPreferences prefs;
|
||||
|
||||
private ArrayList<Contribution> photosList = null;
|
||||
|
||||
private MultipleUploadListFragment uploadsList;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private boolean locationPermitted = false;
|
||||
private boolean isMultipleUploadsPrepared = false;
|
||||
private boolean isMultipleUploadsFinalised = false; // Checks is user clicked to upload button or regret before this phase
|
||||
private final String TAG="#MultipleShareActivity#";
|
||||
private AlertDialog storagePermissionInfoDialog;
|
||||
private DexterBuilder dexterStoragePermissionBuilder;
|
||||
|
||||
private PermissionDeniedResponse permissionDeniedResponse;
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return photosList.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTotalMediaCount() {
|
||||
if (photosList == null) {
|
||||
return 0;
|
||||
}
|
||||
return photosList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyDatasetChanged() {
|
||||
if (uploadsList != null) {
|
||||
uploadsList.notifyDatasetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDataSetObserver(DataSetObserver observer) {
|
||||
// fixme implement me if needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterDataSetObserver(DataSetObserver observer) {
|
||||
// fixme implement me if needed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int index, long item) {
|
||||
showDetail(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void OnMultipleUploadInitiated() {
|
||||
// No need to request external permission here, because if user can reach this point, then she permission granted
|
||||
Timber.d("OnMultipleUploadInitiated");
|
||||
multipleUploadBegins();
|
||||
}
|
||||
|
||||
private void multipleUploadBegins() {
|
||||
|
||||
Timber.d("Multiple upload begins");
|
||||
final ProgressDialog dialog = new ProgressDialog(this);
|
||||
dialog.setIndeterminate(false);
|
||||
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||
dialog.setMax(photosList.size());
|
||||
dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size()));
|
||||
dialog.show();
|
||||
|
||||
for (int i = 0; i < photosList.size(); i++) {
|
||||
Contribution up = photosList.get(i);
|
||||
final int uploadCount = i + 1; // Goddamn Java
|
||||
|
||||
uploadController.startUpload(up, contribution -> {
|
||||
dialog.setProgress(uploadCount);
|
||||
if (uploadCount == photosList.size()) {
|
||||
dialog.dismiss();
|
||||
Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
|
||||
startingToast.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
uploadsList.setImageOnlyMode(true);
|
||||
|
||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||
if (categorizationFragment == null) {
|
||||
categorizationFragment = new CategorizationFragment();
|
||||
}
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = getCurrentFocus();
|
||||
if (target != null) {
|
||||
InputMethodManager imm = (InputMethodManager) target.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null)
|
||||
imm.hideSoftInputFromWindow(target.getWindowToken(), 0);
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(R.id.uploadsFragmentContainer, categorizationFragment, "categorization")
|
||||
.commitAllowingStateLoss();
|
||||
isMultipleUploadsFinalised = true;
|
||||
//See http://stackoverflow.com/questions/7469082/getting-exception-illegalstateexception-can-not-perform-this-action-after-onsa
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCategoriesSave(List<String> categories) {
|
||||
if (categories.size() > 0) {
|
||||
for (Contribution contribution : photosList) {
|
||||
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
|
||||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
|
||||
modifierSequenceDao.save(categoriesSequence);
|
||||
}
|
||||
}
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (mediaDetails.isVisible()) {
|
||||
getSupportFragmentManager().popBackStack();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_multiple_uploads);
|
||||
ButterKnife.bind(this);
|
||||
initDrawer();
|
||||
initPermissionsRationaleDialog();
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
|
||||
}
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
requestAuthToken();
|
||||
|
||||
//TODO: 15/10/17 should location permission be explicitly requested if not provided?
|
||||
//check if location permission is enabled
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
||||
{
|
||||
locationPermitted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* We have agreed to show a dialog showing why we need a particular permission.
|
||||
* This method is used to initialise the dialog which is going to show the permission's rationale.
|
||||
* The dialog is initialised along with a callback for positive and negative user actions.
|
||||
*/
|
||||
private void initPermissionsRationaleDialog() {
|
||||
if (storagePermissionInfoDialog == null) {
|
||||
storagePermissionInfoDialog = DialogUtil
|
||||
.getAlertDialogWithPositiveAndNegativeCallbacks(
|
||||
MultipleShareActivity.this,
|
||||
getString(R.string.storage_permission), getString(
|
||||
R.string.write_storage_permission_rationale_for_image_share),
|
||||
R.drawable.ic_launcher, new Callback() {
|
||||
@Override
|
||||
public void onPositiveButtonClicked() {
|
||||
//If the user is willing to give us the permission
|
||||
//But had somehow previously choose never ask again, we take him to app settings to manually enable permission
|
||||
if(null== permissionDeniedResponse){
|
||||
//Dexter returned null, lets see if this ever happens
|
||||
return;
|
||||
}
|
||||
else if (permissionDeniedResponse.isPermanentlyDenied()) {
|
||||
PermissionUtils.askUserToManuallyEnablePermissionFromSettings(MultipleShareActivity.this);
|
||||
} else {
|
||||
//or if we still have chance to show runtime permission dialog, we show him that.
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeButtonClicked() {
|
||||
//This was the behaviour as of now, I was planning to maybe snack him with some message
|
||||
//and then call finish after some time, or may be it could be associated with some action on the snack
|
||||
//If the user does not want us to give the permission, even after showing rationale dialog, lets not trouble him anymore
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
getSupportFragmentManager().removeOnBackStackChangedListener(this);
|
||||
uploadController.cleanup();
|
||||
}
|
||||
|
||||
private void showDetail(int i) {
|
||||
if (mediaDetails == null || !mediaDetails.isVisible()) {
|
||||
mediaDetails = new MediaDetailPagerFragment(true, false);
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.uploadsFragmentContainer, mediaDetails)
|
||||
.addToBackStack(null)
|
||||
.commit();
|
||||
getSupportFragmentManager().executePendingTransactions();
|
||||
}
|
||||
mediaDetails.showImage(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
/* This will be true if permission request is granted before we request. Otherwise we will
|
||||
* explicitly call operations under this method again.
|
||||
*/
|
||||
if (isMultipleUploadsPrepared) {
|
||||
super.onSaveInstanceState(outState);
|
||||
Timber.d("onSaveInstanceState multiple uploads is prepared, permission granted");
|
||||
outState.putParcelableArrayList("uploadsList", photosList);
|
||||
} else {
|
||||
Timber.d("onSaveInstanceState multiple uploads is not prepared, permission not granted");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
// Multiple uploads prepared boolean is used to decide when to call multipleUploadsBegin()
|
||||
isMultipleUploadsFinalised = false;
|
||||
isMultipleUploadsPrepared = false;
|
||||
mwApi.setAuthCookie(authCookie);
|
||||
if (!ExternalStorageUtils.isStoragePermissionGranted(this)) {
|
||||
//If permission is not there, handle the negative cases
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
isMultipleUploadsPrepared = false;
|
||||
return; // Postpone operation to do after gettion permission
|
||||
} else {
|
||||
isMultipleUploadsPrepared = true;
|
||||
prepareMultipleUploadList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method initialised the Dexter's permission builder (if not already initialised). Also makes sure that the builder is initialised
|
||||
* only once, otherwise we would'nt know on which instance of it, the user is working on. And after the builder is initialised, it checks for the required
|
||||
* permission and then handles the permission status, thanks to Dexter's appropriate callbacks.
|
||||
*/
|
||||
private void askDexterToHandleExternalStoragePermission() {
|
||||
Timber.d(TAG, "External storage permission is being requested");
|
||||
if (null == dexterStoragePermissionBuilder) {
|
||||
dexterStoragePermissionBuilder = Dexter.withActivity(this)
|
||||
.withPermission(permission.WRITE_EXTERNAL_STORAGE)
|
||||
.withListener(new BasePermissionListener() {
|
||||
@Override
|
||||
public void onPermissionGranted(PermissionGrantedResponse response) {
|
||||
Timber.d(TAG,"User has granted us the permission for writing the external storage");
|
||||
//If permission is granted, well and good
|
||||
prepareMultipleUploadList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPermissionDenied(PermissionDeniedResponse response) {
|
||||
Timber.d(TAG,"User has granted us the permission for writing the external storage");
|
||||
//If permission is not granted in whatsoever scenario, we show him a dialog stating why we need the permission
|
||||
permissionDeniedResponse=response;
|
||||
if (null != storagePermissionInfoDialog && !storagePermissionInfoDialog
|
||||
.isShowing()) {
|
||||
storagePermissionInfoDialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
dexterStoragePermissionBuilder.check();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) {
|
||||
//OnActivity result, no matter what the result is, our function can handle that.
|
||||
askDexterToHandleExternalStoragePermission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a list from files will be uploaded. Saves these files temporarily to external
|
||||
* storage. Adds them to uploads list
|
||||
*/
|
||||
private void prepareMultipleUploadList() {
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
||||
if (photosList == null) {
|
||||
photosList = new ArrayList<>();
|
||||
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
for (int i = 0; i < urisList.size(); i++) {
|
||||
Contribution up = new Contribution();
|
||||
Uri uri = urisList.get(i);
|
||||
// Use temporarily saved file Uri instead
|
||||
uri = ContributionUtils.saveFileBeingUploadedTemporarily(this, uri);
|
||||
up.setLocalUri(uri);
|
||||
up.setTag("mimeType", intent.getType());
|
||||
up.setTag("sequence", i);
|
||||
up.setSource(Contribution.SOURCE_EXTERNAL);
|
||||
up.setMultiple(true);
|
||||
String imageGpsCoordinates = extractImageGpsData(uri);
|
||||
if (imageGpsCoordinates != null) {
|
||||
Timber.d("GPS data for image found!");
|
||||
up.setDecimalCoords(imageGpsCoordinates);
|
||||
}
|
||||
photosList.add(up);
|
||||
}
|
||||
}
|
||||
|
||||
uploadsList = (MultipleUploadListFragment) getSupportFragmentManager().findFragmentByTag("uploadsList");
|
||||
if (uploadsList == null) {
|
||||
uploadsList = new MultipleUploadListFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.uploadsFragmentContainer, uploadsList, "uploadsList")
|
||||
.commit();
|
||||
}
|
||||
setTitle(getResources().getQuantityString(R.plurals.multiple_uploads_title, photosList.size(), photosList.size()));
|
||||
uploadController.prepareService();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||
failureToast.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(mediaDetails != null && mediaDetails.isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Will attempt to extract the gps coordinates using exif data or by using the current
|
||||
* location if available for the image who's imageUri has been provided.
|
||||
* @param imageUri The uri of the image who's GPS coordinates data we wish to extract
|
||||
* @return GPS coordinates as a String as is returned by {@link GPSExtractor}
|
||||
*/
|
||||
@Nullable
|
||||
private String extractImageGpsData(Uri imageUri) {
|
||||
Timber.d("Entering extractImagesGpsData");
|
||||
|
||||
if (imageUri == null) {
|
||||
//now why would you do that???
|
||||
return null;
|
||||
}
|
||||
|
||||
GPSExtractor gpsExtractor = null;
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
ParcelFileDescriptor fd = getContentResolver().openFileDescriptor(imageUri,"r");
|
||||
if (fd != null) {
|
||||
gpsExtractor = new GPSExtractor(fd.getFileDescriptor());
|
||||
}
|
||||
} else {
|
||||
String filePath = FileUtils.getPath(this,imageUri);
|
||||
if (filePath != null) {
|
||||
gpsExtractor = new GPSExtractor(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (gpsExtractor != null) {
|
||||
//get image coordinates from exif data or user location
|
||||
return gpsExtractor.getCoords();
|
||||
}
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
Timber.w(fnfe);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// If on back pressed before sharing
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
// Remove saved files if activity is stopped before upload operation, ie user changed mind
|
||||
if (!isMultipleUploadsFinalised) {
|
||||
if (photosList != null) {
|
||||
for (Contribution contribution : photosList) {
|
||||
Timber.d("User changed mind, didn't click to upload button, deleted file: "+contribution.getLocalUri());
|
||||
ContributionUtils.removeTemporaryFile(contribution.getLocalUri());
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.GridView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import dagger.android.support.AndroidSupportInjection;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
|
||||
public class MultipleUploadListFragment extends Fragment {
|
||||
|
||||
public interface OnMultipleUploadInitiatedHandler {
|
||||
void OnMultipleUploadInitiated();
|
||||
}
|
||||
|
||||
@BindView(R.id.multipleShareBackground)
|
||||
GridView photosGrid;
|
||||
|
||||
@BindView(R.id.multipleBaseTitle)
|
||||
EditText baseTitle;
|
||||
|
||||
private PhotoDisplayAdapter photosAdapter;
|
||||
private TitleTextWatcher textWatcher = new TitleTextWatcher();
|
||||
|
||||
private Point photoSize;
|
||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
|
||||
|
||||
private boolean imageOnlyMode;
|
||||
|
||||
private static class UploadHolderView {
|
||||
private Uri imageUri;
|
||||
private SimpleDraweeView image;
|
||||
private TextView title;
|
||||
private RelativeLayout overlay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
AndroidSupportInjection.inject(this);
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
private class PhotoDisplayAdapter extends BaseAdapter {
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return detailProvider.getTotalMediaCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int i) {
|
||||
return detailProvider.getMediaAtPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int i, View view, ViewGroup viewGroup) {
|
||||
UploadHolderView holder;
|
||||
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(getContext()).inflate(R.layout.layout_upload_item, viewGroup, false);
|
||||
holder = new UploadHolderView();
|
||||
holder.image = view.findViewById(R.id.uploadImage);
|
||||
holder.title = view.findViewById(R.id.uploadTitle);
|
||||
holder.overlay = view.findViewById(R.id.uploadOverlay);
|
||||
|
||||
holder.image.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, photoSize.y));
|
||||
holder.image.setHierarchy(GenericDraweeHierarchyBuilder
|
||||
.newInstance(getResources())
|
||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_image_black_24dp, getContext().getTheme()))
|
||||
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_error_outline_black_24dp, getContext().getTheme()))
|
||||
.build());
|
||||
view.setTag(holder);
|
||||
} else {
|
||||
holder = (UploadHolderView) view.getTag();
|
||||
}
|
||||
|
||||
Contribution up = (Contribution) this.getItem(i);
|
||||
|
||||
if (holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
|
||||
holder.image.setImageURI(up.getLocalUri().toString());
|
||||
holder.imageUri = up.getLocalUri();
|
||||
}
|
||||
|
||||
if (!imageOnlyMode) {
|
||||
holder.overlay.setVisibility(View.VISIBLE);
|
||||
holder.title.setText(up.getFilename());
|
||||
} else {
|
||||
holder.overlay.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = getActivity().getCurrentFocus();
|
||||
ViewUtil.hideKeyboard(target);
|
||||
}
|
||||
|
||||
// FIXME: Wrong result type
|
||||
private Point calculatePicDimension(int count) {
|
||||
DisplayMetrics screenMetrics = getResources().getDisplayMetrics();
|
||||
int screenWidth = screenMetrics.widthPixels;
|
||||
int screenHeight = screenMetrics.heightPixels;
|
||||
|
||||
int picWidth = Math.min((int) Math.sqrt(screenWidth * screenHeight / count), screenWidth);
|
||||
picWidth = Math.min((int) (192 * screenMetrics.density), Math.max((int) (120 * screenMetrics.density), picWidth / 48 * 48));
|
||||
int picHeight = Math.min(picWidth, (int) (192 * screenMetrics.density)); // Max Height is same as Contributions list
|
||||
|
||||
return new Point(picWidth, picHeight);
|
||||
}
|
||||
|
||||
public void notifyDatasetChanged() {
|
||||
if (photosAdapter != null) {
|
||||
photosAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void setImageOnlyMode(boolean mode) {
|
||||
imageOnlyMode = mode;
|
||||
if (imageOnlyMode) {
|
||||
baseTitle.setVisibility(View.GONE);
|
||||
} else {
|
||||
baseTitle.setVisibility(View.VISIBLE);
|
||||
}
|
||||
photosAdapter.notifyDataSetChanged();
|
||||
photosGrid.setEnabled(!mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_multiple_uploads_list, container, false);
|
||||
ButterKnife.bind(this,view);
|
||||
photosAdapter = new PhotoDisplayAdapter();
|
||||
photosGrid.setAdapter(photosAdapter);
|
||||
photosGrid.setOnItemClickListener((AdapterView.OnItemClickListener) getActivity());
|
||||
photoSize = calculatePicDimension(detailProvider.getTotalMediaCount());
|
||||
photosGrid.setColumnWidth(photoSize.x);
|
||||
|
||||
baseTitle.addTextChangedListener(textWatcher);
|
||||
|
||||
baseTitle.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
baseTitle.removeTextChangedListener(textWatcher);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
menu.clear();
|
||||
inflater.inflate(R.menu.fragment_multiple_upload_list, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_upload_multiple:
|
||||
if (baseTitle.getText().toString().trim().isEmpty()) {
|
||||
Toast.makeText(getContext(), R.string.add_set_name_toast, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
multipleUploadInitiatedHandler.OnMultipleUploadInitiated();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider) getActivity();
|
||||
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
private class TitleTextWatcher implements TextWatcher {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i1, int i2, int i3) {
|
||||
for (int i = 0; i < detailProvider.getTotalMediaCount(); i++) {
|
||||
Contribution up = (Contribution) detailProvider.getMediaAtPosition(i);
|
||||
Boolean isDirty = (Boolean) up.getTag("isDirty");
|
||||
if (isDirty == null || !isDirty) {
|
||||
if (!TextUtils.isEmpty(charSequence)) {
|
||||
up.setFilename(charSequence.toString() + " - " + ((Integer) up.getTag("sequence") + 1));
|
||||
} else {
|
||||
up.setFilename("");
|
||||
}
|
||||
}
|
||||
}
|
||||
detailProvider.notifyDatasetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,674 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
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.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.github.chrisbanes.photoview.PhotoView;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.caching.CacheController;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequenceDao;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.CategoryApi;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.utils.ContributionUtils;
|
||||
import fr.free.nrw.commons.utils.ExternalStorageUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
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.FileUtils.getSHA1;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
|
||||
|
||||
/**
|
||||
* Activity for the title/desc screen after image is selected. Also starts processing image
|
||||
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
|
||||
*/
|
||||
public class ShareActivity
|
||||
extends AuthenticatedActivity
|
||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||
OnCategoriesSaveHandler,
|
||||
ActivityCompat.OnRequestPermissionsResultCallback {
|
||||
|
||||
private static final int REQUEST_PERM_ON_SUBMIT_STORAGE = 4;
|
||||
//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
|
||||
MediaWikiApi mwApi;
|
||||
@Inject
|
||||
CacheController cacheController;
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
UploadController uploadController;
|
||||
@Inject
|
||||
ModifierSequenceDao modifierSequenceDao;
|
||||
@Inject
|
||||
CategoryApi apiCall;
|
||||
@Inject @Named("application_preferences") SharedPreferences applicationPrefs;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
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 mimeType;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
private Uri mediaUri;
|
||||
private Uri contentProviderUri;
|
||||
private Contribution contribution;
|
||||
private GPSExtractor gpsObj;
|
||||
private String decimalCoords;
|
||||
private FileProcessor fileObj;
|
||||
private boolean useNewPermissions = false;
|
||||
private boolean storagePermitted = false;
|
||||
private boolean locationPermitted = false;
|
||||
private String title;
|
||||
private String description;
|
||||
private String wikiDataEntityId;
|
||||
private boolean duplicateCheckPassed = false;
|
||||
private boolean isNearbyUpload = false;
|
||||
private Animator CurrentAnimator;
|
||||
private long ShortAnimationDuration;
|
||||
private boolean isFABOpen = false;
|
||||
private float startScaleFinal;
|
||||
private Bundle savedInstanceState;
|
||||
private boolean isUploadFinalised = false; // Checks is user clicked to upload button or regret before this phase
|
||||
private boolean isZoom = false;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Called when user taps the submit button.
|
||||
* Requests Storage permission, if needed.
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void uploadActionInitiated(String title, String description) {
|
||||
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
|
||||
|
||||
if (sessionManager.getCurrentAccount() != null) {
|
||||
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()) {
|
||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
REQUEST_PERM_ON_SUBMIT_STORAGE);
|
||||
} else {
|
||||
uploadBegins();
|
||||
}
|
||||
} else {
|
||||
uploadBegins();
|
||||
}
|
||||
}
|
||||
else //Send user to login activity
|
||||
{
|
||||
Toast.makeText(this, "You need to login first!", Toast.LENGTH_SHORT).show();
|
||||
Intent loginIntent = new Intent(ShareActivity.this, LoginActivity.class);
|
||||
startActivity(loginIntent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
private boolean needsToRequestStoragePermission() {
|
||||
return !FileUtils.isSelfOwned(getApplicationContext(), mediaUri)
|
||||
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED);
|
||||
//return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called after permission checks are done.
|
||||
* Gets file metadata for category suggestions, displays toast, caches categories found, calls uploadController
|
||||
*/
|
||||
|
||||
private void uploadBegins() {
|
||||
fileObj.processFileCoordinates(locationPermitted);
|
||||
|
||||
Toast startingToast = Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG);
|
||||
startingToast.show();
|
||||
|
||||
if (!fileObj.isCacheFound()) {
|
||||
//Has to be called after apiCall.request()
|
||||
cacheController.cacheCategory();
|
||||
Timber.d("Cache the categories found");
|
||||
}
|
||||
|
||||
uploadController.startUpload(title, contentProviderUri, mediaUri, description, mimeType, source, decimalCoords, wikiDataEntityId, c -> {
|
||||
ShareActivity.this.contribution = c;
|
||||
showPostUpload();
|
||||
});
|
||||
isUploadFinalised = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts CategorizationFragment after uploadBegins.
|
||||
*/
|
||||
|
||||
private void showPostUpload() {
|
||||
if (categorizationFragment == null) {
|
||||
categorizationFragment = new CategorizationFragment();
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.single_upload_fragment_container, categorizationFragment, "categorization")
|
||||
.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send categories to modifications queue after they are selected
|
||||
*
|
||||
* @param categories categories selected
|
||||
*/
|
||||
@Override
|
||||
public void onCategoriesSave(List<String> categories) {
|
||||
if (categories.size() > 0) {
|
||||
ModifierSequence categoriesSequence = new ModifierSequence(contribution.getContentUri());
|
||||
|
||||
categoriesSequence.queueModifier(new CategoryModifier(categories.toArray(new String[]{})));
|
||||
categoriesSequence.queueModifier(new TemplateRemoveModifier("Uncategorized"));
|
||||
modifierSequenceDao.save(categoriesSequence);
|
||||
}
|
||||
|
||||
// FIXME: Make sure that the content provider is up
|
||||
// This is the wrong place for it, but bleh - better than not having it turned on by default for people who don't go throughl ogin
|
||||
ContentResolver.setSyncAutomatically(sessionManager.getCurrentAccount(), BuildConfig.MODIFICATION_AUTHORITY, true); // Enable sync by default!
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
if (contribution != null) {
|
||||
outState.putParcelable("contribution", contribution);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
mwApi.setAuthCookie(authCookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||
failureToast.show();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
isUploadFinalised = false;
|
||||
setContentView(R.layout.activity_share);
|
||||
ButterKnife.bind(this);
|
||||
initBack();
|
||||
backgroundImageView.setHierarchy(GenericDraweeHierarchyBuilder
|
||||
.newInstance(getResources())
|
||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_image_black_24dp, getTheme()))
|
||||
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
||||
R.drawable.ic_error_outline_black_24dp, getTheme()))
|
||||
.build());
|
||||
if (!ExternalStorageUtils.isStoragePermissionGranted(this)) {
|
||||
this.savedInstanceState = savedInstanceState;
|
||||
ExternalStorageUtils.requestExternalStoragePermission(this);
|
||||
return; // Postpone operation to do after getting permission
|
||||
} else {
|
||||
receiveImageIntent();
|
||||
createContributionWithReceivedIntent(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
// If upload is not finalised with failure or success, but contribution is created,
|
||||
// we have to remove temp file, to prevent using unnecessary memory
|
||||
if (!isUploadFinalised) {
|
||||
if (mediaUri != null) {
|
||||
ContributionUtils.removeTemporaryFile(mediaUri);
|
||||
}
|
||||
}
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
private void createContributionWithReceivedIntent(Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
contribution = savedInstanceState.getParcelable("contribution");
|
||||
}
|
||||
|
||||
requestAuthToken();
|
||||
Timber.d("Uri: %s", mediaUri.toString());
|
||||
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
|
||||
|
||||
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();
|
||||
if (sessionManager.getCurrentAccount() == null) {
|
||||
Toast.makeText(this, getString(R.string.login_alert_message), Toast.LENGTH_SHORT).show();
|
||||
applicationPrefs.edit().putBoolean("login_skipped", false).apply();
|
||||
Intent loginIntent = new Intent(ShareActivity.this, LoginActivity.class);
|
||||
startActivity(loginIntent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive intent from ContributionController.java when user selects picture to upload
|
||||
*/
|
||||
private void receiveImageIntent() {
|
||||
Intent intent = getIntent();
|
||||
|
||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||
mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
contentProviderUri = mediaUri;
|
||||
mediaUri = ContributionUtils.saveFileBeingUploadedTemporarily(this, mediaUri);
|
||||
|
||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
||||
} else {
|
||||
source = Contribution.SOURCE_EXTERNAL;
|
||||
}
|
||||
|
||||
boolean isDirectUpload = intent.getBooleanExtra("isDirectUpload", false);
|
||||
|
||||
if (isDirectUpload) {
|
||||
Timber.d("This was initiated by a direct upload from Nearby");
|
||||
isNearbyUpload = true;
|
||||
wikiDataEntityId = intent.getStringExtra(WIKIDATA_ENTITY_ID_PREF);
|
||||
Timber.d("Received wikiDataEntityId from contribution controller %s", wikiDataEntityId);
|
||||
}
|
||||
mimeType = intent.getType();
|
||||
}
|
||||
|
||||
if (mediaUri != null) {
|
||||
backgroundImageView.setImageURI(mediaUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 submit button permission request (for storage)
|
||||
*
|
||||
* @param requestCode type of request
|
||||
* @param permissions permissions requested
|
||||
* @param grantResults grant results
|
||||
*/
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
if (requestCode == 1 && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Timber.d("onRequestPermissionsResult external storage permission granted");
|
||||
// You can receive image intent and save image to a temp file only if ext storage permission is granted
|
||||
receiveImageIntent();
|
||||
createContributionWithReceivedIntent(savedInstanceState);
|
||||
|
||||
if (requestCode == REQUEST_PERM_ON_SUBMIT_STORAGE) {
|
||||
checkIfFileExists();
|
||||
//Uploading only begins if storage permission granted from arrow icon
|
||||
uploadBegins();
|
||||
}
|
||||
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file user wants to upload already exists on Commons
|
||||
*/
|
||||
private void checkIfFileExists() {
|
||||
if (!useNewPermissions || storagePermitted) {
|
||||
if (!duplicateCheckPassed) {
|
||||
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
||||
try {
|
||||
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
|
||||
String fileSHA1 = getSHA1(inputStream);
|
||||
Timber.d("Input stream created from %s", mediaUri.toString());
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
uploadController.cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
if (categorizationFragment != null && categorizationFragment.isVisible()) {
|
||||
categorizationFragment.showBackButtonDialog();
|
||||
} else {
|
||||
onBackPressed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
closeFABMenu();
|
||||
mainFab.setVisibility(View.GONE);
|
||||
|
||||
InputStream input = null;
|
||||
try {
|
||||
input = this.getContentResolver().openInputStream(imageuri);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Zoom zoomObj = new Zoom(thumbView, flContainer, this.getContentResolver());
|
||||
Bitmap scaledImage = zoomObj.createScaledImage(input, imageuri);
|
||||
|
||||
// Load the high-resolution "zoomed-in" image.
|
||||
expandedImageView.setImageBitmap(scaledImage);
|
||||
float startScale = zoomObj.adjustStartEndBounds(startBounds, finalBounds, globalOffset);
|
||||
|
||||
// Hide the thumbnail and show the zoomed-in view. When the animation
|
||||
// begins, it will position the zoomed-in view in the place of the
|
||||
// thumbnail.
|
||||
thumbView.setAlpha(0f);
|
||||
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);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
public interface SimilarImageInterface {
|
||||
void showSimilarImageFragment(String originalFilePath, String possibleFilePath);
|
||||
}
|
||||
|
|
@ -1,389 +0,0 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.view.ViewCompat;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import butterknife.OnItemSelected;
|
||||
import butterknife.OnTouch;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.view.MotionEvent.ACTION_UP;
|
||||
|
||||
public class SingleUploadFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
@BindView(R.id.titleEdit) EditText titleEdit;
|
||||
@BindView(R.id.rv_descriptions) RecyclerView rvDescriptions;
|
||||
@BindView(R.id.titleDescButton) Button titleDescButton;
|
||||
@BindView(R.id.share_license_summary) TextView licenseSummaryView;
|
||||
@BindView(R.id.licenseSpinner) Spinner licenseSpinner;
|
||||
|
||||
|
||||
@Inject @Named("default_preferences") SharedPreferences prefs;
|
||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||
|
||||
private String license;
|
||||
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
||||
private TitleTextWatcher textWatcher = new TitleTextWatcher();
|
||||
private DescriptionsAdapter descriptionsAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.activity_share, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
//What happens when the 'submit' icon is tapped
|
||||
case R.id.menu_upload_single:
|
||||
|
||||
if (titleEdit.getText().toString().trim().isEmpty()) {
|
||||
Toast.makeText(getContext(), R.string.add_title_toast, Toast.LENGTH_LONG).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
String title = titleEdit.getText().toString();
|
||||
String descriptionsInVariousLanguages = getDescriptionsInAppropriateFormat();
|
||||
|
||||
//Save the title/desc in short-lived cache so next time this fragment is loaded, we can access these
|
||||
prefs.edit()
|
||||
.putString("Title", title)
|
||||
.putString("Desc", new Gson().toJson(descriptionsAdapter
|
||||
.getDescriptions()))//Description, now is not just a string, its a list of description objects
|
||||
.apply();
|
||||
|
||||
uploadActionInitiatedHandler
|
||||
.uploadActionInitiated(title, descriptionsInVariousLanguages);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private String getDescriptionsInAppropriateFormat() {
|
||||
List<Description> descriptions = descriptionsAdapter.getDescriptions();
|
||||
StringBuilder descriptionsInAppropriateFormat = new StringBuilder();
|
||||
for (Description description : descriptions) {
|
||||
String individualDescription = String.format("{{%s|1=%s}}", description.getLanguageId(),
|
||||
description.getDescriptionText());
|
||||
descriptionsInAppropriateFormat.append(individualDescription);
|
||||
}
|
||||
return descriptionsInAppropriateFormat.toString();
|
||||
|
||||
}
|
||||
|
||||
private List<Description> getDescriptions() {
|
||||
List<Description> descriptions = descriptionsAdapter.getDescriptions();
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_single_upload, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
|
||||
initRecyclerView();
|
||||
|
||||
Intent activityIntent = getActivity().getIntent();
|
||||
if (activityIntent.hasExtra("title")) {
|
||||
titleEdit.setText(activityIntent.getStringExtra("title"));
|
||||
}
|
||||
if (activityIntent.hasExtra("description") && descriptionsAdapter.getDescriptions() != null
|
||||
&& descriptionsAdapter.getDescriptions().size() > 0) {
|
||||
descriptionsAdapter.getDescriptions().get(0)
|
||||
.setDescriptionText(activityIntent.getStringExtra("description"));
|
||||
descriptionsAdapter.notifyItemChanged(0);
|
||||
}
|
||||
|
||||
|
||||
ArrayList<String> licenseItems = new ArrayList<>();
|
||||
licenseItems.add(getString(R.string.license_name_cc0));
|
||||
licenseItems.add(getString(R.string.license_name_cc_by));
|
||||
licenseItems.add(getString(R.string.license_name_cc_by_sa));
|
||||
licenseItems.add(getString(R.string.license_name_cc_by_four));
|
||||
licenseItems.add(getString(R.string.license_name_cc_by_sa_four));
|
||||
|
||||
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
|
||||
// If this is a direct upload from Nearby, autofill title and desc fields with the Place's values
|
||||
boolean isNearbyUpload = ((ShareActivity) getActivity()).isNearbyUpload();
|
||||
|
||||
if (isNearbyUpload) {
|
||||
String imageTitle = directPrefs.getString("Title", "");
|
||||
String imageDesc = directPrefs.getString("Desc", "");
|
||||
String imageCats = directPrefs.getString("Category", "");
|
||||
Timber.d("Image title: " + imageTitle + ", image desc: " + imageDesc + ", image categories: " + imageCats);
|
||||
titleEdit.setText(imageTitle);
|
||||
if (descriptionsAdapter.getDescriptions() != null
|
||||
&& descriptionsAdapter.getDescriptions().size() > 0) {
|
||||
descriptionsAdapter.getDescriptions().get(0).setDescriptionText(imageDesc);
|
||||
descriptionsAdapter.notifyItemChanged(0);
|
||||
}
|
||||
}
|
||||
|
||||
// check if this is the first time we have uploaded
|
||||
if (prefs.getString("Title", "").trim().length() == 0
|
||||
&& prefs.getString("Desc", "").trim().length() == 0) {
|
||||
titleDescButton.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
Timber.d(license);
|
||||
|
||||
ArrayAdapter<String> adapter;
|
||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme", false)) {
|
||||
// dark theme
|
||||
adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_dropdown_item, licenseItems);
|
||||
} else {
|
||||
// light theme
|
||||
adapter = new ArrayAdapter<>(getActivity(), R.layout.light_simple_spinner_dropdown_item, licenseItems);
|
||||
}
|
||||
|
||||
licenseSpinner.setAdapter(adapter);
|
||||
|
||||
int position = licenseItems.indexOf(getString(Utils.licenseNameFor(license)));
|
||||
|
||||
// Check position is valid
|
||||
if (position < 0) {
|
||||
Timber.d("Invalid position: %d. Using default license", position);
|
||||
position = 4;
|
||||
}
|
||||
|
||||
Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)));
|
||||
licenseSpinner.setSelection(position);
|
||||
|
||||
titleEdit.addTextChangedListener(textWatcher);
|
||||
|
||||
titleEdit.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (!hasFocus) {
|
||||
ViewUtil.hideKeyboard(v);
|
||||
}
|
||||
});
|
||||
|
||||
setLicenseSummary(license);
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
descriptionsAdapter = new DescriptionsAdapter();
|
||||
descriptionsAdapter.setCallback(this::showInfoAlert);
|
||||
descriptionsAdapter.setLanguages(getLocaleSupportedByDevice());
|
||||
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||
}
|
||||
|
||||
private List<Language> getLocaleSupportedByDevice() {
|
||||
List<Language> languages = new ArrayList<>();
|
||||
Locale[] localesArray = Locale.getAvailableLocales();
|
||||
List<Locale> locales = Arrays.asList(localesArray);
|
||||
for (Locale locale : locales) {
|
||||
languages.add(new Language(locale));
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
titleEdit.removeTextChangedListener(textWatcher);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@OnItemSelected(R.id.licenseSpinner)
|
||||
void onLicenseSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
String licenseName = parent.getItemAtPosition(position).toString();
|
||||
|
||||
// Set selected color to white because it should be readable on random images.
|
||||
TextView selectedText = (TextView) licenseSpinner.getChildAt(0);
|
||||
if (selectedText != null) {
|
||||
selectedText.setTextColor(Color.WHITE);
|
||||
selectedText.setBackgroundColor(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
String license;
|
||||
if (getString(R.string.license_name_cc0).equals(licenseName)) {
|
||||
license = Prefs.Licenses.CC0;
|
||||
} else if (getString(R.string.license_name_cc_by).equals(licenseName)) {
|
||||
license = Prefs.Licenses.CC_BY_3;
|
||||
} else if (getString(R.string.license_name_cc_by_sa).equals(licenseName)) {
|
||||
license = Prefs.Licenses.CC_BY_SA_3;
|
||||
} else if (getString(R.string.license_name_cc_by_four).equals(licenseName)) {
|
||||
license = Prefs.Licenses.CC_BY_4;
|
||||
} else if (getString(R.string.license_name_cc_by_sa_four).equals(licenseName)) {
|
||||
license = Prefs.Licenses.CC_BY_SA_4;
|
||||
} else {
|
||||
throw new IllegalStateException("Unknown licenseName: " + licenseName);
|
||||
}
|
||||
|
||||
setLicenseSummary(license);
|
||||
prefs.edit()
|
||||
.putString(Prefs.DEFAULT_LICENSE, license)
|
||||
.apply();
|
||||
}
|
||||
|
||||
|
||||
@OnClick(R.id.titleDescButton)
|
||||
void setTitleDescButton() {
|
||||
//Retrieve last title and desc entered
|
||||
String title = prefs.getString("Title", "");
|
||||
String descriptionJson = prefs.getString("Desc", "");
|
||||
Timber.d("Title: %s, Desc: %s", title, descriptionJson);
|
||||
|
||||
titleEdit.setText(title);
|
||||
Type typeOfDest = new TypeToken<List<Description>>() {
|
||||
}.getType();
|
||||
|
||||
List<Description> descriptions = new Gson().fromJson(descriptionJson, typeOfDest);
|
||||
descriptionsAdapter.setDescriptions(descriptions);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from https://stackoverflow.com/a/26269435/8065933
|
||||
*/
|
||||
@OnTouch(R.id.titleEdit)
|
||||
boolean titleInfo(View view, MotionEvent motionEvent) {
|
||||
final int value;
|
||||
if (ViewCompat.getLayoutDirection(getView()) == ViewCompat.LAYOUT_DIRECTION_LTR) {
|
||||
value = titleEdit.getRight() - titleEdit.getCompoundDrawables()[2].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() >= value) {
|
||||
showInfoAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
value = titleEdit.getLeft() + titleEdit.getCompoundDrawables()[0].getBounds().width();
|
||||
if (motionEvent.getAction() == ACTION_UP && motionEvent.getRawX() <= value) {
|
||||
showInfoAlert(R.string.media_detail_title, R.string.title_info);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private void setLicenseSummary(String 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
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
uploadActionInitiatedHandler = (OnUploadActionInitiated) getActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
|
||||
// FIXME: Stops the keyboard from being shown 'stale' while moving out of this fragment into the next
|
||||
View target = getActivity().getCurrentFocus();
|
||||
ViewUtil.hideKeyboard(target);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String licenseUrlFor(String license) {
|
||||
switch (license) {
|
||||
case Prefs.Licenses.CC_BY_3:
|
||||
return "https://creativecommons.org/licenses/by/3.0/";
|
||||
case Prefs.Licenses.CC_BY_4:
|
||||
return "https://creativecommons.org/licenses/by/4.0/";
|
||||
case Prefs.Licenses.CC_BY_SA_3:
|
||||
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
||||
case Prefs.Licenses.CC_BY_SA_4:
|
||||
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
||||
case Prefs.Licenses.CC0:
|
||||
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||
}
|
||||
throw new RuntimeException("Unrecognized license value: " + license);
|
||||
}
|
||||
|
||||
public interface OnUploadActionInitiated {
|
||||
|
||||
void uploadActionInitiated(String title, String description);
|
||||
}
|
||||
|
||||
private class TitleTextWatcher implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showInfoAlert (int titleStringID, int messageStringID){
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setTitle(titleStringID)
|
||||
.setMessage(messageStringID)
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@OnClick(R.id.ll_add_description)
|
||||
public void onLLAddDescriptionClicked() {
|
||||
descriptionsAdapter.addDescription(new Description());
|
||||
rvDescriptions.scrollToPosition(descriptionsAdapter.getItemCount() - 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +1,83 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.utils.BiMap;
|
||||
|
||||
public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
||||
|
||||
private final int resource;
|
||||
private final LayoutInflater layoutInflater;
|
||||
List<Language> languages;
|
||||
private List<String> languageNamesList;
|
||||
private List<String> languageCodesList;
|
||||
private final BiMap<AdapterView, String> selectedLanguages;
|
||||
public String selectedLangCode="";
|
||||
|
||||
|
||||
|
||||
public SpinnerLanguagesAdapter(@NonNull Context context,
|
||||
int resource) {
|
||||
int resource, BiMap<AdapterView, String> selectedLanguages) {
|
||||
super(context, resource);
|
||||
this.resource = resource;
|
||||
this.layoutInflater = LayoutInflater.from(context);
|
||||
languages = new ArrayList<>();
|
||||
languageNamesList = new ArrayList<>();
|
||||
languageCodesList = new ArrayList<>();
|
||||
prepareLanguages();
|
||||
this.selectedLanguages = selectedLanguages;
|
||||
}
|
||||
|
||||
public void setLanguages(List<Language> languages) {
|
||||
this.languages = languages;
|
||||
private void prepareLanguages() {
|
||||
List<Language> languages = getLocaleSupportedByDevice();
|
||||
|
||||
for(Language language: languages) {
|
||||
if(!languageCodesList.contains(language.getLocale().getLanguage())) {
|
||||
languageNamesList.add(language.getLocale().getDisplayName());
|
||||
languageCodesList.add(language.getLocale().getLanguage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Language> getLocaleSupportedByDevice() {
|
||||
List<Language> languages = new ArrayList<>();
|
||||
Locale[] localesArray = Locale.getAvailableLocales();
|
||||
for (Locale locale : localesArray) {
|
||||
languages.add(new Language(locale));
|
||||
}
|
||||
|
||||
Collections.sort(languages, (language, t1) -> language.getLocale().getDisplayName()
|
||||
.compareTo(t1.getLocale().getDisplayName()));
|
||||
return languages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(int position) {
|
||||
return !languageCodesList.get(position).isEmpty()&&
|
||||
(!selectedLanguages.containsKey(languageCodesList.get(position)) ||
|
||||
languageCodesList.get(position).equals(selectedLangCode));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return languages.size();
|
||||
return languageNamesList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -75,19 +115,40 @@ public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
|||
}
|
||||
|
||||
public void init(int position, boolean isDropDownView) {
|
||||
Language language = languages.get(position);
|
||||
if (!isDropDownView) {
|
||||
view.setVisibility(View.GONE);
|
||||
tvLanguage.setText(
|
||||
language.getLocale().getLanguage());
|
||||
if(languageCodesList.get(position).length()>2)
|
||||
tvLanguage.setText(languageCodesList.get(position).subSequence(0,2));
|
||||
else
|
||||
tvLanguage.setText(languageCodesList.get(position));
|
||||
|
||||
} else {
|
||||
view.setVisibility(View.VISIBLE);
|
||||
if (languageCodesList.get(position).isEmpty()) {
|
||||
tvLanguage.setText(languageNamesList.get(position));
|
||||
tvLanguage.setTextColor(Color.GRAY);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
tvLanguage.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
}
|
||||
} else {
|
||||
tvLanguage.setText(
|
||||
String.format("%s [%s]", language.getLocale().getDisplayName(),
|
||||
language.getLocale().getLanguage()));
|
||||
String.format("%s [%s]", languageNamesList.get(position), languageCodesList.get(position)));
|
||||
if(selectedLanguages.containsKey(languageCodesList.get(position))&&
|
||||
!languageCodesList.get(position).equals(selectedLangCode))
|
||||
tvLanguage.setTextColor(Color.GRAY);
|
||||
else
|
||||
tvLanguage.setTextColor(Color.BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getLanguageCode(int position) {
|
||||
return languageCodesList.get(position);
|
||||
}
|
||||
|
||||
int getIndexOfUserDefaultLocale(Context context) {
|
||||
return languageCodesList.indexOf(context.getResources().getConfiguration().locale.getLanguage());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
public interface ThumbnailClickedListener {
|
||||
void thumbnailClicked(UploadModel.UploadItem content);
|
||||
}
|
||||
37
app/src/main/java/fr/free/nrw/commons/upload/Title.java
Normal file
37
app/src/main/java/fr/free/nrw/commons/upload/Title.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import timber.log.Timber;
|
||||
|
||||
class Title{
|
||||
|
||||
private String titleText;
|
||||
private boolean set;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return titleText;
|
||||
}
|
||||
|
||||
public void setTitleText(String titleText) {
|
||||
this.titleText = titleText;
|
||||
|
||||
if (!TextUtils.isEmpty(titleText)) {
|
||||
set = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSet() {
|
||||
return set;
|
||||
}
|
||||
|
||||
public void setSet(boolean set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return titleText==null || titleText.isEmpty();
|
||||
}
|
||||
}
|
||||
607
app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
Normal file
607
app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java
Normal file
|
|
@ -0,0 +1,607 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.Manifest;
|
||||
import android.animation.LayoutTransition;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.constraint.ConstraintLayout;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Html;
|
||||
import android.text.TextUtils;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.ViewFlipper;
|
||||
|
||||
import com.github.chrisbanes.photoview.PhotoView;
|
||||
import com.jakewharton.rxbinding2.view.RxView;
|
||||
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.LoginActivity;
|
||||
import fr.free.nrw.commons.category.CategoriesModel;
|
||||
import fr.free.nrw.commons.category.CategoryItem;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.StringUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.Result;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.WIKIDATA_ENTITY_ID_PREF;
|
||||
|
||||
public class UploadActivity extends AuthenticatedActivity implements UploadView, SimilarImageInterface {
|
||||
@Inject InputMethodManager inputMethodManager;
|
||||
@Inject MediaWikiApi mwApi;
|
||||
@Inject @Named("direct_nearby_upload_prefs") SharedPreferences directPrefs;
|
||||
@Inject UploadPresenter presenter;
|
||||
@Inject CategoriesModel categoriesModel;
|
||||
|
||||
// Main GUI
|
||||
@BindView(R.id.backgroundImage) PhotoView background;
|
||||
@BindView(R.id.activity_upload_cards) ConstraintLayout cardLayout;
|
||||
@BindView(R.id.view_flipper) ViewFlipper viewFlipper;
|
||||
|
||||
// Top Card
|
||||
@BindView(R.id.top_card) CardView topCard;
|
||||
@BindView(R.id.top_card_expand_button) ImageView topCardExpandButton;
|
||||
@BindView(R.id.top_card_title) TextView topCardTitle;
|
||||
@BindView(R.id.top_card_thumbnails) RecyclerView topCardThumbnails;
|
||||
|
||||
// Bottom Card
|
||||
@BindView(R.id.bottom_card) CardView bottomCard;
|
||||
@BindView(R.id.bottom_card_expand_button) ImageView bottomCardExpandButton;
|
||||
@BindView(R.id.bottom_card_title) TextView bottomCardTitle;
|
||||
@BindView(R.id.bottom_card_subtitle) TextView bottomCardSubtitle;
|
||||
@BindView(R.id.bottom_card_next) Button next;
|
||||
@BindView(R.id.bottom_card_previous) Button previous;
|
||||
@BindView(R.id.bottom_card_add_desc) Button bottomCardAddDescription;
|
||||
|
||||
//Right Card
|
||||
@BindView(R.id.right_card) CardView rightCard;
|
||||
@BindView(R.id.right_card_expand_button) ImageView rightCardExpandButton;
|
||||
@BindView(R.id.right_card_map_button) View rightCardMapButton;
|
||||
|
||||
// Category Search
|
||||
@BindView(R.id.categories_title) TextView categoryTitle;
|
||||
@BindView(R.id.category_next) Button categoryNext;
|
||||
@BindView(R.id.category_previous) Button categoryPrevious;
|
||||
@BindView(R.id.categoriesSearchInProgress) ProgressBar categoriesSearchInProgress;
|
||||
@BindView(R.id.category_search) EditText categoriesSearch;
|
||||
@BindView(R.id.category_search_container) TextInputLayout categoriesSearchContainer;
|
||||
@BindView(R.id.categories) RecyclerView categoriesList;
|
||||
|
||||
// Final Submission
|
||||
@BindView(R.id.license_title) TextView licenseTitle;
|
||||
@BindView(R.id.share_license_summary) TextView licenseSummary;
|
||||
@BindView(R.id.media_upload_policy) TextView licensePolicy;
|
||||
@BindView(R.id.license_list) Spinner licenseSpinner;
|
||||
@BindView(R.id.submit) Button submit;
|
||||
@BindView(R.id.license_previous) Button licensePrevious;
|
||||
@BindView(R.id.rv_descriptions) RecyclerView rvDescriptions;
|
||||
|
||||
private DescriptionsAdapter descriptionsAdapter;
|
||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
|
||||
DexterPermissionObtainer dexterPermissionObtainer;
|
||||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.activity_upload);
|
||||
ButterKnife.bind(this);
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
|
||||
configureLayout();
|
||||
configureTopCard();
|
||||
configureBottomCard();
|
||||
initRecyclerView();
|
||||
configureRightCard();
|
||||
configureNavigationButtons();
|
||||
configureCategories();
|
||||
configureLicenses();
|
||||
|
||||
presenter.init();
|
||||
|
||||
dexterPermissionObtainer = new DexterPermissionObtainer(this,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
getString(R.string.storage_permission),
|
||||
getString(R.string.write_storage_permission_rationale_for_image_share));
|
||||
|
||||
dexterPermissionObtainer.confirmStoragePermissions().subscribe(this::receiveSharedItems);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkIfLoggedIn() {
|
||||
if (!sessionManager.isUserLoggedIn()) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(this, getString(R.string.user_not_logged_in));
|
||||
Intent loginIntent = new Intent(UploadActivity.this, LoginActivity.class);
|
||||
startActivity(loginIntent);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
presenter.cleanup();
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkIfLoggedIn();
|
||||
compositeDisposable.add(
|
||||
dexterPermissionObtainer.confirmStoragePermissions()
|
||||
.subscribe(() -> presenter.addView(this)));
|
||||
compositeDisposable.add(
|
||||
RxTextView.textChanges(categoriesSearch)
|
||||
.doOnEach(v -> categoriesSearchContainer.setError(null))
|
||||
.takeUntil(RxView.detaches(categoriesSearch))
|
||||
.debounce(500, TimeUnit.MILLISECONDS)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(filter -> updateCategoryList(filter.toString()), Timber::e)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
presenter.removeView();
|
||||
compositeDisposable.dispose();
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThumbnails(List<UploadModel.UploadItem> uploads) {
|
||||
int uploadCount = uploads.size();
|
||||
topCardThumbnails.setAdapter(new UploadThumbnailsAdapterFactory(presenter::thumbnailClicked).create(uploads));
|
||||
topCardTitle.setText(getResources().getQuantityString(R.plurals.upload_count_title, uploadCount, uploadCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRightCardContent(boolean gpsPresent) {
|
||||
if(gpsPresent){
|
||||
rightCardMapButton.setVisibility(View.VISIBLE);
|
||||
}else{
|
||||
rightCardMapButton.setVisibility(View.GONE);
|
||||
}
|
||||
//The card should be disabled if it has no buttons.
|
||||
setRightCardVisibility(gpsPresent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBottomCardContent(int currentStep,
|
||||
int stepCount,
|
||||
UploadModel.UploadItem uploadItem,
|
||||
boolean isShowingItem) {
|
||||
String cardTitle = getResources().getString(R.string.step_count, currentStep, stepCount);
|
||||
String cardSubTitle = getResources().getString(R.string.image_in_set_label, currentStep);
|
||||
bottomCardTitle.setText(cardTitle);
|
||||
bottomCardSubtitle.setText(cardSubTitle);
|
||||
categoryTitle.setText(cardTitle);
|
||||
licenseTitle.setText(cardTitle);
|
||||
if(isShowingItem) {
|
||||
descriptionsAdapter.setItems(uploadItem.title, uploadItem.descriptions);
|
||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLicenses(List<String> licenses, String selectedLicense) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, licenses);
|
||||
licenseSpinner.setAdapter(adapter);
|
||||
|
||||
int position = licenses.indexOf(getString(Utils.licenseNameFor(selectedLicense)));
|
||||
|
||||
// Check position is valid
|
||||
if (position < 0) {
|
||||
Timber.d("Invalid position: %d. Using default license", position);
|
||||
position = licenses.size() - 1;
|
||||
}
|
||||
|
||||
Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(selectedLicense)));
|
||||
licenseSpinner.setSelection(position);
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
@Override
|
||||
public void updateLicenseSummary(String selectedLicense) {
|
||||
String licenseHyperLink = "<a href='" + Utils.licenseUrlFor(selectedLicense)+"'>" +
|
||||
getString(Utils.licenseNameFor(selectedLicense)) + "</a><br>";
|
||||
licenseSummary.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
licenseSummary.setText(
|
||||
Html.fromHtml(
|
||||
getString(R.string.share_license_summary, licenseHyperLink)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTopCardContent() {
|
||||
RecyclerView.Adapter adapter = topCardThumbnails.getAdapter();
|
||||
if (adapter != null) {
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNextEnabled(boolean available) {
|
||||
next.setEnabled(available);
|
||||
categoryNext.setEnabled(available);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSubmitEnabled(boolean available) {
|
||||
submit.setEnabled(available);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPreviousEnabled(boolean available) {
|
||||
previous.setEnabled(available);
|
||||
categoryPrevious.setEnabled(available);
|
||||
licensePrevious.setEnabled(available);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTopCardState(boolean state) {
|
||||
updateCardState(state, topCardExpandButton, topCardThumbnails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTopCardVisibility(boolean visible) {
|
||||
topCard.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBottomCardVisibility(boolean visible) {
|
||||
bottomCard.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRightCardVisibility(boolean visible) {
|
||||
rightCard.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBottomCardVisibility(@UploadPage int page) {
|
||||
if (page == TITLE_CARD) {
|
||||
viewFlipper.setDisplayedChild(0);
|
||||
} else if (page == CATEGORIES) {
|
||||
viewFlipper.setDisplayedChild(1);
|
||||
} else if (page == LICENSE) {
|
||||
viewFlipper.setDisplayedChild(2);
|
||||
dismissKeyboard();
|
||||
} else if (page == PLEASE_WAIT) {
|
||||
viewFlipper.setDisplayedChild(3);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBottomCardState(boolean state) {
|
||||
updateCardState(state, bottomCardExpandButton, rvDescriptions, previous, next, bottomCardAddDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRightCardState(boolean state) {
|
||||
rightCardExpandButton.animate().rotation(rightCardExpandButton.getRotation() + (state ? -180 : 180)).start();
|
||||
//Add all items in rightCard here
|
||||
rightCardMapButton.setVisibility(state ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBackground(Uri mediaUri) {
|
||||
background.setImageURI(mediaUri);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dismissKeyboard() {
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
|
||||
|
||||
// verify if the soft keyboard is open
|
||||
if (imm != null && imm.isAcceptingText() && getCurrentFocus() != null) {
|
||||
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showBadPicturePopup(@Result int result) {
|
||||
String errorMessageForResult = getErrorMessageForResult(this, result);
|
||||
if (StringUtils.isNullOrWhiteSpace(errorMessageForResult)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DialogUtil.showAlertDialog(this,
|
||||
getString(R.string.warning),
|
||||
errorMessageForResult,
|
||||
() -> presenter.deletePicture(),
|
||||
() -> presenter.keepPicture());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDuplicatePicturePopup() {
|
||||
DialogUtil.showAlertDialog(this,
|
||||
getString(R.string.warning),
|
||||
String.format(getString(R.string.upload_title_duplicate), presenter.getCurrentImageFileName()),
|
||||
null,
|
||||
() -> {
|
||||
presenter.keepPicture();
|
||||
presenter.handleNext(descriptionsAdapter.getTitle(), getDescriptions());
|
||||
});
|
||||
}
|
||||
|
||||
public void showNoCategorySelectedWarning() {
|
||||
DialogUtil.showAlertDialog(this,
|
||||
getString(R.string.no_categories_selected),
|
||||
getString(R.string.no_categories_selected_warning_desc),
|
||||
getString(R.string.no_go_back),
|
||||
getString(R.string.yes_submit),
|
||||
null,
|
||||
() -> presenter.handleCategoryNext(categoriesModel, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchMapActivity(String decCoords) {
|
||||
Utils.handleGeoCoordinates(this, decCoords);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showErrorMessage(int resourceId) {
|
||||
ViewUtil.showShortToast(this, resourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initDefaultCategories() {
|
||||
updateCategoryList("");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
mwApi.setAuthCookie(authCookie);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == CommonsApplication.OPEN_APPLICATION_DETAIL_SETTINGS) {
|
||||
dexterPermissionObtainer.onManualPermissionReturned();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onAuthFailure() {
|
||||
Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
private void configureLicenses() {
|
||||
licenseSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
String licenseName = parent.getItemAtPosition(position).toString();
|
||||
presenter.selectLicense(licenseName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
presenter.selectLicense(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void configureLayout() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
cardLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
|
||||
}
|
||||
background.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
background.setOnScaleChangeListener((scaleFactor, x, y) -> presenter.closeAllCards());
|
||||
}
|
||||
|
||||
private void configureTopCard() {
|
||||
topCardExpandButton.setOnClickListener(v -> presenter.toggleTopCardState());
|
||||
topCardThumbnails.setLayoutManager(new LinearLayoutManager(this,
|
||||
LinearLayoutManager.HORIZONTAL, false));
|
||||
}
|
||||
|
||||
private void configureBottomCard() {
|
||||
bottomCardExpandButton.setOnClickListener(v -> presenter.toggleBottomCardState());
|
||||
bottomCardAddDescription.setOnClickListener(v -> addNewDescription());
|
||||
}
|
||||
|
||||
private void addNewDescription() {
|
||||
descriptionsAdapter.addDescription(new Description());
|
||||
rvDescriptions.scrollToPosition(descriptionsAdapter.getItemCount() - 1);
|
||||
}
|
||||
|
||||
private void configureRightCard() {
|
||||
rightCardExpandButton.setOnClickListener(v -> presenter.toggleRightCardState());
|
||||
rightCardMapButton.setOnClickListener(v -> presenter.openCoordinateMap());
|
||||
}
|
||||
|
||||
private void configureNavigationButtons() {
|
||||
// Navigation next / previous for each image as we're collecting title + description
|
||||
next.setOnClickListener(v -> {
|
||||
setTitleAndDescriptions();
|
||||
presenter.handleNext(descriptionsAdapter.getTitle(),
|
||||
descriptionsAdapter.getDescriptions());
|
||||
});
|
||||
previous.setOnClickListener(v -> presenter.handlePrevious());
|
||||
|
||||
// Next / previous for the category selection currentPage
|
||||
categoryNext.setOnClickListener(v -> presenter.handleCategoryNext(categoriesModel, false));
|
||||
categoryPrevious.setOnClickListener(v -> presenter.handlePrevious());
|
||||
|
||||
// Finally, the previous / submit buttons on the final currentPage of the wizard
|
||||
licensePrevious.setOnClickListener(v -> presenter.handlePrevious());
|
||||
submit.setOnClickListener(v -> {
|
||||
Toast.makeText(this, R.string.uploading_started, Toast.LENGTH_LONG).show();
|
||||
presenter.handleSubmit(categoriesModel);
|
||||
finish();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void setTitleAndDescriptions() {
|
||||
List<Description> descriptions = descriptionsAdapter.getDescriptions();
|
||||
Timber.d("Descriptions size is %d are %s", descriptions.size(), descriptions);
|
||||
}
|
||||
|
||||
private void configureCategories() {
|
||||
categoriesAdapter = new UploadCategoriesAdapterFactory(categoriesModel).create(new ArrayList<>());
|
||||
categoriesList.setLayoutManager(new LinearLayoutManager(this));
|
||||
categoriesList.setAdapter(categoriesAdapter);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void updateCategoryList(String filter) {
|
||||
List<String> imageTitleList = presenter.getImageTitleList();
|
||||
Observable.fromIterable(categoriesModel.getSelectedCategories())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnSubscribe(disposable -> {
|
||||
categoriesSearchInProgress.setVisibility(View.VISIBLE);
|
||||
categoriesSearchContainer.setError(null);
|
||||
categoriesAdapter.clear();
|
||||
})
|
||||
.observeOn(Schedulers.io())
|
||||
.concatWith(
|
||||
categoriesModel.searchAll(filter, imageTitleList)
|
||||
.mergeWith(categoriesModel.searchCategories(filter, imageTitleList))
|
||||
.concatWith(TextUtils.isEmpty(filter)
|
||||
? categoriesModel.defaultCategories(imageTitleList) : Observable.empty())
|
||||
)
|
||||
.filter(categoryItem -> !categoriesModel.containsYear(categoryItem.getName()))
|
||||
.distinct()
|
||||
.sorted(categoriesModel.sortBySimilarity(filter))
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
s -> categoriesAdapter.add(s),
|
||||
Timber::e,
|
||||
() -> {
|
||||
categoriesAdapter.notifyDataSetChanged();
|
||||
categoriesSearchInProgress.setVisibility(View.GONE);
|
||||
|
||||
if (categoriesAdapter.getItemCount() == categoriesModel.selectedCategoriesCount()
|
||||
&& !categoriesSearch.getText().toString().isEmpty()) {
|
||||
categoriesSearchContainer.setError("No categories found");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void receiveSharedItems() {
|
||||
Intent intent = getIntent();
|
||||
String mimeType = intent.getType();
|
||||
String source;
|
||||
|
||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
||||
} else {
|
||||
source = Contribution.SOURCE_EXTERNAL;
|
||||
}
|
||||
|
||||
if (Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||
Uri mediaUri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
|
||||
if (intent.getBooleanExtra("isDirectUpload", false)) {
|
||||
String imageTitle = directPrefs.getString("Title", "");
|
||||
String imageDesc = directPrefs.getString("Desc", "");
|
||||
Timber.i("Received direct upload with title %s and description %s", imageTitle, imageDesc);
|
||||
String wikidataEntityIdPref = intent.getStringExtra(WIKIDATA_ENTITY_ID_PREF);
|
||||
presenter.receiveDirect(mediaUri, mimeType, source, wikidataEntityIdPref, imageTitle, imageDesc);
|
||||
} else {
|
||||
Timber.i("Received single upload");
|
||||
presenter.receive(mediaUri, mimeType, source);
|
||||
}
|
||||
} else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
||||
ArrayList<Uri> urisList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||
Timber.i("Received multiple upload %s", urisList.size());
|
||||
presenter.receive(urisList, mimeType, source);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCardState(boolean state, ImageView button, View... content) {
|
||||
button.animate().rotation(button.getRotation() + (state ? 180 : -180)).start();
|
||||
if (content != null) {
|
||||
for (View view : content) {
|
||||
view.setVisibility(state ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Description> getDescriptions() {
|
||||
return descriptionsAdapter.getDescriptions();
|
||||
}
|
||||
|
||||
private void initRecyclerView() {
|
||||
descriptionsAdapter = new DescriptionsAdapter(this);
|
||||
descriptionsAdapter.setCallback(this::showInfoAlert);
|
||||
rvDescriptions.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
|
||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||
addNewDescription();
|
||||
}
|
||||
|
||||
|
||||
private void showInfoAlert(int titleStringID, int messageStringId, String... formatArgs) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle(titleStringID)
|
||||
.setMessage(getString(messageStringId, (Object[]) formatArgs))
|
||||
.setCancelable(true)
|
||||
.setNeutralButton(android.R.string.ok, (dialog, id) -> dialog.cancel())
|
||||
.create()
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putString("originalImagePath", originalFilePath);
|
||||
args.putString("possibleImagePath", possibleFilePath);
|
||||
newFragment.setArguments(args);
|
||||
newFragment.show(getSupportFragmentManager(), "dialog");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
import com.pedrogomez.renderers.RendererBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.category.CategoryClickedListener;
|
||||
import fr.free.nrw.commons.category.CategoryItem;
|
||||
|
||||
public class UploadCategoriesAdapterFactory {
|
||||
private final CategoryClickedListener listener;
|
||||
|
||||
public UploadCategoriesAdapterFactory(CategoryClickedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public RVRendererAdapter<CategoryItem> create(List<CategoryItem> placeList) {
|
||||
RendererBuilder<CategoryItem> builder = new RendererBuilder<CategoryItem>()
|
||||
.bind(CategoryItem.class, new UploadCategoriesRenderer(listener));
|
||||
ListAdapteeCollection<CategoryItem> collection = new ListAdapteeCollection<>(
|
||||
placeList != null ? placeList : Collections.emptyList());
|
||||
return new RVRendererAdapter<>(builder, collection);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
import com.pedrogomez.renderers.Renderer;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoryClickedListener;
|
||||
import fr.free.nrw.commons.category.CategoryItem;
|
||||
|
||||
public class UploadCategoriesRenderer extends Renderer<CategoryItem> {
|
||||
@BindView(R.id.tvName) CheckBox checkedView;
|
||||
private final CategoryClickedListener listener;
|
||||
|
||||
UploadCategoriesRenderer(CategoryClickedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
||||
return layoutInflater.inflate(R.layout.layout_upload_categories_item, viewGroup, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUpView(View view) {
|
||||
ButterKnife.bind(this, view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookListeners(View view) {
|
||||
view.setOnClickListener(v -> {
|
||||
CategoryItem item = getContent();
|
||||
item.setSelected(!item.isSelected());
|
||||
checkedView.setChecked(item.isSelected());
|
||||
if (listener != null) {
|
||||
listener.categoryClicked(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
CategoryItem item = getContent();
|
||||
checkedView.setChecked(item.isSelected());
|
||||
checkedView.setText(item.getName());
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,6 @@ import java.io.InputStream;
|
|||
import java.util.Date;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
|
|
@ -87,49 +86,11 @@ public class UploadController {
|
|||
|
||||
/**
|
||||
* Starts a new upload task.
|
||||
* @param title the title of the contribution
|
||||
* @param mediaUri the media URI of the contribution
|
||||
* @param description the description of the contribution
|
||||
* @param mimeType the MIME type of the contribution
|
||||
* @param source the source of the contribution
|
||||
* @param decimalCoords the coordinates in decimal. (e.g. "37.51136|-77.602615")
|
||||
* @param wikiDataEntityId
|
||||
* @param onComplete the progress tracker
|
||||
*
|
||||
* @param contribution the contribution object
|
||||
*/
|
||||
public void startUpload(String title, Uri contentProviderUri, Uri mediaUri, String description, String mimeType, String source, String decimalCoords, String wikiDataEntityId, ContributionUploadProgress onComplete) {
|
||||
Contribution contribution;
|
||||
|
||||
|
||||
//TODO: Modify this to include coords
|
||||
contribution = new Contribution(mediaUri, null, title, description, -1,
|
||||
null, null, sessionManager.getCurrentAccount().name,
|
||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
|
||||
|
||||
|
||||
contribution.setTag("mimeType", mimeType);
|
||||
contribution.setSource(source);
|
||||
|
||||
Timber.d("Wikidata entity ID received from Share activity is %s", wikiDataEntityId);
|
||||
//TODO: Modify this to include coords
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if(currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(context);
|
||||
return;
|
||||
}
|
||||
contribution = new Contribution(mediaUri, null, title, description, -1,
|
||||
null, null, currentAccount.name,
|
||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, decimalCoords);
|
||||
|
||||
|
||||
contribution.setTag("mimeType", mimeType);
|
||||
contribution.setSource(source);
|
||||
contribution.setWikiDataEntityId(wikiDataEntityId);
|
||||
contribution.setContentProviderUri(contentProviderUri);
|
||||
|
||||
//Calls the next overloaded method
|
||||
startUpload(contribution, onComplete);
|
||||
public void startUpload(Contribution contribution) {
|
||||
startUpload(contribution, c -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -142,7 +103,14 @@ public class UploadController {
|
|||
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
|
||||
//Set creator, desc, and license
|
||||
if (TextUtils.isEmpty(contribution.getCreator())) {
|
||||
contribution.setCreator(sessionManager.getCurrentAccount().name);
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(context, context.getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(context);
|
||||
return;
|
||||
}
|
||||
contribution.setCreator(currentAccount.name);
|
||||
}
|
||||
|
||||
if (contribution.getDescription() == null) {
|
||||
|
|
@ -163,8 +131,6 @@ public class UploadController {
|
|||
long length;
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
try {
|
||||
|
||||
//TODO: understand do we really need this code
|
||||
if (contribution.getDataLength() <= 0) {
|
||||
Timber.d("UploadController/doInBackground, contribution.getLocalUri():" + contribution.getLocalUri());
|
||||
AssetFileDescriptor assetFileDescriptor = contentResolver
|
||||
|
|
|
|||
400
app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
Normal file
400
app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.BitmapRegionDecoder;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import io.reactivex.subjects.BehaviorSubject;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadModel {
|
||||
|
||||
private MediaWikiApi mwApi;
|
||||
private static UploadItem DUMMY = new UploadItem(Uri.EMPTY, "", "", GPSExtractor.DUMMY, "", null,-1l) {
|
||||
};
|
||||
private final SharedPreferences prefs;
|
||||
private final List<String> licenses;
|
||||
private String license;
|
||||
private final Map<String, String> licensesByName;
|
||||
private List<UploadItem> items = new ArrayList<>();
|
||||
private boolean topCardState = true;
|
||||
private boolean bottomCardState = true;
|
||||
private boolean rightCardState = true;
|
||||
private int currentStepIndex = 0;
|
||||
private Context context;
|
||||
private ContentResolver contentResolver;
|
||||
private boolean useExtStorage;
|
||||
private Disposable badImageSubscription;
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
private Uri currentMediaUri;
|
||||
|
||||
@Inject
|
||||
UploadModel(@Named("licenses") List<String> licenses,
|
||||
@Named("default_preferences") SharedPreferences prefs,
|
||||
@Named("licenses_by_name") Map<String, String> licensesByName,
|
||||
Context context,
|
||||
MediaWikiApi mwApi) {
|
||||
this.licenses = licenses;
|
||||
this.prefs = prefs;
|
||||
this.license = Prefs.Licenses.CC_BY_SA_3;
|
||||
this.licensesByName = licensesByName;
|
||||
this.context = context;
|
||||
this.mwApi = mwApi;
|
||||
this.contentResolver = context.getContentResolver();
|
||||
useExtStorage = this.prefs.getBoolean("useExternalStorage", false);
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
void receive(List<Uri> mediaUri, String mimeType, String source, SimilarImageInterface similarImageInterface) {
|
||||
initDefaultValues();
|
||||
Observable<UploadItem> itemObservable = Observable.fromIterable(mediaUri)
|
||||
.map(media -> {
|
||||
currentMediaUri=media;
|
||||
return cacheFileUpload(media);
|
||||
})
|
||||
.map(filePath -> {
|
||||
long fileCreatedDate = getFileCreatedDate(currentMediaUri);
|
||||
Uri uri = Uri.fromFile(new File(filePath));
|
||||
FileProcessor fp = new FileProcessor(filePath, context.getContentResolver(), context);
|
||||
UploadItem item = new UploadItem(uri, mimeType, source, fp.processFileCoordinates(similarImageInterface),
|
||||
FileUtils.getFileExt(filePath), null,fileCreatedDate);
|
||||
Single.zip(
|
||||
Single.fromCallable(() ->
|
||||
new FileInputStream(filePath))
|
||||
.map(FileUtils::getSHA1)
|
||||
.map(mwApi::existingFile)
|
||||
.map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK),
|
||||
Single.fromCallable(() ->
|
||||
new FileInputStream(filePath))
|
||||
.map(file -> BitmapRegionDecoder.newInstance(file, false))
|
||||
.map(ImageUtils::checkIfImageIsTooDark), //Returns IMAGE_DARK or IMAGE_OK
|
||||
(dupe, dark) -> dupe | dark)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(item.imageQuality::onNext, Timber::e);
|
||||
return item;
|
||||
});
|
||||
items = itemObservable.toList().blockingGet();
|
||||
items.get(0).selected = true;
|
||||
items.get(0).first = true;
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
void receiveDirect(Uri media, String mimeType, String source, String wikidataEntityIdPref, String title, String desc, SimilarImageInterface similarImageInterface) {
|
||||
initDefaultValues();
|
||||
long fileCreatedDate = getFileCreatedDate(media);
|
||||
String filePath = this.cacheFileUpload(media);
|
||||
Uri uri = Uri.fromFile(new File(filePath));
|
||||
FileProcessor fp = new FileProcessor(filePath, context.getContentResolver(), context);
|
||||
UploadItem item = new UploadItem(uri, mimeType, source, fp.processFileCoordinates(similarImageInterface),
|
||||
FileUtils.getFileExt(filePath), wikidataEntityIdPref,fileCreatedDate);
|
||||
item.title.setTitleText(title);
|
||||
item.descriptions.get(0).setDescriptionText(desc);
|
||||
//TODO figure out if default descriptions in other languages exist
|
||||
item.descriptions.get(0).setLanguageCode("en");
|
||||
Single.zip(
|
||||
Single.fromCallable(() ->
|
||||
new FileInputStream(filePath))
|
||||
.map(FileUtils::getSHA1)
|
||||
.map(mwApi::existingFile)
|
||||
.map(b -> b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK),
|
||||
Single.fromCallable(() ->
|
||||
new FileInputStream(filePath))
|
||||
.map(file -> BitmapRegionDecoder.newInstance(file, false))
|
||||
.map(ImageUtils::checkIfImageIsTooDark), //Returns IMAGE_DARK or IMAGE_OK
|
||||
(dupe, dark) -> dupe | dark).subscribe(item.imageQuality::onNext);
|
||||
items.add(item);
|
||||
items.get(0).selected = true;
|
||||
items.get(0).first = true;
|
||||
}
|
||||
|
||||
private void initDefaultValues() {
|
||||
currentStepIndex = 0;
|
||||
topCardState = true;
|
||||
bottomCardState = true;
|
||||
rightCardState = true;
|
||||
items = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file creation date from uri from all possible content providers
|
||||
* @param media
|
||||
* @return
|
||||
*/
|
||||
private long getFileCreatedDate(Uri media) {
|
||||
try {
|
||||
Cursor cursor = contentResolver.query(media, null, null, null, null);
|
||||
if (cursor == null) {
|
||||
return -1;//Could not fetch last_modified
|
||||
}
|
||||
//Content provider contracts for opening gallery from the app and that by sharing from gallery from outside are different and we need to handle both the cases
|
||||
int lastModifiedColumnIndex = cursor.getColumnIndex("last_modified");//If gallery is opened from in app
|
||||
if(lastModifiedColumnIndex==-1){
|
||||
lastModifiedColumnIndex=cursor.getColumnIndex("datetaken");
|
||||
}
|
||||
//If both the content providers do not give the data, lets leave it to Jesus
|
||||
if(lastModifiedColumnIndex==-1){
|
||||
return -1l;
|
||||
}
|
||||
cursor.moveToFirst();
|
||||
return cursor.getLong(lastModifiedColumnIndex);
|
||||
} catch (Exception e) {
|
||||
return -1;////Could not fetch last_modified
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPreviousAvailable() {
|
||||
return currentStepIndex > 0;
|
||||
}
|
||||
|
||||
boolean isNextAvailable() {
|
||||
return currentStepIndex < (items.size() + 1);
|
||||
}
|
||||
|
||||
boolean isSubmitAvailable() {
|
||||
int count = items.size();
|
||||
boolean hasError = license == null;
|
||||
for (int i = 0; i < count; i++) {
|
||||
UploadItem item = items.get(i);
|
||||
hasError |= item.error;
|
||||
}
|
||||
return !hasError;
|
||||
}
|
||||
|
||||
int getCurrentStep() {
|
||||
return currentStepIndex + 1;
|
||||
}
|
||||
|
||||
int getStepCount() {
|
||||
return items.size() + 2;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public List<UploadItem> getUploads() {
|
||||
return items;
|
||||
}
|
||||
|
||||
boolean isTopCardState() {
|
||||
return topCardState;
|
||||
}
|
||||
|
||||
void setTopCardState(boolean topCardState) {
|
||||
this.topCardState = topCardState;
|
||||
}
|
||||
|
||||
boolean isBottomCardState() {
|
||||
return bottomCardState;
|
||||
}
|
||||
|
||||
void setRightCardState(boolean rightCardState) {
|
||||
this.rightCardState = rightCardState;
|
||||
}
|
||||
|
||||
boolean isRightCardState() {
|
||||
return rightCardState;
|
||||
}
|
||||
|
||||
void setBottomCardState(boolean bottomCardState) {
|
||||
this.bottomCardState = bottomCardState;
|
||||
}
|
||||
|
||||
public void next() {
|
||||
if (badImageSubscription != null)
|
||||
badImageSubscription.dispose();
|
||||
markCurrentUploadVisited();
|
||||
if (currentStepIndex < items.size() + 1) {
|
||||
currentStepIndex++;
|
||||
}
|
||||
updateItemState();
|
||||
}
|
||||
|
||||
public void setCurrentTitleAndDescriptions(Title title, List<Description> descriptions) {
|
||||
setCurrentUploadTitle(title);
|
||||
setCurrentUploadDescriptions(descriptions);
|
||||
}
|
||||
|
||||
private void setCurrentUploadTitle(Title title) {
|
||||
if (currentStepIndex < items.size() && currentStepIndex >= 0) {
|
||||
items.get(currentStepIndex).title = title;
|
||||
}
|
||||
}
|
||||
|
||||
private void setCurrentUploadDescriptions(List<Description> descriptions) {
|
||||
if (currentStepIndex < items.size() && currentStepIndex >= 0) {
|
||||
items.get(currentStepIndex).descriptions = descriptions;
|
||||
}
|
||||
}
|
||||
|
||||
public void previous() {
|
||||
if (badImageSubscription != null)
|
||||
badImageSubscription.dispose();
|
||||
markCurrentUploadVisited();
|
||||
if (currentStepIndex > 0) {
|
||||
currentStepIndex--;
|
||||
}
|
||||
updateItemState();
|
||||
}
|
||||
|
||||
void jumpTo(UploadItem item) {
|
||||
currentStepIndex = items.indexOf(item);
|
||||
item.visited = true;
|
||||
updateItemState();
|
||||
}
|
||||
|
||||
UploadItem getCurrentItem() {
|
||||
return isShowingItem() ? items.get(currentStepIndex) : DUMMY;
|
||||
}
|
||||
|
||||
boolean isShowingItem() {
|
||||
return currentStepIndex < items.size();
|
||||
}
|
||||
|
||||
private void updateItemState() {
|
||||
int count = items.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
UploadItem item = items.get(i);
|
||||
item.selected = (currentStepIndex >= count || i == currentStepIndex);
|
||||
item.error = item.title == null || item.title.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private void markCurrentUploadVisited() {
|
||||
if (currentStepIndex < items.size() && currentStepIndex >= 0) {
|
||||
items.get(currentStepIndex).visited = true;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getLicenses() {
|
||||
return licenses;
|
||||
}
|
||||
|
||||
String getSelectedLicense() {
|
||||
return license;
|
||||
}
|
||||
|
||||
void setSelectedLicense(String licenseName) {
|
||||
this.license = licensesByName.get(licenseName);
|
||||
}
|
||||
|
||||
Observable<Contribution> buildContributions(List<String> categoryStringList) {
|
||||
return Observable.fromIterable(items).map(item ->
|
||||
{
|
||||
Contribution contribution = new Contribution(item.mediaUri, null, item.title + "." + item.fileExt,
|
||||
Description.formatList(item.descriptions), -1,
|
||||
null, null, sessionManager.getUserName(),
|
||||
CommonsApplication.DEFAULT_EDIT_SUMMARY, item.gpsCoords.getCoords());
|
||||
contribution.setWikiDataEntityId(item.wikidataEntityId);
|
||||
contribution.setCategories(categoryStringList);
|
||||
contribution.setTag("mimeType", item.mimeType);
|
||||
contribution.setSource(item.source);
|
||||
contribution.setContentProviderUri(item.mediaUri);
|
||||
if (item.createdTimestamp != -1l) {
|
||||
contribution.setDateCreated(new Date(item.createdTimestamp));
|
||||
//Set the date only if you have it, else the upload service is gonna try it the other way
|
||||
}
|
||||
return contribution;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy files into local storage and return file path
|
||||
*
|
||||
* @param media Uri of the file
|
||||
* @return path of the enw file
|
||||
*/
|
||||
private String cacheFileUpload(Uri media) {
|
||||
try {
|
||||
String copyPath;
|
||||
if (useExtStorage)
|
||||
copyPath = FileUtils.createExternalCopyPathAndCopy(media, contentResolver);
|
||||
else
|
||||
copyPath = FileUtils.createCopyPathAndCopy(media, context);
|
||||
Timber.i("File path is " + copyPath);
|
||||
return copyPath;
|
||||
} catch (IOException e) {
|
||||
Timber.w(e, "Error in copying URI " + media.getPath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void keepPicture() {
|
||||
items.get(currentStepIndex).imageQuality.onNext(ImageUtils.IMAGE_KEEP);
|
||||
}
|
||||
|
||||
void deletePicture() {
|
||||
badImageSubscription.dispose();
|
||||
items.remove(currentStepIndex).imageQuality.onComplete();
|
||||
updateItemState();
|
||||
}
|
||||
|
||||
void subscribeBadPicture(Consumer<Integer> consumer) {
|
||||
badImageSubscription = getCurrentItem().imageQuality.subscribe(consumer, Timber::e);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
static class UploadItem {
|
||||
public final Uri mediaUri;
|
||||
public final String mimeType;
|
||||
public final String source;
|
||||
public final GPSExtractor gpsCoords;
|
||||
|
||||
public boolean selected = false;
|
||||
public boolean first = false;
|
||||
public String fileExt;
|
||||
public BehaviorSubject<Integer> imageQuality;
|
||||
Title title;
|
||||
List<Description> descriptions;
|
||||
public String wikidataEntityId;
|
||||
public boolean visited;
|
||||
public boolean error;
|
||||
public long createdTimestamp;
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
UploadItem(Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords, String fileExt, @Nullable String wikidataEntityId, long createdTimestamp) {
|
||||
title = new Title();
|
||||
descriptions = new ArrayList<>();
|
||||
descriptions.add(new Description());
|
||||
this.wikidataEntityId = wikidataEntityId;
|
||||
this.mediaUri = mediaUri;
|
||||
this.mimeType = mimeType;
|
||||
this.source = source;
|
||||
this.gpsCoords = gpsCoords;
|
||||
this.fileExt = fileExt;
|
||||
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
||||
this.createdTimestamp=createdTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,430 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.category.CategoriesModel;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.EMPTY_TITLE;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.FILE_NAME_EXISTS;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_KEEP;
|
||||
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
|
||||
|
||||
/**
|
||||
* The MVP pattern presenter of Upload GUI
|
||||
*/
|
||||
@Singleton
|
||||
public class UploadPresenter {
|
||||
|
||||
private final UploadModel uploadModel;
|
||||
private final UploadController uploadController;
|
||||
private final MediaWikiApi mediaWikiApi;
|
||||
|
||||
private static final UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
||||
new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
||||
private UploadView view = DUMMY;
|
||||
|
||||
private static final SimilarImageInterface SIMILAR_IMAGE = (SimilarImageInterface) Proxy.newProxyInstance(SimilarImageInterface.class.getClassLoader(),
|
||||
new Class[]{SimilarImageInterface.class}, (proxy, method, methodArgs) -> null);
|
||||
private SimilarImageInterface similarImageInterface = SIMILAR_IMAGE;
|
||||
|
||||
@UploadView.UploadPage
|
||||
private int currentPage = UploadView.PLEASE_WAIT;
|
||||
|
||||
|
||||
@Inject
|
||||
UploadPresenter(UploadModel uploadModel,
|
||||
UploadController uploadController,
|
||||
MediaWikiApi mediaWikiApi) {
|
||||
this.uploadModel = uploadModel;
|
||||
this.uploadController = uploadController;
|
||||
this.mediaWikiApi = mediaWikiApi;
|
||||
}
|
||||
|
||||
void receive(Uri mediaUri, String mimeType, String source) {
|
||||
receive(Collections.singletonList(mediaUri), mimeType, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the items received to {@link #uploadModel} and displays the items.
|
||||
*
|
||||
* @param media The Uri's of the media being uploaded.
|
||||
* @param mimeType the mimeType of the files.
|
||||
* @param source File source from {@link Contribution.FileSource}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void receive(List<Uri> media, String mimeType, @Contribution.FileSource String source) {
|
||||
Completable.fromRunnable(() -> uploadModel.receive(media, mimeType, source, similarImageInterface))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
updateCards();
|
||||
updateLicenses();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem())
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}, Timber::e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the direct upload item received to {@link #uploadModel} and displays the items.
|
||||
*
|
||||
* @param media The Uri's of the media being uploaded.
|
||||
* @param mimeType the mimeType of the files.
|
||||
* @param source File source from {@link Contribution.FileSource}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void receiveDirect(Uri media, String mimeType, @Contribution.FileSource String source, String wikidataEntityIdPref, String title, String desc) {
|
||||
Completable.fromRunnable(() -> uploadModel.receiveDirect(media, mimeType, source, wikidataEntityIdPref, title, desc, similarImageInterface))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(() -> {
|
||||
updateCards();
|
||||
updateLicenses();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem())
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}, Timber::e);
|
||||
}
|
||||
/**
|
||||
* Sets the license to parameter and updates {@link UploadActivity}
|
||||
*
|
||||
* @param licenseName license name
|
||||
*/
|
||||
void selectLicense(String licenseName) {
|
||||
uploadModel.setSelectedLicense(licenseName);
|
||||
view.updateLicenseSummary(uploadModel.getSelectedLicense());
|
||||
}
|
||||
|
||||
//region Wizard step management
|
||||
|
||||
/**
|
||||
* Called by the next button in {@link UploadActivity}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void handleNext(Title title,
|
||||
List<Description> descriptions) {
|
||||
validateCurrentItemTitle()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(errorCode -> handleImage(errorCode, title, descriptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the next button in {@link UploadActivity}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void handleCategoryNext(CategoriesModel categoriesModel,
|
||||
boolean noCategoryWarningShown) {
|
||||
if (categoriesModel.selectedCategoriesCount() < 1 && !noCategoryWarningShown) {
|
||||
view.showNoCategorySelectedWarning();
|
||||
} else {
|
||||
nextUploadedItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleImage(Integer errorCode, Title title, List<Description> descriptions) {
|
||||
switch (errorCode) {
|
||||
case EMPTY_TITLE:
|
||||
view.showErrorMessage(R.string.add_title_toast);
|
||||
break;
|
||||
case FILE_NAME_EXISTS:
|
||||
if(getCurrentItem().imageQuality.getValue().equals(IMAGE_KEEP)) {
|
||||
setTitleAndDescription(title, descriptions);
|
||||
nextUploadedItem();
|
||||
} else {
|
||||
view.showDuplicatePicturePopup();
|
||||
}
|
||||
break;
|
||||
case IMAGE_OK:
|
||||
default:
|
||||
setTitleAndDescription(title, descriptions);
|
||||
nextUploadedItem();
|
||||
}
|
||||
}
|
||||
|
||||
private void nextUploadedItem() {
|
||||
uploadModel.next();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem()) {
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}
|
||||
view.dismissKeyboard();
|
||||
}
|
||||
|
||||
private void setTitleAndDescription(Title title, List<Description> descriptions) {
|
||||
uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
|
||||
}
|
||||
|
||||
private Title getCurrentImageTitle() {
|
||||
return getCurrentItem().title;
|
||||
}
|
||||
|
||||
String getCurrentImageFileName() {
|
||||
UploadItem currentItem = getCurrentItem();
|
||||
return currentItem.title + "." + uploadModel.getCurrentItem().fileExt;
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private Observable<Integer> validateCurrentItemTitle() {
|
||||
Title title = getCurrentImageTitle();
|
||||
if (title.isEmpty()) {
|
||||
view.showErrorMessage(R.string.add_title_toast);
|
||||
return Observable.just(EMPTY_TITLE);
|
||||
}
|
||||
|
||||
return Observable.fromCallable(() -> mediaWikiApi.fileExistsWithName(getCurrentImageFileName()))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.map(doesFileExist -> {
|
||||
if (doesFileExist) {
|
||||
return FILE_NAME_EXISTS;
|
||||
}
|
||||
return IMAGE_OK;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the previous button in {@link UploadActivity}
|
||||
*/
|
||||
void handlePrevious() {
|
||||
uploadModel.previous();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem()) {
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
}
|
||||
view.dismissKeyboard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the pictures on the top card is clicked on in {@link UploadActivity}
|
||||
*/
|
||||
void thumbnailClicked(UploadItem item) {
|
||||
uploadModel.jumpTo(item);
|
||||
updateContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the submit button in {@link UploadActivity}
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
void handleSubmit(CategoriesModel categoriesModel) {
|
||||
if (view.checkIfLoggedIn())
|
||||
uploadModel.buildContributions(categoriesModel.getCategoryStringList())
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribe(uploadController::startUpload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the map button on the right card in {@link UploadActivity}
|
||||
*/
|
||||
void openCoordinateMap() {
|
||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().gpsCoords;
|
||||
if (gpsObj != null && gpsObj.imageCoordsExists) {
|
||||
view.launchMapActivity(gpsObj.getDecLatitude() + "," + gpsObj.getDecLongitude());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by the image processors when a result is obtained.
|
||||
*
|
||||
* @param result the result returned by the image procesors.
|
||||
*/
|
||||
private void handleBadPicture(@ImageUtils.Result int result) {
|
||||
view.showBadPicturePopup(result);
|
||||
}
|
||||
|
||||
void keepPicture() {
|
||||
uploadModel.keepPicture();
|
||||
}
|
||||
|
||||
void deletePicture() {
|
||||
if (uploadModel.getCount() == 1)
|
||||
view.finish();
|
||||
else {
|
||||
uploadModel.deletePicture();
|
||||
updateCards();
|
||||
updateContent();
|
||||
if (uploadModel.isShowingItem())
|
||||
uploadModel.subscribeBadPicture(this::handleBadPicture);
|
||||
view.dismissKeyboard();
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region Top Bottom and Right card state management
|
||||
|
||||
|
||||
/**
|
||||
* Toggles the top card's state between open and closed.
|
||||
*/
|
||||
void toggleTopCardState() {
|
||||
uploadModel.setTopCardState(!uploadModel.isTopCardState());
|
||||
view.setTopCardState(uploadModel.isTopCardState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the bottom card's state between open and closed.
|
||||
*/
|
||||
void toggleBottomCardState() {
|
||||
uploadModel.setBottomCardState(!uploadModel.isBottomCardState());
|
||||
view.setBottomCardState(uploadModel.isBottomCardState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the right card's state between open and closed.
|
||||
*/
|
||||
void toggleRightCardState() {
|
||||
uploadModel.setRightCardState(!uploadModel.isRightCardState());
|
||||
view.setRightCardState(uploadModel.isRightCardState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all the cards' states to closed.
|
||||
*/
|
||||
void closeAllCards() {
|
||||
if (uploadModel.isTopCardState()) {
|
||||
uploadModel.setTopCardState(false);
|
||||
view.setTopCardState(false);
|
||||
}
|
||||
if (uploadModel.isRightCardState()) {
|
||||
uploadModel.setRightCardState(false);
|
||||
view.setRightCardState(false);
|
||||
}
|
||||
if (uploadModel.isBottomCardState()) {
|
||||
uploadModel.setBottomCardState(false);
|
||||
view.setBottomCardState(false);
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region View / Lifecycle management
|
||||
public void init() {
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
uploadController.cleanup();
|
||||
}
|
||||
|
||||
void removeView() {
|
||||
this.view = DUMMY;
|
||||
}
|
||||
|
||||
void addView(UploadView view) {
|
||||
this.view = view;
|
||||
|
||||
updateCards();
|
||||
updateLicenses();
|
||||
updateContent();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the cards for when there is a change to the amount of items being uploaded.
|
||||
*/
|
||||
private void updateCards() {
|
||||
Timber.i("uploadModel.getCount():" + uploadModel.getCount());
|
||||
view.updateThumbnails(uploadModel.getUploads());
|
||||
view.setTopCardVisibility(uploadModel.getCount() > 1);
|
||||
view.setBottomCardVisibility(uploadModel.getCount() > 0);
|
||||
view.setTopCardState(uploadModel.isTopCardState());
|
||||
view.setBottomCardState(uploadModel.isBottomCardState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the list of licences and the default license.
|
||||
*/
|
||||
private void updateLicenses() {
|
||||
String selectedLicense = uploadModel.getSelectedLicense();
|
||||
view.updateLicenses(uploadModel.getLicenses(), selectedLicense);
|
||||
view.updateLicenseSummary(selectedLicense);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the cards and the background when a new currentPage is selected.
|
||||
*/
|
||||
private void updateContent() {
|
||||
Timber.i("Updating content for currentPage" + uploadModel.getCurrentStep());
|
||||
view.setNextEnabled(uploadModel.isNextAvailable());
|
||||
view.setPreviousEnabled(uploadModel.isPreviousAvailable());
|
||||
view.setSubmitEnabled(uploadModel.isSubmitAvailable());
|
||||
|
||||
view.setBackground(uploadModel.getCurrentItem().mediaUri);
|
||||
|
||||
view.updateBottomCardContent(uploadModel.getCurrentStep(),
|
||||
uploadModel.getStepCount(),
|
||||
uploadModel.getCurrentItem(),
|
||||
uploadModel.isShowingItem());
|
||||
|
||||
view.updateTopCardContent();
|
||||
|
||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().gpsCoords;
|
||||
view.updateRightCardContent(gpsObj != null && gpsObj.imageCoordsExists);
|
||||
|
||||
showCorrectCards(uploadModel.getCurrentStep(), uploadModel.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the layout to show the correct bottom card.
|
||||
*
|
||||
* @param currentStep the current step
|
||||
* @param uploadCount how many items are being uploaded
|
||||
*/
|
||||
private void showCorrectCards(int currentStep, int uploadCount) {
|
||||
if (uploadCount == 0) {
|
||||
currentPage = UploadView.PLEASE_WAIT;
|
||||
} else if (currentStep <= uploadCount) {
|
||||
currentPage = UploadView.TITLE_CARD;
|
||||
view.setTopCardVisibility(uploadModel.getCount() > 1);
|
||||
} else if (currentStep == uploadCount + 1) {
|
||||
currentPage = UploadView.CATEGORIES;
|
||||
view.setTopCardVisibility(false);
|
||||
view.setRightCardVisibility(false);
|
||||
view.initDefaultCategories();
|
||||
} else {
|
||||
currentPage = UploadView.LICENSE;
|
||||
view.setTopCardVisibility(false);
|
||||
view.setRightCardVisibility(false);
|
||||
}
|
||||
view.setBottomCardVisibility(currentPage);
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
/**
|
||||
* @return the item currently being displayed
|
||||
*/
|
||||
private UploadItem getCurrentItem() {
|
||||
return uploadModel.getCurrentItem();
|
||||
}
|
||||
|
||||
List<String> getImageTitleList() {
|
||||
List<String> titleList = new ArrayList<>();
|
||||
for (UploadItem item : uploadModel.getUploads()) {
|
||||
if (item.title.isSet()) {
|
||||
titleList.add(item.title.toString());
|
||||
}
|
||||
}
|
||||
return titleList;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ import fr.free.nrw.commons.Utils;
|
|||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionDao;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.mwapi.UploadResult;
|
||||
|
|
@ -62,7 +62,9 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
private NotificationCompat.Builder curProgressNotification;
|
||||
private int toUpload;
|
||||
|
||||
// The file names of unfinished uploads, used to prevent overwriting
|
||||
/**
|
||||
* The file names of unfinished uploads, used to prevent overwriting
|
||||
*/
|
||||
private Set<String> unfinishedUploads = new HashSet<>();
|
||||
|
||||
// DO NOT HAVE NOTIFICATION ID OF 0 FOR ANYTHING
|
||||
|
|
@ -193,7 +195,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
|
||||
.setOngoing(true)
|
||||
.setProgress(100, 0, true)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
|
||||
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()));
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +232,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
//As the fileInputStream is null there's no point in continuing the upload process
|
||||
//mwapi.upload accepts a NonNull input stream
|
||||
if(fileInputStream == null) {
|
||||
if (fileInputStream == null) {
|
||||
Timber.d("File not found");
|
||||
return;
|
||||
}
|
||||
|
|
@ -314,11 +316,12 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
@SuppressWarnings("deprecation")
|
||||
private void showFailedNotification(Contribution contribution) {
|
||||
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, MainActivity.class), 0))
|
||||
.setTicker(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
|
||||
.setContentTitle(getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
|
||||
.setContentText(getString(R.string.upload_failed_notification_subtitle))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import com.pedrogomez.renderers.Renderer;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
|
||||
class UploadThumbnailRenderer extends Renderer<UploadModel.UploadItem> {
|
||||
private ThumbnailClickedListener listener;
|
||||
private SimpleDraweeView background;
|
||||
private View space;
|
||||
private ImageView error;
|
||||
|
||||
public UploadThumbnailRenderer(ThumbnailClickedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflate(LayoutInflater inflater, ViewGroup parent) {
|
||||
return inflater.inflate(R.layout.item_upload_thumbnail, parent, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUpView(View rootView) {
|
||||
error = rootView.findViewById(R.id.error);
|
||||
space = rootView.findViewById(R.id.left_space);
|
||||
background = rootView.findViewById(R.id.thumbnail);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void hookListeners(View rootView) {
|
||||
background.setOnClickListener(v -> listener.thumbnailClicked(getContent()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render() {
|
||||
UploadModel.UploadItem content = getContent();
|
||||
background.setImageURI(content.mediaUri);
|
||||
background.setAlpha(content.selected ? 1.0f : 0.5f);
|
||||
space.setVisibility(content.first ? View.VISIBLE : View.GONE);
|
||||
error.setVisibility(content.visited && content.error ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||
import com.pedrogomez.renderers.RendererBuilder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class UploadThumbnailsAdapterFactory {
|
||||
private ThumbnailClickedListener listener;
|
||||
|
||||
UploadThumbnailsAdapterFactory(ThumbnailClickedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public RVRendererAdapter<UploadModel.UploadItem> create(List<UploadModel.UploadItem> placeList) {
|
||||
RendererBuilder<UploadModel.UploadItem> builder = new RendererBuilder<UploadModel.UploadItem>()
|
||||
.bind(UploadModel.UploadItem.class, new UploadThumbnailRenderer(listener));
|
||||
ListAdapteeCollection<UploadModel.UploadItem> collection = new ListAdapteeCollection<>(
|
||||
placeList != null ? placeList : Collections.emptyList());
|
||||
return new RVRendererAdapter<>(builder, collection);
|
||||
}
|
||||
}
|
||||
82
app/src/main/java/fr/free/nrw/commons/upload/UploadView.java
Normal file
82
app/src/main/java/fr/free/nrw/commons/upload/UploadView.java
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.IntDef;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
public interface UploadView {
|
||||
// Dummy implementation of the view interface to allow us to have a 'null object pattern'
|
||||
// in the presenter and avoid constant NULL checking.
|
||||
// UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
||||
// new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
||||
|
||||
List<Description> getDescriptions();
|
||||
|
||||
@Retention(SOURCE)
|
||||
@IntDef({PLEASE_WAIT, TITLE_CARD, CATEGORIES, LICENSE})
|
||||
@interface UploadPage {}
|
||||
|
||||
int PLEASE_WAIT = 0;
|
||||
|
||||
int TITLE_CARD = 1;
|
||||
int CATEGORIES = 2;
|
||||
int LICENSE = 3;
|
||||
|
||||
boolean checkIfLoggedIn();
|
||||
|
||||
void updateThumbnails(List<UploadModel.UploadItem> uploads);
|
||||
|
||||
void setNextEnabled(boolean available);
|
||||
|
||||
void setSubmitEnabled(boolean available);
|
||||
|
||||
void setPreviousEnabled(boolean available);
|
||||
|
||||
void setTopCardState(boolean state);
|
||||
|
||||
void setRightCardVisibility(boolean visible);
|
||||
|
||||
void setBottomCardState(boolean state);
|
||||
|
||||
void setRightCardState(boolean bottomCardState);
|
||||
|
||||
void setBackground(Uri mediaUri);
|
||||
|
||||
void setTopCardVisibility(boolean visible);
|
||||
|
||||
void setBottomCardVisibility(boolean visible);
|
||||
|
||||
void setBottomCardVisibility(@UploadPage int page);
|
||||
|
||||
void updateRightCardContent(boolean gpsPresent);
|
||||
|
||||
void updateBottomCardContent(int currentStep, int stepCount, UploadModel.UploadItem uploadItem, boolean isShowingItem);
|
||||
|
||||
void updateLicenses(List<String> licenses, String selectedLicense);
|
||||
|
||||
void updateLicenseSummary(String selectedLicense);
|
||||
|
||||
void updateTopCardContent();
|
||||
|
||||
void dismissKeyboard();
|
||||
|
||||
void showBadPicturePopup(@ImageUtils.Result int errorMessage);
|
||||
|
||||
void showDuplicatePicturePopup();
|
||||
|
||||
void finish();
|
||||
|
||||
void launchMapActivity(String decCoords);
|
||||
|
||||
void showErrorMessage(int resourceId);
|
||||
|
||||
void initDefaultCategories();
|
||||
|
||||
void showNoCategorySelectedWarning();
|
||||
}
|
||||
|
|
@ -7,62 +7,62 @@ import java.util.HashMap;
|
|||
* info in the user language
|
||||
*/
|
||||
public class UrlLicense {
|
||||
HashMap<String,String> urlLicense = new HashMap<String, String>();
|
||||
public void initialize(){
|
||||
urlLicense.put("en","https://commons.wikimedia.org/wiki/Commons:Licensing");
|
||||
urlLicense.put("ar","https://commons.wikimedia.org/wiki/Commons:Licensing/ar");
|
||||
urlLicense.put("ast","https://commons.wikimedia.org/wiki/Commons:Licensing/ast");
|
||||
urlLicense.put("az","https://commons.wikimedia.org/wiki/Commons:Licensing/az");
|
||||
urlLicense.put("be","https://commons.wikimedia.org/wiki/Commons:Licensing/be");
|
||||
urlLicense.put("bg","https://commons.wikimedia.org/wiki/Commons:Licensing/bg");
|
||||
urlLicense.put("bn","https://commons.wikimedia.org/wiki/Commons:Licensing/bn");
|
||||
urlLicense.put("ca","https://commons.wikimedia.org/wiki/Commons:Licensing/ca");
|
||||
urlLicense.put("cs","https://commons.wikimedia.org/wiki/Commons:Licensing/cs");
|
||||
urlLicense.put("da","https://commons.wikimedia.org/wiki/Commons:Licensing/da");
|
||||
urlLicense.put("de","https://commons.wikimedia.org/wiki/Commons:Licensing/de");
|
||||
urlLicense.put("el","https://commons.wikimedia.org/wiki/Commons:Licensing/el");
|
||||
urlLicense.put("eo","https://commons.wikimedia.org/wiki/Commons:Licensing/eo");
|
||||
urlLicense.put("es","https://commons.wikimedia.org/wiki/Commons:Licensing/es");
|
||||
urlLicense.put("eu","https://commons.wikimedia.org/wiki/Commons:Licensing/eu");
|
||||
urlLicense.put("fa","https://commons.wikimedia.org/wiki/Commons:Licensing/fa");
|
||||
urlLicense.put("fi","https://commons.wikimedia.org/wiki/Commons:Licensing/fi");
|
||||
urlLicense.put("fr","https://commons.wikimedia.org/wiki/Commons:Licensing/fr");
|
||||
urlLicense.put("gl","https://commons.wikimedia.org/wiki/Commons:Licensing/gl");
|
||||
urlLicense.put("gsw","https://commons.wikimedia.org/wiki/Commons:Licensing/gsw");
|
||||
urlLicense.put("he","https://commons.wikimedia.org/wiki/Commons:Licensing/he");
|
||||
urlLicense.put("hi","https://commons.wikimedia.org/wiki/Commons:Licensing/hi");
|
||||
urlLicense.put("hu","https://commons.wikimedia.org/wiki/Commons:Licensing/hu");
|
||||
urlLicense.put("id","https://commons.wikimedia.org/wiki/Commons:Licensing/id");
|
||||
urlLicense.put("is","https://commons.wikimedia.org/wiki/Commons:Licensing/is");
|
||||
urlLicense.put("it","https://commons.wikimedia.org/wiki/Commons:Licensing/it");
|
||||
urlLicense.put("ja","https://commons.wikimedia.org/wiki/Commons:Licensing/ja");
|
||||
urlLicense.put("ka","https://commons.wikimedia.org/wiki/Commons:Licensing/ka");
|
||||
urlLicense.put("km","https://commons.wikimedia.org/wiki/Commons:Licensing/km");
|
||||
urlLicense.put("ko","https://commons.wikimedia.org/wiki/Commons:Licensing/ko");
|
||||
urlLicense.put("ku","https://commons.wikimedia.org/wiki/Commons:Licensing/ku");
|
||||
urlLicense.put("mk","https://commons.wikimedia.org/wiki/Commons:Licensing/mk");
|
||||
urlLicense.put("mr","https://commons.wikimedia.org/wiki/Commons:Licensing/mr");
|
||||
urlLicense.put("ms","https://commons.wikimedia.org/wiki/Commons:Licensing/ms");
|
||||
urlLicense.put("my","https://commons.wikimedia.org/wiki/Commons:Licensing/my");
|
||||
urlLicense.put("nl","https://commons.wikimedia.org/wiki/Commons:Licensing/nl");
|
||||
urlLicense.put("oc","https://commons.wikimedia.org/wiki/Commons:Licensing/oc");
|
||||
urlLicense.put("pl","https://commons.wikimedia.org/wiki/Commons:Licensing/pl");
|
||||
urlLicense.put("pt","https://commons.wikimedia.org/wiki/Commons:Licensing/pt");
|
||||
urlLicense.put("pt-br","https://commons.wikimedia.org/wiki/Commons:Licensing/pt-br");
|
||||
urlLicense.put("ro","https://commons.wikimedia.org/wiki/Commons:Licensing/ro");
|
||||
urlLicense.put("ru","https://commons.wikimedia.org/wiki/Commons:Licensing/ru");
|
||||
urlLicense.put("scn","https://commons.wikimedia.org/wiki/Commons:Licensing/scn");
|
||||
urlLicense.put("sk","https://commons.wikimedia.org/wiki/Commons:Licensing/sk");
|
||||
urlLicense.put("sl","https://commons.wikimedia.org/wiki/Commons:Licensing/sl");
|
||||
urlLicense.put("sv","https://commons.wikimedia.org/wiki/Commons:Licensing/sv");
|
||||
urlLicense.put("tr","https://commons.wikimedia.org/wiki/Commons:Licensing/tr");
|
||||
urlLicense.put("uk","https://commons.wikimedia.org/wiki/Commons:Licensing/uk");
|
||||
urlLicense.put("ur","https://commons.wikimedia.org/wiki/Commons:Licensing/ur");
|
||||
urlLicense.put("vi","https://commons.wikimedia.org/wiki/Commons:Licensing/vi");
|
||||
urlLicense.put("zh","https://commons.wikimedia.org/wiki/Commons:Licensing/zh");
|
||||
public static HashMap<String,String> urlLicense = new HashMap<>();
|
||||
static {
|
||||
urlLicense.put("en", "https://commons.wikimedia.org/wiki/Commons:Licensing");
|
||||
urlLicense.put("ar", "https://commons.wikimedia.org/wiki/Commons:Licensing/ar");
|
||||
urlLicense.put("ast", "https://commons.wikimedia.org/wiki/Commons:Licensing/ast");
|
||||
urlLicense.put("az", "https://commons.wikimedia.org/wiki/Commons:Licensing/az");
|
||||
urlLicense.put("be", "https://commons.wikimedia.org/wiki/Commons:Licensing/be");
|
||||
urlLicense.put("bg", "https://commons.wikimedia.org/wiki/Commons:Licensing/bg");
|
||||
urlLicense.put("bn", "https://commons.wikimedia.org/wiki/Commons:Licensing/bn");
|
||||
urlLicense.put("ca", "https://commons.wikimedia.org/wiki/Commons:Licensing/ca");
|
||||
urlLicense.put("cs", "https://commons.wikimedia.org/wiki/Commons:Licensing/cs");
|
||||
urlLicense.put("da", "https://commons.wikimedia.org/wiki/Commons:Licensing/da");
|
||||
urlLicense.put("de", "https://commons.wikimedia.org/wiki/Commons:Licensing/de");
|
||||
urlLicense.put("el", "https://commons.wikimedia.org/wiki/Commons:Licensing/el");
|
||||
urlLicense.put("eo", "https://commons.wikimedia.org/wiki/Commons:Licensing/eo");
|
||||
urlLicense.put("es", "https://commons.wikimedia.org/wiki/Commons:Licensing/es");
|
||||
urlLicense.put("eu", "https://commons.wikimedia.org/wiki/Commons:Licensing/eu");
|
||||
urlLicense.put("fa", "https://commons.wikimedia.org/wiki/Commons:Licensing/fa");
|
||||
urlLicense.put("fi", "https://commons.wikimedia.org/wiki/Commons:Licensing/fi");
|
||||
urlLicense.put("fr", "https://commons.wikimedia.org/wiki/Commons:Licensing/fr");
|
||||
urlLicense.put("gl", "https://commons.wikimedia.org/wiki/Commons:Licensing/gl");
|
||||
urlLicense.put("gsw", "https://commons.wikimedia.org/wiki/Commons:Licensing/gsw");
|
||||
urlLicense.put("he", "https://commons.wikimedia.org/wiki/Commons:Licensing/he");
|
||||
urlLicense.put("hi", "https://commons.wikimedia.org/wiki/Commons:Licensing/hi");
|
||||
urlLicense.put("hu", "https://commons.wikimedia.org/wiki/Commons:Licensing/hu");
|
||||
urlLicense.put("id", "https://commons.wikimedia.org/wiki/Commons:Licensing/id");
|
||||
urlLicense.put("is", "https://commons.wikimedia.org/wiki/Commons:Licensing/is");
|
||||
urlLicense.put("it", "https://commons.wikimedia.org/wiki/Commons:Licensing/it");
|
||||
urlLicense.put("ja", "https://commons.wikimedia.org/wiki/Commons:Licensing/ja");
|
||||
urlLicense.put("ka", "https://commons.wikimedia.org/wiki/Commons:Licensing/ka");
|
||||
urlLicense.put("km", "https://commons.wikimedia.org/wiki/Commons:Licensing/km");
|
||||
urlLicense.put("ko", "https://commons.wikimedia.org/wiki/Commons:Licensing/ko");
|
||||
urlLicense.put("ku", "https://commons.wikimedia.org/wiki/Commons:Licensing/ku");
|
||||
urlLicense.put("mk", "https://commons.wikimedia.org/wiki/Commons:Licensing/mk");
|
||||
urlLicense.put("mr", "https://commons.wikimedia.org/wiki/Commons:Licensing/mr");
|
||||
urlLicense.put("ms", "https://commons.wikimedia.org/wiki/Commons:Licensing/ms");
|
||||
urlLicense.put("my", "https://commons.wikimedia.org/wiki/Commons:Licensing/my");
|
||||
urlLicense.put("nl", "https://commons.wikimedia.org/wiki/Commons:Licensing/nl");
|
||||
urlLicense.put("oc", "https://commons.wikimedia.org/wiki/Commons:Licensing/oc");
|
||||
urlLicense.put("pl", "https://commons.wikimedia.org/wiki/Commons:Licensing/pl");
|
||||
urlLicense.put("pt", "https://commons.wikimedia.org/wiki/Commons:Licensing/pt");
|
||||
urlLicense.put("pt-br", "https://commons.wikimedia.org/wiki/Commons:Licensing/pt-br");
|
||||
urlLicense.put("ro", "https://commons.wikimedia.org/wiki/Commons:Licensing/ro");
|
||||
urlLicense.put("ru", "https://commons.wikimedia.org/wiki/Commons:Licensing/ru");
|
||||
urlLicense.put("scn", "https://commons.wikimedia.org/wiki/Commons:Licensing/scn");
|
||||
urlLicense.put("sk", "https://commons.wikimedia.org/wiki/Commons:Licensing/sk");
|
||||
urlLicense.put("sl", "https://commons.wikimedia.org/wiki/Commons:Licensing/sl");
|
||||
urlLicense.put("sv", "https://commons.wikimedia.org/wiki/Commons:Licensing/sv");
|
||||
urlLicense.put("tr", "https://commons.wikimedia.org/wiki/Commons:Licensing/tr");
|
||||
urlLicense.put("uk", "https://commons.wikimedia.org/wiki/Commons:Licensing/uk");
|
||||
urlLicense.put("ur", "https://commons.wikimedia.org/wiki/Commons:Licensing/ur");
|
||||
urlLicense.put("vi", "https://commons.wikimedia.org/wiki/Commons:Licensing/vi");
|
||||
urlLicense.put("zh", "https://commons.wikimedia.org/wiki/Commons:Licensing/zh");
|
||||
}
|
||||
public String getLicenseUrl ( String language){
|
||||
if(urlLicense.containsKey(language)) {
|
||||
public static String getLicenseUrl ( String language){
|
||||
if (urlLicense.containsKey(language)) {
|
||||
return urlLicense.get(language);
|
||||
} else {
|
||||
return urlLicense.get("en");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
|
||||
public class AbstractTextWatcher implements TextWatcher {
|
||||
private final TextChange textChange;
|
||||
|
||||
public AbstractTextWatcher(@NonNull TextChange textChange) {
|
||||
this.textChange = textChange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
textChange.onTextChanged(s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
public interface TextChange {
|
||||
void onTextChanged(String value);
|
||||
}
|
||||
}
|
||||
41
app/src/main/java/fr/free/nrw/commons/utils/BiMap.java
Normal file
41
app/src/main/java/fr/free/nrw/commons/utils/BiMap.java
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* HashMap that can be searched in both the forward and reverse directions.
|
||||
*/
|
||||
public class BiMap<K, V> {
|
||||
|
||||
private HashMap<K, V> map = new HashMap<K, V>();
|
||||
private HashMap<V, K> inversedMap = new HashMap<V, K>();
|
||||
|
||||
public void put(K k, V v) {
|
||||
map.put(k, v);
|
||||
inversedMap.put(v, k);
|
||||
}
|
||||
|
||||
public V get(K k) {
|
||||
return map.get(k);
|
||||
}
|
||||
|
||||
public K getKey(V v) {
|
||||
return inversedMap.get(v);
|
||||
}
|
||||
|
||||
public Set<V> getEntrySet(){
|
||||
return inversedMap.keySet();
|
||||
}
|
||||
|
||||
public void remove(K k){
|
||||
inversedMap.remove(map.remove(k));
|
||||
}
|
||||
|
||||
|
||||
public boolean containsKey(V v){
|
||||
return inversedMap.containsKey(v);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
/**
|
||||
* This class includes utilities for contribution list fragment indicators, such as number of
|
||||
* uploads, notification and nearby cards and their progress bar behind them.
|
||||
*/
|
||||
public class ContributionListViewUtils {
|
||||
|
||||
/**
|
||||
* Sets indicator and progress bar visibility according to 3 states, data is ready to display,
|
||||
* data still loading, both should be invisible because media details fragment is visible
|
||||
* @param indicator this can be numOfUploads text view, notification/nearby card views
|
||||
* @param progressBar this is the progress bar behind indicators, displays they are loading
|
||||
* @param isIndicatorReady is indicator fetched the information will be displayed
|
||||
* @param isBothInvisible true if contribution list fragment is not active (ie. Media Details Fragment is active)
|
||||
*/
|
||||
public static void setIndicatorVisibility(View indicator, View progressBar, boolean isIndicatorReady, boolean isBothInvisible) {
|
||||
if (indicator!=null && progressBar!=null) {
|
||||
if (isIndicatorReady) {
|
||||
// Indicator ready, display them
|
||||
indicator.setVisibility(View.VISIBLE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (isBothInvisible) {
|
||||
// Media Details Fragment is visible, hide both
|
||||
indicator.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
} else {
|
||||
// Indicator is not ready, still loading
|
||||
indicator.setVisibility(View.GONE);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import android.app.AlertDialog;
|
|||
import android.app.AlertDialog.Builder;
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
|
|
@ -114,7 +115,49 @@ public class DialogUtil {
|
|||
.setIcon(iconResourceId).create();
|
||||
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
public static void showAlertDialog(Activity activity,
|
||||
String title,
|
||||
String message,
|
||||
final Runnable onPositiveBtnClick,
|
||||
final Runnable onNegativeBtnClick) {
|
||||
showAlertDialog(activity,
|
||||
title,
|
||||
message,
|
||||
activity.getString(R.string.no),
|
||||
activity.getString(R.string.yes),
|
||||
onPositiveBtnClick,
|
||||
onNegativeBtnClick);
|
||||
}
|
||||
|
||||
public static void showAlertDialog(Activity activity,
|
||||
String title,
|
||||
String message,
|
||||
String positiveButtonText,
|
||||
String negativeButtonText,
|
||||
final Runnable onPositiveBtnClick,
|
||||
final Runnable onNegativeBtnClick) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle(title);
|
||||
builder.setMessage(message);
|
||||
|
||||
builder.setPositiveButton(positiveButtonText, (dialogInterface, i) -> {
|
||||
dialogInterface.dismiss();
|
||||
if (onPositiveBtnClick != null) {
|
||||
onPositiveBtnClick.run();
|
||||
}
|
||||
});
|
||||
|
||||
builder.setNegativeButton(negativeButtonText, (DialogInterface dialogInterface, int i) -> {
|
||||
dialogInterface.dismiss();
|
||||
if (onNegativeBtnClick != null) {
|
||||
onNegativeBtnClick.run();
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog dialog = builder.create();
|
||||
showSafely(activity, dialog);
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ public class FileUtils {
|
|||
*/
|
||||
public static boolean checkIfDirectoryExists(String pathToCheck) {
|
||||
File director = new File(pathToCheck);
|
||||
if(director.exists() && director.isDirectory()) {
|
||||
if (director.exists() && director.isDirectory()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.graphics.BitmapRegionDecoder;
|
|||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import com.facebook.common.executors.CallerThreadExecutor;
|
||||
|
|
@ -20,6 +21,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
|||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import timber.log.Timber;
|
||||
|
|
@ -30,20 +33,44 @@ import timber.log.Timber;
|
|||
|
||||
public class ImageUtils {
|
||||
|
||||
public enum Result {
|
||||
public static final int IMAGE_DARK = 1;
|
||||
public static final int IMAGE_BLURRY = 1 << 1;
|
||||
public static final int IMAGE_DUPLICATE = 1 << 2;
|
||||
public static final int IMAGE_OK = 0;
|
||||
public static final int IMAGE_KEEP = -1;
|
||||
public static final int IMAGE_WAIT = -2;
|
||||
public static final int EMPTY_TITLE = -3;
|
||||
public static final int FILE_NAME_EXISTS = -4;
|
||||
public static final int NO_CATEGORY_SELECTED = -5;
|
||||
|
||||
@IntDef(
|
||||
flag = true,
|
||||
value = {
|
||||
IMAGE_DARK,
|
||||
IMAGE_OK
|
||||
IMAGE_BLURRY,
|
||||
IMAGE_DUPLICATE,
|
||||
IMAGE_OK,
|
||||
IMAGE_KEEP,
|
||||
IMAGE_WAIT,
|
||||
EMPTY_TITLE,
|
||||
FILE_NAME_EXISTS,
|
||||
NO_CATEGORY_SELECTED
|
||||
}
|
||||
)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Result {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bitmapRegionDecoder BitmapRegionDecoder for the image we wish to process
|
||||
* @return Result.IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
||||
* Result.IMAGE_DARK if image is too dark
|
||||
* @return IMAGE_OK if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
|
||||
* IMAGE_DARK if image is too dark
|
||||
*/
|
||||
public static Result checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
||||
public static @Result
|
||||
int checkIfImageIsTooDark(BitmapRegionDecoder bitmapRegionDecoder) {
|
||||
if (bitmapRegionDecoder == null) {
|
||||
Timber.e("Expected bitmapRegionDecoder was null");
|
||||
return Result.IMAGE_OK;
|
||||
return IMAGE_OK;
|
||||
}
|
||||
|
||||
int loadImageHeight = bitmapRegionDecoder.getHeight();
|
||||
|
|
@ -59,10 +86,10 @@ public class ImageUtils {
|
|||
Bitmap processBitmap = bitmapRegionDecoder.decodeRegion(rect,null);
|
||||
|
||||
if (checkIfImageIsDark(processBitmap)) {
|
||||
return Result.IMAGE_DARK;
|
||||
return IMAGE_DARK;
|
||||
}
|
||||
|
||||
return Result.IMAGE_OK;
|
||||
return IMAGE_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -132,8 +159,9 @@ public class ImageUtils {
|
|||
/**
|
||||
* 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
|
||||
*
|
||||
* @param context context
|
||||
* @param imageUrl Url of the image
|
||||
*/
|
||||
public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) {
|
||||
Timber.d("Trying to set wallpaper from url %s", imageUrl.toString());
|
||||
|
|
@ -150,7 +178,7 @@ public class ImageUtils {
|
|||
|
||||
@Override
|
||||
public void onNewResultImpl(@Nullable Bitmap bitmap) {
|
||||
if (dataSource.isFinished() && bitmap != null){
|
||||
if (dataSource.isFinished() && bitmap != null) {
|
||||
Timber.d("Bitmap loaded from url %s", imageUrl.toString());
|
||||
setWallpaper(context, Bitmap.createBitmap(bitmap));
|
||||
dataSource.close();
|
||||
|
|
@ -173,7 +201,29 @@ public class ImageUtils {
|
|||
wallpaperManager.setBitmap(bitmap);
|
||||
ViewUtil.showLongToast(context, context.getString(R.string.wallpaper_set_successfully));
|
||||
} catch (IOException e) {
|
||||
Timber.e(e,"Error setting wallpaper");
|
||||
Timber.e(e, "Error setting wallpaper");
|
||||
}
|
||||
}
|
||||
|
||||
public static String getErrorMessageForResult(Context context, @Result int result) {
|
||||
String errorMessage;
|
||||
if (result == ImageUtils.IMAGE_DARK)
|
||||
errorMessage = context.getString(R.string.upload_image_problem_dark);
|
||||
else if (result == ImageUtils.IMAGE_BLURRY)
|
||||
errorMessage = context.getString(R.string.upload_image_problem_blurry);
|
||||
else if (result == ImageUtils.IMAGE_DUPLICATE)
|
||||
errorMessage = context.getString(R.string.upload_image_problem_duplicate);
|
||||
else if (result == (ImageUtils.IMAGE_DARK|ImageUtils.IMAGE_BLURRY))
|
||||
errorMessage = context.getString(R.string.upload_image_problem_dark_blurry);
|
||||
else if (result == (ImageUtils.IMAGE_DARK|ImageUtils.IMAGE_DUPLICATE))
|
||||
errorMessage = context.getString(R.string.upload_image_problem_dark_duplicate);
|
||||
else if (result == (ImageUtils.IMAGE_BLURRY|ImageUtils.IMAGE_DUPLICATE))
|
||||
errorMessage = context.getString(R.string.upload_image_problem_blurry_duplicate);
|
||||
else if (result == (ImageUtils.IMAGE_DARK|ImageUtils.IMAGE_BLURRY|ImageUtils.IMAGE_DUPLICATE))
|
||||
errorMessage = context.getString(R.string.upload_image_problem_dark_blurry_duplicate);
|
||||
else
|
||||
return "";
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,14 @@ import android.support.v4.content.ContextCompat;
|
|||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
|
||||
|
||||
public class PermissionUtils {
|
||||
|
||||
public static final int CAMERA_PERMISSION_FROM_CONTRIBUTION_LIST = 100;
|
||||
public static final int GALLERY_PERMISSION_FROM_CONTRIBUTION_LIST = 101;
|
||||
public static final int CAMERA_PERMISSION_FROM_NEARBY_MAP = 102;
|
||||
public static final int GALLERY_PERMISSION_FROM_NEARBY_MAP = 103;
|
||||
|
||||
/**
|
||||
* This method can be used by any activity which requires a permission which has been blocked(marked never ask again by the user)
|
||||
It open the app settings from where the user can manually give us the required permission.
|
||||
|
|
|
|||
|
|
@ -12,4 +12,8 @@ public class StringUtils {
|
|||
return Html.fromHtml(source).toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isNullOrWhiteSpace(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue