mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Refactor uploads (#2981)
* Feature/refractor uploads [WIP] (#2887) * Fix duplicate param information (#2515) * Bug fix issue #2476 (#2526) * Added wikidataEntityID in all db versions, handled db.execSql via method runQuery * Versioning and changelog for v2.10.2 (#2531) * Update changelog.md * Versioning for v2.10.2 * Update changelog.md * Bugfix/issue 2580 (#2584) * Corrected string placedholders in certain string files * Corrected string placedholders in certain string files[Bug fix #2580] * Bug Fix #2585 (#2647) * Bug Fix #2585 * Added null checks on view in SearchImageFragment when updating views from external sources * Disposed the disposables in SearchActivity and SearchImageFragment when no longer in active lifecycle * use FragmentUtils to verify fragment active state * Bug Fix issue #2648 (#2678) * Bug Fix issue #2648 * Handled external storage permission before file download * * Removed redudant check for permission in MediaDetailPagerFragment (Dexter already does that) * Removed duplicate code in PermissionUtil$checkPermissionsAndPerformAction, used the existing function with conditional extra parameters * string name typo correction * BugFix issue #2652 (#2706) * Addded null check on bookmark before operating on it * BugFix issue #2711 (#2712) * Added null checks in OkHttpJsonApiClient$searchImages MwQueryResponse * BugFix #2718 (#2719) * Handled null auth cookies * Fix #2791: NPE when nominating for deletion and leaving screen (#2792) * Bug Fix issue #2789 (#2790) * Handled Illegal State Exception for non existent appropriate view parents in ViewUtils$showShortSnackbar * BugFix #2720 (#2831) BugFix deprecated licenes #2720 * ui fixes, wip, upload * *Issue #2886, BugFix #2832[wip] * updated UploadActivity code * modified ui * Updated UploadPresenterTest * * updated interfaces names to follow names suffixed with Contract * added test cases * card view elevation * view pager disabled swipe * bug fix, duplicate image * used existing non-swipable view pager * Avoid image view resize with keyboard, added adjustPan and stateVisible as softinputMode for UploadActivity * retain UploadBaseFragment instances on orientation changes * * Added test cases for UploadMediaPresenter * Injected io and main thread schedulers * categories presenter test cased wip * Added CategoriesPresenter test * * Added the logic to show open map (with to be uploaded image's coordinates while uploading image) * codacy suggested changes * added java docs * Added travis_wait fot android-wait-for-emulator * ranamed interface onResponseCallback to Callback * * Added api to delete picture in UploadModel * cleanUp in UploadModel. once upload has been initiated * Removed unused methods from UploadModel and the corresponding test class * * Added tests for UploadPresenter * Travis suggested changes * Addded copy previous title and description * * Made the upload add descriptions visible when keyboard visible * add description request focus only when user manually requests it * Added JavaDocs, review suggested changes * Fix dagger injection * use DialogUtil to show info in descriptions * use activity context for DialogUtil * Minor changes * Bug fix, reduced the add description edit text clickable bound (#2973) * Bugfix/uploads (#3000) * merged with master * BugFix IllegalStateException * setRetainState(true), not required with FragmentStatePagerAdapter * Increase the ViewPager's Offscreen Limit, we want all the fragments to be active * BugFix, clear selected categoris for previous upload session * Clear Selected Categories * Addded JavaDocs for CategoriesModel * Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java * Added class level JavaDoc UploadRemoteDataSource * Added class level JavaDoc for UploadRepository * Added JavaDocs for ThumbnailsAdapter * Added JavaDocs for MediaLicensePresenter, CategoriesPresenter * Removed null check on category query * Show default catgeories based on image title and gps location when category text empty * Allow search for empty category search * Attached image scale listener to upload media image * Bug fix, reduced the add description edit text clickable bound * Fix memory leak (#3001) * Bugfix/uploads (#3002) * merged with master * BugFix IllegalStateException * setRetainState(true), not required with FragmentStatePagerAdapter * Increase the ViewPager's Offscreen Limit, we want all the fragments to be active * BugFix, clear selected categoris for previous upload session * Clear Selected Categories * Addded JavaDocs for CategoriesModel * Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java * Added class level JavaDoc UploadRemoteDataSource * Added class level JavaDoc for UploadRepository * Added JavaDocs for ThumbnailsAdapter * Added JavaDocs for MediaLicensePresenter, CategoriesPresenter * Removed null check on category query * Show default catgeories based on image title and gps location when category text empty * Allow search for empty category search * Attached image scale listener to upload media image * Bug fix, reduced the add description edit text clickable bound * Added tooltip in Title in UploadMediaFragment * BugFix recent categories * Updated test methods * Bugfix/uploads (#3011) * merged with master * BugFix IllegalStateException * setRetainState(true), not required with FragmentStatePagerAdapter * Increase the ViewPager's Offscreen Limit, we want all the fragments to be active * BugFix, clear selected categoris for previous upload session * Clear Selected Categories * Addded JavaDocs for CategoriesModel * Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java * Added class level JavaDoc UploadRemoteDataSource * Added class level JavaDoc for UploadRepository * Added JavaDocs for ThumbnailsAdapter * Added JavaDocs for MediaLicensePresenter, CategoriesPresenter * Removed null check on category query * Show default catgeories based on image title and gps location when category text empty * Allow search for empty category search * Attached image scale listener to upload media image * Bug fix, reduced the add description edit text clickable bound * Added tooltip in Title in UploadMediaFragment * BugFix recent categories * Updated test methods * Avoid memory leak, free the adpater in MediaLicenseFragment.onDestroyView * bugfix/uploads (#3012) * merged with master * BugFix IllegalStateException * setRetainState(true), not required with FragmentStatePagerAdapter * Increase the ViewPager's Offscreen Limit, we want all the fragments to be active * BugFix, clear selected categoris for previous upload session * Clear Selected Categories * Addded JavaDocs for CategoriesModel * Code Formatting in app/src/main/java/fr/free/nrw/commons/upload/UploadModel.java * Added class level JavaDoc UploadRemoteDataSource * Added class level JavaDoc for UploadRepository * Added JavaDocs for ThumbnailsAdapter * Added JavaDocs for MediaLicensePresenter, CategoriesPresenter * Removed null check on category query * Show default catgeories based on image title and gps location when category text empty * Allow search for empty category search * Attached image scale listener to upload media image * Bug fix, reduced the add description edit text clickable bound * Added tooltip in Title in UploadMediaFragment * BugFix recent categories * Updated test methods * Avoid memory leak, free the adpater in MediaLicenseFragment.onDestroyView * BugFix Illegal State Exception in ViewpPagerAdapter * Remove irrelevant comment * merge conflict with strings (#3016)
This commit is contained in:
parent
04b051b37a
commit
7a5dc77057
68 changed files with 3753 additions and 2086 deletions
|
|
@ -65,6 +65,8 @@ dependencies {
|
||||||
testImplementation 'androidx.test:core:1.2.0'
|
testImplementation 'androidx.test:core:1.2.0'
|
||||||
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
|
testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
|
||||||
testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
|
testImplementation 'com.squareup.okhttp3:mockwebserver:3.10.0'
|
||||||
|
testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5"
|
||||||
|
testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5"
|
||||||
|
|
||||||
// Android testing
|
// Android testing
|
||||||
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
androidTestImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$KOTLIN_VERSION"
|
||||||
|
|
|
||||||
|
|
@ -50,10 +50,13 @@
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".WelcomeActivity" />
|
<activity android:name=".WelcomeActivity" />
|
||||||
|
|
||||||
<activity android:name=".upload.UploadActivity"
|
<activity
|
||||||
|
android:name=".upload.UploadActivity"
|
||||||
|
android:configChanges="orientation|screenSize|keyboard"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:configChanges="orientation|screenSize|keyboard">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
>
|
||||||
<intent-filter android:label="@string/intent_share_upload_label">
|
<intent-filter android:label="@string/intent_share_upload_label">
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ package fr.free.nrw.commons;
|
||||||
/**
|
/**
|
||||||
* Base presenter, enforcing contracts to atach and detach view
|
* Base presenter, enforcing contracts to atach and detach view
|
||||||
*/
|
*/
|
||||||
public interface BasePresenter {
|
public interface BasePresenter<T> {
|
||||||
/**
|
/**
|
||||||
* Until a view is attached, it is open to listen events from the presenter
|
* Until a view is attached, it is open to listen events from the presenter
|
||||||
*/
|
*/
|
||||||
void onAttachView(MvpView view);
|
void onAttachView(T view);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaching a view makes sure that the view no more receives events from the presenter
|
* Detaching a view makes sure that the view no more receives events from the presenter
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import org.wikipedia.dataclient.WikiSite;
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
import org.wikipedia.page.PageTitle;
|
import org.wikipedia.page.PageTitle;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
@ -18,9 +20,7 @@ import java.util.regex.Pattern;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.browser.customtabs.CustomTabsIntent;
|
import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import timber.log.Timber;
|
||||||
* success and error
|
* success and error
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class CampaignsPresenter implements BasePresenter {
|
public class CampaignsPresenter implements BasePresenter<ICampaignsView> {
|
||||||
private final OkHttpJsonApiClient okHttpJsonApiClient;
|
private final OkHttpJsonApiClient okHttpJsonApiClient;
|
||||||
|
|
||||||
private ICampaignsView view;
|
private ICampaignsView view;
|
||||||
|
|
@ -40,8 +40,9 @@ public class CampaignsPresenter implements BasePresenter {
|
||||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onAttachView(MvpView view) {
|
@Override
|
||||||
this.view = (ICampaignsView) view;
|
public void onAttachView(ICampaignsView view) {
|
||||||
|
this.view = view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public void onDetachView() {
|
@Override public void onDetachView() {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
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 java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
|
||||||
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;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class CategoriesModel implements CategoryClickedListener {
|
/**
|
||||||
|
* The model class for categories in upload
|
||||||
|
*/
|
||||||
|
public class CategoriesModel{
|
||||||
private static final int SEARCH_CATS_LIMIT = 25;
|
private static final int SEARCH_CATS_LIMIT = 25;
|
||||||
|
|
||||||
private final MediaWikiApi mwApi;
|
private final MediaWikiApi mwApi;
|
||||||
|
|
@ -41,13 +41,22 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
this.selectedCategories = new ArrayList<>();
|
this.selectedCategories = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//region Misc. utility methods
|
/**
|
||||||
|
* Sorts CategoryItem by similarity
|
||||||
|
* @param filter
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public Comparator<CategoryItem> sortBySimilarity(final String filter) {
|
public Comparator<CategoryItem> sortBySimilarity(final String filter) {
|
||||||
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
|
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
|
||||||
return (firstItem, secondItem) -> stringSimilarityComparator
|
return (firstItem, secondItem) -> stringSimilarityComparator
|
||||||
.compare(firstItem.getName(), secondItem.getName());
|
.compare(firstItem.getName(), secondItem.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns if the item contains an year
|
||||||
|
* @param item
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public boolean containsYear(String item) {
|
public boolean containsYear(String item) {
|
||||||
//Check for current and previous year to exclude these categories from removal
|
//Check for current and previous year to exclude these categories from removal
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
|
|
@ -67,6 +76,10 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
|
|| (item.matches(".*0s.*") && !item.matches(".*(200|201)0s.*")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates category count in category dao
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
public void updateCategoryCount(CategoryItem item) {
|
public void updateCategoryCount(CategoryItem item) {
|
||||||
Category category = categoryDao.find(item.getName());
|
Category category = categoryDao.find(item.getName());
|
||||||
|
|
||||||
|
|
@ -78,29 +91,27 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
category.incTimesUsed();
|
category.incTimesUsed();
|
||||||
categoryDao.save(category);
|
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) {
|
boolean cacheContainsKey(String term) {
|
||||||
return categoriesCache.containsKey(term);
|
return categoriesCache.containsKey(term);
|
||||||
}
|
}
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region Category searching
|
/**
|
||||||
|
* Regional category search
|
||||||
|
* @param term
|
||||||
|
* @param imageTitleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
public Observable<CategoryItem> searchAll(String term, List<String> imageTitleList) {
|
||||||
//If user hasn't typed anything in yet, get GPS and recent items
|
//If query text is empty, show him category based on gps and title and recent searches
|
||||||
if (TextUtils.isEmpty(term)) {
|
if (TextUtils.isEmpty(term)) {
|
||||||
return gpsCategories()
|
Observable<CategoryItem> categoryItemObservable = gpsCategories()
|
||||||
.concatWith(titleCategories(imageTitleList))
|
.concatWith(titleCategories(imageTitleList));
|
||||||
.concatWith(recentCategories());
|
if (hasDirectCategories()) {
|
||||||
|
categoryItemObservable.concatWith(directCategories().concatWith(recentCategories()));
|
||||||
|
}
|
||||||
|
return categoryItemObservable;
|
||||||
}
|
}
|
||||||
|
|
||||||
//if user types in something that is in cache, return cached category
|
//if user types in something that is in cache, return cached category
|
||||||
|
|
@ -115,43 +126,28 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
.map(name -> new CategoryItem(name, false));
|
.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));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns cached categories
|
||||||
|
* @param term
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private ArrayList<String> getCachedCategories(String term) {
|
private ArrayList<String> getCachedCategories(String term) {
|
||||||
return categoriesCache.get(term);
|
return categoriesCache.get(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<CategoryItem> defaultCategories(List<String> titleList) {
|
/**
|
||||||
Observable<CategoryItem> directCat = directCategories();
|
* Returns if we have a category in DirectKV Store
|
||||||
if (hasDirectCategories()) {
|
* @return
|
||||||
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() {
|
private boolean hasDirectCategories() {
|
||||||
return !directKvStore.getString("Category", "").equals("");
|
return !directKvStore.getString("Category", "").equals("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns categories in DirectKVStore
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private Observable<CategoryItem> directCategories() {
|
private Observable<CategoryItem> directCategories() {
|
||||||
String directCategory = directKvStore.getString("Category", "");
|
String directCategory = directKvStore.getString("Category", "");
|
||||||
List<String> categoryList = new ArrayList<>();
|
List<String> categoryList = new ArrayList<>();
|
||||||
|
|
@ -164,30 +160,49 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
|
return Observable.fromIterable(categoryList).map(name -> new CategoryItem(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns GPS categories
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
Observable<CategoryItem> gpsCategories() {
|
Observable<CategoryItem> gpsCategories() {
|
||||||
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
return Observable.fromIterable(gpsCategoryModel.getCategoryList())
|
||||||
.map(name -> new CategoryItem(name, false));
|
.map(name -> new CategoryItem(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns title based categories
|
||||||
|
* @param titleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private Observable<CategoryItem> titleCategories(List<String> titleList) {
|
private Observable<CategoryItem> titleCategories(List<String> titleList) {
|
||||||
return Observable.fromIterable(titleList)
|
return Observable.fromIterable(titleList)
|
||||||
.concatMap(this::getTitleCategories);
|
.concatMap(this::getTitleCategories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return category for single title
|
||||||
|
* @param title
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private Observable<CategoryItem> getTitleCategories(String title) {
|
private Observable<CategoryItem> getTitleCategories(String title) {
|
||||||
return mwApi.searchTitles(title, SEARCH_CATS_LIMIT)
|
return mwApi.searchTitles(title, SEARCH_CATS_LIMIT)
|
||||||
.map(name -> new CategoryItem(name, false));
|
.map(name -> new CategoryItem(name, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns recent categories
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
private Observable<CategoryItem> recentCategories() {
|
private Observable<CategoryItem> recentCategories() {
|
||||||
return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
|
return Observable.fromIterable(categoryDao.recentCategories(SEARCH_CATS_LIMIT))
|
||||||
.map(s -> new CategoryItem(s, false));
|
.map(s -> new CategoryItem(s, false));
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region Category Selection
|
/**
|
||||||
@Override
|
* Handles category item selection
|
||||||
public void categoryClicked(CategoryItem item) {
|
* @param item
|
||||||
|
*/
|
||||||
|
public void onCategoryItemClicked(CategoryItem item) {
|
||||||
if (item.isSelected()) {
|
if (item.isSelected()) {
|
||||||
selectCategory(item);
|
selectCategory(item);
|
||||||
updateCategoryCount(item);
|
updateCategoryCount(item);
|
||||||
|
|
@ -196,22 +211,35 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select's category
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
public void selectCategory(CategoryItem item) {
|
public void selectCategory(CategoryItem item) {
|
||||||
selectedCategories.add(item);
|
selectedCategories.add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unselect Category
|
||||||
|
* @param item
|
||||||
|
*/
|
||||||
public void unselectCategory(CategoryItem item) {
|
public void unselectCategory(CategoryItem item) {
|
||||||
selectedCategories.remove(item);
|
selectedCategories.remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int selectedCategoriesCount() {
|
|
||||||
return selectedCategories.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Selected Categories
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public List<CategoryItem> getSelectedCategories() {
|
public List<CategoryItem> getSelectedCategories() {
|
||||||
return selectedCategories;
|
return selectedCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Categories String List
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public List<String> getCategoryStringList() {
|
public List<String> getCategoryStringList() {
|
||||||
List<String> output = new ArrayList<>();
|
List<String> output = new ArrayList<>();
|
||||||
for (CategoryItem item : selectedCategories) {
|
for (CategoryItem item : selectedCategories) {
|
||||||
|
|
@ -219,6 +247,12 @@ public class CategoriesModel implements CategoryClickedListener {
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup the existing in memory cache's
|
||||||
|
*/
|
||||||
|
public void cleanUp() {
|
||||||
|
this.categoriesCache.clear();
|
||||||
|
this.selectedCategories.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ public class CategoryItem implements Parcelable {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
CategoryItem(String name, boolean selected) {
|
public CategoryItem(String name, boolean selected) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.selected = selected;
|
this.selected = selected;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ public class ContributionDao {
|
||||||
cv.put(Table.COLUMN_UPLOADED, contribution.getDateUploaded().getTime());
|
cv.put(Table.COLUMN_UPLOADED, contribution.getDateUploaded().getTime());
|
||||||
}
|
}
|
||||||
cv.put(Table.COLUMN_LENGTH, contribution.getDataLength());
|
cv.put(Table.COLUMN_LENGTH, contribution.getDataLength());
|
||||||
//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
|
//This was always meant to store the date created..If somehow date created is not fetched while actually saving the contribution, lets saveValue today's date
|
||||||
cv.put(Table.COLUMN_TIMESTAMP, contribution.getDateCreated()==null?System.currentTimeMillis():contribution.getDateCreated().getTime());
|
cv.put(Table.COLUMN_TIMESTAMP, contribution.getDateCreated()==null?System.currentTimeMillis():contribution.getDateCreated().getTime());
|
||||||
cv.put(Table.COLUMN_STATE, contribution.getState());
|
cv.put(Table.COLUMN_STATE, contribution.getState());
|
||||||
cv.put(Table.COLUMN_TRANSFERRED, contribution.getTransferred());
|
cv.put(Table.COLUMN_TRANSFERRED, contribution.getTransferred());
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||||
import fr.free.nrw.commons.review.ReviewController;
|
import fr.free.nrw.commons.review.ReviewController;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
import fr.free.nrw.commons.upload.FileProcessor;
|
import fr.free.nrw.commons.upload.FileProcessor;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModule;
|
||||||
import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ import fr.free.nrw.commons.widget.PicOfDayAppWidget;
|
||||||
ActivityBuilderModule.class,
|
ActivityBuilderModule.class,
|
||||||
FragmentBuilderModule.class,
|
FragmentBuilderModule.class,
|
||||||
ServiceBuilderModule.class,
|
ServiceBuilderModule.class,
|
||||||
ContentProviderBuilderModule.class
|
ContentProviderBuilderModule.class, UploadModule.class
|
||||||
})
|
})
|
||||||
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
public interface CommonsApplicationComponent extends AndroidInjector<ApplicationlessInjection> {
|
||||||
void inject(CommonsApplication application);
|
void inject(CommonsApplication application);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ import com.google.gson.Gson;
|
||||||
|
|
||||||
import org.wikipedia.dataclient.WikiSite;
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import org.wikipedia.dataclient.WikiSite;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -37,6 +42,8 @@ import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl;
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
public class CommonsApplicationModule {
|
public class CommonsApplicationModule {
|
||||||
private Context applicationContext;
|
private Context applicationContext;
|
||||||
|
public static final String IO_THREAD="io_thread";
|
||||||
|
public static final String MAIN_THREAD="main_thread";
|
||||||
|
|
||||||
public CommonsApplicationModule(Context applicationContext) {
|
public CommonsApplicationModule(Context applicationContext) {
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
|
|
@ -172,4 +179,16 @@ public class CommonsApplicationModule {
|
||||||
public boolean provideIsBetaVariant() {
|
public boolean provideIsBetaVariant() {
|
||||||
return ConfigUtils.isBetaFlavour();
|
return ConfigUtils.isBetaFlavour();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Named(IO_THREAD)
|
||||||
|
@Provides
|
||||||
|
public Scheduler providesIoThread(){
|
||||||
|
return Schedulers.io();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Named(MAIN_THREAD)
|
||||||
|
@Provides
|
||||||
|
public Scheduler providesMainThread(){
|
||||||
|
return AndroidSchedulers.mainThread();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -18,6 +18,9 @@ import fr.free.nrw.commons.nearby.NearbyListFragment;
|
||||||
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
import fr.free.nrw.commons.nearby.NearbyMapFragment;
|
||||||
import fr.free.nrw.commons.review.ReviewImageFragment;
|
import fr.free.nrw.commons.review.ReviewImageFragment;
|
||||||
import fr.free.nrw.commons.settings.SettingsFragment;
|
import fr.free.nrw.commons.settings.SettingsFragment;
|
||||||
|
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
|
|
@ -71,4 +74,12 @@ public abstract class FragmentBuilderModule {
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector
|
||||||
abstract ReviewImageFragment bindReviewOutOfContextFragment();
|
abstract ReviewImageFragment bindReviewOutOfContextFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract UploadMediaDetailFragment bindUploadMediaDetailFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract UploadCategoriesFragment bindUploadCategoriesFragment();
|
||||||
|
|
||||||
|
@ContributesAndroidInjector
|
||||||
|
abstract MediaLicenseFragment bindMediaLicenseFragment();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,6 @@ import butterknife.ButterKnife;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.ButterKnife;
|
|
||||||
import com.jakewharton.rxbinding2.view.RxView;
|
|
||||||
import com.jakewharton.rxbinding2.widget.RxSearchView;
|
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
import fr.free.nrw.commons.explore.categories.SearchCategoryFragment;
|
||||||
|
|
@ -33,13 +29,6 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents search screen of this app
|
* Represents search screen of this app
|
||||||
|
|
|
||||||
|
|
@ -57,9 +57,6 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.wikipedia.util.DateUtil;
|
|
||||||
import org.wikipedia.util.StringUtil;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
package fr.free.nrw.commons.repository;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Local Data Source for UploadRepository, fetches and returns data from local db/shared prefernces
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class UploadLocalDataSource {
|
||||||
|
|
||||||
|
private final UploadModel uploadModel;
|
||||||
|
private JsonKvStore defaultKVStore;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UploadLocalDataSource(
|
||||||
|
@Named("default_preferences") JsonKvStore defaultKVStore,
|
||||||
|
UploadModel uploadModel) {
|
||||||
|
this.defaultKVStore = defaultKVStore;
|
||||||
|
this.uploadModel = uploadModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns the string list of valid licenses
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getLicenses() {
|
||||||
|
return uploadModel.getLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of Upload Items
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getCount() {
|
||||||
|
return uploadModel.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and return the selected license for the current upload
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getSelectedLicense() {
|
||||||
|
return uploadModel.getSelectedLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set selected license for the current upload
|
||||||
|
*
|
||||||
|
* @param licenseName
|
||||||
|
*/
|
||||||
|
public void setSelectedLicense(String licenseName) {
|
||||||
|
uploadModel.setSelectedLicense(licenseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the current upload item
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @param uploadItem
|
||||||
|
*/
|
||||||
|
public void updateUploadItem(int index, UploadItem uploadItem) {
|
||||||
|
uploadModel.updateUploadItem(index, uploadItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* upload is halted, cleanup the acquired resources
|
||||||
|
*/
|
||||||
|
public void cleanUp() {
|
||||||
|
uploadModel.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the upload item at the current index
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
public void deletePicture(String filePath) {
|
||||||
|
uploadModel.deletePicture(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fethces and returns the previous upload item, if any, returns null otherwise
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public UploadItem getPreviousUploadItem(int index) {
|
||||||
|
if (index - 1 >= 0) {
|
||||||
|
return uploadModel.getItems().get(index - 1);
|
||||||
|
}
|
||||||
|
return null; //There is no previous item to copy details
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* saves boolean value in default store
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void saveValue(String key, boolean value) {
|
||||||
|
defaultKVStore.putBoolean(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* saves string value in default store
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void saveValue(String key, String value) {
|
||||||
|
defaultKVStore.putString(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns string value from the default store
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param defaultValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getValue(String key, String defaultValue) {
|
||||||
|
return defaultKVStore.getString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns boolean value from the default store
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param defaultValue
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean getValue(String key, boolean defaultValue) {
|
||||||
|
return defaultKVStore.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
package fr.free.nrw.commons.repository;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.category.CategoriesModel;
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
import fr.free.nrw.commons.upload.UploadController;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class would act as the data source for remote operations for UploadActivity
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class UploadRemoteDataSource {
|
||||||
|
|
||||||
|
private UploadModel uploadModel;
|
||||||
|
private UploadController uploadController;
|
||||||
|
private CategoriesModel categoriesModel;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UploadRemoteDataSource(UploadModel uploadModel, UploadController uploadController,
|
||||||
|
CategoriesModel categoriesModel) {
|
||||||
|
this.uploadModel = uploadModel;
|
||||||
|
this.uploadController = uploadController;
|
||||||
|
this.categoriesModel = categoriesModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the UploadModel to build the contributions
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<Contribution> buildContributions() {
|
||||||
|
return uploadModel.buildContributions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the UploadService to star the uplaod for
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
public void startUpload(Contribution contribution) {
|
||||||
|
uploadController.startUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the list of UploadItem from the UploadModel
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<UploadItem> getUploads() {
|
||||||
|
return uploadModel.getUploads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare the UploadService for the upload
|
||||||
|
*/
|
||||||
|
public void prepareService() {
|
||||||
|
uploadController.prepareService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the UploadController
|
||||||
|
*/
|
||||||
|
public void cleanup() {
|
||||||
|
uploadController.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up the selected categories
|
||||||
|
*/
|
||||||
|
public void clearSelectedCategories(){
|
||||||
|
//This needs further refactoring, this should not be here, right now the structure wont suppoort rhis
|
||||||
|
categoriesModel.cleanUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returnt the list of selected categories
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<CategoryItem> getSelectedCategories() {
|
||||||
|
return categoriesModel.getSelectedCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* all categories from MWApi
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @param imageTitleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) {
|
||||||
|
return categoriesModel.searchAll(query, imageTitleList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the string list of categories
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getCategoryStringList() {
|
||||||
|
return categoriesModel.getCategoryStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the selected categories in the UploadModel
|
||||||
|
*
|
||||||
|
* @param categoryStringList
|
||||||
|
*/
|
||||||
|
public void setSelectedCategories(List<String> categoryStringList) {
|
||||||
|
uploadModel.setSelectedCategories(categoryStringList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles category selection/unselection
|
||||||
|
*
|
||||||
|
* @param categoryItem
|
||||||
|
*/
|
||||||
|
public void onCategoryClicked(CategoryItem categoryItem) {
|
||||||
|
categoriesModel.onCategoryItemClicked(categoryItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns category sorted based on similarity with query
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Comparator<CategoryItem> sortBySimilarity(String query) {
|
||||||
|
return categoriesModel.sortBySimilarity(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prunes the category list for irrelevant categories see #750
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean containsYear(String name) {
|
||||||
|
return categoriesModel.containsYear(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pre process the UploadableFile
|
||||||
|
*
|
||||||
|
* @param uploadableFile
|
||||||
|
* @param place
|
||||||
|
* @param source
|
||||||
|
* @param similarImageInterface
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
||||||
|
String source, SimilarImageInterface similarImageInterface) {
|
||||||
|
return uploadModel.preProcessImage(uploadableFile, place, source, similarImageInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask the UplaodModel for the image quality of the UploadItem
|
||||||
|
*
|
||||||
|
* @param uploadItem
|
||||||
|
* @param shouldValidateTitle
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean shouldValidateTitle) {
|
||||||
|
return uploadModel.getImageQuality(uploadItem, shouldValidateTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
package fr.free.nrw.commons.repository;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The repository class for UploadActivity
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class UploadRepository {
|
||||||
|
|
||||||
|
private UploadLocalDataSource localDataSource;
|
||||||
|
private UploadRemoteDataSource remoteDataSource;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UploadRepository(UploadLocalDataSource localDataSource,
|
||||||
|
UploadRemoteDataSource remoteDataSource) {
|
||||||
|
this.localDataSource = localDataSource;
|
||||||
|
this.remoteDataSource = remoteDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the RemoteDataSource to build contributions
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<Contribution> buildContributions() {
|
||||||
|
return remoteDataSource.buildContributions();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the RemoteDataSource to start upload for the contribution
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
public void startUpload(Contribution contribution) {
|
||||||
|
remoteDataSource.startUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns all the Upload Items
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<UploadItem> getUploads() {
|
||||||
|
return remoteDataSource.getUploads();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the RemoteDataSource to prepare the Upload Service
|
||||||
|
*/
|
||||||
|
public void prepareService() {
|
||||||
|
remoteDataSource.prepareService();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Prepare for a fresh upload
|
||||||
|
*/
|
||||||
|
public void cleanup() {
|
||||||
|
localDataSource.cleanUp();
|
||||||
|
remoteDataSource.clearSelectedCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and returns the selected categories for the current upload
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<CategoryItem> getSelectedCategories() {
|
||||||
|
return remoteDataSource.getSelectedCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* all categories from MWApi
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @param imageTitleList
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<CategoryItem> searchAll(String query, List<String> imageTitleList) {
|
||||||
|
return remoteDataSource.searchAll(query, imageTitleList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the string list of categories
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
public List<String> getCategoryStringList() {
|
||||||
|
return remoteDataSource.getCategoryStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the list of selected categories for the current upload
|
||||||
|
*
|
||||||
|
* @param categoryStringList
|
||||||
|
*/
|
||||||
|
public void setSelectedCategories(List<String> categoryStringList) {
|
||||||
|
remoteDataSource.setSelectedCategories(categoryStringList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles the category selection/deselection
|
||||||
|
*
|
||||||
|
* @param categoryItem
|
||||||
|
*/
|
||||||
|
public void onCategoryClicked(CategoryItem categoryItem) {
|
||||||
|
remoteDataSource.onCategoryClicked(categoryItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns category sorted based on similarity with query
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Comparator<? super CategoryItem> sortBySimilarity(String query) {
|
||||||
|
return remoteDataSource.sortBySimilarity(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prunes the category list for irrelevant categories see #750
|
||||||
|
*
|
||||||
|
* @param name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean containsYear(String name) {
|
||||||
|
return remoteDataSource.containsYear(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retursn the string list of available license from the LocalDataSource
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getLicenses() {
|
||||||
|
return localDataSource.getLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the selected license for the current upload
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getSelectedLicense() {
|
||||||
|
return localDataSource.getSelectedLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns the number of Upload Items
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public int getCount() {
|
||||||
|
return localDataSource.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask the RemoteDataSource to pre process the image
|
||||||
|
*
|
||||||
|
* @param uploadableFile
|
||||||
|
* @param place
|
||||||
|
* @param source
|
||||||
|
* @param similarImageInterface
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile, Place place,
|
||||||
|
String source, SimilarImageInterface similarImageInterface) {
|
||||||
|
return remoteDataSource
|
||||||
|
.preProcessImage(uploadableFile, place, source, similarImageInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* query the RemoteDataSource for image quality
|
||||||
|
*
|
||||||
|
* @param uploadItem
|
||||||
|
* @param shouldValidateTitle
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean shouldValidateTitle) {
|
||||||
|
return remoteDataSource.getImageQuality(uploadItem, shouldValidateTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the LocalDataSource to update the Upload Item
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @param uploadItem
|
||||||
|
*/
|
||||||
|
public void updateUploadItem(int index, UploadItem uploadItem) {
|
||||||
|
localDataSource.updateUploadItem(index, uploadItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the LocalDataSource to delete the file with the given file path
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
*/
|
||||||
|
public void deletePicture(String filePath) {
|
||||||
|
localDataSource.deletePicture(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetches and returns the previous upload item
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public UploadItem getPreviousUploadItem(int index) {
|
||||||
|
return localDataSource.getPreviousUploadItem(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save boolean value locally
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void saveValue(String key, boolean value) {
|
||||||
|
localDataSource.saveValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* save string value locally
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
*/
|
||||||
|
public void saveValue(String key, String value) {
|
||||||
|
localDataSource.saveValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch the string value for the associated key
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public String getValue(String key, String value) {
|
||||||
|
return localDataSource.getValue(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set selected license for the current upload
|
||||||
|
*
|
||||||
|
* @param licenseName
|
||||||
|
*/
|
||||||
|
public void setSelectedLicense(String licenseName) {
|
||||||
|
localDataSource.setSelectedLicense(licenseName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,11 +5,12 @@ import java.util.List;
|
||||||
/**
|
/**
|
||||||
* Holds a description of an item being uploaded by {@link UploadActivity}
|
* Holds a description of an item being uploaded by {@link UploadActivity}
|
||||||
*/
|
*/
|
||||||
class Description {
|
public class Description {
|
||||||
|
|
||||||
private String languageCode;
|
private String languageCode;
|
||||||
private String descriptionText;
|
private String descriptionText;
|
||||||
private int selectedLanguageIndex = -1;
|
private int selectedLanguageIndex = -1;
|
||||||
|
private boolean isManuallyAdded=false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The language code ie. "en" or "fr"
|
* @return The language code ie. "en" or "fr"
|
||||||
|
|
@ -47,6 +48,21 @@ class Description {
|
||||||
this.selectedLanguageIndex = selectedLanguageIndex;
|
this.selectedLanguageIndex = selectedLanguageIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns if the description was added manually (by the user, or we have added it programaticallly)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isManuallyAdded() {
|
||||||
|
return isManuallyAdded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets to true if the description was manually added by the user
|
||||||
|
* @param manuallyAdded
|
||||||
|
*/
|
||||||
|
public void setManuallyAdded(boolean manuallyAdded) {
|
||||||
|
isManuallyAdded = manuallyAdded;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the list of descriptions into the format Commons requires for uploads.
|
* Formats the list of descriptions into the format Commons requires for uploads.
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemSelectedListener;
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatEditText;
|
||||||
import androidx.appcompat.widget.AppCompatSpinner;
|
import androidx.appcompat.widget.AppCompatSpinner;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
|
@ -24,60 +24,35 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
import fr.free.nrw.commons.utils.AbstractTextWatcher;
|
||||||
import fr.free.nrw.commons.utils.BiMap;
|
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 timber.log.Timber;
|
||||||
|
|
||||||
class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
|
public class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewHolder> {
|
||||||
|
|
||||||
private Title title;
|
|
||||||
private List<Description> descriptions;
|
private List<Description> descriptions;
|
||||||
private Context context;
|
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
private Subject<String> titleChangedSubject;
|
|
||||||
|
|
||||||
private BiMap<AdapterView, String> selectedLanguages;
|
private BiMap<AdapterView, String> selectedLanguages;
|
||||||
private UploadView uploadView;
|
|
||||||
|
|
||||||
DescriptionsAdapter(UploadView uploadView) {
|
public DescriptionsAdapter() {
|
||||||
title = new Title();
|
|
||||||
descriptions = new ArrayList<>();
|
descriptions = new ArrayList<>();
|
||||||
titleChangedSubject = BehaviorSubject.create();
|
|
||||||
selectedLanguages = new BiMap<>();
|
selectedLanguages = new BiMap<>();
|
||||||
this.uploadView = uploadView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCallback(Callback callback) {
|
public void setCallback(Callback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setItems(Title title, List<Description> descriptions) {
|
public void setItems(List<Description> descriptions) {
|
||||||
this.descriptions = descriptions;
|
this.descriptions = descriptions;
|
||||||
this.title = title;
|
|
||||||
selectedLanguages = new BiMap<>();
|
selectedLanguages = new BiMap<>();
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(int position) {
|
|
||||||
if (position == 0) return 1;
|
|
||||||
else return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View view;
|
return new ViewHolder(LayoutInflater.from(parent.getContext())
|
||||||
if (viewType == 1) {
|
.inflate(R.layout.row_item_description, parent, false));
|
||||||
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();
|
|
||||||
return new ViewHolder(view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -87,29 +62,21 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return descriptions.size() + 1;
|
return descriptions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets descriptions
|
* Gets descriptions
|
||||||
|
*
|
||||||
* @return List of descriptions
|
* @return List of descriptions
|
||||||
*/
|
*/
|
||||||
List<Description> getDescriptions() {
|
public List<Description> getDescriptions() {
|
||||||
return descriptions;
|
return descriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addDescription(Description description) {
|
public void addDescription(Description description) {
|
||||||
this.descriptions.add(description);
|
this.descriptions.add(description);
|
||||||
notifyItemInserted(descriptions.size() + 1);
|
notifyItemInserted(descriptions.size());
|
||||||
}
|
|
||||||
|
|
||||||
public Title getTitle() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTitle(Title title) {
|
|
||||||
this.title = title;
|
|
||||||
notifyItemInserted(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ViewHolder extends RecyclerView.ViewHolder {
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
@ -119,98 +86,53 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
||||||
AppCompatSpinner spinnerDescriptionLanguages;
|
AppCompatSpinner spinnerDescriptionLanguages;
|
||||||
|
|
||||||
@BindView(R.id.description_item_edit_text)
|
@BindView(R.id.description_item_edit_text)
|
||||||
EditText descItemEditText;
|
AppCompatEditText descItemEditText;
|
||||||
|
|
||||||
private View view;
|
|
||||||
|
|
||||||
public ViewHolder(View itemView) {
|
public ViewHolder(View itemView) {
|
||||||
super(itemView);
|
super(itemView);
|
||||||
ButterKnife.bind(this, itemView);
|
ButterKnife.bind(this, itemView);
|
||||||
this.view = itemView;
|
|
||||||
Timber.i("descItemEditText:" + descItemEditText);
|
Timber.i("descItemEditText:" + descItemEditText);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public void init(int position) {
|
public void init(int position) {
|
||||||
|
Description description = descriptions.get(position);
|
||||||
|
Timber.d("Description is " + description);
|
||||||
|
if (!TextUtils.isEmpty(description.getDescriptionText())) {
|
||||||
|
descItemEditText.setText(description.getDescriptionText());
|
||||||
|
} else {
|
||||||
|
descItemEditText.setText("");
|
||||||
|
}
|
||||||
if (position == 0) {
|
if (position == 0) {
|
||||||
Timber.d("Title is " + title);
|
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(),
|
||||||
if (!title.isEmpty()) {
|
null);
|
||||||
descItemEditText.setText(title.toString());
|
|
||||||
} else {
|
|
||||||
descItemEditText.setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
descItemEditText.setOnTouchListener((v, event) -> {
|
descItemEditText.setOnTouchListener((v, event) -> {
|
||||||
// Check this is a touch up event
|
//2 is for drawable right
|
||||||
if(event.getAction() != MotionEvent.ACTION_UP) return false;
|
float twelveDpInPixels = convertDpToPixel(12, descItemEditText.getContext());
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP && descItemEditText.getCompoundDrawables()[2].getBounds().contains((int)(descItemEditText.getWidth()-(event.getX()+twelveDpInPixels)),(int)(event.getY()-twelveDpInPixels))){
|
||||||
// Check we are tapping within 15px of the info icon
|
if (getAdapterPosition() == 0) {
|
||||||
int extraTapArea = 15;
|
callback.showAlert(R.string.media_detail_description,
|
||||||
Drawable info = descItemEditText.getCompoundDrawables()[2];
|
R.string.description_info);
|
||||||
int infoHitboxX = descItemEditText.getWidth() - info.getBounds().width();
|
}
|
||||||
if (event.getX() + extraTapArea < infoHitboxX) return false;
|
return true;
|
||||||
|
}
|
||||||
// If the above are true, show the info dialog
|
return false;
|
||||||
callback.showAlert(R.string.media_detail_title, R.string.title_info);
|
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Description description = descriptions.get(position - 1);
|
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
|
||||||
Timber.d("Description is " + description);
|
|
||||||
if (!TextUtils.isEmpty(description.getDescriptionText())) {
|
|
||||||
descItemEditText.setText(description.getDescriptionText());
|
|
||||||
} else {
|
|
||||||
descItemEditText.setText("");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the info icon for the first description
|
|
||||||
if (position == 1) {
|
|
||||||
descItemEditText.setCompoundDrawablesWithIntrinsicBounds(null, null, getInfoIcon(), null);
|
|
||||||
descItemEditText.setOnTouchListener((v, event) -> {
|
|
||||||
// Check this is a touch up event
|
|
||||||
if(event.getAction() != MotionEvent.ACTION_UP) return false;
|
|
||||||
|
|
||||||
// Check we are tapping within 15px of the info icon
|
|
||||||
int extraTapArea = 15;
|
|
||||||
Drawable info = descItemEditText.getCompoundDrawables()[2];
|
|
||||||
int infoHitboxX = descItemEditText.getWidth() - info.getBounds().width();
|
|
||||||
if (event.getX() + extraTapArea < infoHitboxX) return false;
|
|
||||||
|
|
||||||
// If the above are true, show the info dialog
|
|
||||||
callback.showAlert(R.string.media_detail_description, R.string.description_info);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
descItemEditText.addTextChangedListener(new AbstractTextWatcher(descriptionText->{
|
|
||||||
descriptions.get(position - 1).setDescriptionText(descriptionText);
|
|
||||||
}));
|
|
||||||
|
|
||||||
descItemEditText.setOnFocusChangeListener((v, hasFocus) -> {
|
|
||||||
if (!hasFocus) {
|
|
||||||
ViewUtil.hideKeyboard(v);
|
|
||||||
} else {
|
|
||||||
uploadView.setTopCardState(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
initLanguageSpinner(position, description);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
descItemEditText.addTextChangedListener(new AbstractTextWatcher(
|
||||||
|
descriptionText -> descriptions.get(position)
|
||||||
|
.setDescriptionText(descriptionText)));
|
||||||
|
initLanguageSpinner(position, description);
|
||||||
|
|
||||||
|
//If the description was manually added by the user, it deserves focus, if not, let the user decide
|
||||||
|
if (description.isManuallyAdded()) {
|
||||||
|
descItemEditText.requestFocus();
|
||||||
|
} else {
|
||||||
|
descItemEditText.clearFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -219,48 +141,24 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
||||||
* @param description
|
* @param description
|
||||||
*/
|
*/
|
||||||
private void initLanguageSpinner(int position, Description description) {
|
private void initLanguageSpinner(int position, Description description) {
|
||||||
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(context,
|
SpinnerLanguagesAdapter languagesAdapter = new SpinnerLanguagesAdapter(
|
||||||
|
spinnerDescriptionLanguages.getContext(),
|
||||||
R.layout.row_item_languages_spinner, selectedLanguages);
|
R.layout.row_item_languages_spinner, selectedLanguages);
|
||||||
languagesAdapter.notifyDataSetChanged();
|
languagesAdapter.notifyDataSetChanged();
|
||||||
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
|
spinnerDescriptionLanguages.setAdapter(languagesAdapter);
|
||||||
|
|
||||||
if (description.getSelectedLanguageIndex() == -1) {
|
|
||||||
if (position == 1) {
|
|
||||||
int defaultLocaleIndex = languagesAdapter.getIndexOfUserDefaultLocale(context);
|
|
||||||
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex);
|
|
||||||
} else {
|
|
||||||
// availableLangIndex gives the index of first non-selected language
|
|
||||||
int availableLangIndex = -1;
|
|
||||||
|
|
||||||
// loops over the languagesAdapter and finds the index of first non-selected language
|
|
||||||
for (int i = 0; i < languagesAdapter.getCount(); i++) {
|
|
||||||
if (!selectedLanguages.containsKey(languagesAdapter.getLanguageCode(i))) {
|
|
||||||
availableLangIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (availableLangIndex >= 0) {
|
|
||||||
// sets the spinner value to the index of first non-selected language
|
|
||||||
spinnerDescriptionLanguages.setSelection(availableLangIndex);
|
|
||||||
selectedLanguages.put(spinnerDescriptionLanguages, languagesAdapter.getLanguageCode(position));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
|
|
||||||
selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO do it the butterknife way
|
|
||||||
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
|
spinnerDescriptionLanguages.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
|
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
|
||||||
long l) {
|
long l) {
|
||||||
description.setSelectedLanguageIndex(position);
|
description.setSelectedLanguageIndex(position);
|
||||||
String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter()).getLanguageCode(position);
|
String languageCode = ((SpinnerLanguagesAdapter) adapterView.getAdapter())
|
||||||
|
.getLanguageCode(position);
|
||||||
description.setLanguageCode(languageCode);
|
description.setLanguageCode(languageCode);
|
||||||
selectedLanguages.remove(adapterView);
|
selectedLanguages.remove(adapterView);
|
||||||
selectedLanguages.put(adapterView, languageCode);
|
selectedLanguages.put(adapterView, languageCode);
|
||||||
((SpinnerLanguagesAdapter) adapterView.getAdapter()).selectedLangCode = languageCode;
|
((SpinnerLanguagesAdapter) adapterView
|
||||||
|
.getAdapter()).selectedLangCode = languageCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -268,18 +166,43 @@ class DescriptionsAdapter extends RecyclerView.Adapter<DescriptionsAdapter.ViewH
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (description.getSelectedLanguageIndex() == -1) {
|
||||||
|
if (position == 0) {
|
||||||
|
int defaultLocaleIndex = languagesAdapter
|
||||||
|
.getIndexOfUserDefaultLocale(spinnerDescriptionLanguages.getContext());
|
||||||
|
spinnerDescriptionLanguages.setSelection(defaultLocaleIndex, true);
|
||||||
|
} else {
|
||||||
|
spinnerDescriptionLanguages.setSelection(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spinnerDescriptionLanguages.setSelection(description.getSelectedLanguageIndex());
|
||||||
|
selectedLanguages.put(spinnerDescriptionLanguages, description.getLanguageCode());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracted out the method to get the icon drawable
|
* Extracted out the method to get the icon drawable
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
private Drawable getInfoIcon() {
|
private Drawable getInfoIcon() {
|
||||||
return context.getResources().getDrawable(R.drawable.mapbox_info_icon_default);
|
return descItemEditText.getContext()
|
||||||
|
.getResources()
|
||||||
|
.getDrawable(R.drawable.mapbox_info_icon_default);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Callback {
|
public interface Callback {
|
||||||
|
|
||||||
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
void showAlert(int mediaDetailDescription, int descriptionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts dp to pixel
|
||||||
|
* @param dp
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private float convertDpToPixel(float dp, Context context) {
|
||||||
|
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageDialogFragment.Callback;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
@ -37,7 +38,7 @@ import timber.log.Timber;
|
||||||
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
* Processing of the image filePath that is about to be uploaded via ShareActivity is done here
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
public class FileProcessor implements Callback {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CacheController cacheController;
|
CacheController cacheController;
|
||||||
|
|
@ -58,7 +59,7 @@ public class FileProcessor implements SimilarImageDialogFragment.onResponse {
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
FileProcessor() {
|
public FileProcessor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cleanup() {
|
public void cleanup() {
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ import timber.log.Timber;
|
||||||
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
||||||
* is uploaded, extract latitude and longitude from EXIF data of image.
|
* is uploaded, extract latitude and longitude from EXIF data of image.
|
||||||
*/
|
*/
|
||||||
class GPSExtractor {
|
public class GPSExtractor {
|
||||||
|
|
||||||
static final GPSExtractor DUMMY= new GPSExtractor();
|
static final GPSExtractor DUMMY= new GPSExtractor();
|
||||||
private double decLatitude;
|
private double decLatitude;
|
||||||
private double decLongitude;
|
private double decLongitude;
|
||||||
boolean imageCoordsExists;
|
public boolean imageCoordsExists;
|
||||||
private String latitude;
|
private String latitude;
|
||||||
private String longitude;
|
private String longitude;
|
||||||
private String latitudeRef;
|
private String latitudeRef;
|
||||||
|
|
@ -96,11 +96,11 @@ class GPSExtractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double getDecLatitude() {
|
public double getDecLatitude() {
|
||||||
return decLatitude;
|
return decLatitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
double getDecLongitude() {
|
public double getDecLongitude() {
|
||||||
return decLongitude;
|
return decLongitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,17 +37,21 @@ public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
Button positiveButton;
|
Button positiveButton;
|
||||||
@BindView(R.id.negative_button)
|
@BindView(R.id.negative_button)
|
||||||
Button negativeButton;
|
Button negativeButton;
|
||||||
onResponse mOnResponse;//Implemented interface from shareActivity
|
Callback callback;//Implemented interface from shareActivity
|
||||||
Boolean gotResponse = false;
|
Boolean gotResponse = false;
|
||||||
|
|
||||||
public SimilarImageDialogFragment() {
|
public SimilarImageDialogFragment() {
|
||||||
}
|
}
|
||||||
public interface onResponse{
|
public interface Callback {
|
||||||
void onPositiveResponse();
|
void onPositiveResponse();
|
||||||
|
|
||||||
void onNegativeResponse();
|
void onNegativeResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCallback(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
|
View view = inflater.inflate(R.layout.fragment_similar_image_dialog, container, false);
|
||||||
|
|
@ -77,7 +81,6 @@ public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mOnResponse = (onResponse) getActivity();//Interface Implementation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -91,21 +94,21 @@ public class SimilarImageDialogFragment extends DialogFragment {
|
||||||
public void onDismiss(DialogInterface dialog) {
|
public void onDismiss(DialogInterface dialog) {
|
||||||
// I user dismisses dialog by pressing outside the dialog.
|
// I user dismisses dialog by pressing outside the dialog.
|
||||||
if (!gotResponse) {
|
if (!gotResponse) {
|
||||||
mOnResponse.onNegativeResponse();
|
callback.onNegativeResponse();
|
||||||
}
|
}
|
||||||
super.onDismiss(dialog);
|
super.onDismiss(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.negative_button)
|
@OnClick(R.id.negative_button)
|
||||||
public void onNegativeButtonClicked() {
|
public void onNegativeButtonClicked() {
|
||||||
mOnResponse.onNegativeResponse();
|
callback.onNegativeResponse();
|
||||||
gotResponse = true;
|
gotResponse = true;
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.postive_button)
|
@OnClick(R.id.postive_button)
|
||||||
public void onPositiveButtonClicked() {
|
public void onPositiveButtonClicked() {
|
||||||
mOnResponse.onPositiveResponse();
|
callback.onPositiveResponse();
|
||||||
gotResponse = true;
|
gotResponse = true;
|
||||||
dismiss();
|
dismiss();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -22,6 +21,10 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.utils.BiMap;
|
import fr.free.nrw.commons.utils.BiMap;
|
||||||
import fr.free.nrw.commons.utils.LangCodeUtils;
|
import fr.free.nrw.commons.utils.LangCodeUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
||||||
|
|
||||||
|
|
@ -83,27 +86,32 @@ public class SpinnerLanguagesAdapter extends ArrayAdapter {
|
||||||
@Override
|
@Override
|
||||||
public View getDropDownView(int position, @Nullable View convertView,
|
public View getDropDownView(int position, @Nullable View convertView,
|
||||||
@NonNull ViewGroup parent) {
|
@NonNull ViewGroup parent) {
|
||||||
View view = layoutInflater.inflate(resource, parent, false);
|
if (convertView == null) {
|
||||||
ViewHolder holder = new ViewHolder(view);
|
convertView = layoutInflater.inflate(resource, parent, false);
|
||||||
|
}
|
||||||
|
ViewHolder holder = new ViewHolder(convertView);
|
||||||
holder.init(position, true);
|
holder.init(position, true);
|
||||||
return view;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull
|
public @NonNull
|
||||||
View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||||
View view = layoutInflater.inflate(resource, parent, false);
|
ViewHolder holder;
|
||||||
ViewHolder holder = new ViewHolder(view);
|
if (convertView == null) {
|
||||||
|
convertView = layoutInflater.inflate(resource, parent, false);
|
||||||
|
holder = new ViewHolder(convertView);
|
||||||
|
convertView.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (ViewHolder) convertView.getTag();
|
||||||
|
}
|
||||||
holder.init(position, false);
|
holder.init(position, false);
|
||||||
return view;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class ViewHolder {
|
public class ViewHolder {
|
||||||
|
|
||||||
@BindView(R.id.ll_container_description_language)
|
|
||||||
LinearLayout llContainerDescriptionLanguage;
|
|
||||||
|
|
||||||
@BindView(R.id.tv_language)
|
@BindView(R.id.tv_language)
|
||||||
TextView tvLanguage;
|
TextView tvLanguage;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
|
||||||
public interface ThumbnailClickedListener {
|
public interface ThumbnailClickedListener {
|
||||||
void thumbnailClicked(UploadModel.UploadItem content);
|
void thumbnailClicked(UploadableFile content);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build.VERSION;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter class for image thumbnails to be shown while uploading.
|
||||||
|
*/
|
||||||
|
class ThumbnailsAdapter extends RecyclerView.Adapter<ThumbnailsAdapter.ViewHolder> {
|
||||||
|
|
||||||
|
List<UploadableFile> uploadableFiles;
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
|
public ThumbnailsAdapter(Callback callback) {
|
||||||
|
this.uploadableFiles = new ArrayList<>();
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the data, the media files
|
||||||
|
* @param uploadableFiles
|
||||||
|
*/
|
||||||
|
public void setUploadableFiles(
|
||||||
|
List<UploadableFile> uploadableFiles) {
|
||||||
|
this.uploadableFiles=uploadableFiles;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
|
||||||
|
return new ViewHolder(LayoutInflater.from(viewGroup.getContext())
|
||||||
|
.inflate(R.layout.item_upload_thumbnail, viewGroup, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
|
||||||
|
viewHolder.bind(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return uploadableFiles.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.rl_container)
|
||||||
|
RelativeLayout rlContainer;
|
||||||
|
@BindView(R.id.iv_thumbnail)
|
||||||
|
SimpleDraweeView background;
|
||||||
|
@BindView(R.id.iv_error)
|
||||||
|
ImageView ivError;
|
||||||
|
|
||||||
|
public ViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
ButterKnife.bind(this, itemView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a row item to the ViewHolder
|
||||||
|
* @param position
|
||||||
|
*/
|
||||||
|
public void bind(int position) {
|
||||||
|
UploadableFile uploadableFile = uploadableFiles.get(position);
|
||||||
|
Uri uri = uploadableFile.getMediaUri();
|
||||||
|
background.setImageURI(Uri.fromFile(new File(String.valueOf(uri))));
|
||||||
|
|
||||||
|
if (position == callback.getCurrentSelectedFilePosition()) {
|
||||||
|
rlContainer.setEnabled(true);
|
||||||
|
rlContainer.setClickable(true);
|
||||||
|
rlContainer.setAlpha(1.0f);
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||||
|
rlContainer.setElevation(10);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rlContainer.setEnabled(false);
|
||||||
|
rlContainer.setClickable(false);
|
||||||
|
rlContainer.setAlpha(0.5f);
|
||||||
|
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
|
||||||
|
rlContainer.setElevation(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback used to get the current selected file position
|
||||||
|
*/
|
||||||
|
interface Callback {
|
||||||
|
|
||||||
|
int getCurrentSelectedFilePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,4 +31,8 @@ public class Title{
|
||||||
public boolean isEmpty() {
|
public boolean isEmpty() {
|
||||||
return titleText==null || titleText.isEmpty();
|
return titleText==null || titleText.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTitleText() {
|
||||||
|
return titleText;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,162 +1,115 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
||||||
|
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
||||||
|
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.cardview.widget.CardView;
|
import androidx.cardview.widget.CardView;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.text.TextUtils;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import android.text.method.LinkMovementMethod;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import android.text.style.ClickableSpan;
|
|
||||||
import android.text.style.URLSpan;
|
|
||||||
import android.view.MotionEvent;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.widget.ImageButton;
|
||||||
import android.widget.AdapterView;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.Spinner;
|
|
||||||
import android.widget.TextView;
|
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.ArrayList;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
import fr.free.nrw.commons.category.CategoriesModel;
|
||||||
import fr.free.nrw.commons.category.CategoryItem;
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionController;
|
import fr.free.nrw.commons.contributions.ContributionController;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
import fr.free.nrw.commons.upload.categories.UploadCategoriesFragment;
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
import fr.free.nrw.commons.upload.license.MediaLicenseFragment;
|
||||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.UploadMediaDetailFragmentCallback;
|
||||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
import fr.free.nrw.commons.utils.PermissionUtils;
|
||||||
import fr.free.nrw.commons.utils.ViewUtil;
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import java.util.Collections;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.SOURCE_EXTERNAL;
|
public class UploadActivity extends BaseActivity implements UploadContract.View ,UploadBaseFragment.Callback{
|
||||||
import static fr.free.nrw.commons.contributions.ContributionController.ACTION_INTERNAL_UPLOADS;
|
|
||||||
import static fr.free.nrw.commons.upload.UploadService.EXTRA_FILES;
|
|
||||||
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
|
|
||||||
|
|
||||||
public class UploadActivity extends BaseActivity implements UploadView, SimilarImageInterface {
|
|
||||||
@Inject MediaWikiApi mwApi;
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionController contributionController;
|
ContributionController contributionController;
|
||||||
@Inject @Named("default_preferences") JsonKvStore directKvStore;
|
@Inject @Named("default_preferences") JsonKvStore directKvStore;
|
||||||
@Inject UploadPresenter presenter;
|
@Inject UploadContract.UserActionListener presenter;
|
||||||
@Inject CategoriesModel categoriesModel;
|
@Inject CategoriesModel categoriesModel;
|
||||||
@Inject SessionManager sessionManager;
|
@Inject SessionManager sessionManager;
|
||||||
|
|
||||||
// Main GUI
|
@BindView(R.id.cv_container_top_card)
|
||||||
@BindView(R.id.backgroundImage) PhotoView background;
|
CardView cvContainerTopCard;
|
||||||
@BindView(R.id.upload_root_layout)
|
|
||||||
RelativeLayout rootLayout;
|
|
||||||
@BindView(R.id.view_flipper) ViewFlipper viewFlipper;
|
|
||||||
|
|
||||||
// Top Card
|
@BindView(R.id.ll_container_top_card)
|
||||||
@BindView(R.id.top_card) CardView topCard;
|
LinearLayout llContainerTopCard;
|
||||||
@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.rl_container_title)
|
||||||
@BindView(R.id.bottom_card) CardView bottomCard;
|
RelativeLayout rlContainerTitle;
|
||||||
@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;
|
|
||||||
@BindView(R.id.prev_title_desc) Button prevTitleDecs;
|
|
||||||
@BindView(R.id.categories_subtitle) TextView categoriesSubtitle;
|
|
||||||
@BindView(R.id.license_subtitle) TextView licenseSubtitle;
|
|
||||||
@BindView(R.id.please_wait_text_view) TextView pleaseWaitTextView;
|
|
||||||
|
|
||||||
|
@BindView(R.id.tv_top_card_title)
|
||||||
|
TextView tvTopCardTitle;
|
||||||
|
|
||||||
@BindView(R.id.right_card_map_button) View rightCardMapButton;
|
@BindView(R.id.ib_toggle_top_card)
|
||||||
|
ImageButton ibToggleTopCard;
|
||||||
|
|
||||||
// Category Search
|
@BindView(R.id.rv_thumbnails)
|
||||||
@BindView(R.id.categories_title) TextView categoryTitle;
|
RecyclerView rvThumbnails;
|
||||||
@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;
|
|
||||||
@BindView(R.id.category_search_layout)
|
|
||||||
FrameLayout categoryFrameLayout;
|
|
||||||
|
|
||||||
// Final Submission
|
@BindView(R.id.vp_upload)
|
||||||
@BindView(R.id.license_title) TextView licenseTitle;
|
ViewPager vpUpload;
|
||||||
@BindView(R.id.share_license_summary) HtmlTextView licenseSummary;
|
|
||||||
@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 boolean isTitleExpanded=true;
|
||||||
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
|
||||||
|
private CompositeDisposable compositeDisposable;
|
||||||
private ProgressDialog progressDialog;
|
private ProgressDialog progressDialog;
|
||||||
private boolean multipleUpload = false, flagForSubmit = false;
|
private UploadImageAdapter uploadImagesAdapter;
|
||||||
|
private List<Fragment> fragments;
|
||||||
|
private UploadCategoriesFragment uploadCategoriesFragment;
|
||||||
|
private MediaLicenseFragment mediaLicenseFragment;
|
||||||
|
private ThumbnailsAdapter thumbnailsAdapter;
|
||||||
|
|
||||||
|
|
||||||
|
private String source;
|
||||||
|
private Place place;
|
||||||
|
private List<UploadableFile> uploadableFiles= Collections.emptyList();
|
||||||
|
private int currentSelectedPosition=0;
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.activity_upload);
|
setContentView(R.layout.activity_upload);
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
compositeDisposable = new CompositeDisposable();
|
||||||
configureLayout();
|
init();
|
||||||
configureTopCard();
|
|
||||||
configureBottomCard();
|
|
||||||
initRecyclerView();
|
|
||||||
configureRightCard();
|
|
||||||
configureNavigationButtons();
|
|
||||||
configureCategories();
|
|
||||||
configureLicenses();
|
|
||||||
|
|
||||||
presenter.init();
|
|
||||||
|
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(this,
|
PermissionUtils.checkPermissionsAndPerformAction(this,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
|
|
@ -165,283 +118,150 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
R.string.write_storage_permission_rationale_for_image_share);
|
R.string.write_storage_permission_rationale_for_image_share);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void init() {
|
||||||
public boolean checkIfLoggedIn() {
|
initProgressDialog();
|
||||||
if (!sessionManager.isUserLoggedIn()) {
|
initViewPager();
|
||||||
Timber.d("Current account is null");
|
initThumbnailsRecyclerView();
|
||||||
ViewUtil.showLongToast(this, getString(R.string.user_not_logged_in));
|
//And init other things you need to
|
||||||
Intent loginIntent = new Intent(UploadActivity.this, LoginActivity.class);
|
}
|
||||||
startActivity(loginIntent);
|
|
||||||
return false;
|
private void initProgressDialog() {
|
||||||
}
|
progressDialog = new ProgressDialog(this);
|
||||||
return true;
|
progressDialog.setMessage(getString(R.string.please_wait));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initThumbnailsRecyclerView() {
|
||||||
|
rvThumbnails.setLayoutManager(new LinearLayoutManager(this,
|
||||||
|
LinearLayoutManager.HORIZONTAL, false));
|
||||||
|
thumbnailsAdapter=new ThumbnailsAdapter(() -> currentSelectedPosition);
|
||||||
|
rvThumbnails.setAdapter(thumbnailsAdapter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViewPager() {
|
||||||
|
uploadImagesAdapter=new UploadImageAdapter(getSupportFragmentManager());
|
||||||
|
vpUpload.setAdapter(uploadImagesAdapter);
|
||||||
|
vpUpload.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPageScrolled(int position, float positionOffset,
|
||||||
|
int positionOffsetPixels) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageSelected(int position) {
|
||||||
|
currentSelectedPosition=position;
|
||||||
|
if (position >= uploadableFiles.size()) {
|
||||||
|
cvContainerTopCard.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
thumbnailsAdapter.notifyDataSetChanged();
|
||||||
|
cvContainerTopCard.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPageScrollStateChanged(int state) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
public boolean isLoggedIn() {
|
||||||
presenter.cleanup();
|
return sessionManager.isUserLoggedIn();
|
||||||
super.onDestroy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
checkIfLoggedIn();
|
presenter.onAttachView(this);
|
||||||
|
if (!isLoggedIn()) {
|
||||||
|
askUserToLogIn();
|
||||||
|
}
|
||||||
checkStoragePermissions();
|
checkStoragePermissions();
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkStoragePermissions() {
|
private void checkStoragePermissions() {
|
||||||
PermissionUtils.checkPermissionsAndPerformAction(this,
|
PermissionUtils.checkPermissionsAndPerformAction(this,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
() -> presenter.addView(this),
|
() -> {
|
||||||
|
//TODO handle this
|
||||||
|
},
|
||||||
R.string.storage_permission_title,
|
R.string.storage_permission_title,
|
||||||
R.string.write_storage_permission_rationale_for_image_share);
|
R.string.write_storage_permission_rationale_for_image_share);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
presenter.removeView();
|
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateThumbnails(List<UploadModel.UploadItem> uploads) {
|
protected void onStop() {
|
||||||
int uploadCount = uploads.size();
|
super.onStop();
|
||||||
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) {
|
|
||||||
boolean saveForPrevImage = false;
|
|
||||||
int singleUploadStepCount = 3;
|
|
||||||
|
|
||||||
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 (currentStep == stepCount) {
|
|
||||||
dismissKeyboard();
|
|
||||||
}
|
|
||||||
if (stepCount > singleUploadStepCount) {
|
|
||||||
multipleUpload = true;
|
|
||||||
}
|
|
||||||
if (multipleUpload && currentStep != 1) {
|
|
||||||
saveForPrevImage = true;
|
|
||||||
}
|
|
||||||
configurePrevButton(saveForPrevImage);
|
|
||||||
if(isShowingItem) {
|
|
||||||
descriptionsAdapter.setItems(uploadItem.getTitle(), uploadItem.getDescriptions());
|
|
||||||
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, int imageCount) {
|
|
||||||
String licenseHyperLink = "<a href='" + Utils.licenseUrlFor(selectedLicense) + "'>" +
|
|
||||||
getString(Utils.licenseNameFor(selectedLicense)) + "</a><br>";
|
|
||||||
licenseSummary.setHtmlText(getResources().getQuantityString(R.plurals.share_license_summary, imageCount, 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) {
|
|
||||||
rightCardMapButton.setVisibility(visible ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBottomCardVisibility(@UploadPage int page, int uploadCount) {
|
|
||||||
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);
|
|
||||||
pleaseWaitTextView.setText(getResources().getQuantityText(R.plurals.receiving_shared_content, uploadCount));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only show the subtitle ("For all images in set") if multiple images being uploaded
|
* Show/Hide the progress dialog
|
||||||
* @param imageCount Number of images being uploaded
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void updateSubtitleVisibility(int imageCount) {
|
public void showProgress(boolean shouldShow) {
|
||||||
categoriesSubtitle.setVisibility(imageCount > 1 ? View.VISIBLE : View.GONE);
|
if (shouldShow) {
|
||||||
licenseSubtitle.setVisibility(imageCount > 1 ? View.VISIBLE : View.GONE);
|
if (!progressDialog.isShowing()) {
|
||||||
}
|
progressDialog.show();
|
||||||
|
}
|
||||||
@Override
|
} else {
|
||||||
public void setBottomCardState(boolean state) {
|
if (progressDialog != null && !isFinishing()) {
|
||||||
updateCardState(state, bottomCardExpandButton, rvDescriptions, previous, next, prevTitleDecs, bottomCardAddDescription);
|
progressDialog.dismiss();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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
|
@Override
|
||||||
public void showBadPicturePopup(String errorMessage) {
|
public int getIndexInViewFlipper(UploadBaseFragment fragment) {
|
||||||
DialogUtil.showAlertDialog(this,
|
return fragments.indexOf(fragment);
|
||||||
getString(R.string.warning),
|
|
||||||
errorMessage,
|
|
||||||
() -> presenter.deletePicture(),
|
|
||||||
() -> presenter.keepPicture());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showDuplicatePicturePopup() {
|
public int getTotalNumberOfSteps() {
|
||||||
DialogUtil.showAlertDialog(this,
|
return fragments.size();
|
||||||
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
|
@Override
|
||||||
public void showProgressDialog() {
|
public void showMessage(int messageResourceId) {
|
||||||
if (progressDialog == null) {
|
ViewUtil.showLongToast(this, messageResourceId);
|
||||||
progressDialog = new ProgressDialog(this);
|
|
||||||
}
|
|
||||||
progressDialog.setMessage(getString(R.string.please_wait));
|
|
||||||
progressDialog.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void hideProgressDialog() {
|
public List<UploadableFile> getUploadableFiles() {
|
||||||
if (progressDialog != null && !isFinishing()) {
|
return uploadableFiles;
|
||||||
progressDialog.dismiss();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void launchMapActivity(LatLng decCoords) {
|
public void showHideTopCard(boolean shouldShow) {
|
||||||
Utils.handleGeoCoordinates(this, decCoords);
|
llContainerTopCard.setVisibility(shouldShow?View.VISIBLE:View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showErrorMessage(int resourceId) {
|
public void onUploadMediaDeleted(int index) {
|
||||||
ViewUtil.showShortToast(this, resourceId);
|
fragments.remove(index);//Remove the corresponding fragment
|
||||||
|
uploadableFiles.remove(index);//Remove the files from the list
|
||||||
|
thumbnailsAdapter.notifyItemRemoved(index); //Notify the thumbnails adapter
|
||||||
|
uploadImagesAdapter.notifyDataSetChanged(); //Notify the ViewPager
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initDefaultCategories() {
|
public void updateTopCardTitle() {
|
||||||
updateCategoryList("");
|
tvTopCardTitle.setText(getResources()
|
||||||
|
.getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(), uploadableFiles.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void askUserToLogIn() {
|
||||||
|
Timber.d("current session is null, asking user to login");
|
||||||
|
ViewUtil.showLongToast(this, getString(R.string.user_not_logged_in));
|
||||||
|
Intent loginIntent = new Intent(UploadActivity.this, LoginActivity.class);
|
||||||
|
startActivity(loginIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
@ -450,179 +270,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
|
||||||
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() {
|
|
||||||
boolean flagVal = directKvStore.getBoolean("flagForSubmit");
|
|
||||||
if(flagVal){
|
|
||||||
prevTitleDecs.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
prevTitleDecs.setVisibility(View.INVISIBLE);
|
|
||||||
}
|
|
||||||
bottomCardExpandButton.setOnClickListener(v -> presenter.toggleBottomCardState());
|
|
||||||
bottomCard.setOnClickListener(v -> presenter.toggleBottomCardState());
|
|
||||||
bottomCardAddDescription.setOnClickListener(v -> addNewDescription());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addNewDescription() {
|
|
||||||
descriptionsAdapter.addDescription(new Description());
|
|
||||||
rvDescriptions.scrollToPosition(descriptionsAdapter.getItemCount() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureRightCard() {
|
|
||||||
rightCardMapButton.setOnClickListener(v -> presenter.openCoordinateMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public void configurePrevButton(Boolean saveForPrevImage){
|
|
||||||
prevTitleDecs.setCompoundDrawablesWithIntrinsicBounds(null, null, getResources().getDrawable(R.drawable.mapbox_info_icon_default), null);
|
|
||||||
|
|
||||||
String name = "prev_";
|
|
||||||
if (saveForPrevImage) {
|
|
||||||
name = name + "image_";
|
|
||||||
} else {
|
|
||||||
name = name + "upload_";
|
|
||||||
}
|
|
||||||
String title = directKvStore.getString(name + "title");
|
|
||||||
Title t = new Title();
|
|
||||||
t.setTitleText(title);
|
|
||||||
|
|
||||||
List<Description> finalDesc = new LinkedList<>();
|
|
||||||
int descCount = directKvStore.getInt(name + "descCount");
|
|
||||||
for (int i = 0; i < descCount; i++) {
|
|
||||||
Description description= new Description();
|
|
||||||
String desc = directKvStore.getString(name + "description_<" + i + ">");
|
|
||||||
description.setDescriptionText(desc);
|
|
||||||
finalDesc.add(description);
|
|
||||||
int position = directKvStore.getInt(name + "spinnerPosition_<" + i + ">");
|
|
||||||
description.setSelectedLanguageIndex(position);
|
|
||||||
}
|
|
||||||
prevTitleDecs.setOnTouchListener((v, event) -> {
|
|
||||||
// Check this is a touch up event
|
|
||||||
if(event.getAction() != MotionEvent.ACTION_UP) return false;
|
|
||||||
// Check we are tapping within 15px of the info icon
|
|
||||||
int extraTapArea = 15;
|
|
||||||
Drawable info = prevTitleDecs.getCompoundDrawables()[2];
|
|
||||||
int infoHintbox = prevTitleDecs.getWidth() - info.getBounds().width();
|
|
||||||
if (event.getX() + extraTapArea < infoHintbox) return false;
|
|
||||||
|
|
||||||
DialogUtil.showAlertDialog(this, null, getString(R.string.previous_button_tooltip_message), "okay", null, null, null);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
prevTitleDecs.setOnClickListener((View v) -> {
|
|
||||||
descriptionsAdapter.setItems(t, finalDesc);
|
|
||||||
rvDescriptions.setAdapter(descriptionsAdapter);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureNavigationButtons() {
|
|
||||||
// Navigation next / previous for each image as we're collecting title + description
|
|
||||||
next.setOnClickListener(v -> {
|
|
||||||
if (!NetworkUtils.isInternetConnectionEstablished(this)) {
|
|
||||||
ViewUtil.showShortSnackbar(rootLayout, R.string.no_internet);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setTitleAndDescriptions();
|
|
||||||
if (multipleUpload) {
|
|
||||||
savePrevTitleDesc("prev_image_");
|
|
||||||
}
|
|
||||||
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 -> {
|
|
||||||
flagForSubmit = true;
|
|
||||||
directKvStore.putBoolean("flagForSubmit", flagForSubmit);
|
|
||||||
savePrevTitleDesc("prev_upload_");
|
|
||||||
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() {
|
|
||||||
categoryFrameLayout.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
|
|
||||||
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();
|
|
||||||
compositeDisposable.add(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() {
|
private void receiveSharedItems() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
|
|
@ -631,21 +278,79 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
} else if (ACTION_INTERNAL_UPLOADS.equals(action)) {
|
} else if (ACTION_INTERNAL_UPLOADS.equals(action)) {
|
||||||
receiveInternalSharedItems();
|
receiveInternalSharedItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uploadableFiles == null || uploadableFiles.isEmpty()) {
|
||||||
|
handleNullMedia();
|
||||||
|
} else {
|
||||||
|
//Show thumbnails
|
||||||
|
if (uploadableFiles.size()
|
||||||
|
> 1) {//If there is only file, no need to show the image thumbnails
|
||||||
|
thumbnailsAdapter.setUploadableFiles(uploadableFiles);
|
||||||
|
} else {
|
||||||
|
llContainerTopCard.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
tvTopCardTitle.setText(getResources()
|
||||||
|
.getQuantityString(R.plurals.upload_count_title, uploadableFiles.size(),uploadableFiles.size()));
|
||||||
|
|
||||||
|
fragments = new ArrayList<>();
|
||||||
|
for (UploadableFile uploadableFile : uploadableFiles) {
|
||||||
|
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
|
||||||
|
uploadMediaDetailFragment.setImageTobeUploaded(uploadableFile, source, place);
|
||||||
|
uploadMediaDetailFragment.setCallback(new UploadMediaDetailFragmentCallback(){
|
||||||
|
@Override
|
||||||
|
public void deletePictureAtIndex(int index) {
|
||||||
|
presenter.deletePictureAtIndex(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNextButtonClicked(int index) {
|
||||||
|
UploadActivity.this.onNextButtonClicked(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreviousButtonClicked(int index) {
|
||||||
|
UploadActivity.this.onPreviousButtonClicked(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showProgress(boolean shouldShow) {
|
||||||
|
UploadActivity.this.showProgress(shouldShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIndexInViewFlipper(UploadBaseFragment fragment) {
|
||||||
|
return fragments.indexOf(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalNumberOfSteps() {
|
||||||
|
return fragments.size();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fragments.add(uploadMediaDetailFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadCategoriesFragment = new UploadCategoriesFragment();
|
||||||
|
uploadCategoriesFragment.setCallback(this);
|
||||||
|
|
||||||
|
mediaLicenseFragment = new MediaLicenseFragment();
|
||||||
|
mediaLicenseFragment.setCallback(this);
|
||||||
|
|
||||||
|
|
||||||
|
fragments.add(uploadCategoriesFragment);
|
||||||
|
fragments.add(mediaLicenseFragment);
|
||||||
|
|
||||||
|
uploadImagesAdapter.setFragments(fragments);
|
||||||
|
vpUpload.setOffscreenPageLimit(fragments.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveExternalSharedItems() {
|
private void receiveExternalSharedItems() {
|
||||||
List<UploadableFile> uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent());
|
uploadableFiles = contributionController.handleExternalImagesPicked(this, getIntent());
|
||||||
if (uploadableFiles.isEmpty()) {
|
|
||||||
handleNullMedia();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
presenter.receive(uploadableFiles, SOURCE_EXTERNAL, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveInternalSharedItems() {
|
private void receiveInternalSharedItems() {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
String source;
|
|
||||||
|
|
||||||
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
if (intent.hasExtra(UploadService.EXTRA_SOURCE)) {
|
||||||
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
source = intent.getStringExtra(UploadService.EXTRA_SOURCE);
|
||||||
|
|
@ -658,17 +363,10 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
intent.getAction(),
|
intent.getAction(),
|
||||||
source);
|
source);
|
||||||
|
|
||||||
ArrayList<UploadableFile> uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
|
uploadableFiles = intent.getParcelableArrayListExtra(EXTRA_FILES);
|
||||||
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
Timber.i("Received multiple upload %s", uploadableFiles.size());
|
||||||
|
|
||||||
if (uploadableFiles.isEmpty()) {
|
place = intent.getParcelableExtra(PLACE_OBJECT);
|
||||||
handleNullMedia();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Place place = intent.getParcelableExtra(PLACE_OBJECT);
|
|
||||||
presenter.receive(uploadableFiles, source, place);
|
|
||||||
|
|
||||||
resetDirectPrefs();
|
resetDirectPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -685,39 +383,6 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the button and shows or hides the content based on the given state. Typically used
|
|
||||||
* for collapsing or expanding {@link CardView} animation.
|
|
||||||
*
|
|
||||||
* @param state the expanded state of the View whose elements are to be updated. True if
|
|
||||||
* expanded.
|
|
||||||
* @param button the image to rotate. Typically an arrow points up when the CardView is
|
|
||||||
* collapsed and down when it is expanded.
|
|
||||||
* @param content the Views that should be shown or hidden based on the state.
|
|
||||||
*/
|
|
||||||
private void updateCardState(boolean state, ImageView button, View... content) {
|
|
||||||
button.animate().rotation(state ? 180 : 0).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) {
|
private void showInfoAlert(int titleStringID, int messageStringId, String... formatArgs) {
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(titleStringID)
|
.setTitle(titleStringID)
|
||||||
|
|
@ -729,23 +394,66 @@ public class UploadActivity extends BaseActivity implements UploadView, SimilarI
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
public void onNextButtonClicked(int index) {
|
||||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
if (index < fragments.size()-1) {
|
||||||
Bundle args = new Bundle();
|
vpUpload.setCurrentItem(index + 1, false);
|
||||||
args.putString("originalImagePath", originalFilePath);
|
} else {
|
||||||
args.putString("possibleImagePath", possibleFilePath);
|
presenter.handleSubmit();
|
||||||
newFragment.setArguments(args);
|
|
||||||
newFragment.show(getSupportFragmentManager(), "dialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void savePrevTitleDesc(String name){
|
|
||||||
|
|
||||||
directKvStore.putString(name + "title", descriptionsAdapter.getTitle().toString());
|
|
||||||
int n = descriptionsAdapter.getItemCount() - 1;
|
|
||||||
directKvStore.putInt(name + "descCount", n);
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
directKvStore.putString(name + "description_<" + i + ">", descriptionsAdapter.getDescriptions().get(i).getDescriptionText());
|
|
||||||
directKvStore.putInt(name + "spinnerPosition_<" + i + ">", descriptionsAdapter.getDescriptions().get(i).getSelectedLanguageIndex());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPreviousButtonClicked(int index) {
|
||||||
|
if (index != 0) {
|
||||||
|
vpUpload.setCurrentItem(index - 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter used to show image upload intermediate fragments
|
||||||
|
*/
|
||||||
|
|
||||||
|
private class UploadImageAdapter extends FragmentStatePagerAdapter {
|
||||||
|
List<Fragment> fragments;
|
||||||
|
|
||||||
|
public UploadImageAdapter(FragmentManager fragmentManager) {
|
||||||
|
super(fragmentManager);
|
||||||
|
this.fragments = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFragments(List<Fragment> fragments) {
|
||||||
|
this.fragments = fragments;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Fragment getItem(int position) {
|
||||||
|
return fragments.get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public int getCount() {
|
||||||
|
return fragments.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemPosition(Object object){
|
||||||
|
return PagerAdapter.POSITION_NONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OnClick(R.id.rl_container_title)
|
||||||
|
public void onRlContainerTitleClicked(){
|
||||||
|
rvThumbnails.setVisibility(isTitleExpanded ? View.GONE : View.VISIBLE);
|
||||||
|
isTitleExpanded = !isTitleExpanded;
|
||||||
|
ibToggleTopCard.setRotation(ibToggleTopCard.getRotation() + 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
presenter.onDetachView();
|
||||||
|
compositeDisposable.clear();
|
||||||
|
mediaLicenseFragment.setCallback(null);
|
||||||
|
uploadCategoriesFragment.setCallback(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
|
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base fragment of the fragments in upload
|
||||||
|
*/
|
||||||
|
public class UploadBaseFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
|
public Callback callback;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
|
||||||
|
void onNextButtonClicked(int index);
|
||||||
|
|
||||||
|
void onPreviousButtonClicked(int index);
|
||||||
|
|
||||||
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
int getIndexInViewFlipper(UploadBaseFragment fragment);
|
||||||
|
|
||||||
|
int getTotalNumberOfSteps();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract using which the UplaodActivity would communicate with its presenter
|
||||||
|
*/
|
||||||
|
public interface UploadContract {
|
||||||
|
|
||||||
|
public interface View {
|
||||||
|
|
||||||
|
boolean isLoggedIn();
|
||||||
|
|
||||||
|
void finish();
|
||||||
|
|
||||||
|
void askUserToLogIn();
|
||||||
|
|
||||||
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
void showMessage(int messageResourceId);
|
||||||
|
|
||||||
|
List<UploadableFile> getUploadableFiles();
|
||||||
|
|
||||||
|
void showHideTopCard(boolean shouldShow);
|
||||||
|
|
||||||
|
void onUploadMediaDeleted(int index);
|
||||||
|
|
||||||
|
void updateTopCardTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
void handleSubmit();
|
||||||
|
|
||||||
|
void deletePictureAtIndex(int index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ public class UploadController {
|
||||||
/**
|
/**
|
||||||
* Prepares the upload service.
|
* Prepares the upload service.
|
||||||
*/
|
*/
|
||||||
void prepareService() {
|
public void prepareService() {
|
||||||
Intent uploadServiceIntent = new Intent(context, UploadService.class);
|
Intent uploadServiceIntent = new Intent(context, UploadService.class);
|
||||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||||
context.startService(uploadServiceIntent);
|
context.startService(uploadServiceIntent);
|
||||||
|
|
@ -85,7 +85,7 @@ public class UploadController {
|
||||||
/**
|
/**
|
||||||
* Disconnects the upload service.
|
* Disconnects the upload service.
|
||||||
*/
|
*/
|
||||||
void cleanup() {
|
public void cleanup() {
|
||||||
if (isUploadServiceConnected) {
|
if (isUploadServiceConnected) {
|
||||||
context.unbindService(uploadServiceConnection);
|
context.unbindService(uploadServiceConnection);
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +96,7 @@ public class UploadController {
|
||||||
*
|
*
|
||||||
* @param contribution the contribution object
|
* @param contribution the contribution object
|
||||||
*/
|
*/
|
||||||
void startUpload(Contribution contribution) {
|
public void startUpload(Contribution contribution) {
|
||||||
startUpload(contribution, c -> {});
|
startUpload(contribution, c -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,7 @@ package fr.free.nrw.commons.upload;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.auth.SessionManager;
|
import fr.free.nrw.commons.auth.SessionManager;
|
||||||
|
|
@ -25,14 +16,20 @@ import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.utils.ImageUtils;
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.Single;
|
import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import io.reactivex.subjects.BehaviorSubject;
|
import io.reactivex.subjects.BehaviorSubject;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
public class UploadModel {
|
public class UploadModel {
|
||||||
|
|
||||||
private static UploadItem DUMMY = new UploadItem(
|
private static UploadItem DUMMY = new UploadItem(
|
||||||
|
|
@ -49,24 +46,22 @@ public class UploadModel {
|
||||||
private String license;
|
private String license;
|
||||||
private final Map<String, String> licensesByName;
|
private final Map<String, String> licensesByName;
|
||||||
private List<UploadItem> items = new ArrayList<>();
|
private List<UploadItem> items = new ArrayList<>();
|
||||||
private boolean topCardState = true;
|
|
||||||
private boolean bottomCardState = true;
|
|
||||||
private boolean rightCardState = true;
|
|
||||||
private int currentStepIndex = 0;
|
private int currentStepIndex = 0;
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
private SessionManager sessionManager;
|
private SessionManager sessionManager;
|
||||||
private FileProcessor fileProcessor;
|
private FileProcessor fileProcessor;
|
||||||
private final ImageProcessingService imageProcessingService;
|
private final ImageProcessingService imageProcessingService;
|
||||||
|
private List<String> selectedCategories;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UploadModel(@Named("licenses") List<String> licenses,
|
UploadModel(@Named("licenses") List<String> licenses,
|
||||||
@Named("default_preferences") JsonKvStore store,
|
@Named("default_preferences") JsonKvStore store,
|
||||||
@Named("licenses_by_name") Map<String, String> licensesByName,
|
@Named("licenses_by_name") Map<String, String> licensesByName,
|
||||||
Context context,
|
Context context,
|
||||||
SessionManager sessionManager,
|
SessionManager sessionManager,
|
||||||
FileProcessor fileProcessor,
|
FileProcessor fileProcessor,
|
||||||
ImageProcessingService imageProcessingService) {
|
ImageProcessingService imageProcessingService) {
|
||||||
this.licenses = licenses;
|
this.licenses = licenses;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
this.license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||||
|
|
@ -77,31 +72,61 @@ public class UploadModel {
|
||||||
this.imageProcessingService = imageProcessingService;
|
this.imageProcessingService = imageProcessingService;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanup() {
|
/**
|
||||||
|
* cleanup the resources, I am Singleton, preparing for fresh upload
|
||||||
|
*/
|
||||||
|
public void cleanUp() {
|
||||||
compositeDisposable.clear();
|
compositeDisposable.clear();
|
||||||
fileProcessor.cleanup();
|
fileProcessor.cleanup();
|
||||||
|
this.items.clear();
|
||||||
|
if (this.selectedCategories != null) {
|
||||||
|
this.selectedCategories.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSelectedCategories(List<String> selectedCategories) {
|
||||||
|
if (null == selectedCategories) {
|
||||||
|
selectedCategories = new ArrayList<>();
|
||||||
|
}
|
||||||
|
this.selectedCategories = selectedCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pre process a list of items
|
||||||
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles,
|
Observable<UploadItem> preProcessImages(List<UploadableFile> uploadableFiles,
|
||||||
Place place,
|
Place place,
|
||||||
String source,
|
String source,
|
||||||
SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
initDefaultValues();
|
|
||||||
return Observable.fromIterable(uploadableFiles)
|
return Observable.fromIterable(uploadableFiles)
|
||||||
.map(uploadableFile -> getUploadItem(uploadableFile, place, source, similarImageInterface));
|
.map(uploadableFile -> getUploadItem(uploadableFile, place, source,
|
||||||
|
similarImageInterface));
|
||||||
}
|
}
|
||||||
|
|
||||||
Single<Integer> getImageQuality(UploadItem uploadItem, boolean checkTitle) {
|
|
||||||
|
/**
|
||||||
|
* pre process a one item at a time
|
||||||
|
*/
|
||||||
|
public Observable<UploadItem> preProcessImage(UploadableFile uploadableFile,
|
||||||
|
Place place,
|
||||||
|
String source,
|
||||||
|
SimilarImageInterface similarImageInterface) {
|
||||||
|
return Observable.just(getUploadItem(uploadableFile, place, source, similarImageInterface));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Single<Integer> getImageQuality(UploadItem uploadItem, boolean checkTitle) {
|
||||||
return imageProcessingService.validateImage(uploadItem, checkTitle);
|
return imageProcessingService.validateImage(uploadItem, checkTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private UploadItem getUploadItem(UploadableFile uploadableFile,
|
private UploadItem getUploadItem(UploadableFile uploadableFile,
|
||||||
Place place,
|
Place place,
|
||||||
String source,
|
String source,
|
||||||
SimilarImageInterface similarImageInterface) {
|
SimilarImageInterface similarImageInterface) {
|
||||||
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()), context.getContentResolver());
|
fileProcessor.initFileDetails(Objects.requireNonNull(uploadableFile.getFilePath()),
|
||||||
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile.getFileCreatedDate(context);
|
context.getContentResolver());
|
||||||
|
UploadableFile.DateTimeWithSource dateTimeWithSource = uploadableFile
|
||||||
|
.getFileCreatedDate(context);
|
||||||
long fileCreatedDate = -1;
|
long fileCreatedDate = -1;
|
||||||
String createdTimestampSource = "";
|
String createdTimestampSource = "";
|
||||||
if (dateTimeWithSource != null) {
|
if (dateTimeWithSource != null) {
|
||||||
|
|
@ -109,52 +134,21 @@ public class UploadModel {
|
||||||
createdTimestampSource = dateTimeWithSource.getSource();
|
createdTimestampSource = dateTimeWithSource.getSource();
|
||||||
}
|
}
|
||||||
Timber.d("File created date is %d", fileCreatedDate);
|
Timber.d("File created date is %d", fileCreatedDate);
|
||||||
GPSExtractor gpsExtractor = fileProcessor.processFileCoordinates(similarImageInterface, context);
|
GPSExtractor gpsExtractor = fileProcessor
|
||||||
return new UploadItem(uploadableFile.getContentUri(), Uri.parse(uploadableFile.getFilePath()), uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate, createdTimestampSource);
|
.processFileCoordinates(similarImageInterface, context);
|
||||||
}
|
UploadItem uploadItem = new UploadItem(uploadableFile.getContentUri(),
|
||||||
|
Uri.parse(uploadableFile.getFilePath()),
|
||||||
void onItemsProcessed(Place place, List<UploadItem> uploadItems) {
|
uploadableFile.getMimeType(context), source, gpsExtractor, place, fileCreatedDate,
|
||||||
items = uploadItems;
|
createdTimestampSource);
|
||||||
if (items.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UploadItem uploadItem = items.get(0);
|
|
||||||
uploadItem.selected = true;
|
|
||||||
uploadItem.first = true;
|
|
||||||
|
|
||||||
if (place != null) {
|
if (place != null) {
|
||||||
uploadItem.title.setTitleText(place.getName());
|
uploadItem.title.setTitleText(place.name);
|
||||||
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription().equals("?")?"":place.getLongDescription());
|
uploadItem.descriptions.get(0).setDescriptionText(place.getLongDescription());
|
||||||
//TODO figure out if default descriptions in other languages exist
|
|
||||||
uploadItem.descriptions.get(0).setLanguageCode("en");
|
uploadItem.descriptions.get(0).setLanguageCode("en");
|
||||||
}
|
}
|
||||||
}
|
if (!items.contains(uploadItem)) {
|
||||||
|
items.add(uploadItem);
|
||||||
private void initDefaultValues() {
|
|
||||||
currentStepIndex = 0;
|
|
||||||
topCardState = true;
|
|
||||||
bottomCardState = true;
|
|
||||||
rightCardState = true;
|
|
||||||
items = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
return uploadItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getCurrentStep() {
|
int getCurrentStep() {
|
||||||
|
|
@ -173,110 +167,20 @@ public class UploadModel {
|
||||||
return items;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
public void next() {
|
|
||||||
markCurrentUploadVisited();
|
|
||||||
if (currentStepIndex < items.size() + 1) {
|
|
||||||
currentStepIndex++;
|
|
||||||
}
|
|
||||||
updateItemState();
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
cleanup();
|
|
||||||
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() {
|
|
||||||
Timber.d("Updating item state");
|
|
||||||
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() {
|
|
||||||
Timber.d("Marking current upload visited");
|
|
||||||
if (currentStepIndex < items.size() && currentStepIndex >= 0) {
|
|
||||||
items.get(currentStepIndex).visited = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getLicenses() {
|
public List<String> getLicenses() {
|
||||||
return licenses;
|
return licenses;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getSelectedLicense() {
|
public String getSelectedLicense() {
|
||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setSelectedLicense(String licenseName) {
|
public void setSelectedLicense(String licenseName) {
|
||||||
this.license = licensesByName.get(licenseName);
|
this.license = licensesByName.get(licenseName);
|
||||||
store.putString(Prefs.DEFAULT_LICENSE, license);
|
store.putString(Prefs.DEFAULT_LICENSE, license);
|
||||||
}
|
}
|
||||||
|
|
||||||
Observable<Contribution> buildContributions(List<String> categoryStringList) {
|
public Observable<Contribution> buildContributions() {
|
||||||
return Observable.fromIterable(items).map(item ->
|
return Observable.fromIterable(items).map(item ->
|
||||||
{
|
{
|
||||||
Contribution contribution = new Contribution(item.mediaUri, null,
|
Contribution contribution = new Contribution(item.mediaUri, null,
|
||||||
|
|
@ -287,7 +191,10 @@ public class UploadModel {
|
||||||
if (item.place != null) {
|
if (item.place != null) {
|
||||||
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
contribution.setWikiDataEntityId(item.place.getWikiDataEntityId());
|
||||||
}
|
}
|
||||||
contribution.setCategories(categoryStringList);
|
if (null == selectedCategories) {//Just a fail safe, this should never be null
|
||||||
|
selectedCategories = new ArrayList<>();
|
||||||
|
}
|
||||||
|
contribution.setCategories(selectedCategories);
|
||||||
contribution.setTag("mimeType", item.mimeType);
|
contribution.setTag("mimeType", item.mimeType);
|
||||||
contribution.setSource(item.source);
|
contribution.setSource(item.source);
|
||||||
contribution.setContentProviderUri(item.mediaUri);
|
contribution.setContentProviderUri(item.mediaUri);
|
||||||
|
|
@ -304,21 +211,16 @@ public class UploadModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void keepPicture() {
|
public void deletePicture(String filePath) {
|
||||||
items.get(currentStepIndex).setImageQuality(ImageUtils.IMAGE_KEEP);
|
Iterator<UploadItem> iterator = items.iterator();
|
||||||
}
|
while (iterator.hasNext()) {
|
||||||
|
if (iterator.next().mediaUri.toString().contains(filePath)) {
|
||||||
void deletePicture() {
|
iterator.remove();
|
||||||
cleanup();
|
break;
|
||||||
updateItemState();
|
}
|
||||||
}
|
}
|
||||||
|
if (items.isEmpty()) {
|
||||||
void subscribeBadPicture(Consumer<Integer> consumer, boolean checkTitle) {
|
cleanUp();
|
||||||
if (isShowingItem()) {
|
|
||||||
compositeDisposable.add(getImageQuality(getCurrentItem(), checkTitle)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(consumer, Timber::e));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -326,8 +228,15 @@ public class UploadModel {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateUploadItem(int index, UploadItem uploadItem) {
|
||||||
|
UploadItem uploadItem1 = items.get(index);
|
||||||
|
uploadItem1.setDescriptions(uploadItem.descriptions);
|
||||||
|
uploadItem1.setTitle(uploadItem.title);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
static class UploadItem {
|
public static class UploadItem {
|
||||||
|
|
||||||
private final Uri originalContentUri;
|
private final Uri originalContentUri;
|
||||||
private final Uri mediaUri;
|
private final Uri mediaUri;
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
|
|
@ -347,10 +256,10 @@ public class UploadModel {
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
UploadItem(Uri originalContentUri,
|
UploadItem(Uri originalContentUri,
|
||||||
Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
|
Uri mediaUri, String mimeType, String source, GPSExtractor gpsCoords,
|
||||||
Place place,
|
Place place,
|
||||||
long createdTimestamp,
|
long createdTimestamp,
|
||||||
String createdTimestampSource) {
|
String createdTimestampSource) {
|
||||||
this.originalContentUri = originalContentUri;
|
this.originalContentUri = originalContentUri;
|
||||||
this.createdTimestampSource = createdTimestampSource;
|
this.createdTimestampSource = createdTimestampSource;
|
||||||
title = new Title();
|
title = new Title();
|
||||||
|
|
@ -426,16 +335,40 @@ public class UploadModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileName() {
|
public String getFileName() {
|
||||||
return Utils.fixExtension(title.toString(), getFileExt());
|
return title
|
||||||
|
!= null ? Utils.fixExtension(title.toString(), getFileExt()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Place getPlace() {
|
public Place getPlace() {
|
||||||
return place;
|
return place;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTitle(Title title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescriptions(List<Description> descriptions) {
|
||||||
|
this.descriptions = descriptions;
|
||||||
|
}
|
||||||
|
|
||||||
public Uri getContentUri() {
|
public Uri getContentUri() {
|
||||||
return originalContentUri;
|
return originalContentUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (!(obj instanceof UploadItem)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Travis is complaining :P
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return super.hashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import dagger.Binds;
|
||||||
|
import dagger.Module;
|
||||||
|
import fr.free.nrw.commons.upload.categories.CategoriesContract;
|
||||||
|
import fr.free.nrw.commons.upload.categories.CategoriesPresenter;
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicenseContract;
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicensePresenter;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaPresenter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Dagger Module for upload related presenters and (some other objects maybe in future)
|
||||||
|
*/
|
||||||
|
@Module
|
||||||
|
public abstract class UploadModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract UploadContract.UserActionListener bindHomePresenter(UploadPresenter
|
||||||
|
presenter);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract CategoriesContract.UserActionListener bindsCategoriesPresenter(CategoriesPresenter
|
||||||
|
presenter);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract MediaLicenseContract.UserActionListener bindsMediaLicensePresenter(
|
||||||
|
MediaLicensePresenter
|
||||||
|
presenter);
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
public abstract UploadMediaDetailsContract.UserActionListener bindsUploadMediaPresenter(
|
||||||
|
UploadMediaPresenter
|
||||||
|
presenter);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,420 +1,126 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.category.CategoriesModel;
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import io.reactivex.Observer;
|
||||||
import fr.free.nrw.commons.nearby.Place;
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
|
||||||
import fr.free.nrw.commons.utils.CustomProxy;
|
|
||||||
import fr.free.nrw.commons.utils.CustomProxy;
|
|
||||||
import fr.free.nrw.commons.utils.StringSortingUtils;
|
|
||||||
import io.reactivex.Observable;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Named;
|
|
||||||
import javax.inject.Singleton;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
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;
|
|
||||||
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The MVP pattern presenter of Upload GUI
|
* The MVP pattern presenter of Upload GUI
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadPresenter {
|
public class UploadPresenter implements UploadContract.UserActionListener {
|
||||||
|
|
||||||
private static final UploadView DUMMY =
|
private static final UploadContract.View DUMMY = (UploadContract.View) Proxy.newProxyInstance(
|
||||||
(UploadView) CustomProxy.newInstance(UploadView.class.getClassLoader(),
|
UploadContract.View.class.getClassLoader(),
|
||||||
new Class[] { UploadView.class });
|
new Class[]{UploadContract.View.class}, (proxy, method, methodArgs) -> null);
|
||||||
|
private final UploadRepository repository;
|
||||||
|
private UploadContract.View view = DUMMY;
|
||||||
|
|
||||||
private UploadView view = DUMMY;
|
private CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
private static final SimilarImageInterface SIMILAR_IMAGE =
|
|
||||||
(SimilarImageInterface) CustomProxy.newInstance(
|
|
||||||
SimilarImageInterface.class.getClassLoader(),
|
|
||||||
new Class[] { SimilarImageInterface.class });
|
|
||||||
private SimilarImageInterface similarImageInterface = SIMILAR_IMAGE;
|
|
||||||
|
|
||||||
@UploadView.UploadPage
|
|
||||||
private int currentPage = UploadView.PLEASE_WAIT;
|
|
||||||
|
|
||||||
private final UploadModel uploadModel;
|
|
||||||
private final UploadController uploadController;
|
|
||||||
private final Context context;
|
|
||||||
private final JsonKvStore directKvStore;
|
|
||||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
UploadPresenter(UploadModel uploadModel,
|
UploadPresenter(UploadRepository uploadRepository) {
|
||||||
UploadController uploadController,
|
this.repository = uploadRepository;
|
||||||
Context context,
|
compositeDisposable = new CompositeDisposable();
|
||||||
@Named("default_preferences") JsonKvStore directKvStore) {
|
|
||||||
this.uploadModel = uploadModel;
|
|
||||||
this.uploadController = uploadController;
|
|
||||||
this.context = context;
|
|
||||||
this.directKvStore = directKvStore;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Passes the items received to {@link #uploadModel} and displays the items.
|
|
||||||
*
|
|
||||||
* @param media The Uri's of the media being uploaded.
|
|
||||||
* @param source File source from {@link Contribution.FileSource}
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
void receive(List<UploadableFile> media,
|
|
||||||
@Contribution.FileSource String source,
|
|
||||||
Place place) {
|
|
||||||
Observable<UploadItem> uploadItemObservable = uploadModel
|
|
||||||
.preProcessImages(media, place, source, similarImageInterface);
|
|
||||||
|
|
||||||
compositeDisposable.add(uploadItemObservable
|
|
||||||
.toList()
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(uploadItems -> onImagesProcessed(uploadItems, place),
|
|
||||||
throwable -> Timber.e(throwable, "Error occurred in processing images")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onImagesProcessed(List<UploadItem> uploadItems, Place place) {
|
|
||||||
uploadModel.onItemsProcessed(place, uploadItems);
|
|
||||||
updateCards();
|
|
||||||
updateLicenses();
|
|
||||||
updateContent();
|
|
||||||
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the license to parameter and updates {@link UploadActivity}
|
|
||||||
*
|
|
||||||
* @param licenseName license name
|
|
||||||
*/
|
|
||||||
void selectLicense(String licenseName) {
|
|
||||||
uploadModel.setSelectedLicense(licenseName);
|
|
||||||
view.updateLicenseSummary(uploadModel.getSelectedLicense(), uploadModel.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
//region Wizard step management
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the next button in {@link UploadActivity}
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
void handleNext(Title title,
|
|
||||||
List<Description> descriptions) {
|
|
||||||
Timber.e("Inside handleNext");
|
|
||||||
view.showProgressDialog();
|
|
||||||
setTitleAndDescription(title, descriptions);
|
|
||||||
compositeDisposable.add(uploadModel.getImageQuality(uploadModel.getCurrentItem(), true)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(imageResult -> handleImage(title, descriptions, imageResult),
|
|
||||||
throwable -> Timber.e(throwable, "Error occurred while handling image")));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleImage(Title title, List<Description> descriptions, Integer imageResult) {
|
|
||||||
view.hideProgressDialog();
|
|
||||||
if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
|
|
||||||
Timber.d("Set title and desc; Show next uploaded item");
|
|
||||||
setTitleAndDescription(title, descriptions);
|
|
||||||
directKvStore.putBoolean("Picture_Has_Correct_Location", true);
|
|
||||||
nextUploadedItem();
|
|
||||||
} else {
|
|
||||||
handleBadImage(imageResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 handleBadImage(Integer errorCode) {
|
|
||||||
Timber.d("Handle bad picture with error code %d", errorCode);
|
|
||||||
if (errorCode >= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
|
|
||||||
directKvStore.putBoolean("Picture_Has_Correct_Location", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (errorCode) {
|
|
||||||
case EMPTY_TITLE:
|
|
||||||
Timber.d("Title is empty. Showing toast");
|
|
||||||
view.showErrorMessage(R.string.add_title_toast);
|
|
||||||
break;
|
|
||||||
case FILE_NAME_EXISTS:
|
|
||||||
Timber.d("Trying to show duplicate picture popup");
|
|
||||||
view.showDuplicatePicturePopup();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
String errorMessageForResult = getErrorMessageForResult(context, errorCode);
|
|
||||||
if (TextUtils.isEmpty(errorMessageForResult)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
view.showBadPicturePopup(errorMessageForResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void nextUploadedItem() {
|
|
||||||
Timber.d("Trying to show next uploaded item");
|
|
||||||
uploadModel.next();
|
|
||||||
updateContent();
|
|
||||||
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
|
||||||
view.dismissKeyboard();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setTitleAndDescription(Title title, List<Description> descriptions) {
|
|
||||||
Timber.d("setTitleAndDescription: Setting title and desc");
|
|
||||||
uploadModel.setCurrentTitleAndDescriptions(title, descriptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
String getCurrentImageFileName() {
|
|
||||||
UploadItem currentItem = getCurrentItem();
|
|
||||||
return currentItem.getFileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by the previous button in {@link UploadActivity}
|
|
||||||
*/
|
|
||||||
void handlePrevious() {
|
|
||||||
uploadModel.previous();
|
|
||||||
updateContent();
|
|
||||||
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
|
||||||
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}
|
* Called by the submit button in {@link UploadActivity}
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
void handleSubmit(CategoriesModel categoriesModel) {
|
@Override
|
||||||
if (view.checkIfLoggedIn())
|
public void handleSubmit() {
|
||||||
compositeDisposable.add(uploadModel.buildContributions(categoriesModel.getCategoryStringList())
|
if (view.isLoggedIn()) {
|
||||||
|
view.showProgress(true);
|
||||||
|
repository.buildContributions()
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.subscribe(uploadController::startUpload));
|
.subscribe(new Observer<Contribution>() {
|
||||||
}
|
@Override
|
||||||
|
public void onSubscribe(Disposable d) {
|
||||||
|
view.showProgress(false);
|
||||||
|
view.showMessage(R.string.uploading_started);
|
||||||
|
compositeDisposable.add(d);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Called by the map button on the right card in {@link UploadActivity}
|
public void onNext(Contribution contribution) {
|
||||||
*/
|
repository.startUpload(contribution);
|
||||||
void openCoordinateMap() {
|
}
|
||||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
|
|
||||||
if (gpsObj != null && gpsObj.imageCoordsExists) {
|
|
||||||
view.launchMapActivity(new LatLng(gpsObj.getDecLatitude(), gpsObj.getDecLongitude(), 0.0f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void keepPicture() {
|
@Override
|
||||||
uploadModel.keepPicture();
|
public void onError(Throwable e) {
|
||||||
}
|
view.showMessage(R.string.upload_failed);
|
||||||
|
repository.cleanup();
|
||||||
|
view.finish();
|
||||||
|
compositeDisposable.clear();
|
||||||
|
Timber.e("failed to upload: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
void deletePicture() {
|
@Override
|
||||||
if (uploadModel.getCount() == 1)
|
public void onComplete() {
|
||||||
view.finish();
|
repository.cleanup();
|
||||||
else {
|
view.finish();
|
||||||
uploadModel.deletePicture();
|
compositeDisposable.clear();
|
||||||
updateCards();
|
}
|
||||||
updateContent();
|
});
|
||||||
uploadModel.subscribeBadPicture(this::handleBadImage, false);
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets all the cards' states to closed.
|
|
||||||
*/
|
|
||||||
void closeAllCards() {
|
|
||||||
if (uploadModel.isTopCardState()) {
|
|
||||||
uploadModel.setTopCardState(false);
|
|
||||||
view.setTopCardState(false);
|
|
||||||
}
|
|
||||||
if (uploadModel.isRightCardState()) {
|
|
||||||
uploadModel.setRightCardState(false);
|
|
||||||
}
|
|
||||||
if (uploadModel.isBottomCardState()) {
|
|
||||||
uploadModel.setBottomCardState(false);
|
|
||||||
view.setBottomCardState(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//endregion
|
|
||||||
|
|
||||||
//region View / Lifecycle management
|
|
||||||
public void init() {
|
|
||||||
uploadController.prepareService();
|
|
||||||
}
|
|
||||||
|
|
||||||
void cleanup() {
|
|
||||||
compositeDisposable.clear();
|
|
||||||
uploadModel.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 = directKvStore.getString(Prefs.DEFAULT_LICENSE,
|
|
||||||
Prefs.Licenses.CC_BY_SA_4);//CC_BY_SA_4 is the default one used by the commons web app
|
|
||||||
try {//I have to make sure that the stored default license was not one of the deprecated one's
|
|
||||||
Utils.licenseNameFor(selectedLicense);
|
|
||||||
} catch (IllegalStateException exception) {
|
|
||||||
Timber.e(exception.getMessage());
|
|
||||||
selectedLicense = Prefs.Licenses.CC_BY_SA_4;
|
|
||||||
directKvStore.putString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4);
|
|
||||||
}
|
|
||||||
view.updateLicenses(uploadModel.getLicenses(), selectedLicense);
|
|
||||||
view.updateLicenseSummary(selectedLicense, uploadModel.getCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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().getMediaUri());
|
|
||||||
|
|
||||||
view.updateBottomCardContent(uploadModel.getCurrentStep(),
|
|
||||||
uploadModel.getStepCount(),
|
|
||||||
uploadModel.getCurrentItem(),
|
|
||||||
uploadModel.isShowingItem());
|
|
||||||
|
|
||||||
view.updateTopCardContent();
|
|
||||||
|
|
||||||
GPSExtractor gpsObj = uploadModel.getCurrentItem().getGpsCoords();
|
|
||||||
view.updateRightCardContent(gpsObj != null && gpsObj.imageCoordsExists);
|
|
||||||
|
|
||||||
view.updateSubtitleVisibility(uploadModel.getCount());
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
currentPage = UploadView.LICENSE;
|
view.askUserToLogIn();
|
||||||
view.setTopCardVisibility(false);
|
|
||||||
view.setRightCardVisibility(false);
|
|
||||||
}
|
}
|
||||||
view.setBottomCardVisibility(currentPage, uploadCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//endregion
|
@Override
|
||||||
|
public void deletePictureAtIndex(int index) {
|
||||||
|
List<UploadableFile> uploadableFiles = view.getUploadableFiles();
|
||||||
|
if (index == uploadableFiles.size() - 1) {//If the next fragment to be shown is not one of the MediaDetailsFragment, lets hide the top card
|
||||||
|
view.showHideTopCard(false);
|
||||||
|
}
|
||||||
|
//Ask the repository to delete the picture
|
||||||
|
repository.deletePicture(uploadableFiles.get(index).getFilePath());
|
||||||
|
if (uploadableFiles.size() == 1) {
|
||||||
|
view.showMessage(R.string.upload_cancelled);
|
||||||
|
view.finish();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
view.onUploadMediaDeleted(index);
|
||||||
|
}
|
||||||
|
if (uploadableFiles.size() < 2) {
|
||||||
|
view.showHideTopCard(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//In case lets update the number of uploadable media
|
||||||
|
view.updateTopCardTitle();
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the item currently being displayed
|
|
||||||
*/
|
|
||||||
private UploadItem getCurrentItem() {
|
|
||||||
return uploadModel.getCurrentItem();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getImageTitleList() {
|
@Override
|
||||||
List<String> titleList = new ArrayList<>();
|
public void onAttachView(UploadContract.View view) {
|
||||||
for (UploadItem item : uploadModel.getUploads()) {
|
this.view = view;
|
||||||
if (item.getTitle().isSet()) {
|
repository.prepareService();
|
||||||
titleList.add(item.getTitle().toString());
|
}
|
||||||
}
|
|
||||||
}
|
@Override
|
||||||
return titleList;
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
compositeDisposable.clear();
|
||||||
|
repository.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
package fr.free.nrw.commons.upload;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
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 java.io.File;
|
|
||||||
|
|
||||||
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();
|
|
||||||
Uri uri = Uri.parse(content.getMediaUri().toString());
|
|
||||||
background.setImageURI(Uri.fromFile(new File(String.valueOf(uri))));
|
|
||||||
background.setAlpha(content.isSelected() ? 1.0f : 0.5f);
|
|
||||||
space.setVisibility(content.isFirst() ? View.VISIBLE : View.GONE);
|
|
||||||
error.setVisibility(content.isVisited() && content.isError() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -15,7 +15,6 @@ public interface UploadView {
|
||||||
// UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
// UploadView DUMMY = (UploadView) Proxy.newProxyInstance(UploadView.class.getClassLoader(),
|
||||||
// new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
// new Class[]{UploadView.class}, (proxy, method, methodArgs) -> null);
|
||||||
|
|
||||||
List<Description> getDescriptions();
|
|
||||||
|
|
||||||
@Retention(SOURCE)
|
@Retention(SOURCE)
|
||||||
@IntDef({PLEASE_WAIT, TITLE_CARD, CATEGORIES, LICENSE})
|
@IntDef({PLEASE_WAIT, TITLE_CARD, CATEGORIES, LICENSE})
|
||||||
|
|
@ -82,4 +81,6 @@ public interface UploadView {
|
||||||
void showProgressDialog();
|
void showProgressDialog();
|
||||||
|
|
||||||
void hideProgressDialog();
|
void hideProgressDialog();
|
||||||
|
|
||||||
|
void askUserToLogIn();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
package fr.free.nrw.commons.upload.categories;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with with UploadCategoriesFragment and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface CategoriesContract {
|
||||||
|
|
||||||
|
public interface View {
|
||||||
|
|
||||||
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
void showError(String error);
|
||||||
|
|
||||||
|
void showError(int stringResourceId);
|
||||||
|
|
||||||
|
void setCategories(List<CategoryItem> categories);
|
||||||
|
|
||||||
|
void addCategory(CategoryItem category);
|
||||||
|
|
||||||
|
void goToNextScreen();
|
||||||
|
|
||||||
|
void showNoCategorySelected();
|
||||||
|
|
||||||
|
void setSelectedCategories(List<CategoryItem> selectedCategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
void searchForCategories(String query);
|
||||||
|
|
||||||
|
void verifyCategories();
|
||||||
|
|
||||||
|
void onCategoryItemClicked(CategoryItem categoryItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
package fr.free.nrw.commons.upload.categories;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The presenter class for UploadCategoriesFragment
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
public class CategoriesPresenter implements CategoriesContract.UserActionListener {
|
||||||
|
|
||||||
|
private static final CategoriesContract.View DUMMY = (CategoriesContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
CategoriesContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{CategoriesContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
private final Scheduler ioScheduler;
|
||||||
|
private final Scheduler mainThreadScheduler;
|
||||||
|
|
||||||
|
CategoriesContract.View view = DUMMY;
|
||||||
|
private UploadRepository repository;
|
||||||
|
|
||||||
|
private CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CategoriesPresenter(UploadRepository repository, @Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
compositeDisposable = new CompositeDisposable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(CategoriesContract.View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
compositeDisposable.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository to fetch categories for the query
|
||||||
|
* @param query
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void searchForCategories(String query) {
|
||||||
|
List<CategoryItem> categoryItems = new ArrayList<>();
|
||||||
|
List<String> imageTitleList = getImageTitleList();
|
||||||
|
Observable<CategoryItem> distinctCategoriesObservable = Observable
|
||||||
|
.fromIterable(repository.getSelectedCategories())
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.doOnSubscribe(disposable -> {
|
||||||
|
view.showProgress(true);
|
||||||
|
view.showError(null);
|
||||||
|
view.setCategories(null);
|
||||||
|
})
|
||||||
|
.observeOn(ioScheduler)
|
||||||
|
.concatWith(
|
||||||
|
repository.searchAll(query, imageTitleList)
|
||||||
|
)
|
||||||
|
.filter(categoryItem -> !repository.containsYear(categoryItem.getName()))
|
||||||
|
.distinct();
|
||||||
|
if(!TextUtils.isEmpty(query)) {
|
||||||
|
distinctCategoriesObservable=distinctCategoriesObservable.sorted(repository.sortBySimilarity(query));
|
||||||
|
}
|
||||||
|
Disposable searchCategoriesDisposable = distinctCategoriesObservable
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(
|
||||||
|
s -> categoryItems.add(s),
|
||||||
|
Timber::e,
|
||||||
|
() -> {
|
||||||
|
view.setCategories(categoryItems);
|
||||||
|
view.showProgress(false);
|
||||||
|
|
||||||
|
if (categoryItems.isEmpty()) {
|
||||||
|
view.showError(R.string.no_categories_found);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
compositeDisposable.add(searchCategoriesDisposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns image title list from UploadItem
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private List<String> getImageTitleList() {
|
||||||
|
List<String> titleList = new ArrayList<>();
|
||||||
|
for (UploadItem item : repository.getUploads()) {
|
||||||
|
if (item.getTitle().isSet()) {
|
||||||
|
titleList.add(item.getTitle().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return titleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies the number of categories selected, prompts the user if none selected
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void verifyCategories() {
|
||||||
|
List<CategoryItem> selectedCategories = repository.getSelectedCategories();
|
||||||
|
if (selectedCategories != null && !selectedCategories.isEmpty()) {
|
||||||
|
repository.setSelectedCategories(repository.getCategoryStringList());
|
||||||
|
view.goToNextScreen();
|
||||||
|
} else {
|
||||||
|
view.showNoCategorySelected();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask repository to handle category clicked
|
||||||
|
*
|
||||||
|
* @param categoryItem
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCategoryItemClicked(CategoryItem categoryItem) {
|
||||||
|
repository.onCategoryClicked(categoryItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
package fr.free.nrw.commons.upload.categories;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.jakewharton.rxbinding2.view.RxView;
|
||||||
|
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.category.CategoryClickedListener;
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem;
|
||||||
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
import fr.free.nrw.commons.upload.UploadCategoriesAdapterFactory;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class UploadCategoriesFragment extends UploadBaseFragment implements CategoriesContract.View,
|
||||||
|
CategoryClickedListener {
|
||||||
|
|
||||||
|
@BindView(R.id.tv_title)
|
||||||
|
TextView tvTitle;
|
||||||
|
@BindView(R.id.til_container_search)
|
||||||
|
TextInputLayout tilContainerEtSearch;
|
||||||
|
@BindView(R.id.et_search)
|
||||||
|
TextInputEditText etSearch;
|
||||||
|
@BindView(R.id.pb_categories)
|
||||||
|
ProgressBar pbCategories;
|
||||||
|
@BindView(R.id.rv_categories)
|
||||||
|
RecyclerView rvCategories;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
CategoriesContract.UserActionListener presenter;
|
||||||
|
private RVRendererAdapter<CategoryItem> adapter;
|
||||||
|
private List<String> mediaTitleList=new ArrayList<>();
|
||||||
|
private Disposable subscribe;
|
||||||
|
private List<CategoryItem> categories;
|
||||||
|
private boolean isVisible;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaTitleList(List<String> mediaTitleList) {
|
||||||
|
this.mediaTitleList = mediaTitleList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.upload_categories_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
||||||
|
callback.getTotalNumberOfSteps()));
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
initRecyclerView();
|
||||||
|
addTextChangeListenerToEtSearch();
|
||||||
|
//get default categories for empty query
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (presenter != null && isVisible && (categories == null || categories.isEmpty())) {
|
||||||
|
presenter.searchForCategories(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addTextChangeListenerToEtSearch() {
|
||||||
|
subscribe = RxTextView.textChanges(etSearch)
|
||||||
|
.doOnEach(v -> tilContainerEtSearch.setError(null))
|
||||||
|
.takeUntil(RxView.detaches(etSearch))
|
||||||
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(filter -> searchForCategory(filter.toString()), Timber::e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchForCategory(String query) {
|
||||||
|
presenter.searchForCategories(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initRecyclerView() {
|
||||||
|
adapter = new UploadCategoriesAdapterFactory(this)
|
||||||
|
.create(new ArrayList<>());
|
||||||
|
rvCategories.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
rvCategories.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
presenter.onDetachView();
|
||||||
|
subscribe.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showProgress(boolean shouldShow) {
|
||||||
|
pbCategories.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showError(String error) {
|
||||||
|
tilContainerEtSearch.setError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showError(int stringResourceId) {
|
||||||
|
tilContainerEtSearch.setError(getString(stringResourceId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCategories(List<CategoryItem> categories) {
|
||||||
|
adapter.clear();
|
||||||
|
if (categories != null) {
|
||||||
|
this.categories = categories;
|
||||||
|
adapter.addAll(categories);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCategory(CategoryItem category) {
|
||||||
|
adapter.add(category);
|
||||||
|
adapter.notifyItemInserted(adapter.getItemCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void goToNextScreen() {
|
||||||
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showNoCategorySelected() {
|
||||||
|
DialogUtil.showAlertDialog(getActivity(),
|
||||||
|
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,
|
||||||
|
() -> goToNextScreen());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedCategories(List<CategoryItem> selectedCategories) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_next)
|
||||||
|
public void onNextButtonClicked() {
|
||||||
|
presenter.verifyCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_previous)
|
||||||
|
public void onPreviousButtonClicked() {
|
||||||
|
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void categoryClicked(CategoryItem item) {
|
||||||
|
presenter.onCategoryItemClicked(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserVisibleHint(boolean isVisibleToUser) {
|
||||||
|
super.setUserVisibleHint(isVisibleToUser);
|
||||||
|
isVisible = isVisibleToUser;
|
||||||
|
|
||||||
|
if (presenter != null && isResumed() && (categories == null || categories.isEmpty())) {
|
||||||
|
presenter.searchForCategories(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package fr.free.nrw.commons.upload.license;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with with MediaLicenseFragment and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface MediaLicenseContract {
|
||||||
|
|
||||||
|
interface View {
|
||||||
|
void setLicenses(List<String> licenses);
|
||||||
|
|
||||||
|
void setSelectedLicense(String license);
|
||||||
|
|
||||||
|
void updateLicenseSummary(String selectedLicense, int numberOfItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
void getLicenses();
|
||||||
|
|
||||||
|
void selectLicense(String licenseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,181 @@
|
||||||
|
package fr.free.nrw.commons.upload.license;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.AdapterView.OnItemSelectedListener;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class MediaLicenseFragment extends UploadBaseFragment implements MediaLicenseContract.View {
|
||||||
|
|
||||||
|
@BindView(R.id.tv_title)
|
||||||
|
TextView tvTitle;
|
||||||
|
@BindView(R.id.spinner_license_list)
|
||||||
|
Spinner spinnerLicenseList;
|
||||||
|
@BindView(R.id.tv_share_license_summary)
|
||||||
|
TextView tvShareLicenseSummary;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MediaLicenseContract.UserActionListener presenter;
|
||||||
|
|
||||||
|
private ArrayAdapter<String> adapter;
|
||||||
|
private List<String> licenses;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_media_license, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
||||||
|
callback.getTotalNumberOfSteps()));
|
||||||
|
initPresenter();
|
||||||
|
initLicenseSpinner();
|
||||||
|
presenter.getLicenses();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initPresenter() {
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the license spinner
|
||||||
|
*/
|
||||||
|
private void initLicenseSpinner() {
|
||||||
|
adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
spinnerLicenseList.setAdapter(adapter);
|
||||||
|
spinnerLicenseList.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> adapterView, View view, int position,
|
||||||
|
long l) {
|
||||||
|
String licenseName = adapterView.getItemAtPosition(position).toString();
|
||||||
|
presenter.selectLicense(licenseName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||||
|
presenter.selectLicense(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLicenses(List<String> licenses) {
|
||||||
|
adapter.clear();
|
||||||
|
this.licenses = licenses;
|
||||||
|
adapter.addAll(this.licenses);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSelectedLicense(String license) {
|
||||||
|
int position = licenses.indexOf(getString(Utils.licenseNameFor(license)));
|
||||||
|
// Check if position is valid
|
||||||
|
if (position < 0) {
|
||||||
|
Timber.d("Invalid position: %d. Using default licenses", position);
|
||||||
|
position = licenses.size() - 1;
|
||||||
|
} else {
|
||||||
|
Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)));
|
||||||
|
}
|
||||||
|
spinnerLicenseList.setSelection(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateLicenseSummary(String licenseSummary, int numberOfItems) {
|
||||||
|
String licenseHyperLink = "<a href='" + Utils.licenseUrlFor(licenseSummary) + "'>" +
|
||||||
|
getString(Utils.licenseNameFor(licenseSummary)) + "</a><br>";
|
||||||
|
|
||||||
|
setTextViewHTML(tvShareLicenseSummary, getResources()
|
||||||
|
.getQuantityString(R.plurals.share_license_summary, numberOfItems,
|
||||||
|
licenseHyperLink));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTextViewHTML(TextView textView, String text) {
|
||||||
|
CharSequence sequence = Html.fromHtml(text);
|
||||||
|
SpannableStringBuilder strBuilder = new SpannableStringBuilder(sequence);
|
||||||
|
URLSpan[] urls = strBuilder.getSpans(0, sequence.length(), URLSpan.class);
|
||||||
|
for (URLSpan span : urls) {
|
||||||
|
makeLinkClickable(strBuilder, span);
|
||||||
|
}
|
||||||
|
textView.setText(strBuilder);
|
||||||
|
textView.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeLinkClickable(SpannableStringBuilder strBuilder, final URLSpan span) {
|
||||||
|
int start = strBuilder.getSpanStart(span);
|
||||||
|
int end = strBuilder.getSpanEnd(span);
|
||||||
|
int flags = strBuilder.getSpanFlags(span);
|
||||||
|
ClickableSpan clickable = new ClickableSpan() {
|
||||||
|
public void onClick(View view) {
|
||||||
|
// Handle hyperlink click
|
||||||
|
String hyperLink = span.getURL();
|
||||||
|
launchBrowser(hyperLink);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
strBuilder.setSpan(clickable, start, end, flags);
|
||||||
|
strBuilder.removeSpan(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void launchBrowser(String hyperLink) {
|
||||||
|
Utils.handleWebUrl(getContext(), Uri.parse(hyperLink));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
presenter.onDetachView();
|
||||||
|
//Free the adapter to avoid memory leaks
|
||||||
|
adapter=null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_previous)
|
||||||
|
public void onPreviousButtonClicked() {
|
||||||
|
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_submit)
|
||||||
|
public void onSubmitButtonClicked() {
|
||||||
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package fr.free.nrw.commons.upload.license;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicenseContract.View;
|
||||||
|
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Added JavaDocs for MediaLicensePresenter
|
||||||
|
*/
|
||||||
|
public class MediaLicensePresenter implements MediaLicenseContract.UserActionListener {
|
||||||
|
|
||||||
|
private static final MediaLicenseContract.View DUMMY = (MediaLicenseContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
MediaLicenseContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{MediaLicenseContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
|
||||||
|
private final UploadRepository repository;
|
||||||
|
private MediaLicenseContract.View view = DUMMY;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public MediaLicensePresenter(UploadRepository uploadRepository) {
|
||||||
|
this.repository = uploadRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository for the available licenses, and informs the view on the same
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void getLicenses() {
|
||||||
|
List<String> licenses = repository.getLicenses();
|
||||||
|
view.setLicenses(licenses);
|
||||||
|
|
||||||
|
String selectedLicense = repository.getValue(Prefs.DEFAULT_LICENSE,
|
||||||
|
Prefs.Licenses.CC_BY_SA_4);//CC_BY_SA_4 is the default one used by the commons web app
|
||||||
|
try {//I have to make sure that the stored default license was not one of the deprecated one's
|
||||||
|
Utils.licenseNameFor(selectedLicense);
|
||||||
|
} catch (IllegalStateException exception) {
|
||||||
|
Timber.e(exception.getMessage());
|
||||||
|
selectedLicense = Prefs.Licenses.CC_BY_SA_4;
|
||||||
|
repository.saveValue(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_4);
|
||||||
|
}
|
||||||
|
view.setSelectedLicense(selectedLicense);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ask the repository to select a license for the current upload
|
||||||
|
*
|
||||||
|
* @param licenseName
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void selectLicense(String licenseName) {
|
||||||
|
repository.setSelectedLicense(licenseName);
|
||||||
|
view.updateLicenseSummary(repository.getSelectedLicense(), repository.getCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,402 @@
|
||||||
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatButton;
|
||||||
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.github.chrisbanes.photoview.OnScaleChangedListener;
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
|
import com.jakewharton.rxbinding2.widget.RxTextView;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.upload.Description;
|
||||||
|
import fr.free.nrw.commons.upload.DescriptionsAdapter;
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
||||||
|
import fr.free.nrw.commons.upload.Title;
|
||||||
|
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils;
|
||||||
|
import fr.free.nrw.commons.utils.ViewUtil;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult;
|
||||||
|
|
||||||
|
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||||
|
UploadMediaDetailsContract.View {
|
||||||
|
|
||||||
|
@BindView(R.id.tv_title)
|
||||||
|
TextView tvTitle;
|
||||||
|
@BindView(R.id.ib_map)
|
||||||
|
AppCompatImageButton ibMap;
|
||||||
|
@BindView(R.id.ib_expand_collapse)
|
||||||
|
AppCompatImageButton ibExpandCollapse;
|
||||||
|
@BindView(R.id.ll_container_media_detail)
|
||||||
|
LinearLayout llContainerMediaDetail;
|
||||||
|
@BindView(R.id.et_title)
|
||||||
|
EditText etTitle;
|
||||||
|
@BindView(R.id.rv_descriptions)
|
||||||
|
RecyclerView rvDescriptions;
|
||||||
|
@BindView(R.id.backgroundImage)
|
||||||
|
PhotoView photoViewBackgroundImage;
|
||||||
|
@BindView(R.id.btn_next)
|
||||||
|
AppCompatButton btnNext;
|
||||||
|
@BindView(R.id.btn_previous)
|
||||||
|
AppCompatButton btnPrevious;
|
||||||
|
private DescriptionsAdapter descriptionsAdapter;
|
||||||
|
@BindView(R.id.btn_copy_prev_title_desc)
|
||||||
|
AppCompatButton btnCopyPreviousTitleDesc;
|
||||||
|
|
||||||
|
private UploadModel.UploadItem uploadItem;
|
||||||
|
private List<Description> descriptions;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
UploadMediaDetailsContract.UserActionListener presenter;
|
||||||
|
private UploadableFile uploadableFile;
|
||||||
|
private String source;
|
||||||
|
private Place place;
|
||||||
|
|
||||||
|
private Title title;
|
||||||
|
private boolean isExpanded = true;
|
||||||
|
|
||||||
|
private UploadMediaDetailFragmentCallback callback;
|
||||||
|
|
||||||
|
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImageTobeUploaded(UploadableFile uploadableFile, String source, Place place) {
|
||||||
|
this.uploadableFile = uploadableFile;
|
||||||
|
this.source = source;
|
||||||
|
this.place = place;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||||
|
@Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.fragment_upload_media_detail_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
|
||||||
|
callback.getTotalNumberOfSteps()));
|
||||||
|
title = new Title();
|
||||||
|
initRecyclerView();
|
||||||
|
initPresenter();
|
||||||
|
Disposable disposable = RxTextView.textChanges(etTitle)
|
||||||
|
.subscribe(text -> {
|
||||||
|
if (!TextUtils.isEmpty(text)) {
|
||||||
|
btnNext.setEnabled(true);
|
||||||
|
btnNext.setClickable(true);
|
||||||
|
btnNext.setAlpha(1.0f);
|
||||||
|
title.setTitleText(text.toString());
|
||||||
|
uploadItem.setTitle(title);
|
||||||
|
} else {
|
||||||
|
btnNext.setAlpha(0.5f);
|
||||||
|
btnNext.setEnabled(false);
|
||||||
|
btnNext.setClickable(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
compositeDisposable.add(disposable);
|
||||||
|
presenter.receiveImage(uploadableFile, source, place);
|
||||||
|
|
||||||
|
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||||
|
btnPrevious.setEnabled(false);
|
||||||
|
btnPrevious.setAlpha(0.5f);
|
||||||
|
} else {
|
||||||
|
btnPrevious.setEnabled(true);
|
||||||
|
btnPrevious.setAlpha(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If this is the first media, we have nothing to copy, lets not show the button
|
||||||
|
if (callback.getIndexInViewFlipper(this) == 0) {
|
||||||
|
btnCopyPreviousTitleDesc.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
btnCopyPreviousTitleDesc.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
attachImageViewScaleChangeListener();
|
||||||
|
|
||||||
|
addEtTitleTouchListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the drawable click listener for Edit Text
|
||||||
|
*/
|
||||||
|
private void addEtTitleTouchListener() {
|
||||||
|
etTitle.setOnTouchListener((v, event) -> {
|
||||||
|
//2 is for drawable right
|
||||||
|
float twelveDpInPixels = convertDpToPixel(12, getContext());
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP && etTitle.getCompoundDrawables()[2].getBounds().contains((int)(etTitle.getWidth()-(event.getX()+twelveDpInPixels)),(int)(event.getY()-twelveDpInPixels))){
|
||||||
|
showInfoAlert(R.string.media_detail_title,R.string.title_info);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* converts dp to pixel
|
||||||
|
* @param dp
|
||||||
|
* @param context
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private float convertDpToPixel(float dp, Context context) {
|
||||||
|
return dp * ((float) context.getResources().getDisplayMetrics().densityDpi / DisplayMetrics.DENSITY_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches the scale change listener to the image view
|
||||||
|
*/
|
||||||
|
private void attachImageViewScaleChangeListener() {
|
||||||
|
photoViewBackgroundImage.setOnScaleChangeListener(
|
||||||
|
(scaleFactor, focusX, focusY) -> {
|
||||||
|
//Whenever the uses plays with the image, lets collapse the media detail container
|
||||||
|
expandCollapseLlMediaDetail(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* attach the presenter with the view
|
||||||
|
*/
|
||||||
|
private void initPresenter() {
|
||||||
|
presenter.onAttachView(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* init the recycler veiw
|
||||||
|
*/
|
||||||
|
private void initRecyclerView() {
|
||||||
|
descriptionsAdapter = new DescriptionsAdapter();
|
||||||
|
descriptionsAdapter.setCallback(this::showInfoAlert);
|
||||||
|
rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
rvDescriptions.setAdapter(descriptionsAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show dialog with info
|
||||||
|
* @param titleStringID
|
||||||
|
* @param messageStringId
|
||||||
|
*/
|
||||||
|
private void showInfoAlert(int titleStringID, int messageStringId) {
|
||||||
|
DialogUtil.showAlertDialog(getActivity(), getString(titleStringID), getString(messageStringId), getString(android.R.string.ok), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_next)
|
||||||
|
public void onNextButtonClicked() {
|
||||||
|
uploadItem.setDescriptions(descriptionsAdapter.getDescriptions());
|
||||||
|
presenter.verifyImageQuality(uploadItem, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_previous)
|
||||||
|
public void onPreviousButtonClicked() {
|
||||||
|
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_add_description)
|
||||||
|
public void onButtonAddDescriptionClicked() {
|
||||||
|
Description description = new Description();
|
||||||
|
description.setManuallyAdded(true);//This was manually added by the user
|
||||||
|
descriptionsAdapter.addDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
||||||
|
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||||
|
newFragment.setCallback(new SimilarImageDialogFragment.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onPositiveResponse() {
|
||||||
|
Timber.d("positive response from similar image fragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNegativeResponse() {
|
||||||
|
Timber.d("negative response from similar image fragment");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString("originalImagePath", originalFilePath);
|
||||||
|
args.putString("possibleImagePath", possibleFilePath);
|
||||||
|
newFragment.setArguments(args);
|
||||||
|
newFragment.show(getChildFragmentManager(), "dialog");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageProcessed(UploadItem uploadItem, Place place) {
|
||||||
|
this.uploadItem = uploadItem;
|
||||||
|
if (uploadItem.getTitle() != null) {
|
||||||
|
etTitle.setText(uploadItem.getTitle().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptions = uploadItem.getDescriptions();
|
||||||
|
photoViewBackgroundImage.setImageURI(uploadItem.getMediaUri());
|
||||||
|
setDescriptionsInAdapter(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showProgress(boolean shouldShow) {
|
||||||
|
callback.showProgress(shouldShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageValidationSuccess() {
|
||||||
|
presenter.setUploadItem(callback.getIndexInViewFlipper(this), uploadItem);
|
||||||
|
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showMessage(int stringResourceId, int colorResourceId) {
|
||||||
|
ViewUtil.showLongToast(getContext(), stringResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showMessage(String message, int colorResourceId) {
|
||||||
|
ViewUtil.showLongToast(getContext(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showDuplicatePicturePopup() {
|
||||||
|
String uploadTitleFormat = getString(R.string.upload_title_duplicate);
|
||||||
|
DialogUtil.showAlertDialog(getActivity(),
|
||||||
|
getString(R.string.warning),
|
||||||
|
String.format(Locale.getDefault(),
|
||||||
|
uploadTitleFormat,
|
||||||
|
uploadItem.getFileName()),
|
||||||
|
() -> {
|
||||||
|
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||||
|
onNextButtonClicked();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showBadImagePopup(Integer errorCode) {
|
||||||
|
String errorMessageForResult = getErrorMessageForResult(getContext(), errorCode);
|
||||||
|
if (!StringUtils.isBlank(errorMessageForResult)) {
|
||||||
|
DialogUtil.showAlertDialog(getActivity(),
|
||||||
|
getString(R.string.warning),
|
||||||
|
errorMessageForResult,
|
||||||
|
() -> deleteThisPicture(),
|
||||||
|
() -> {
|
||||||
|
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||||
|
onNextButtonClicked();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//If the error message is null, we will probably not show anything
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void showMapWithImageCoordinates(boolean shouldShow) {
|
||||||
|
ibMap.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTitleAndDescription(String title, List<Description> descriptions) {
|
||||||
|
etTitle.setText(title);
|
||||||
|
setDescriptionsInAdapter(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteThisPicture() {
|
||||||
|
callback.deletePictureAtIndex(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
presenter.onDetachView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.rl_container_title)
|
||||||
|
public void onRlContainerTitleClicked() {
|
||||||
|
expandCollapseLlMediaDetail(!isExpanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show hide media detail based on
|
||||||
|
* @param shouldExpand
|
||||||
|
*/
|
||||||
|
private void expandCollapseLlMediaDetail(boolean shouldExpand){
|
||||||
|
llContainerMediaDetail.setVisibility(shouldExpand ? View.VISIBLE : View.GONE);
|
||||||
|
isExpanded = !isExpanded;
|
||||||
|
ibExpandCollapse.setRotation(ibExpandCollapse.getRotation() + 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.ib_map) public void onIbMapClicked() {
|
||||||
|
Utils.handleGeoCoordinates(getContext(),
|
||||||
|
new LatLng(uploadItem.getGpsCoords().getDecLatitude(),
|
||||||
|
uploadItem.getGpsCoords().getDecLongitude(), 0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface UploadMediaDetailFragmentCallback extends Callback {
|
||||||
|
|
||||||
|
void deletePictureAtIndex(int index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@OnClick(R.id.btn_copy_prev_title_desc)
|
||||||
|
public void onButtonCopyPreviousTitleDesc(){
|
||||||
|
presenter.fetchPreviousTitleAndDescription(callback.getIndexInViewFlipper(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDescriptionsInAdapter(List<Description> descriptions){
|
||||||
|
if(descriptions==null){
|
||||||
|
descriptions=new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(descriptions.size()==0){
|
||||||
|
descriptions.add(new Description());
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptionsAdapter.setItems(descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.BasePresenter;
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.upload.Description;
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
import fr.free.nrw.commons.upload.Title;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The contract with with UploadMediaDetails and its presenter would talk to each other
|
||||||
|
*/
|
||||||
|
public interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
|
interface View extends SimilarImageInterface {
|
||||||
|
|
||||||
|
void onImageProcessed(UploadItem uploadItem, Place place);
|
||||||
|
|
||||||
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
|
void onImageValidationSuccess();
|
||||||
|
|
||||||
|
void showMessage(int stringResourceId, int colorResourceId);
|
||||||
|
|
||||||
|
void showMessage(String message, int colorResourceId);
|
||||||
|
|
||||||
|
void showDuplicatePicturePopup();
|
||||||
|
|
||||||
|
void showBadImagePopup(Integer errorCode);
|
||||||
|
|
||||||
|
void showMapWithImageCoordinates(boolean shouldShow);
|
||||||
|
|
||||||
|
void setTitleAndDescription(String title, List<Description> descriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
|
void receiveImage(UploadableFile uploadableFile, @Contribution.FileSource String source,
|
||||||
|
Place place);
|
||||||
|
|
||||||
|
void verifyImageQuality(UploadItem uploadItem, boolean validateTitle);
|
||||||
|
|
||||||
|
void setUploadItem(int index, UploadItem uploadItem);
|
||||||
|
|
||||||
|
void fetchPreviousTitleAndDescription(int indexInViewFlipper);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
package fr.free.nrw.commons.upload.mediaDetails;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
|
||||||
|
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||||
|
import fr.free.nrw.commons.nearby.Place;
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository;
|
||||||
|
import fr.free.nrw.commons.upload.GPSExtractor;
|
||||||
|
import fr.free.nrw.commons.upload.SimilarImageInterface;
|
||||||
|
import fr.free.nrw.commons.upload.UploadModel.UploadItem;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.UserActionListener;
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract.View;
|
||||||
|
import io.reactivex.Scheduler;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class UploadMediaPresenter implements UserActionListener, SimilarImageInterface {
|
||||||
|
|
||||||
|
private static final UploadMediaDetailsContract.View DUMMY = (UploadMediaDetailsContract.View) Proxy
|
||||||
|
.newProxyInstance(
|
||||||
|
UploadMediaDetailsContract.View.class.getClassLoader(),
|
||||||
|
new Class[]{UploadMediaDetailsContract.View.class},
|
||||||
|
(proxy, method, methodArgs) -> null);
|
||||||
|
|
||||||
|
private final UploadRepository repository;
|
||||||
|
private UploadMediaDetailsContract.View view = DUMMY;
|
||||||
|
|
||||||
|
private CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
|
private Scheduler ioScheduler;
|
||||||
|
private Scheduler mainThreadScheduler;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UploadMediaPresenter(UploadRepository uploadRepository,
|
||||||
|
@Named(IO_THREAD) Scheduler ioScheduler,
|
||||||
|
@Named(MAIN_THREAD) Scheduler mainThreadScheduler) {
|
||||||
|
this.repository = uploadRepository;
|
||||||
|
this.ioScheduler = ioScheduler;
|
||||||
|
this.mainThreadScheduler = mainThreadScheduler;
|
||||||
|
compositeDisposable = new CompositeDisposable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachView(View view) {
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDetachView() {
|
||||||
|
this.view = DUMMY;
|
||||||
|
compositeDisposable.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives the corresponding uploadable file, processes it and return the view with and uplaod item
|
||||||
|
*
|
||||||
|
* @param uploadableFile
|
||||||
|
* @param source
|
||||||
|
* @param place
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receiveImage(UploadableFile uploadableFile, String source, Place place) {
|
||||||
|
view.showProgress(true);
|
||||||
|
Disposable uploadItemDisposable = repository
|
||||||
|
.preProcessImage(uploadableFile, place, source, this)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(uploadItem ->
|
||||||
|
{
|
||||||
|
view.onImageProcessed(uploadItem, place);
|
||||||
|
GPSExtractor gpsCoords = uploadItem.getGpsCoords();
|
||||||
|
view.showMapWithImageCoordinates((gpsCoords != null && gpsCoords.imageCoordsExists) ? true : false);
|
||||||
|
view.showProgress(false);
|
||||||
|
},
|
||||||
|
throwable -> Timber.e(throwable, "Error occurred in processing images"));
|
||||||
|
compositeDisposable.add(uploadItemDisposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* asks the repository to verify image quality
|
||||||
|
*
|
||||||
|
* @param uploadItem
|
||||||
|
* @param validateTitle
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void verifyImageQuality(UploadItem uploadItem, boolean validateTitle) {
|
||||||
|
view.showProgress(true);
|
||||||
|
Disposable imageQualityDisposable = repository
|
||||||
|
.getImageQuality(uploadItem, true)
|
||||||
|
.subscribeOn(ioScheduler)
|
||||||
|
.observeOn(mainThreadScheduler)
|
||||||
|
.subscribe(imageResult -> {
|
||||||
|
view.showProgress(false);
|
||||||
|
handleImageResult(imageResult);
|
||||||
|
},
|
||||||
|
throwable -> {
|
||||||
|
view.showProgress(false);
|
||||||
|
view.showMessage("" + throwable.getLocalizedMessage(),
|
||||||
|
R.color.color_error);
|
||||||
|
Timber.e(throwable, "Error occurred while handling image");
|
||||||
|
});
|
||||||
|
|
||||||
|
compositeDisposable.add(imageQualityDisposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the corresponding upload item to the repository
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @param uploadItem
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void setUploadItem(int index, UploadItem uploadItem) {
|
||||||
|
repository.updateUploadItem(index, uploadItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches and sets the title and desctiption of the previous item
|
||||||
|
*
|
||||||
|
* @param indexInViewFlipper
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void fetchPreviousTitleAndDescription(int indexInViewFlipper) {
|
||||||
|
UploadItem previousUploadItem = repository.getPreviousUploadItem(indexInViewFlipper);
|
||||||
|
if (null != previousUploadItem) {
|
||||||
|
view.setTitleAndDescription(previousUploadItem.getTitle().getTitleText(), previousUploadItem.getDescriptions());
|
||||||
|
} else {
|
||||||
|
view.showMessage(R.string.previous_image_title_description_not_found, R.color.color_error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* handles image quality verifications
|
||||||
|
*
|
||||||
|
* @param imageResult
|
||||||
|
*/
|
||||||
|
public void handleImageResult(Integer imageResult) {
|
||||||
|
if (imageResult == IMAGE_KEEP || imageResult == IMAGE_OK) {
|
||||||
|
view.onImageValidationSuccess();
|
||||||
|
} else {
|
||||||
|
handleBadImage(imageResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle images, say empty title, duplicate file name, bad picture(in all other cases)
|
||||||
|
*
|
||||||
|
* @param errorCode
|
||||||
|
*/
|
||||||
|
private void handleBadImage(Integer errorCode) {
|
||||||
|
Timber.d("Handle bad picture with error code %d", errorCode);
|
||||||
|
if (errorCode
|
||||||
|
>= 8) { // If location of image and nearby does not match, then set shared preferences to disable wikidata edits
|
||||||
|
repository.saveValue("Picture_Has_Correct_Location", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (errorCode) {
|
||||||
|
case EMPTY_TITLE:
|
||||||
|
Timber.d("Title is empty. Showing toast");
|
||||||
|
view.showMessage(R.string.add_title_toast, R.color.color_error);
|
||||||
|
break;
|
||||||
|
case FILE_NAME_EXISTS:
|
||||||
|
Timber.d("Trying to show duplicate picture popup");
|
||||||
|
view.showDuplicatePicturePopup();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
view.showBadImagePopup(errorCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notifies the user that a similar image exists
|
||||||
|
*
|
||||||
|
* @param originalFilePath
|
||||||
|
* @param possibleFilePath
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath) {
|
||||||
|
view.showSimilarImageFragment(originalFilePath, possibleFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -140,4 +140,31 @@ public class DialogUtil {
|
||||||
showSafely(activity, dialog);
|
showSafely(activity, dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* show a dialog with just a positive button
|
||||||
|
* @param activity
|
||||||
|
* @param title
|
||||||
|
* @param message
|
||||||
|
* @param positiveButtonText
|
||||||
|
* @param positiveButtonClick
|
||||||
|
* @param cancellable
|
||||||
|
*/
|
||||||
|
public static void showAlertDialog(Activity activity, String title, String message, String positiveButtonText, final Runnable positiveButtonClick, boolean cancellable) {
|
||||||
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
builder.setTitle(title);
|
||||||
|
builder.setMessage(message);
|
||||||
|
builder.setCancelable(cancellable);
|
||||||
|
|
||||||
|
builder.setPositiveButton(positiveButtonText, (dialogInterface, i) -> {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
if (positiveButtonClick != null) {
|
||||||
|
positiveButtonClick.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AlertDialog dialog = builder.create();
|
||||||
|
showSafely(activity, dialog);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/drawable_thumbnail_image.xml
Normal file
9
app/src/main/res/drawable/drawable_thumbnail_image.xml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/thumbnail_selected"
|
||||||
|
android:state_pressed="true"/>
|
||||||
|
<item android:drawable="@drawable/thumbnail_selected"
|
||||||
|
android:state_focused="true"/>
|
||||||
|
<item android:state_selected="true" android:drawable="@drawable/thumbnail_selected"/>
|
||||||
|
<item android:drawable="@drawable/thumbnail_not_selected"/>
|
||||||
|
</selector>
|
||||||
5
app/src/main/res/drawable/thumbnail_not_selected.xml
Normal file
5
app/src/main/res/drawable/thumbnail_not_selected.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white"></solid>
|
||||||
|
</shape>
|
||||||
11
app/src/main/res/drawable/thumbnail_selected.xml
Normal file
11
app/src/main/res/drawable/thumbnail_selected.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<stroke
|
||||||
|
android:color="@color/primaryDarkColor"
|
||||||
|
android:width="2dp"/>
|
||||||
|
<solid android:color="#F8F7F5"/>
|
||||||
|
<padding
|
||||||
|
android:bottom="2dp"
|
||||||
|
android:left="2dp"
|
||||||
|
android:right="2dp"
|
||||||
|
android:top="2dp"/>
|
||||||
|
</shape>
|
||||||
|
|
@ -1,34 +1,79 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/upload_root_layout"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/upload_root_layout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
>
|
||||||
<com.github.chrisbanes.photoview.PhotoView
|
<fr.free.nrw.commons.contributions.UnswipableViewPager
|
||||||
android:id="@+id/backgroundImage"
|
android:id="@+id/vp_upload"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/commons_app_blue_dark"
|
||||||
|
/>
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/cv_container_top_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:elevation="@dimen/cardview_default_elevation"
|
||||||
|
>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_container_top_card"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/toolbar"
|
android:orientation="vertical"
|
||||||
android:background="@color/commons_app_blue_dark"
|
android:padding="@dimen/standard_gap"
|
||||||
app:actualImageScaleType="fitCenter" />
|
>
|
||||||
|
|
||||||
<ViewFlipper
|
<RelativeLayout
|
||||||
android:id="@+id/view_flipper"
|
android:id="@+id/rl_container_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:clipChildren="false"
|
>
|
||||||
android:measureAllChildren="false">
|
<TextView
|
||||||
|
android:id="@+id/tv_top_card_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginTop="@dimen/small_gap"
|
||||||
|
android:textSize="@dimen/normal_text"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="4 Uploads"
|
||||||
|
/>
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/ib_toggle_top_card"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginBottom="@dimen/small_gap"
|
||||||
|
android:layout_marginEnd="@dimen/small_gap"
|
||||||
|
android:layout_marginStart="@dimen/small_gap"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
app:srcCompat="@drawable/ic_expand_less_black_24dp"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
/>
|
||||||
|
|
||||||
<include
|
</RelativeLayout>
|
||||||
layout="@layout/activity_upload_bottom_card"
|
|
||||||
android:visibility="visible" />
|
|
||||||
|
|
||||||
<include layout="@layout/activity_upload_categories" />
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_thumbnails"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/small_gap"
|
||||||
|
/>
|
||||||
|
|
||||||
<include layout="@layout/activity_upload_license" />
|
</LinearLayout>
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
<include layout="@layout/activity_upload_please_wait" />
|
|
||||||
|
|
||||||
</ViewFlipper>
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
|
|
@ -1,199 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/top_card"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginRight="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:elevation="@dimen/cardview_default_elevation"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:ignore="UnusedAttribute"
|
|
||||||
tools:showIn="@layout/activity_upload">
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/top_card_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_marginStart="@dimen/small_gap"
|
|
||||||
android:layout_marginTop="@dimen/small_gap"
|
|
||||||
android:layout_marginEnd="@dimen/small_gap"
|
|
||||||
android:layout_marginBottom="@dimen/small_gap"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/normal_text"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="4 Uploads" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/top_card_expand_button"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_marginStart="@dimen/small_gap"
|
|
||||||
android:layout_marginTop="@dimen/small_gap"
|
|
||||||
android:layout_marginEnd="@dimen/small_gap"
|
|
||||||
android:layout_marginBottom="@dimen/small_gap"
|
|
||||||
android:padding="0dp"
|
|
||||||
app:srcCompat="@drawable/ic_expand_less_black_24dp" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/top_card_thumbnails"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="90dp"
|
|
||||||
android:layout_below="@id/top_card_title"
|
|
||||||
android:layout_marginBottom="@dimen/small_gap" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
|
||||||
android:id="@+id/bottom_card"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:elevation="@dimen/cardview_default_elevation"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
tools:ignore="UnusedAttribute"
|
|
||||||
tools:showIn="@layout/activity_upload">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:id="@+id/relativeLayout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="@dimen/small_gap">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/bottom_card_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/normal_text"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Step 1 of 15" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/bottom_card_subtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/subtitle_text"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@id/bottom_card_title"
|
|
||||||
tools:text="1st image" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/right_card_map_button"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="8dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginRight="16dp"
|
|
||||||
android:visibility="visible"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/bottom_card_expand_button"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_map_white_24dp" />
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/bottom_card_expand_button"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:padding="0dp"
|
|
||||||
android:rotation="180"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/ic_expand_less_black_24dp" />
|
|
||||||
|
|
||||||
<fr.free.nrw.commons.widget.HeightLimitedRecyclerView
|
|
||||||
android:id="@+id/rv_descriptions"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginBottom="20dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/prev_title_desc"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/bottom_card_subtitle"
|
|
||||||
tools:visibility="gone" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/prev_title_desc"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="25dp"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:text="@string/previous_image_title_description"
|
|
||||||
android:textColor="@color/button_blue"
|
|
||||||
android:layout_marginBottom="15dp"
|
|
||||||
android:layout_marginEnd="3.5dp"
|
|
||||||
android:gravity="center"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottom_card_previous"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/rv_descriptions" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/bottom_card_next"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/next"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/bottom_card_previous"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:text="@string/previous"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/bottom_card_next"
|
|
||||||
app:layout_constraintRight_toLeftOf="@id/bottom_card_next" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/bottom_card_add_desc"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:minWidth="48dp"
|
|
||||||
android:text="+"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:showIn="@layout/activity_upload">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/categories_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/normal_text"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Step 1 of 15" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/categories_subtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/tiny_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/subtitle_text"
|
|
||||||
android:text="@string/upload_flow_all_images_in_set"
|
|
||||||
android:layout_below="@+id/categories_title"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content" android:id="@+id/category_search_layout"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_below="@id/categories_subtitle">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/category_search_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/category_search"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/categories_search_text_hint"
|
|
||||||
android:imeOptions="actionSearch"
|
|
||||||
android:inputType="text"
|
|
||||||
android:maxLines="1" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/categoriesSearchInProgress"
|
|
||||||
style="?android:progressBarStyleSmall"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/tiny_gap"
|
|
||||||
android:layout_marginRight="@dimen/tiny_gap"
|
|
||||||
android:layout_gravity="center_vertical|end"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:indeterminateOnly="true"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/categories"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_above="@+id/button_divider"
|
|
||||||
android:layout_below="@id/category_search_layout" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/button_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_above="@+id/category_next"
|
|
||||||
android:background="@color/divider_grey" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/category_next"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:layout_marginRight="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:text="@string/next" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/category_previous"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:layout_toStartOf="@id/category_next"
|
|
||||||
android:layout_toLeftOf="@id/category_next"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:text="@string/previous" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
tools:showIn="@layout/activity_upload">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/license_title"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:layout_alignParentTop="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/normal_text"
|
|
||||||
android:textStyle="bold"
|
|
||||||
tools:text="Step 1 of 15" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/license_subtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/tiny_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_alignParentLeft="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:textSize="@dimen/subtitle_text"
|
|
||||||
android:text="@string/upload_flow_all_images_in_set"
|
|
||||||
android:layout_below="@+id/license_title"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/license_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_below="@id/license_subtitle"
|
|
||||||
tools:visibility="gone"/>
|
|
||||||
|
|
||||||
<fr.free.nrw.commons.ui.widget.HtmlTextView
|
|
||||||
android:id="@+id/share_license_summary"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_below="@id/license_list"
|
|
||||||
android:text="@plurals/share_license_summary" />
|
|
||||||
|
|
||||||
<fr.free.nrw.commons.ui.widget.HtmlTextView
|
|
||||||
android:id="@+id/media_upload_policy"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_marginBottom="@dimen/standard_gap"
|
|
||||||
android:layout_above="@+id/button_divider"
|
|
||||||
android:gravity="start"
|
|
||||||
android:text="@string/media_upload_policy" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/button_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:layout_above="@+id/submit"
|
|
||||||
android:background="@color/divider_grey" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/submit"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="24dp"
|
|
||||||
android:layout_marginRight="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_alignParentRight="true"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:text="@string/submit" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/license_previous"
|
|
||||||
style="@style/Widget.AppCompat.Button.Borderless"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:layout_toStartOf="@id/submit"
|
|
||||||
android:layout_toLeftOf="@id/submit"
|
|
||||||
android:layout_alignParentBottom="true"
|
|
||||||
android:text="@string/previous" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/shareInProgress"
|
|
||||||
style="?android:progressBarStyleLarge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:indeterminateOnly="true" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/please_wait_text_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="@dimen/standard_gap"
|
|
||||||
android:layout_marginLeft="@dimen/standard_gap"
|
|
||||||
android:layout_marginTop="@dimen/standard_gap"
|
|
||||||
android:layout_marginEnd="@dimen/standard_gap"
|
|
||||||
android:layout_marginRight="@dimen/standard_gap"
|
|
||||||
android:gravity="center" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
102
app/src/main/res/layout/fragment_media_license.xml
Normal file
102
app/src/main/res/layout/fragment_media_license.xml
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:padding="@dimen/standard_gap"
|
||||||
|
>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@+id/ll_container_license_desc"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="@dimen/normal_text"
|
||||||
|
tools:text="Step 1 of 15"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginTop="@dimen/tiny_gap"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/upload_flow_all_images_in_set"
|
||||||
|
android:textSize="@dimen/subtitle_text"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinner_license_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
android:padding="0dp"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_share_license_summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
android:text="@plurals/share_license_summary"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_container_license_desc"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<fr.free.nrw.commons.ui.widget.HtmlTextView
|
||||||
|
android:id="@+id/tv_media_upload_policy"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text="@string/media_upload_policy"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/button_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:background="@color/divider_grey"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_previous"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/previous"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_submit"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/submit"
|
||||||
|
android:textColor="@android:color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
@ -0,0 +1,161 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
|
android:id="@+id/backgroundImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:actualImageScaleType="fitXY" />
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:elevation="@dimen/cardview_default_elevation">
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_margin="10dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/rl_container_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="@dimen/normal_text"
|
||||||
|
android:textStyle="bold"
|
||||||
|
tools:text="Step 1 of 15" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/ib_map"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginRight="16dp"
|
||||||
|
android:layout_toLeftOf="@id/ib_expand_collapse"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:srcCompat="@drawable/ic_map_white_24dp"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageButton
|
||||||
|
android:id="@+id/ib_expand_collapse"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_marginStart="@dimen/small_gap"
|
||||||
|
android:layout_marginEnd="@dimen/small_gap"
|
||||||
|
android:layout_marginBottom="@dimen/small_gap"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"
|
||||||
|
android:padding="12dp"
|
||||||
|
app:srcCompat="@drawable/ic_expand_less_black_24dp" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_container_media_detail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_container_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
|
android:id="@+id/et_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/share_title_hint"
|
||||||
|
android:imeOptions="actionNext"
|
||||||
|
android:drawableEnd="@drawable/mapbox_info_icon_default"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<fr.free.nrw.commons.widget.HeightLimitedRecyclerView
|
||||||
|
android:id="@+id/rv_descriptions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_copy_prev_title_desc"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/previous_image_title_description"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:textAlignment="textEnd"
|
||||||
|
android:textColor="@color/button_blue"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_add_description"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="48dp"
|
||||||
|
android:text="+" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:text="@string/next"
|
||||||
|
android:textColor="@android:color/white" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_previous"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
|
android:layout_toLeftOf="@+id/btn_next"
|
||||||
|
android:text="@string/previous" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
@ -1,37 +1,28 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="wrap_content"
|
xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="wrap_content">
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="90dp"
|
||||||
|
android:layout_height="90dp"
|
||||||
|
android:id="@+id/rl_container"
|
||||||
|
android:background="@drawable/thumbnail_not_selected"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<LinearLayout xmlns:fresco="http://schemas.android.com/apk/res-auto"
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/iv_thumbnail"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="90dp"
|
||||||
android:orientation="horizontal">
|
android:layout_height="90dp"
|
||||||
|
fresco:actualImageScaleType="fitCenter"/>
|
||||||
|
|
||||||
<androidx.legacy.widget.Space
|
<ImageView
|
||||||
android:id="@+id/left_space"
|
android:id="@+id/iv_error"
|
||||||
android:layout_width="8dp"
|
android:layout_width="24dp"
|
||||||
android:layout_height="90dp" />
|
android:layout_height="24dp"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
<com.facebook.drawee.view.SimpleDraweeView
|
android:layout_alignParentRight="true"
|
||||||
android:id="@+id/thumbnail"
|
android:layout_gravity="end"
|
||||||
android:layout_width="90dp"
|
android:visibility="gone"
|
||||||
android:layout_height="90dp"
|
app:srcCompat="@drawable/ic_error_red_24dp"
|
||||||
fresco:actualImageScaleType="fitCenter" />
|
tools:visibility="visible"/>
|
||||||
|
</RelativeLayout>
|
||||||
<androidx.legacy.widget.Space
|
|
||||||
android:id="@+id/right_space"
|
|
||||||
android:layout_width="8dp"
|
|
||||||
android:layout_height="90dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/error"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:srcCompat="@drawable/ic_error_red_24dp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="8">
|
android:layout_weight="8">
|
||||||
|
|
||||||
<EditText
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
android:id="@+id/description_item_edit_text"
|
android:id="@+id/description_item_edit_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:showIn="@layout/activity_upload">
|
tools:showIn="@layout/activity_upload">
|
||||||
|
|
||||||
<EditText
|
<androidx.appcompat.widget.AppCompatEditText
|
||||||
android:id="@+id/description_item_edit_text"
|
android:id="@+id/description_item_edit_text"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
114
app/src/main/res/layout/upload_categories_fragment.xml
Normal file
114
app/src/main/res/layout/upload_categories_fragment.xml
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/rl_container_categories"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp"
|
||||||
|
>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@+id/button_divider"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginEnd="@dimen/standard_gap"
|
||||||
|
android:layout_marginRight="@dimen/standard_gap"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textSize="@dimen/normal_text"
|
||||||
|
tools:text="Step 1 of 15"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_subtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginTop="@dimen/tiny_gap"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:text="@string/upload_flow_all_images_in_set"
|
||||||
|
android:textSize="@dimen/subtitle_text"
|
||||||
|
android:visibility="visible"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/category_search_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/standard_gap"
|
||||||
|
>
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_container_search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/categories_search_text_hint"
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/pb_categories"
|
||||||
|
style="?android:progressBarStyleSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/tiny_gap"
|
||||||
|
android:layout_marginRight="@dimen/tiny_gap"
|
||||||
|
android:layout_gravity="center_vertical|end"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:indeterminateOnly="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_categories"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_above="@+id/button_divider"
|
||||||
|
android:layout_below="@id/category_search_layout"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/button_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:layout_above="@+id/ll_container_buttons"
|
||||||
|
android:background="@color/divider_grey"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/ll_container_buttons"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
>
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_previous"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/previous"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/btn_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/next"
|
||||||
|
android:textColor="@android:color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
|
@ -373,7 +373,7 @@
|
||||||
<string name="write_storage_permission_rationale_for_image_share">Avem nevoie de permisiunea dvs. pentru a accesa spațiul de stocare extern al dispozitivului dvs. pentru a încărca imagini.</string>
|
<string name="write_storage_permission_rationale_for_image_share">Avem nevoie de permisiunea dvs. pentru a accesa spațiul de stocare extern al dispozitivului dvs. pentru a încărca imagini.</string>
|
||||||
<string name="nearby_notification_dismiss_message">Nu veți vedea cel mai apropiat loc care are nevoie de imagini. Cu toate acestea, puteți reactiva această notificare în Setări, dacă doriți.</string>
|
<string name="nearby_notification_dismiss_message">Nu veți vedea cel mai apropiat loc care are nevoie de imagini. Cu toate acestea, puteți reactiva această notificare în Setări, dacă doriți.</string>
|
||||||
<string name="step_count">Pasul %1$d din %2$d</string>
|
<string name="step_count">Pasul %1$d din %2$d</string>
|
||||||
<string name="image_in_set_label">Imaginea% %1$d în set</string>
|
<string name="image_in_set_label">Imaginea %1$d în set</string>
|
||||||
<string name="next">Următor</string>
|
<string name="next">Următor</string>
|
||||||
<string name="previous">Precedent</string>
|
<string name="previous">Precedent</string>
|
||||||
<string name="submit">Trimite</string>
|
<string name="submit">Trimite</string>
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@
|
||||||
<color name="black">#000000</color>
|
<color name="black">#000000</color>
|
||||||
|
|
||||||
<color name="swipe_red" tools:ignore="MissingDefaultResource">#FF0000</color>
|
<color name="swipe_red" tools:ignore="MissingDefaultResource">#FF0000</color>
|
||||||
|
<color name="color_error">#FF0000</color>
|
||||||
<color name="yes_button_color">#B22222</color>
|
<color name="yes_button_color">#B22222</color>
|
||||||
<color name="no_button_color">#006400</color>
|
<color name="no_button_color">#006400</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -409,7 +409,7 @@
|
||||||
<string name="next">Next</string>
|
<string name="next">Next</string>
|
||||||
<string name="previous">Previous</string>
|
<string name="previous">Previous</string>
|
||||||
<string name="submit">Submit</string>
|
<string name="submit">Submit</string>
|
||||||
<string name="upload_title_duplicate">A file with the file name %1$s exists. Are you sure you want to proceed?</string>
|
<string name="upload_title_duplicate" formatted="true">A file with the file name %1$s exists. Are you sure you want to proceed?</string>
|
||||||
<string name="map_application_missing">No compatible map application could be found on your device. Please install a map application to use this feature.</string>
|
<string name="map_application_missing">No compatible map application could be found on your device. Please install a map application to use this feature.</string>
|
||||||
<plurals name="upload_count_title">
|
<plurals name="upload_count_title">
|
||||||
<item quantity="one">%1$d Upload</item>
|
<item quantity="one">%1$d Upload</item>
|
||||||
|
|
@ -554,5 +554,8 @@ Upload your first media by tapping on the add button.</string>
|
||||||
<string name="share_text">Upload photos to Wikimedia Commons on your phone Download the Commons app: %1$s</string>
|
<string name="share_text">Upload photos to Wikimedia Commons on your phone Download the Commons app: %1$s</string>
|
||||||
<string name="share_via">Share app via...</string>
|
<string name="share_via">Share app via...</string>
|
||||||
<string name="image_info">Image Info</string>
|
<string name="image_info">Image Info</string>
|
||||||
|
<string name="no_categories_found">No Categories found</string>
|
||||||
|
<string name="upload_cancelled">Cancelled Upload</string>
|
||||||
|
<string name="previous_image_title_description_not_found">There is no data for previous image\'s title or description</string>
|
||||||
<string name="dialog_box_text_nomination">Why should %1$s be deleted?</string>
|
<string name="dialog_box_text_nomination">Why should %1$s be deleted?</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import com.nhaarman.mockito_kotlin.verify
|
||||||
|
import fr.free.nrw.commons.category.CategoryItem
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
||||||
|
import fr.free.nrw.commons.upload.categories.CategoriesPresenter
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class contains unit test cases for CategoriesPresenter
|
||||||
|
*/
|
||||||
|
class CategoriesPresenterTest {
|
||||||
|
@Mock
|
||||||
|
internal var repository: UploadRepository? = null
|
||||||
|
@Mock
|
||||||
|
internal var view: CategoriesContract.View? = null
|
||||||
|
|
||||||
|
var categoriesPresenter: CategoriesPresenter? = null
|
||||||
|
|
||||||
|
var testScheduler: TestScheduler? = null
|
||||||
|
|
||||||
|
val categoryItems: ArrayList<CategoryItem> = ArrayList()
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
lateinit var categoryItem: CategoryItem
|
||||||
|
|
||||||
|
var testObservable: Observable<CategoryItem>? = null
|
||||||
|
|
||||||
|
private val imageTitleList = ArrayList<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initial setup
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
testScheduler = TestScheduler()
|
||||||
|
categoryItems.add(categoryItem)
|
||||||
|
testObservable = Observable.just(categoryItem)
|
||||||
|
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
|
||||||
|
categoriesPresenter?.onAttachView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test case for method CategoriesPresenter.searchForCategories
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun searchForCategoriesTest() {
|
||||||
|
Mockito.`when`(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
|
||||||
|
Mockito.`when`(repository?.selectedCategories).thenReturn(categoryItems)
|
||||||
|
Mockito.`when`(repository?.searchAll(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty())
|
||||||
|
categoriesPresenter?.searchForCategories("test")
|
||||||
|
verify(view)?.showProgress(true)
|
||||||
|
verify(view)?.showError(null)
|
||||||
|
verify(view)?.setCategories(null)
|
||||||
|
testScheduler?.triggerActions()
|
||||||
|
verify(view)?.setCategories(categoryItems)
|
||||||
|
verify(view)?.showProgress(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test for method CategoriesPresenter.verifyCategories
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun verifyCategoriesTest() {
|
||||||
|
Mockito.`when`(repository?.selectedCategories).thenReturn(categoryItems)
|
||||||
|
categoriesPresenter?.verifyCategories()
|
||||||
|
verify(repository)?.setSelectedCategories(ArgumentMatchers.anyList())
|
||||||
|
verify(view)?.goToNextScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import com.nhaarman.mockito_kotlin.verify
|
||||||
|
import fr.free.nrw.commons.Utils
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicenseContract
|
||||||
|
import fr.free.nrw.commons.upload.license.MediaLicensePresenter
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
import org.powermock.api.mockito.PowerMockito
|
||||||
|
import org.powermock.core.classloader.annotations.PrepareForTest
|
||||||
|
import org.powermock.modules.junit4.PowerMockRunner
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class contains unit test cases for MediaLicensePresenter
|
||||||
|
*/
|
||||||
|
|
||||||
|
@RunWith(PowerMockRunner::class)
|
||||||
|
@PrepareForTest(Utils::class)
|
||||||
|
class MediaLicensePresenterTest {
|
||||||
|
@Mock
|
||||||
|
internal var repository: UploadRepository? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
internal var view: MediaLicenseContract.View? = null
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
var mediaLicensePresenter: MediaLicensePresenter? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initial setup test environemnt
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
mediaLicensePresenter?.onAttachView(view)
|
||||||
|
PowerMockito.mockStatic(Utils::class.java)
|
||||||
|
PowerMockito.`when`(Utils.licenseNameFor(ArgumentMatchers.anyString())).thenReturn(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test case for method MediaLicensePresenter.getLicense
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun getLicenseTest() {
|
||||||
|
mediaLicensePresenter?.getLicenses()
|
||||||
|
verify(view)?.setLicenses(ArgumentMatchers.anyList())
|
||||||
|
verify(view)?.setSelectedLicense(ArgumentMatchers.any())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test case for method MediaLicensePresenter.selectLicense
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun selectLicenseTest() {
|
||||||
|
mediaLicensePresenter?.selectLicense(ArgumentMatchers.anyString())
|
||||||
|
verify(view)?.updateLicenseSummary(ArgumentMatchers.any(), ArgumentMatchers.anyInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||||
|
import fr.free.nrw.commons.nearby.Place
|
||||||
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailsContract
|
||||||
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaPresenter
|
||||||
|
import fr.free.nrw.commons.utils.ImageUtils.*
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.schedulers.TestScheduler
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class contains unit test cases for UploadMediaPresenter
|
||||||
|
*/
|
||||||
|
class UploadMediaPresenterTest {
|
||||||
|
@Mock
|
||||||
|
internal var repository: UploadRepository? = null
|
||||||
|
@Mock
|
||||||
|
internal var view: UploadMediaDetailsContract.View? = null
|
||||||
|
|
||||||
|
private var uploadMediaPresenter: UploadMediaPresenter? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var uploadableFile: UploadableFile? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var place: Place? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private var uploadItem: UploadModel.UploadItem? = null
|
||||||
|
|
||||||
|
private var testObservableUploadItem: Observable<UploadModel.UploadItem>? = null
|
||||||
|
private var testSingleImageResult: Single<Int>? = null
|
||||||
|
|
||||||
|
private var testScheduler: TestScheduler? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initial setup unit test environment
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
@Throws(Exception::class)
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
testObservableUploadItem = Observable.just(uploadItem)
|
||||||
|
testSingleImageResult = Single.just(1)
|
||||||
|
testScheduler = TestScheduler()
|
||||||
|
uploadMediaPresenter = UploadMediaPresenter(repository, testScheduler, testScheduler)
|
||||||
|
uploadMediaPresenter?.onAttachView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test for method UploadMediaPresenter.receiveImage
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun receiveImageTest() {
|
||||||
|
Mockito.`when`(repository?.preProcessImage(ArgumentMatchers.any(UploadableFile::class.java), ArgumentMatchers.any(Place::class.java), ArgumentMatchers.anyString(), ArgumentMatchers.any(UploadMediaPresenter::class.java))).thenReturn(testObservableUploadItem)
|
||||||
|
uploadMediaPresenter?.receiveImage(uploadableFile, ArgumentMatchers.anyString(), place)
|
||||||
|
verify(view)?.showProgress(true)
|
||||||
|
testScheduler?.triggerActions()
|
||||||
|
verify(view)?.onImageProcessed(ArgumentMatchers.any(UploadModel.UploadItem::class.java), ArgumentMatchers.any(Place::class.java))
|
||||||
|
verify(view)?.showProgress(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test for method UploadMediaPresenter.verifyImageQuality
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun verifyImageQualityTest() {
|
||||||
|
Mockito.`when`(repository?.getImageQuality(ArgumentMatchers.any(UploadModel.UploadItem::class.java), ArgumentMatchers.any(Boolean::class.java))).thenReturn(testSingleImageResult)
|
||||||
|
Mockito.`when`(uploadItem?.imageQuality).thenReturn(ArgumentMatchers.anyInt())
|
||||||
|
uploadMediaPresenter?.verifyImageQuality(uploadItem, true)
|
||||||
|
verify(view)?.showProgress(true)
|
||||||
|
testScheduler?.triggerActions()
|
||||||
|
verify(view)?.showProgress(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test for method UploadMediaPresenter.handleImageResult
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun handleImageResult() {
|
||||||
|
//Positive case test
|
||||||
|
uploadMediaPresenter?.handleImageResult(IMAGE_KEEP)
|
||||||
|
verify(view)?.onImageValidationSuccess()
|
||||||
|
|
||||||
|
//Duplicate file name
|
||||||
|
uploadMediaPresenter?.handleImageResult(FILE_NAME_EXISTS)
|
||||||
|
verify(view)?.showDuplicatePicturePopup()
|
||||||
|
|
||||||
|
//Empty Title test
|
||||||
|
uploadMediaPresenter?.handleImageResult(EMPTY_TITLE)
|
||||||
|
verify(view)?.showMessage(ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt())
|
||||||
|
|
||||||
|
//Bad Picture test
|
||||||
|
//Empty Title test
|
||||||
|
uploadMediaPresenter?.handleImageResult(-7)
|
||||||
|
verify(view)?.showBadImagePopup(ArgumentMatchers.anyInt())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -87,24 +87,6 @@ class UploadModelTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun verifyPreviousNotAvailable() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertFalse(uploadModel!!.isPreviousAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun verifyNextAvailable() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isNextAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun isSubmitAvailable() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isNextAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getCurrentStep() {
|
fun getCurrentStep() {
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
||||||
|
|
@ -135,38 +117,6 @@ class UploadModelTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun isTopCardState() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.isTopCardState)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun next() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
|
||||||
uploadModel!!.next()
|
|
||||||
assertTrue(uploadModel!!.currentStep == 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun previous() {
|
|
||||||
uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
|
||||||
uploadModel!!.next()
|
|
||||||
assertTrue(uploadModel!!.currentStep == 2)
|
|
||||||
uploadModel!!.previous()
|
|
||||||
assertTrue(uploadModel!!.currentStep == 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun isShowingItem() {
|
|
||||||
val preProcessImages = uploadModel!!.preProcessImages(getMediaList(), mock(Place::class.java), "external") { _, _ -> }
|
|
||||||
preProcessImages.doOnComplete {
|
|
||||||
assertTrue(uploadModel!!.isShowingItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getMediaList(): List<UploadableFile> {
|
private fun getMediaList(): List<UploadableFile> {
|
||||||
val element = getElement()
|
val element = getElement()
|
||||||
val element2 = getElement()
|
val element2 = getElement()
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,82 @@
|
||||||
package fr.free.nrw.commons.upload
|
package fr.free.nrw.commons.upload
|
||||||
|
|
||||||
|
import com.nhaarman.mockito_kotlin.verify
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi
|
import fr.free.nrw.commons.repository.UploadRepository
|
||||||
import fr.free.nrw.commons.nearby.Place
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.*
|
import org.mockito.ArgumentMatchers
|
||||||
|
import org.mockito.InjectMocks
|
||||||
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.MockitoAnnotations
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The clas contains unit test cases for UploadPresenter
|
||||||
|
*/
|
||||||
class UploadPresenterTest {
|
class UploadPresenterTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
internal var uploadModel: UploadModel? = null
|
internal var repository: UploadRepository? = null
|
||||||
@Mock
|
@Mock
|
||||||
internal var uploadController: UploadController? = null
|
internal var view: UploadContract.View? = null
|
||||||
@Mock
|
@Mock
|
||||||
internal var mediaWikiApi: MediaWikiApi? = null
|
var contribution: Contribution? = null
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var uploadableFile: UploadableFile
|
||||||
|
|
||||||
@InjectMocks
|
@InjectMocks
|
||||||
var uploadPresenter: UploadPresenter? = null
|
var uploadPresenter: UploadPresenter? = null
|
||||||
|
|
||||||
|
private var uploadableFiles: ArrayList<UploadableFile> = ArrayList()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initial setup, test environment
|
||||||
|
*/
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
`when`(uploadModel!!.preProcessImages(ArgumentMatchers.anyListOf(UploadableFile::class.java),
|
uploadPresenter?.onAttachView(view)
|
||||||
ArgumentMatchers.any(Place::class.java),
|
`when`(repository?.buildContributions()).thenReturn(Observable.just(contribution))
|
||||||
ArgumentMatchers.anyString(),
|
`when`(view?.isLoggedIn).thenReturn(true)
|
||||||
ArgumentMatchers.any(SimilarImageInterface::class.java)))
|
uploadableFiles.add(uploadableFile)
|
||||||
.thenReturn(Observable.just(mock(UploadModel.UploadItem::class.java)))
|
`when`(view?.uploadableFiles).thenReturn(uploadableFiles)
|
||||||
|
`when`(uploadableFile?.filePath).thenReturn("data://test")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test case for method UploadPresenter.handleSubmit
|
||||||
|
*/
|
||||||
@Test
|
@Test
|
||||||
fun receiveMultipleItems() {
|
fun handleSubmitTest() {
|
||||||
val element = Mockito.mock(UploadableFile::class.java)
|
uploadPresenter?.handleSubmit()
|
||||||
val element2 = Mockito.mock(UploadableFile::class.java)
|
verify(view)?.isLoggedIn
|
||||||
var uriList: List<UploadableFile> = mutableListOf<UploadableFile>(element, element2)
|
verify(view)?.showProgress(true)
|
||||||
uploadPresenter!!.receive(uriList, "external", mock(Place::class.java))
|
verify(repository)?.buildContributions()
|
||||||
|
val buildContributions = repository?.buildContributions()
|
||||||
|
buildContributions?.test()?.assertNoErrors()?.assertValue {
|
||||||
|
verify(repository)?.prepareService()
|
||||||
|
verify(view)?.showProgress(false)
|
||||||
|
verify(view)?.showMessage(ArgumentMatchers.any(Int::class.java))
|
||||||
|
verify(view)?.finish()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unit test for UploadMediaPresenter.deletePictureAtIndex
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun deletePictureAtIndexTest() {
|
||||||
|
uploadPresenter?.deletePictureAtIndex(0)
|
||||||
|
verify(repository)?.deletePicture(ArgumentMatchers.anyString())
|
||||||
|
verify(view)?.showMessage(ArgumentMatchers.anyInt())//As there is only one while which we are asking for deletion, upload should be cancelled and this flow should be triggered
|
||||||
|
verify(view)?.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
captures/fr.free.nrw.commons_2019.04.15_22.10.li
Normal file
BIN
captures/fr.free.nrw.commons_2019.04.15_22.10.li
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue