mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
* Edited Project.xml to make indent size 4 * Changed files with 2 space indentation to use 4 space indentation * Edited Project.xml to make indent size 4 * changed files with 2 space indent to 4 space indent * fix :Back Pressed Event not work in Explore tab when user not login (#4404) * fix :Back Pressed Event not work in Explore tab * minor changes * fix :Upload count or number of contribution does not get updated when media is successful uploaded (#4399) * * fix:Number of Contributions not updated * Add javadocs * minor changes * made minor changes * String was nonsense and untranslatible, fixed (#4466) * Ability to show captions and descriptions in all entered languages (#4355) * implement Ability to show captions and descriptions in all entered languages *Add Javadoc * handle Back event of fragment(mediaDetailFragment) * fix minor bugs * add internationalization * revert previous changes * fix visibility bug * resolve conflict Co-authored-by: Prince kushwaha <65972015+Prince-kushwaha@users.noreply.github.com> Co-authored-by: neslihanturan <tur.neslihan@gmail.com>
This commit is contained in:
parent
b202f553f0
commit
ca9f6f5e47
36 changed files with 3047 additions and 2988 deletions
3
.idea/codeStyles/Project.xml
generated
3
.idea/codeStyles/Project.xml
generated
|
|
@ -111,7 +111,6 @@
|
||||||
<option name="WHILE_BRACE_FORCE" value="3" />
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
<option name="FOR_BRACE_FORCE" value="3" />
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
<indentOptions>
|
<indentOptions>
|
||||||
<option name="INDENT_SIZE" value="2" />
|
|
||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
<option name="TAB_SIZE" value="2" />
|
<option name="TAB_SIZE" value="2" />
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
|
|
@ -325,4 +324,4 @@
|
||||||
</indentOptions>
|
</indentOptions>
|
||||||
</codeStyleSettings>
|
</codeStyleSettings>
|
||||||
</code_scheme>
|
</code_scheme>
|
||||||
</component>
|
</component>
|
||||||
|
|
@ -63,38 +63,39 @@ import org.wikipedia.language.AppLanguageLookUpTable;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
@AcraCore(
|
@AcraCore(
|
||||||
buildConfigClass = BuildConfig.class,
|
buildConfigClass = BuildConfig.class,
|
||||||
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
||||||
reportFormat = StringFormat.KEY_VALUE_LIST,
|
reportFormat = StringFormat.KEY_VALUE_LIST,
|
||||||
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL, STACK_TRACE}
|
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL,
|
||||||
|
STACK_TRACE}
|
||||||
)
|
)
|
||||||
|
|
||||||
@AcraMailSender(
|
@AcraMailSender(
|
||||||
mailTo = "commons-app-android-private@googlegroups.com",
|
mailTo = "commons-app-android-private@googlegroups.com",
|
||||||
reportAsFile = false
|
reportAsFile = false
|
||||||
)
|
)
|
||||||
|
|
||||||
@AcraDialog(
|
@AcraDialog(
|
||||||
resTheme = R.style.Theme_AppCompat_Dialog,
|
resTheme = R.style.Theme_AppCompat_Dialog,
|
||||||
resText = R.string.crash_dialog_text,
|
resText = R.string.crash_dialog_text,
|
||||||
resTitle = R.string.crash_dialog_title,
|
resTitle = R.string.crash_dialog_title,
|
||||||
resCommentPrompt = R.string.crash_dialog_comment_prompt
|
resCommentPrompt = R.string.crash_dialog_comment_prompt
|
||||||
)
|
)
|
||||||
|
|
||||||
public class CommonsApplication extends MultiDexApplication {
|
public class CommonsApplication extends MultiDexApplication {
|
||||||
|
|
||||||
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
|
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
|
||||||
@Inject
|
@Inject
|
||||||
SessionManager sessionManager;
|
SessionManager sessionManager;
|
||||||
@Inject
|
@Inject
|
||||||
DBOpenHelper dbOpenHelper;
|
DBOpenHelper dbOpenHelper;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
JsonKvStore defaultPrefs;
|
JsonKvStore defaultPrefs;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants begin
|
* Constants begin
|
||||||
|
|
@ -118,23 +119,26 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
private RefWatcher refWatcher;
|
private RefWatcher refWatcher;
|
||||||
|
|
||||||
private static CommonsApplication INSTANCE;
|
private static CommonsApplication INSTANCE;
|
||||||
|
|
||||||
public static CommonsApplication getInstance() {
|
public static CommonsApplication getInstance() {
|
||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppLanguageLookUpTable languageLookUpTable;
|
private AppLanguageLookUpTable languageLookUpTable;
|
||||||
|
|
||||||
public AppLanguageLookUpTable getLanguageLookUpTable() {
|
public AppLanguageLookUpTable getLanguageLookUpTable() {
|
||||||
return languageLookUpTable;
|
return languageLookUpTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject ContributionDao contributionDao;
|
@Inject
|
||||||
|
ContributionDao contributionDao;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In memory list of contributios whose uploads ahve been paused by the user
|
* In memory list of contributios whose uploads ahve been paused by the user
|
||||||
*/
|
*/
|
||||||
public static Map<String, Boolean> pauseUploads = new HashMap<>();
|
public static Map<String, Boolean> pauseUploads = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to declare and initialize various components and dependencies
|
* Used to declare and initialize various components and dependencies
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -146,15 +150,14 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
Mapbox.getInstance(this, getString(R.string.mapbox_commons_app_token));
|
Mapbox.getInstance(this, getString(R.string.mapbox_commons_app_token));
|
||||||
|
|
||||||
ApplicationlessInjection
|
ApplicationlessInjection
|
||||||
.getInstance(this)
|
.getInstance(this)
|
||||||
.getCommonsApplicationComponent()
|
.getCommonsApplicationComponent()
|
||||||
.inject(this);
|
.inject(this);
|
||||||
|
|
||||||
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
|
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
|
||||||
|
|
||||||
initTimber();
|
initTimber();
|
||||||
|
|
||||||
|
|
||||||
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
|
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
|
||||||
Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS);
|
Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS);
|
||||||
if (null == defaultExifTagsSet) {
|
if (null == defaultExifTagsSet) {
|
||||||
|
|
@ -166,9 +169,9 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
|
|
||||||
// Set DownsampleEnabled to True to downsample the image in case it's heavy
|
// Set DownsampleEnabled to True to downsample the image in case it's heavy
|
||||||
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
|
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
|
||||||
.setNetworkFetcher(customOkHttpNetworkFetcher)
|
.setNetworkFetcher(customOkHttpNetworkFetcher)
|
||||||
.setDownsampleEnabled(true)
|
.setDownsampleEnabled(true)
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
Fresco.initialize(this, config);
|
Fresco.initialize(this, config);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -192,46 +195,44 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plants debug and file logging tree.
|
* Plants debug and file logging tree. Timber lets you plant your own logging trees.
|
||||||
* Timber lets you plant your own logging trees.
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
private void initTimber() {
|
private void initTimber() {
|
||||||
boolean isBeta = ConfigUtils.isBetaFlavour();
|
boolean isBeta = ConfigUtils.isBetaFlavour();
|
||||||
String logFileName =
|
String logFileName =
|
||||||
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
|
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
|
||||||
String logDirectory = LogUtils.getLogDirectory();
|
String logDirectory = LogUtils.getLogDirectory();
|
||||||
//Delete stale logs if they have exceeded the specified size
|
//Delete stale logs if they have exceeded the specified size
|
||||||
deleteStaleLogs(logFileName, logDirectory);
|
deleteStaleLogs(logFileName, logDirectory);
|
||||||
|
|
||||||
FileLoggingTree tree = new FileLoggingTree(
|
FileLoggingTree tree = new FileLoggingTree(
|
||||||
Log.VERBOSE,
|
Log.VERBOSE,
|
||||||
logFileName,
|
logFileName,
|
||||||
logDirectory,
|
logDirectory,
|
||||||
1000,
|
1000,
|
||||||
getFileLoggingThreadPool());
|
getFileLoggingThreadPool());
|
||||||
|
|
||||||
Timber.plant(tree);
|
Timber.plant(tree);
|
||||||
Timber.plant(new Timber.DebugTree());
|
Timber.plant(new Timber.DebugTree());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the logs zip file at the specified directory and file locations specified in the
|
* Deletes the logs zip file at the specified directory and file locations specified in the
|
||||||
* params
|
* params
|
||||||
*
|
*
|
||||||
* @param logFileName
|
* @param logFileName
|
||||||
* @param logDirectory
|
* @param logDirectory
|
||||||
*/
|
*/
|
||||||
private void deleteStaleLogs(String logFileName, String logDirectory) {
|
private void deleteStaleLogs(String logFileName, String logDirectory) {
|
||||||
try {
|
try {
|
||||||
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
|
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
|
||||||
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
|
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
|
||||||
file.delete();
|
file.delete();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Timber.e(e);
|
Timber.e(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isRoboUnitTest() {
|
public static boolean isRoboUnitTest() {
|
||||||
return "robolectric".equals(Build.FINGERPRINT);
|
return "robolectric".equals(Build.FINGERPRINT);
|
||||||
|
|
@ -239,30 +240,35 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
|
|
||||||
private ThreadPoolService getFileLoggingThreadPool() {
|
private ThreadPoolService getFileLoggingThreadPool() {
|
||||||
return new ThreadPoolService.Builder("file-logging-thread")
|
return new ThreadPoolService.Builder("file-logging-thread")
|
||||||
.setPriority(Process.THREAD_PRIORITY_LOWEST)
|
.setPriority(Process.THREAD_PRIORITY_LOWEST)
|
||||||
.setPoolSize(1)
|
.setPoolSize(1)
|
||||||
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void createNotificationChannel(@NonNull Context context) {
|
public static void createNotificationChannel(@NonNull Context context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
NotificationManager manager = (NotificationManager) context
|
||||||
NotificationChannel channel = manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
|
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
NotificationChannel channel = manager
|
||||||
|
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
|
||||||
if (channel == null) {
|
if (channel == null) {
|
||||||
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL,
|
channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID_ALL,
|
||||||
context.getString(R.string.notifications_channel_name_all), NotificationManager.IMPORTANCE_DEFAULT);
|
context.getString(R.string.notifications_channel_name_all),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
manager.createNotificationChannel(channel);
|
manager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserAgent() {
|
public String getUserAgent() {
|
||||||
return "Commons/" + ConfigUtils.getVersionNameWithSha(this) + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
|
return "Commons/" + ConfigUtils.getVersionNameWithSha(this)
|
||||||
|
+ " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helps in setting up LeakCanary library
|
* Helps in setting up LeakCanary library
|
||||||
|
*
|
||||||
* @return instance of LeakCanary
|
* @return instance of LeakCanary
|
||||||
*/
|
*/
|
||||||
protected RefWatcher setupLeakCanary() {
|
protected RefWatcher setupLeakCanary() {
|
||||||
|
|
@ -272,7 +278,7 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
return LeakCanary.install(this);
|
return LeakCanary.install(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a way to get member refWatcher
|
* Provides a way to get member refWatcher
|
||||||
*
|
*
|
||||||
* @param context Application context
|
* @param context Application context
|
||||||
|
|
@ -285,7 +291,8 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* clears data of current application
|
* clears data of current application
|
||||||
* @param context Application context
|
*
|
||||||
|
* @param context Application context
|
||||||
* @param logoutListener Implementation of interface LogoutListener
|
* @param logoutListener Implementation of interface LogoutListener
|
||||||
*/
|
*/
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
|
|
@ -302,13 +309,13 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionManager.logout()
|
sessionManager.logout()
|
||||||
.andThen(Completable.fromAction(() ->{
|
.andThen(Completable.fromAction(() -> {
|
||||||
Timber.d("All accounts have been removed");
|
Timber.d("All accounts have been removed");
|
||||||
clearImageCache();
|
clearImageCache();
|
||||||
//TODO: fix preference manager
|
//TODO: fix preference manager
|
||||||
defaultPrefs.clearAll();
|
defaultPrefs.clearAll();
|
||||||
defaultPrefs.putBoolean("firstrun", false);
|
defaultPrefs.putBoolean("firstrun", false);
|
||||||
updateAllDatabases();
|
updateAllDatabases();
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
|
@ -332,12 +339,13 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
|
||||||
|
|
||||||
CategoryDao.Table.onDelete(db);
|
CategoryDao.Table.onDelete(db);
|
||||||
dbOpenHelper.deleteTable(db,CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
|
dbOpenHelper.deleteTable(db,
|
||||||
|
CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contributionDao.deleteAll();
|
contributionDao.deleteAll();
|
||||||
} catch (SQLiteException e) {
|
} catch (SQLiteException e) {
|
||||||
Timber.e(e);
|
Timber.e(e);
|
||||||
}
|
}
|
||||||
BookmarkPicturesDao.Table.onDelete(db);
|
BookmarkPicturesDao.Table.onDelete(db);
|
||||||
BookmarkLocationsDao.Table.onDelete(db);
|
BookmarkLocationsDao.Table.onDelete(db);
|
||||||
|
|
@ -348,6 +356,7 @@ public class CommonsApplication extends MultiDexApplication {
|
||||||
* Interface used to get log-out events
|
* Interface used to get log-out events
|
||||||
*/
|
*/
|
||||||
public interface LogoutListener {
|
public interface LogoutListener {
|
||||||
|
|
||||||
void onLogoutComplete();
|
void onLogoutComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,45 +10,48 @@ import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||||
*/
|
*/
|
||||||
public final class LocationPicker {
|
public final class LocationPicker {
|
||||||
|
|
||||||
/**
|
|
||||||
* Getting camera position from the intent using constants
|
|
||||||
* @param data intent
|
|
||||||
* @return CameraPosition
|
|
||||||
*/
|
|
||||||
public static CameraPosition getCameraPosition(final Intent data) {
|
|
||||||
return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class IntentBuilder {
|
|
||||||
|
|
||||||
private final Intent intent;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new builder that creates an intent to launch the place picker activity.
|
* Getting camera position from the intent using constants
|
||||||
|
*
|
||||||
|
* @param data intent
|
||||||
|
* @return CameraPosition
|
||||||
*/
|
*/
|
||||||
public IntentBuilder() {
|
public static CameraPosition getCameraPosition(final Intent data) {
|
||||||
intent = new Intent();
|
return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static class IntentBuilder {
|
||||||
* Gets and puts location in intent
|
|
||||||
* @param position CameraPosition
|
|
||||||
* @return LocationPicker.IntentBuilder
|
|
||||||
*/
|
|
||||||
public LocationPicker.IntentBuilder defaultLocation(
|
|
||||||
final CameraPosition position) {
|
|
||||||
intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
private final Intent intent;
|
||||||
* Gets and sets the activity
|
|
||||||
* @param activity Activity
|
/**
|
||||||
* @return Intent
|
* Creates a new builder that creates an intent to launch the place picker activity.
|
||||||
*/
|
*/
|
||||||
public Intent build(final Activity activity) {
|
public IntentBuilder() {
|
||||||
intent.setClass(activity, LocationPickerActivity.class);
|
intent = new Intent();
|
||||||
return intent;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and puts location in intent
|
||||||
|
*
|
||||||
|
* @param position CameraPosition
|
||||||
|
* @return LocationPicker.IntentBuilder
|
||||||
|
*/
|
||||||
|
public LocationPicker.IntentBuilder defaultLocation(
|
||||||
|
final CameraPosition position) {
|
||||||
|
intent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION, position);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets the activity
|
||||||
|
*
|
||||||
|
* @param activity Activity
|
||||||
|
* @return Intent
|
||||||
|
*/
|
||||||
|
public Intent build(final Activity activity) {
|
||||||
|
intent.setClass(activity, LocationPickerActivity.class);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,247 +42,252 @@ import timber.log.Timber;
|
||||||
public class LocationPickerActivity extends AppCompatActivity implements OnMapReadyCallback,
|
public class LocationPickerActivity extends AppCompatActivity implements OnMapReadyCallback,
|
||||||
OnCameraMoveStartedListener, OnCameraIdleListener, Observer<CameraPosition> {
|
OnCameraMoveStartedListener, OnCameraIdleListener, Observer<CameraPosition> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* cameraPosition : position of picker
|
* cameraPosition : position of picker
|
||||||
*/
|
*/
|
||||||
private CameraPosition cameraPosition;
|
private CameraPosition cameraPosition;
|
||||||
/**
|
/**
|
||||||
* markerImage : picker image
|
* markerImage : picker image
|
||||||
*/
|
*/
|
||||||
private ImageView markerImage;
|
private ImageView markerImage;
|
||||||
/**
|
/**
|
||||||
* mapboxMap : map
|
* mapboxMap : map
|
||||||
*/
|
*/
|
||||||
private MapboxMap mapboxMap;
|
private MapboxMap mapboxMap;
|
||||||
/**
|
/**
|
||||||
* mapView : view of the map
|
* mapView : view of the map
|
||||||
*/
|
*/
|
||||||
private MapView mapView;
|
private MapView mapView;
|
||||||
/**
|
/**
|
||||||
* tvAttribution : credit
|
* tvAttribution : credit
|
||||||
*/
|
*/
|
||||||
private AppCompatTextView tvAttribution;
|
private AppCompatTextView tvAttribution;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
|
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
|
||||||
final ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
actionBar.hide();
|
actionBar.hide();
|
||||||
}
|
}
|
||||||
setContentView(R.layout.activity_location_picker);
|
setContentView(R.layout.activity_location_picker);
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
cameraPosition = getIntent().getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
cameraPosition = getIntent()
|
||||||
|
.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
||||||
|
}
|
||||||
|
|
||||||
|
final LocationPickerViewModel viewModel = new ViewModelProvider(this)
|
||||||
|
.get(LocationPickerViewModel.class);
|
||||||
|
viewModel.getResult().observe(this, this);
|
||||||
|
|
||||||
|
bindViews();
|
||||||
|
addBackButtonListener();
|
||||||
|
addPlaceSelectedButton();
|
||||||
|
addCredits();
|
||||||
|
getToolbarUI();
|
||||||
|
|
||||||
|
mapView.onCreate(savedInstanceState);
|
||||||
|
mapView.getMapAsync(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
final LocationPickerViewModel viewModel = new ViewModelProvider(this)
|
/**
|
||||||
.get(LocationPickerViewModel.class);
|
* For showing credits
|
||||||
viewModel.getResult().observe(this, this);
|
*/
|
||||||
|
private void addCredits() {
|
||||||
bindViews();
|
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
|
||||||
addBackButtonListener();
|
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
addPlaceSelectedButton();
|
|
||||||
addCredits();
|
|
||||||
getToolbarUI();
|
|
||||||
|
|
||||||
mapView.onCreate(savedInstanceState);
|
|
||||||
mapView.getMapAsync(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For showing credits
|
|
||||||
*/
|
|
||||||
private void addCredits() {
|
|
||||||
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
|
|
||||||
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clicking back button destroy locationPickerActivity
|
|
||||||
*/
|
|
||||||
private void addBackButtonListener() {
|
|
||||||
final ImageView backButton = findViewById(R.id.mapbox_place_picker_toolbar_back_button);
|
|
||||||
backButton.setOnClickListener(view -> finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds mapView and location picker icon
|
|
||||||
*/
|
|
||||||
private void bindViews() {
|
|
||||||
mapView = findViewById(R.id.map_view);
|
|
||||||
markerImage = findViewById(R.id.location_picker_image_view_marker);
|
|
||||||
tvAttribution = findViewById(R.id.tv_attribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Binds the listeners
|
|
||||||
*/
|
|
||||||
private void bindListeners() {
|
|
||||||
mapboxMap.addOnCameraMoveStartedListener(
|
|
||||||
this);
|
|
||||||
mapboxMap.addOnCameraIdleListener(
|
|
||||||
this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets toolbar color
|
|
||||||
*/
|
|
||||||
private void getToolbarUI() {
|
|
||||||
final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
|
|
||||||
toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes action when map is ready to show
|
|
||||||
* @param mapboxMap map
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onMapReady(final MapboxMap mapboxMap) {
|
|
||||||
this.mapboxMap = mapboxMap;
|
|
||||||
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
|
|
||||||
adjustCameraBasedOnOptions();
|
|
||||||
bindListeners();
|
|
||||||
enableLocationComponent(style);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* move the location to the current media coordinates
|
|
||||||
*/
|
|
||||||
private void adjustCameraBasedOnOptions() {
|
|
||||||
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables location components
|
|
||||||
* @param loadedMapStyle Style
|
|
||||||
*/
|
|
||||||
@SuppressWarnings( {"MissingPermission"})
|
|
||||||
private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
|
|
||||||
final UiSettings uiSettings = mapboxMap.getUiSettings();
|
|
||||||
uiSettings.setAttributionEnabled(false);
|
|
||||||
|
|
||||||
// Check if permissions are enabled and if not request
|
|
||||||
if (PermissionsManager.areLocationPermissionsGranted(this)) {
|
|
||||||
|
|
||||||
// Get an instance of the component
|
|
||||||
final LocationComponent locationComponent = mapboxMap.getLocationComponent();
|
|
||||||
|
|
||||||
// Activate with options
|
|
||||||
locationComponent.activateLocationComponent(
|
|
||||||
LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
|
|
||||||
|
|
||||||
// Enable to make component visible
|
|
||||||
locationComponent.setLocationComponentEnabled(true);
|
|
||||||
|
|
||||||
// Set the component's camera mode
|
|
||||||
locationComponent.setCameraMode(CameraMode.NONE);
|
|
||||||
|
|
||||||
// Set the component's render mode
|
|
||||||
locationComponent.setRenderMode(RenderMode.NORMAL);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acts on camera moving
|
* Clicking back button destroy locationPickerActivity
|
||||||
* @param reason int
|
*/
|
||||||
*/
|
private void addBackButtonListener() {
|
||||||
@Override
|
final ImageView backButton = findViewById(R.id.mapbox_place_picker_toolbar_back_button);
|
||||||
public void onCameraMoveStarted(final int reason) {
|
backButton.setOnClickListener(view -> finish());
|
||||||
Timber.v("Map camera has begun moving.");
|
|
||||||
if (markerImage.getTranslationY() == 0) {
|
|
||||||
markerImage.animate().translationY(-75)
|
|
||||||
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Acts on camera idle
|
* Binds mapView and location picker icon
|
||||||
*/
|
*/
|
||||||
@Override
|
private void bindViews() {
|
||||||
public void onCameraIdle() {
|
mapView = findViewById(R.id.map_view);
|
||||||
Timber.v("Map camera is now idling.");
|
markerImage = findViewById(R.id.location_picker_image_view_marker);
|
||||||
markerImage.animate().translationY(0)
|
tvAttribution = findViewById(R.id.tv_attribution);
|
||||||
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes action on camera position
|
|
||||||
* @param position position of picker
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onChanged(@Nullable CameraPosition position) {
|
|
||||||
if (position == null) {
|
|
||||||
position = new Builder()
|
|
||||||
.target(new LatLng(mapboxMap.getCameraPosition().target.getLatitude(),
|
|
||||||
mapboxMap.getCameraPosition().target.getLongitude()))
|
|
||||||
.zoom(16).build();
|
|
||||||
}
|
}
|
||||||
cameraPosition = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select the preferable location
|
* Binds the listeners
|
||||||
*/
|
*/
|
||||||
private void addPlaceSelectedButton() {
|
private void bindListeners() {
|
||||||
final FloatingActionButton placeSelectedButton = findViewById(R.id.location_chosen_button);
|
mapboxMap.addOnCameraMoveStartedListener(
|
||||||
placeSelectedButton.setOnClickListener(view -> placeSelected());
|
this);
|
||||||
}
|
mapboxMap.addOnCameraIdleListener(
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the intent with required data
|
* Gets toolbar color
|
||||||
*/
|
*/
|
||||||
void placeSelected() {
|
private void getToolbarUI() {
|
||||||
final Intent returningIntent = new Intent();
|
final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
|
||||||
returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION,
|
toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
|
||||||
mapboxMap.getCameraPosition());
|
}
|
||||||
setResult(AppCompatActivity.RESULT_OK, returningIntent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected void onStart() {
|
* Takes action when map is ready to show
|
||||||
super.onStart();
|
*
|
||||||
mapView.onStart();
|
* @param mapboxMap map
|
||||||
}
|
*/
|
||||||
|
@Override
|
||||||
|
public void onMapReady(final MapboxMap mapboxMap) {
|
||||||
|
this.mapboxMap = mapboxMap;
|
||||||
|
mapboxMap.setStyle(Style.MAPBOX_STREETS, style -> {
|
||||||
|
adjustCameraBasedOnOptions();
|
||||||
|
bindListeners();
|
||||||
|
enableLocationComponent(style);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected void onResume() {
|
* move the location to the current media coordinates
|
||||||
super.onResume();
|
*/
|
||||||
mapView.onResume();
|
private void adjustCameraBasedOnOptions() {
|
||||||
}
|
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
protected void onPause() {
|
* Enables location components
|
||||||
super.onPause();
|
*
|
||||||
mapView.onPause();
|
* @param loadedMapStyle Style
|
||||||
}
|
*/
|
||||||
|
@SuppressWarnings({"MissingPermission"})
|
||||||
|
private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
|
||||||
|
final UiSettings uiSettings = mapboxMap.getUiSettings();
|
||||||
|
uiSettings.setAttributionEnabled(false);
|
||||||
|
|
||||||
@Override
|
// Check if permissions are enabled and if not request
|
||||||
protected void onStop() {
|
if (PermissionsManager.areLocationPermissionsGranted(this)) {
|
||||||
super.onStop();
|
|
||||||
mapView.onStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// Get an instance of the component
|
||||||
protected void onSaveInstanceState(final @NotNull Bundle outState) {
|
final LocationComponent locationComponent = mapboxMap.getLocationComponent();
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
mapView.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// Activate with options
|
||||||
protected void onDestroy() {
|
locationComponent.activateLocationComponent(
|
||||||
super.onDestroy();
|
LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
|
||||||
mapView.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
// Enable to make component visible
|
||||||
public void onLowMemory() {
|
locationComponent.setLocationComponentEnabled(true);
|
||||||
super.onLowMemory();
|
|
||||||
mapView.onLowMemory();
|
// Set the component's camera mode
|
||||||
}
|
locationComponent.setCameraMode(CameraMode.NONE);
|
||||||
|
|
||||||
|
// Set the component's render mode
|
||||||
|
locationComponent.setRenderMode(RenderMode.NORMAL);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts on camera moving
|
||||||
|
*
|
||||||
|
* @param reason int
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCameraMoveStarted(final int reason) {
|
||||||
|
Timber.v("Map camera has begun moving.");
|
||||||
|
if (markerImage.getTranslationY() == 0) {
|
||||||
|
markerImage.animate().translationY(-75)
|
||||||
|
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acts on camera idle
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCameraIdle() {
|
||||||
|
Timber.v("Map camera is now idling.");
|
||||||
|
markerImage.animate().translationY(0)
|
||||||
|
.setInterpolator(new OvershootInterpolator()).setDuration(250).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes action on camera position
|
||||||
|
*
|
||||||
|
* @param position position of picker
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable CameraPosition position) {
|
||||||
|
if (position == null) {
|
||||||
|
position = new Builder()
|
||||||
|
.target(new LatLng(mapboxMap.getCameraPosition().target.getLatitude(),
|
||||||
|
mapboxMap.getCameraPosition().target.getLongitude()))
|
||||||
|
.zoom(16).build();
|
||||||
|
}
|
||||||
|
cameraPosition = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the preferable location
|
||||||
|
*/
|
||||||
|
private void addPlaceSelectedButton() {
|
||||||
|
final FloatingActionButton placeSelectedButton = findViewById(R.id.location_chosen_button);
|
||||||
|
placeSelectedButton.setOnClickListener(view -> placeSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the intent with required data
|
||||||
|
*/
|
||||||
|
void placeSelected() {
|
||||||
|
final Intent returningIntent = new Intent();
|
||||||
|
returningIntent.putExtra(LocationPickerConstants.MAP_CAMERA_POSITION,
|
||||||
|
mapboxMap.getCameraPosition());
|
||||||
|
setResult(AppCompatActivity.RESULT_OK, returningIntent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
mapView.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
mapView.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
mapView.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
mapView.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSaveInstanceState(final @NotNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
mapView.onSaveInstanceState(outState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
mapView.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLowMemory() {
|
||||||
|
super.onLowMemory();
|
||||||
|
mapView.onLowMemory();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ package fr.free.nrw.commons.LocationPicker;
|
||||||
*/
|
*/
|
||||||
public final class LocationPickerConstants {
|
public final class LocationPickerConstants {
|
||||||
|
|
||||||
public static final String MAP_CAMERA_POSITION
|
public static final String MAP_CAMERA_POSITION
|
||||||
= "location.picker.cameraPosition";
|
= "location.picker.cameraPosition";
|
||||||
|
|
||||||
private LocationPickerConstants() {
|
private LocationPickerConstants() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,45 +16,48 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
public class LocationPickerViewModel extends AndroidViewModel implements Callback<CameraPosition> {
|
public class LocationPickerViewModel extends AndroidViewModel implements Callback<CameraPosition> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrapping CameraPosition with MutableLiveData
|
* Wrapping CameraPosition with MutableLiveData
|
||||||
*/
|
*/
|
||||||
private final MutableLiveData<CameraPosition> result = new MutableLiveData<>();
|
private final MutableLiveData<CameraPosition> result = new MutableLiveData<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for this class
|
* Constructor for this class
|
||||||
* @param application Application
|
*
|
||||||
*/
|
* @param application Application
|
||||||
public LocationPickerViewModel(@NonNull final Application application) {
|
*/
|
||||||
super(application);
|
public LocationPickerViewModel(@NonNull final Application application) {
|
||||||
}
|
super(application);
|
||||||
|
|
||||||
/**
|
|
||||||
* Responses on camera position changing
|
|
||||||
* @param call Call<CameraPosition>
|
|
||||||
* @param response Response<CameraPosition>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onResponse(final @NotNull Call<CameraPosition> call,
|
|
||||||
final Response<CameraPosition> response) {
|
|
||||||
if(response.body()==null){
|
|
||||||
result.setValue(null);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
result.setValue(response.body());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) {
|
* Responses on camera position changing
|
||||||
Timber.e(t);
|
*
|
||||||
}
|
* @param call Call<CameraPosition>
|
||||||
|
* @param response Response<CameraPosition>
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onResponse(final @NotNull Call<CameraPosition> call,
|
||||||
|
final Response<CameraPosition> response) {
|
||||||
|
if (response.body() == null) {
|
||||||
|
result.setValue(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
result.setValue(response.body());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Gets live CameraPosition
|
public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) {
|
||||||
* @return MutableLiveData<CameraPosition>
|
Timber.e(t);
|
||||||
*/
|
}
|
||||||
public MutableLiveData<CameraPosition> getResult() {
|
|
||||||
return result;
|
/**
|
||||||
}
|
* Gets live CameraPosition
|
||||||
|
*
|
||||||
|
* @return MutableLiveData<CameraPosition>
|
||||||
|
*/
|
||||||
|
public MutableLiveData<CameraPosition> getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,84 +27,85 @@ import javax.inject.Named;
|
||||||
|
|
||||||
public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
public class BookmarkFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private FragmentManager supportFragmentManager;
|
private FragmentManager supportFragmentManager;
|
||||||
private BookmarksPagerAdapter adapter;
|
private BookmarksPagerAdapter adapter;
|
||||||
@BindView(R.id.viewPagerBookmarks)
|
@BindView(R.id.viewPagerBookmarks)
|
||||||
ParentViewPager viewPager;
|
ParentViewPager viewPager;
|
||||||
@BindView(R.id.tab_layout)
|
@BindView(R.id.tab_layout)
|
||||||
TabLayout tabLayout;
|
TabLayout tabLayout;
|
||||||
@BindView(R.id.fragmentContainer)
|
@BindView(R.id.fragmentContainer)
|
||||||
FrameLayout fragmentContainer;
|
FrameLayout fragmentContainer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionController controller;
|
ContributionController controller;
|
||||||
/**
|
/**
|
||||||
* To check if the user is loggedIn or not.
|
* To check if the user is loggedIn or not.
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
public
|
public
|
||||||
JsonKvStore applicationKvStore;
|
JsonKvStore applicationKvStore;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static BookmarkFragment newInstance() {
|
public static BookmarkFragment newInstance() {
|
||||||
BookmarkFragment fragment = new BookmarkFragment();
|
BookmarkFragment fragment = new BookmarkFragment();
|
||||||
fragment.setRetainInstance(true);
|
fragment.setRetainInstance(true);
|
||||||
return fragment;
|
return fragment;
|
||||||
}
|
|
||||||
|
|
||||||
public void setScroll(boolean canScroll){
|
|
||||||
viewPager.setCanScroll(canScroll);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater,
|
|
||||||
@Nullable final ViewGroup container,
|
|
||||||
@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
View view = inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
|
|
||||||
// Activity can call methods in the fragment by acquiring a
|
|
||||||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
|
||||||
supportFragmentManager = getChildFragmentManager();
|
|
||||||
|
|
||||||
adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(),
|
|
||||||
applicationKvStore.getBoolean("login_skipped"));
|
|
||||||
viewPager.setAdapter(adapter);
|
|
||||||
tabLayout.setupWithViewPager(viewPager);
|
|
||||||
|
|
||||||
((MainActivity)getActivity()).showTabs();
|
|
||||||
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
|
||||||
|
|
||||||
setupTabLayout();
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method sets up the tab layout.
|
|
||||||
* If the adapter has only one element it sets the visibility of tabLayout to gone.
|
|
||||||
*/
|
|
||||||
public void setupTabLayout(){
|
|
||||||
tabLayout.setVisibility(View.VISIBLE);
|
|
||||||
if (adapter.getCount() == 1) {
|
|
||||||
tabLayout.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public void setScroll(boolean canScroll) {
|
||||||
public void onBackPressed() {
|
viewPager.setCanScroll(canScroll);
|
||||||
if(((BookmarkListRootFragment)(adapter.getItem(tabLayout.getSelectedTabPosition()))).backPressed()) {
|
}
|
||||||
// The event is handled internally by the adapter , no further action required.
|
|
||||||
return;
|
@Override
|
||||||
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
|
@Nullable final ViewGroup container,
|
||||||
|
@Nullable final Bundle savedInstanceState) {
|
||||||
|
super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
View view = inflater.inflate(R.layout.fragment_bookmarks, container, false);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
|
||||||
|
// Activity can call methods in the fragment by acquiring a
|
||||||
|
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||||
|
supportFragmentManager = getChildFragmentManager();
|
||||||
|
|
||||||
|
adapter = new BookmarksPagerAdapter(supportFragmentManager, getContext(),
|
||||||
|
applicationKvStore.getBoolean("login_skipped"));
|
||||||
|
viewPager.setAdapter(adapter);
|
||||||
|
tabLayout.setupWithViewPager(viewPager);
|
||||||
|
|
||||||
|
((MainActivity) getActivity()).showTabs();
|
||||||
|
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
|
|
||||||
|
setupTabLayout();
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method sets up the tab layout. If the adapter has only one element it sets the
|
||||||
|
* visibility of tabLayout to gone.
|
||||||
|
*/
|
||||||
|
public void setupTabLayout() {
|
||||||
|
tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
if (adapter.getCount() == 1) {
|
||||||
|
tabLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void onBackPressed() {
|
||||||
|
if (((BookmarkListRootFragment) (adapter.getItem(tabLayout.getSelectedTabPosition())))
|
||||||
|
.backPressed()) {
|
||||||
|
// The event is handled internally by the adapter , no further action required.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Event is not handled by the adapter ( performed back action ) change action bar.
|
||||||
|
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||||
}
|
}
|
||||||
// Event is not handled by the adapter ( performed back action ) change action bar.
|
|
||||||
((BaseActivity)getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,223 +30,226 @@ import java.util.Iterator;
|
||||||
public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements
|
public class BookmarkListRootFragment extends CommonsDaggerSupportFragment implements
|
||||||
FragmentManager.OnBackStackChangedListener,
|
FragmentManager.OnBackStackChangedListener,
|
||||||
MediaDetailPagerFragment.MediaDetailProvider,
|
MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
AdapterView.OnItemClickListener, CategoryImagesCallback{
|
AdapterView.OnItemClickListener, CategoryImagesCallback {
|
||||||
|
|
||||||
private MediaDetailPagerFragment mediaDetails;
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
//private BookmarkPicturesFragment bookmarkPicturesFragment;
|
//private BookmarkPicturesFragment bookmarkPicturesFragment;
|
||||||
private BookmarkLocationsFragment bookmarkLocationsFragment;
|
private BookmarkLocationsFragment bookmarkLocationsFragment;
|
||||||
public Fragment listFragment;
|
public Fragment listFragment;
|
||||||
private BookmarksPagerAdapter bookmarksPagerAdapter;
|
private BookmarksPagerAdapter bookmarksPagerAdapter;
|
||||||
|
|
||||||
@BindView(R.id.explore_container)
|
@BindView(R.id.explore_container)
|
||||||
FrameLayout container;
|
FrameLayout container;
|
||||||
|
|
||||||
public BookmarkListRootFragment(){
|
public BookmarkListRootFragment() {
|
||||||
//empty constructor necessary otherwise crashes on recreate
|
//empty constructor necessary otherwise crashes on recreate
|
||||||
}
|
|
||||||
|
|
||||||
public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) {
|
|
||||||
String title = bundle.getString("categoryName");
|
|
||||||
int order = bundle.getInt("order");
|
|
||||||
if (order == 0) {
|
|
||||||
listFragment = new BookmarkPicturesFragment();
|
|
||||||
} else {
|
|
||||||
listFragment = new BookmarkLocationsFragment();
|
|
||||||
}
|
}
|
||||||
Bundle featuredArguments = new Bundle();
|
|
||||||
featuredArguments.putString("categoryName", title);
|
|
||||||
listFragment.setArguments(featuredArguments);
|
|
||||||
this.bookmarksPagerAdapter = bookmarksPagerAdapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
public BookmarkListRootFragment(Bundle bundle, BookmarksPagerAdapter bookmarksPagerAdapter) {
|
||||||
@Override
|
String title = bundle.getString("categoryName");
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
|
int order = bundle.getInt("order");
|
||||||
@Nullable final Bundle savedInstanceState) {
|
if (order == 0) {
|
||||||
super.onCreate(savedInstanceState);
|
listFragment = new BookmarkPicturesFragment();
|
||||||
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
|
} else {
|
||||||
ButterKnife.bind(this, view);
|
listFragment = new BookmarkLocationsFragment();
|
||||||
return view;
|
}
|
||||||
}
|
Bundle featuredArguments = new Bundle();
|
||||||
|
featuredArguments.putString("categoryName", title);
|
||||||
@Override
|
listFragment.setArguments(featuredArguments);
|
||||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
this.bookmarksPagerAdapter = bookmarksPagerAdapter;
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
if(savedInstanceState==null) {
|
|
||||||
setFragment(listFragment, mediaDetails);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
@Nullable
|
||||||
if (fragment.isAdded() && otherFragment != null) {
|
@Override
|
||||||
getChildFragmentManager()
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
.beginTransaction()
|
@Nullable final ViewGroup container,
|
||||||
.hide(otherFragment)
|
@Nullable final Bundle savedInstanceState) {
|
||||||
.show( fragment)
|
super.onCreate(savedInstanceState);
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
|
||||||
.commit();
|
ButterKnife.bind(this, view);
|
||||||
getChildFragmentManager().executePendingTransactions();
|
return view;
|
||||||
} else if (fragment.isAdded() && otherFragment == null) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.show( fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
}else if (!fragment.isAdded() && otherFragment != null ) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.hide(otherFragment)
|
|
||||||
.add(R.id.explore_container, fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
} else if (!fragment.isAdded()) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.explore_container, fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFragment(Fragment fragment) {
|
@Override
|
||||||
getChildFragmentManager()
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
.beginTransaction()
|
super.onViewCreated(view, savedInstanceState);
|
||||||
.remove(fragment)
|
if (savedInstanceState == null) {
|
||||||
.commit();
|
setFragment(listFragment, mediaDetails);
|
||||||
getChildFragmentManager().executePendingTransactions();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||||
public void onAttach(final Context context) {
|
if (fragment.isAdded() && otherFragment != null) {
|
||||||
super.onAttach(context);
|
getChildFragmentManager()
|
||||||
}
|
.beginTransaction()
|
||||||
|
.hide(otherFragment)
|
||||||
|
.show(fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
} else if (fragment.isAdded() && otherFragment == null) {
|
||||||
|
getChildFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.show(fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
} else if (!fragment.isAdded() && otherFragment != null) {
|
||||||
|
getChildFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.hide(otherFragment)
|
||||||
|
.add(R.id.explore_container, fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
} else if (!fragment.isAdded()) {
|
||||||
|
getChildFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.explore_container, fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public void removeFragment(Fragment fragment) {
|
||||||
public void onMediaClicked(int position) {
|
getChildFragmentManager()
|
||||||
Log.d("deneme8","on media clicked");
|
.beginTransaction()
|
||||||
|
.remove(fragment)
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(final Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMediaClicked(int position) {
|
||||||
|
Log.d("deneme8", "on media clicked");
|
||||||
/*container.setVisibility(View.VISIBLE);
|
/*container.setVisibility(View.VISIBLE);
|
||||||
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||||
mediaDetails = new MediaDetailPagerFragment(false, true, position);
|
mediaDetails = new MediaDetailPagerFragment(false, true, position);
|
||||||
setFragment(mediaDetails, bookmarkPicturesFragment);*/
|
setFragment(mediaDetails, bookmarkPicturesFragment);*/
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
|
||||||
*
|
|
||||||
* @param i It is the index of which media object is to be returned which is same as current
|
|
||||||
* index of viewPager.
|
|
||||||
* @return Media Object
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Media getMediaAtPosition(int i) {
|
|
||||||
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
|
|
||||||
// not yet ready to return data
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return (Media) bookmarksPagerAdapter.getMediaAdapter().getItem(i);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
|
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||||
* same number of media items as that of media elements in adapter.
|
*
|
||||||
*
|
* @param i It is the index of which media object is to be returned which is same as current
|
||||||
* @return Total Media count in the adapter
|
* index of viewPager.
|
||||||
*/
|
* @return Media Object
|
||||||
@Override
|
*/
|
||||||
public int getTotalMediaCount() {
|
@Override
|
||||||
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
|
public Media getMediaAtPosition(int i) {
|
||||||
return 0;
|
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
|
||||||
}
|
// not yet ready to return data
|
||||||
return bookmarksPagerAdapter.getMediaAdapter().getCount();
|
return null;
|
||||||
}
|
} else {
|
||||||
|
return (Media) bookmarksPagerAdapter.getMediaAdapter().getItem(i);
|
||||||
@Override
|
|
||||||
public Integer getContributionStateAt(int position) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reload media detail fragment once media is nominated
|
|
||||||
*
|
|
||||||
* @param index item position that has been nominated
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void refreshNominatedMedia(int index) {
|
|
||||||
if(mediaDetails != null && !listFragment.isVisible()) {
|
|
||||||
removeFragment(mediaDetails);
|
|
||||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
|
||||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
|
||||||
setFragment(mediaDetails, listFragment);
|
|
||||||
mediaDetails.showImage(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called on success of API call for featured images or mobile uploads. The
|
|
||||||
* viewpager will notified that number of items have changed.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void viewPagerNotifyDataSetChanged() {
|
|
||||||
if (mediaDetails != null) {
|
|
||||||
mediaDetails.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean backPressed() {
|
|
||||||
//check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
|
|
||||||
if(mediaDetails!=null) {
|
|
||||||
if (mediaDetails.isVisible()) {
|
|
||||||
if(mediaDetails.backButtonClicked()) {
|
|
||||||
// mediaDetails handled the back clicked , no further action required.
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
// todo add get list fragment
|
}
|
||||||
((BookmarkFragment) getParentFragment()).setupTabLayout();
|
|
||||||
ArrayList<Integer> removed=mediaDetails.getRemovedItems();
|
/**
|
||||||
removeFragment(mediaDetails);
|
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
|
||||||
((BookmarkFragment) getParentFragment()).setScroll(true);
|
* same number of media items as that of media elements in adapter.
|
||||||
setFragment(listFragment, mediaDetails);
|
*
|
||||||
|
* @return Total Media count in the adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (bookmarksPagerAdapter.getMediaAdapter() == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return bookmarksPagerAdapter.getMediaAdapter().getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getContributionStateAt(int position) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload media detail fragment once media is nominated
|
||||||
|
*
|
||||||
|
* @param index item position that has been nominated
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void refreshNominatedMedia(int index) {
|
||||||
|
if (mediaDetails != null && !listFragment.isVisible()) {
|
||||||
|
removeFragment(mediaDetails);
|
||||||
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
|
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||||
|
setFragment(mediaDetails, listFragment);
|
||||||
|
mediaDetails.showImage(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on success of API call for featured images or mobile uploads. The
|
||||||
|
* viewpager will notified that number of items have changed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void viewPagerNotifyDataSetChanged() {
|
||||||
|
if (mediaDetails != null) {
|
||||||
|
mediaDetails.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean backPressed() {
|
||||||
|
//check mediaDetailPage fragment is not null then we check mediaDetail.is Visible or not to avoid NullPointerException
|
||||||
|
if (mediaDetails != null) {
|
||||||
|
if (mediaDetails.isVisible()) {
|
||||||
|
if (mediaDetails.backButtonClicked()) {
|
||||||
|
// mediaDetails handled the back clicked , no further action required.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// todo add get list fragment
|
||||||
|
((BookmarkFragment) getParentFragment()).setupTabLayout();
|
||||||
|
ArrayList<Integer> removed = mediaDetails.getRemovedItems();
|
||||||
|
removeFragment(mediaDetails);
|
||||||
|
((BookmarkFragment) getParentFragment()).setScroll(true);
|
||||||
|
setFragment(listFragment, mediaDetails);
|
||||||
|
((MainActivity) getActivity()).showTabs();
|
||||||
|
if (listFragment instanceof BookmarkPicturesFragment) {
|
||||||
|
GridViewAdapter adapter = ((GridViewAdapter) ((BookmarkPicturesFragment) listFragment)
|
||||||
|
.getAdapter());
|
||||||
|
Iterator i = removed.iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
adapter.remove(adapter.getItem((int) i.next()));
|
||||||
|
}
|
||||||
|
mediaDetails.clearRemoved();
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
moveToContributionsFragment();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
moveToContributionsFragment();
|
||||||
|
}
|
||||||
|
// notify mediaDetails did not handled the backPressed further actions required.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void moveToContributionsFragment() {
|
||||||
|
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||||
((MainActivity) getActivity()).showTabs();
|
((MainActivity) getActivity()).showTabs();
|
||||||
if(listFragment instanceof BookmarkPicturesFragment){
|
|
||||||
GridViewAdapter adapter=((GridViewAdapter)((BookmarkPicturesFragment)listFragment).getAdapter());
|
|
||||||
Iterator i = removed.iterator();
|
|
||||||
while (i.hasNext()) {
|
|
||||||
adapter.remove(adapter.getItem((int)i.next()));
|
|
||||||
}
|
|
||||||
mediaDetails.clearRemoved();
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
moveToContributionsFragment();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
moveToContributionsFragment();
|
|
||||||
}
|
}
|
||||||
// notify mediaDetails did not handled the backPressed further actions required.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void moveToContributionsFragment(){
|
@Override
|
||||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
((MainActivity) getActivity()).showTabs();
|
Log.d("deneme8", "on media clicked");
|
||||||
}
|
container.setVisibility(View.VISIBLE);
|
||||||
@Override
|
((BookmarkFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
Log.d("deneme8","on media clicked");
|
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||||
container.setVisibility(View.VISIBLE);
|
setFragment(mediaDetails, listFragment);
|
||||||
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
mediaDetails.showImage(position);
|
||||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
}
|
||||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
|
||||||
setFragment(mediaDetails, listFragment);
|
|
||||||
mediaDetails.showImage(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackStackChanged() {
|
public void onBackStackChanged() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,61 +18,61 @@ import java.util.List;
|
||||||
@Dao
|
@Dao
|
||||||
public abstract class ContributionDao {
|
public abstract class ContributionDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM contribution order by media_dateUploaded DESC")
|
@Query("SELECT * FROM contribution order by media_dateUploaded DESC")
|
||||||
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
public abstract void saveSynchronous(Contribution contribution);
|
public abstract void saveSynchronous(Contribution contribution);
|
||||||
|
|
||||||
public Completable save(final Contribution contribution) {
|
public Completable save(final Contribution contribution) {
|
||||||
return Completable
|
return Completable
|
||||||
.fromAction(() -> {
|
.fromAction(() -> {
|
||||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||||
saveSynchronous(contribution);
|
saveSynchronous(contribution);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transaction
|
@Transaction
|
||||||
public void deleteAndSaveContribution(final Contribution oldContribution,
|
public void deleteAndSaveContribution(final Contribution oldContribution,
|
||||||
final Contribution newContribution) {
|
final Contribution newContribution) {
|
||||||
deleteSynchronous(oldContribution);
|
deleteSynchronous(oldContribution);
|
||||||
saveSynchronous(newContribution);
|
saveSynchronous(newContribution);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||||
|
|
||||||
@Delete
|
@Delete
|
||||||
public abstract void deleteSynchronous(Contribution contribution);
|
public abstract void deleteSynchronous(Contribution contribution);
|
||||||
|
|
||||||
public Completable delete(final Contribution contribution) {
|
public Completable delete(final Contribution contribution) {
|
||||||
return Completable
|
return Completable
|
||||||
.fromAction(() -> deleteSynchronous(contribution));
|
.fromAction(() -> deleteSynchronous(contribution));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE media_filename=:fileName")
|
@Query("SELECT * from contribution WHERE media_filename=:fileName")
|
||||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
||||||
public abstract Contribution getContribution(String pageId);
|
public abstract Contribution getContribution(String pageId);
|
||||||
|
|
||||||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||||
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
||||||
|
|
||||||
@Query("SELECT COUNT(*) from contribution WHERE state in (:toUpdateStates)")
|
@Query("SELECT COUNT(*) from contribution WHERE state in (:toUpdateStates)")
|
||||||
public abstract Single<Integer> getPendingUploads(int[] toUpdateStates);
|
public abstract Single<Integer> getPendingUploads(int[] toUpdateStates);
|
||||||
|
|
||||||
@Query("Delete FROM contribution")
|
@Query("Delete FROM contribution")
|
||||||
public abstract void deleteAll() throws SQLiteException;
|
public abstract void deleteAll() throws SQLiteException;
|
||||||
|
|
||||||
@Update
|
@Update
|
||||||
public abstract void updateSynchronous(Contribution contribution);
|
public abstract void updateSynchronous(Contribution contribution);
|
||||||
|
|
||||||
public Completable update(final Contribution contribution) {
|
public Completable update(final Contribution contribution) {
|
||||||
return Completable
|
return Completable
|
||||||
.fromAction(() -> {
|
.fromAction(() -> {
|
||||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||||
updateSynchronous(contribution);
|
updateSynchronous(contribution);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,244 +24,243 @@ import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private final Callback callback;
|
private final Callback callback;
|
||||||
@BindView(R.id.contributionImage)
|
@BindView(R.id.contributionImage)
|
||||||
SimpleDraweeView imageView;
|
SimpleDraweeView imageView;
|
||||||
@BindView(R.id.contributionTitle)
|
@BindView(R.id.contributionTitle)
|
||||||
TextView titleView;
|
TextView titleView;
|
||||||
@BindView(R.id.authorView)
|
@BindView(R.id.authorView)
|
||||||
TextView authorView;
|
TextView authorView;
|
||||||
@BindView(R.id.contributionState)
|
@BindView(R.id.contributionState)
|
||||||
TextView stateView;
|
TextView stateView;
|
||||||
@BindView(R.id.contributionSequenceNumber)
|
@BindView(R.id.contributionSequenceNumber)
|
||||||
TextView seqNumView;
|
TextView seqNumView;
|
||||||
@BindView(R.id.contributionProgress)
|
@BindView(R.id.contributionProgress)
|
||||||
ProgressBar progressView;
|
ProgressBar progressView;
|
||||||
@BindView(R.id.image_options)
|
@BindView(R.id.image_options)
|
||||||
RelativeLayout imageOptions;
|
RelativeLayout imageOptions;
|
||||||
@BindView(R.id.wikipediaButton)
|
@BindView(R.id.wikipediaButton)
|
||||||
ImageButton addToWikipediaButton;
|
ImageButton addToWikipediaButton;
|
||||||
@BindView(R.id.retryButton)
|
@BindView(R.id.retryButton)
|
||||||
ImageButton retryButton;
|
ImageButton retryButton;
|
||||||
@BindView(R.id.cancelButton)
|
@BindView(R.id.cancelButton)
|
||||||
ImageButton cancelButton;
|
ImageButton cancelButton;
|
||||||
@BindView(R.id.pauseResumeButton)
|
@BindView(R.id.pauseResumeButton)
|
||||||
ImageButton pauseResumeButton;
|
ImageButton pauseResumeButton;
|
||||||
|
|
||||||
|
|
||||||
private int position;
|
private int position;
|
||||||
private Contribution contribution;
|
private Contribution contribution;
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
private final MediaClient mediaClient;
|
private final MediaClient mediaClient;
|
||||||
private boolean isWikipediaButtonDisplayed;
|
private boolean isWikipediaButtonDisplayed;
|
||||||
|
|
||||||
ContributionViewHolder(final View parent, final Callback callback,
|
ContributionViewHolder(final View parent, final Callback callback,
|
||||||
final MediaClient mediaClient) {
|
final MediaClient mediaClient) {
|
||||||
super(parent);
|
super(parent);
|
||||||
this.mediaClient = mediaClient;
|
this.mediaClient = mediaClient;
|
||||||
ButterKnife.bind(this, parent);
|
ButterKnife.bind(this, parent);
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
|
||||||
|
|
||||||
public void init(final int position, final Contribution contribution) {
|
|
||||||
|
|
||||||
//handling crashes when the contribution is null.
|
|
||||||
if( null == contribution) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.contribution = contribution;
|
public void init(final int position, final Contribution contribution) {
|
||||||
this.position = position;
|
|
||||||
titleView.setText(contribution.getMedia().getMostRelevantCaption());
|
|
||||||
authorView.setText(contribution.getMedia().getAuthor());
|
|
||||||
|
|
||||||
//Removes flicker of loading image.
|
//handling crashes when the contribution is null.
|
||||||
imageView.getHierarchy().setFadeDuration(0);
|
if (null == contribution) {
|
||||||
|
return;
|
||||||
imageView.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
|
||||||
imageView.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
|
||||||
|
|
||||||
|
|
||||||
final String imageSource = chooseImageSource(contribution.getMedia().getThumbUrl(),
|
|
||||||
contribution.getLocalUri());
|
|
||||||
if (!TextUtils.isEmpty(imageSource)) {
|
|
||||||
final ImageRequest imageRequest =
|
|
||||||
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
|
||||||
.setProgressiveRenderingEnabled(true)
|
|
||||||
.build();
|
|
||||||
imageView.setImageRequest(imageRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
seqNumView.setText(String.valueOf(position + 1));
|
|
||||||
seqNumView.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
addToWikipediaButton.setVisibility(View.GONE);
|
|
||||||
switch (contribution.getState()) {
|
|
||||||
case Contribution.STATE_COMPLETED:
|
|
||||||
stateView.setVisibility(View.GONE);
|
|
||||||
progressView.setVisibility(View.GONE);
|
|
||||||
imageOptions.setVisibility(View.GONE);
|
|
||||||
stateView.setText("");
|
|
||||||
checkIfMediaExistsOnWikipediaPage(contribution);
|
|
||||||
break;
|
|
||||||
case Contribution.STATE_QUEUED:
|
|
||||||
case Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE:
|
|
||||||
stateView.setVisibility(View.VISIBLE);
|
|
||||||
progressView.setVisibility(View.GONE);
|
|
||||||
stateView.setText(R.string.contribution_state_queued);
|
|
||||||
imageOptions.setVisibility(View.GONE);
|
|
||||||
break;
|
|
||||||
case Contribution.STATE_IN_PROGRESS:
|
|
||||||
stateView.setVisibility(View.GONE);
|
|
||||||
progressView.setVisibility(View.VISIBLE);
|
|
||||||
addToWikipediaButton.setVisibility(View.GONE);
|
|
||||||
pauseResumeButton.setVisibility(View.VISIBLE);
|
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
retryButton.setVisibility(View.GONE);
|
|
||||||
imageOptions.setVisibility(View.VISIBLE);
|
|
||||||
final long total = contribution.getDataLength();
|
|
||||||
final long transferred = contribution.getTransferred();
|
|
||||||
if (transferred == 0 || transferred >= total) {
|
|
||||||
progressView.setIndeterminate(true);
|
|
||||||
} else {
|
|
||||||
progressView.setProgress((int) (((double) transferred / (double) total) * 100));
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
case Contribution.STATE_PAUSED:
|
this.contribution = contribution;
|
||||||
stateView.setVisibility(View.VISIBLE);
|
this.position = position;
|
||||||
stateView.setText(R.string.paused);
|
titleView.setText(contribution.getMedia().getMostRelevantCaption());
|
||||||
|
authorView.setText(contribution.getMedia().getAuthor());
|
||||||
|
|
||||||
|
//Removes flicker of loading image.
|
||||||
|
imageView.getHierarchy().setFadeDuration(0);
|
||||||
|
|
||||||
|
imageView.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
||||||
|
imageView.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
||||||
|
|
||||||
|
final String imageSource = chooseImageSource(contribution.getMedia().getThumbUrl(),
|
||||||
|
contribution.getLocalUri());
|
||||||
|
if (!TextUtils.isEmpty(imageSource)) {
|
||||||
|
final ImageRequest imageRequest =
|
||||||
|
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
|
||||||
|
.setProgressiveRenderingEnabled(true)
|
||||||
|
.build();
|
||||||
|
imageView.setImageRequest(imageRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
seqNumView.setText(String.valueOf(position + 1));
|
||||||
|
seqNumView.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
addToWikipediaButton.setVisibility(View.GONE);
|
||||||
|
switch (contribution.getState()) {
|
||||||
|
case Contribution.STATE_COMPLETED:
|
||||||
|
stateView.setVisibility(View.GONE);
|
||||||
|
progressView.setVisibility(View.GONE);
|
||||||
|
imageOptions.setVisibility(View.GONE);
|
||||||
|
stateView.setText("");
|
||||||
|
checkIfMediaExistsOnWikipediaPage(contribution);
|
||||||
|
break;
|
||||||
|
case Contribution.STATE_QUEUED:
|
||||||
|
case Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE:
|
||||||
|
stateView.setVisibility(View.VISIBLE);
|
||||||
|
progressView.setVisibility(View.GONE);
|
||||||
|
stateView.setText(R.string.contribution_state_queued);
|
||||||
|
imageOptions.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
case Contribution.STATE_IN_PROGRESS:
|
||||||
|
stateView.setVisibility(View.GONE);
|
||||||
|
progressView.setVisibility(View.VISIBLE);
|
||||||
|
addToWikipediaButton.setVisibility(View.GONE);
|
||||||
|
pauseResumeButton.setVisibility(View.VISIBLE);
|
||||||
|
cancelButton.setVisibility(View.GONE);
|
||||||
|
retryButton.setVisibility(View.GONE);
|
||||||
|
imageOptions.setVisibility(View.VISIBLE);
|
||||||
|
final long total = contribution.getDataLength();
|
||||||
|
final long transferred = contribution.getTransferred();
|
||||||
|
if (transferred == 0 || transferred >= total) {
|
||||||
|
progressView.setIndeterminate(true);
|
||||||
|
} else {
|
||||||
|
progressView.setProgress((int) (((double) transferred / (double) total) * 100));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Contribution.STATE_PAUSED:
|
||||||
|
stateView.setVisibility(View.VISIBLE);
|
||||||
|
stateView.setText(R.string.paused);
|
||||||
|
setResume();
|
||||||
|
progressView.setVisibility(View.GONE);
|
||||||
|
cancelButton.setVisibility(View.GONE);
|
||||||
|
retryButton.setVisibility(View.GONE);
|
||||||
|
pauseResumeButton.setVisibility(View.VISIBLE);
|
||||||
|
imageOptions.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case Contribution.STATE_FAILED:
|
||||||
|
stateView.setVisibility(View.VISIBLE);
|
||||||
|
stateView.setText(R.string.contribution_state_failed);
|
||||||
|
progressView.setVisibility(View.GONE);
|
||||||
|
cancelButton.setVisibility(View.VISIBLE);
|
||||||
|
retryButton.setVisibility(View.VISIBLE);
|
||||||
|
pauseResumeButton.setVisibility(View.GONE);
|
||||||
|
imageOptions.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made
|
||||||
|
* for the device's current language Wikipedia
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
private void checkIfMediaExistsOnWikipediaPage(final Contribution contribution) {
|
||||||
|
if (contribution.getWikidataPlace() == null
|
||||||
|
|| contribution.getWikidataPlace().getWikipediaArticle() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String wikipediaArticle = contribution.getWikidataPlace().getWikipediaPageTitle();
|
||||||
|
compositeDisposable.add(mediaClient.doesPageContainMedia(wikipediaArticle)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(mediaExists -> {
|
||||||
|
displayWikipediaButton(mediaExists);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle action buttons visibility if the corresponding wikipedia page doesn't contain any
|
||||||
|
* media. This method needs to control the state of just the scenario where media does not
|
||||||
|
* exists as other scenarios are already handled in the init method.
|
||||||
|
*
|
||||||
|
* @param mediaExists
|
||||||
|
*/
|
||||||
|
private void displayWikipediaButton(Boolean mediaExists) {
|
||||||
|
if (!mediaExists) {
|
||||||
|
addToWikipediaButton.setVisibility(View.VISIBLE);
|
||||||
|
isWikipediaButtonDisplayed = true;
|
||||||
|
cancelButton.setVisibility(View.GONE);
|
||||||
|
retryButton.setVisibility(View.GONE);
|
||||||
|
imageOptions.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the image source for the image view, first preference is given to thumbUrl if that is
|
||||||
|
* null, moves to local uri and if both are null return null
|
||||||
|
*
|
||||||
|
* @param thumbUrl
|
||||||
|
* @param localUri
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
|
||||||
|
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
||||||
|
localUri != null ? localUri.toString() :
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retry upload when it is failed
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.retryButton)
|
||||||
|
public void retryUpload() {
|
||||||
|
callback.retryUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a failed upload attempt
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.cancelButton)
|
||||||
|
public void deleteUpload() {
|
||||||
|
callback.deleteUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.contributionImage)
|
||||||
|
public void imageClicked() {
|
||||||
|
callback.openMediaDetail(position, isWikipediaButtonDisplayed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.wikipediaButton)
|
||||||
|
public void wikipediaButtonClicked() {
|
||||||
|
callback.addImageToWikipedia(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers a callback for pause/resume
|
||||||
|
*/
|
||||||
|
@OnClick(R.id.pauseResumeButton)
|
||||||
|
public void onPauseResumeButtonClicked() {
|
||||||
|
if (pauseResumeButton.getTag().toString().equals("pause")) {
|
||||||
|
pause();
|
||||||
|
} else {
|
||||||
|
resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resume() {
|
||||||
|
callback.resumeUpload(contribution);
|
||||||
|
setPaused();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pause() {
|
||||||
|
callback.pauseUpload(contribution);
|
||||||
setResume();
|
setResume();
|
||||||
progressView.setVisibility(View.GONE);
|
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
retryButton.setVisibility(View.GONE);
|
|
||||||
pauseResumeButton.setVisibility(View.VISIBLE);
|
|
||||||
imageOptions.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
case Contribution.STATE_FAILED:
|
|
||||||
stateView.setVisibility(View.VISIBLE);
|
|
||||||
stateView.setText(R.string.contribution_state_failed);
|
|
||||||
progressView.setVisibility(View.GONE);
|
|
||||||
cancelButton.setVisibility(View.VISIBLE);
|
|
||||||
retryButton.setVisibility(View.VISIBLE);
|
|
||||||
pauseResumeButton.setVisibility(View.GONE);
|
|
||||||
imageOptions.setVisibility(View.VISIBLE);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made for
|
* Update pause/resume button to show pause state
|
||||||
* the device's current language Wikipedia
|
*/
|
||||||
*
|
private void setPaused() {
|
||||||
* @param contribution
|
pauseResumeButton.setImageResource(R.drawable.pause_icon);
|
||||||
*/
|
pauseResumeButton.setTag(R.string.pause);
|
||||||
private void checkIfMediaExistsOnWikipediaPage(final Contribution contribution) {
|
|
||||||
if (contribution.getWikidataPlace() == null
|
|
||||||
|| contribution.getWikidataPlace().getWikipediaArticle() == null) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
final String wikipediaArticle = contribution.getWikidataPlace().getWikipediaPageTitle();
|
|
||||||
compositeDisposable.add(mediaClient.doesPageContainMedia(wikipediaArticle)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(mediaExists -> {
|
|
||||||
displayWikipediaButton(mediaExists);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle action buttons visibility if the corresponding wikipedia page doesn't contain any media.
|
* Update pause/resume button to show resume state
|
||||||
* This method needs to control the state of just the scenario where media does not exists as
|
*/
|
||||||
* other scenarios are already handled in the init method.
|
private void setResume() {
|
||||||
*
|
pauseResumeButton.setImageResource(R.drawable.play_icon);
|
||||||
* @param mediaExists
|
pauseResumeButton.setTag(R.string.resume);
|
||||||
*/
|
|
||||||
private void displayWikipediaButton(Boolean mediaExists) {
|
|
||||||
if (!mediaExists) {
|
|
||||||
addToWikipediaButton.setVisibility(View.VISIBLE);
|
|
||||||
isWikipediaButtonDisplayed = true;
|
|
||||||
cancelButton.setVisibility(View.GONE);
|
|
||||||
retryButton.setVisibility(View.GONE);
|
|
||||||
imageOptions.setVisibility(View.VISIBLE);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the image source for the image view, first preference is given to thumbUrl if that is
|
|
||||||
* null, moves to local uri and if both are null return null
|
|
||||||
*
|
|
||||||
* @param thumbUrl
|
|
||||||
* @param localUri
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private String chooseImageSource(final String thumbUrl, final Uri localUri) {
|
|
||||||
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
|
|
||||||
localUri != null ? localUri.toString() :
|
|
||||||
null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retry upload when it is failed
|
|
||||||
*/
|
|
||||||
@OnClick(R.id.retryButton)
|
|
||||||
public void retryUpload() {
|
|
||||||
callback.retryUpload(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a failed upload attempt
|
|
||||||
*/
|
|
||||||
@OnClick(R.id.cancelButton)
|
|
||||||
public void deleteUpload() {
|
|
||||||
callback.deleteUpload(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.contributionImage)
|
|
||||||
public void imageClicked() {
|
|
||||||
callback.openMediaDetail(position, isWikipediaButtonDisplayed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.wikipediaButton)
|
|
||||||
public void wikipediaButtonClicked() {
|
|
||||||
callback.addImageToWikipedia(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers a callback for pause/resume
|
|
||||||
*/
|
|
||||||
@OnClick(R.id.pauseResumeButton)
|
|
||||||
public void onPauseResumeButtonClicked() {
|
|
||||||
if (pauseResumeButton.getTag().toString().equals("pause")) {
|
|
||||||
pause();
|
|
||||||
} else {
|
|
||||||
resume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resume() {
|
|
||||||
callback.resumeUpload(contribution);
|
|
||||||
setPaused();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pause() {
|
|
||||||
callback.pauseUpload(contribution);
|
|
||||||
setResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update pause/resume button to show pause state
|
|
||||||
*/
|
|
||||||
private void setPaused() {
|
|
||||||
pauseResumeButton.setImageResource(R.drawable.pause_icon);
|
|
||||||
pauseResumeButton.setTag(R.string.pause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update pause/resume button to show resume state
|
|
||||||
*/
|
|
||||||
private void setResume() {
|
|
||||||
pauseResumeButton.setImageResource(R.drawable.play_icon);
|
|
||||||
pauseResumeButton.setTag(R.string.resume);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,17 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class ContributionsListContract {
|
public class ContributionsListContract {
|
||||||
|
|
||||||
public interface View {
|
public interface View {
|
||||||
|
|
||||||
void showWelcomeTip(boolean numberOfUploads);
|
void showWelcomeTip(boolean numberOfUploads);
|
||||||
|
|
||||||
void showProgress(boolean shouldShow);
|
void showProgress(boolean shouldShow);
|
||||||
|
|
||||||
void showNoContributionsUI(boolean shouldShow);
|
void showNoContributionsUI(boolean shouldShow);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface UserActionListener extends BasePresenter<View> {
|
public interface UserActionListener extends BasePresenter<View> {
|
||||||
|
|
||||||
void deleteUpload(Contribution contribution);
|
void deleteUpload(Contribution contribution);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,384 +51,388 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
||||||
ContributionsListContract.View, ContributionsListAdapter.Callback,
|
ContributionsListContract.View, ContributionsListAdapter.Callback,
|
||||||
WikipediaInstructionsDialogFragment.Callback {
|
WikipediaInstructionsDialogFragment.Callback {
|
||||||
|
|
||||||
private static final String RV_STATE = "rv_scroll_state";
|
private static final String RV_STATE = "rv_scroll_state";
|
||||||
|
|
||||||
@BindView(R.id.contributionsList)
|
@BindView(R.id.contributionsList)
|
||||||
RecyclerView rvContributionsList;
|
RecyclerView rvContributionsList;
|
||||||
@BindView(R.id.loadingContributionsProgressBar)
|
@BindView(R.id.loadingContributionsProgressBar)
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
@BindView(R.id.fab_plus)
|
@BindView(R.id.fab_plus)
|
||||||
FloatingActionButton fabPlus;
|
FloatingActionButton fabPlus;
|
||||||
@BindView(R.id.fab_camera)
|
@BindView(R.id.fab_camera)
|
||||||
FloatingActionButton fabCamera;
|
FloatingActionButton fabCamera;
|
||||||
@BindView(R.id.fab_gallery)
|
@BindView(R.id.fab_gallery)
|
||||||
FloatingActionButton fabGallery;
|
FloatingActionButton fabGallery;
|
||||||
@BindView(R.id.noContributionsYet)
|
@BindView(R.id.noContributionsYet)
|
||||||
TextView noContributionsYet;
|
TextView noContributionsYet;
|
||||||
@BindView(R.id.fab_layout)
|
@BindView(R.id.fab_layout)
|
||||||
LinearLayout fab_layout;
|
LinearLayout fab_layout;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionController controller;
|
ContributionController controller;
|
||||||
@Inject
|
@Inject
|
||||||
MediaClient mediaClient;
|
MediaClient mediaClient;
|
||||||
|
|
||||||
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
||||||
@Inject
|
@Inject
|
||||||
WikiSite languageWikipediaSite;
|
WikiSite languageWikipediaSite;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionsListPresenter contributionsListPresenter;
|
ContributionsListPresenter contributionsListPresenter;
|
||||||
|
|
||||||
private Animation fab_close;
|
private Animation fab_close;
|
||||||
private Animation fab_open;
|
private Animation fab_open;
|
||||||
private Animation rotate_forward;
|
private Animation rotate_forward;
|
||||||
private Animation rotate_backward;
|
private Animation rotate_backward;
|
||||||
|
|
||||||
|
|
||||||
private boolean isFabOpen;
|
private boolean isFabOpen;
|
||||||
|
|
||||||
private ContributionsListAdapter adapter;
|
private ContributionsListAdapter adapter;
|
||||||
|
|
||||||
private Callback callback;
|
private Callback callback;
|
||||||
|
|
||||||
private final int SPAN_COUNT_LANDSCAPE = 3;
|
private final int SPAN_COUNT_LANDSCAPE = 3;
|
||||||
private final int SPAN_COUNT_PORTRAIT = 1;
|
private final int SPAN_COUNT_PORTRAIT = 1;
|
||||||
|
|
||||||
private int contributionsSize;
|
private int contributionsSize;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(
|
public View onCreateView(
|
||||||
final LayoutInflater inflater, @Nullable final ViewGroup container,
|
final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||||
@Nullable final Bundle savedInstanceState) {
|
@Nullable final Bundle savedInstanceState) {
|
||||||
final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
contributionsListPresenter.onAttachView(this);
|
contributionsListPresenter.onAttachView(this);
|
||||||
initAdapter();
|
initAdapter();
|
||||||
return view;
|
return view;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAttach(Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
if (getParentFragment() != null && getParentFragment() instanceof ContributionsFragment) {
|
|
||||||
callback = ((ContributionsFragment) getParentFragment());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
super.onDetach();
|
|
||||||
callback = null;//To avoid possible memory leak
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initAdapter() {
|
|
||||||
adapter = new ContributionsListAdapter(this, mediaClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
initRecyclerView();
|
|
||||||
initializeAnimations();
|
|
||||||
setListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initRecyclerView() {
|
|
||||||
final GridLayoutManager layoutManager = new GridLayoutManager(getContext(),
|
|
||||||
getSpanCount(getResources().getConfiguration().orientation));
|
|
||||||
rvContributionsList.setLayoutManager(layoutManager);
|
|
||||||
|
|
||||||
//Setting flicker animation of recycler view to false.
|
|
||||||
final ItemAnimator animator = rvContributionsList.getItemAnimator();
|
|
||||||
if (animator instanceof SimpleItemAnimator) {
|
|
||||||
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contributionsListPresenter.setup();
|
@Override
|
||||||
contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), list -> {
|
public void onAttach(Context context) {
|
||||||
contributionsSize = list.size();
|
super.onAttach(context);
|
||||||
adapter.submitList(list);
|
if (getParentFragment() != null && getParentFragment() instanceof ContributionsFragment) {
|
||||||
callback.notifyDataSetChanged();
|
callback = ((ContributionsFragment) getParentFragment());
|
||||||
});
|
|
||||||
rvContributionsList.setAdapter(adapter);
|
|
||||||
adapter.registerAdapterDataObserver(new AdapterDataObserver() {
|
|
||||||
@Override
|
|
||||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
|
||||||
super.onItemRangeInserted(positionStart, itemCount);
|
|
||||||
if (itemCount > 0 && positionStart == 0) {
|
|
||||||
if(adapter.getContributionForPosition(positionStart)!=null) {
|
|
||||||
rvContributionsList.scrollToPosition(0);//Newly upload items are always added to the top
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Called whenever items in the list have changed
|
public void onDetach() {
|
||||||
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
|
super.onDetach();
|
||||||
*/
|
callback = null;//To avoid possible memory leak
|
||||||
@Override
|
}
|
||||||
public void onItemRangeChanged(final int positionStart, final int itemCount) {
|
|
||||||
super.onItemRangeChanged(positionStart, itemCount);
|
|
||||||
callback.viewPagerNotifyDataSetChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Fab close on touch outside (Scrolling or taping on item triggers this action).
|
private void initAdapter() {
|
||||||
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() {
|
adapter = new ContributionsListAdapter(this, mediaClient);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Silently observe and/or take over touch events sent to the RecyclerView before
|
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
* they are handled by either the RecyclerView itself or its child views.
|
super.onViewCreated(view, savedInstanceState);
|
||||||
*/
|
initRecyclerView();
|
||||||
@Override
|
initializeAnimations();
|
||||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
setListeners();
|
||||||
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
}
|
||||||
if (isFabOpen) {
|
|
||||||
animateFAB(isFabOpen);
|
private void initRecyclerView() {
|
||||||
}
|
final GridLayoutManager layoutManager = new GridLayoutManager(getContext(),
|
||||||
|
getSpanCount(getResources().getConfiguration().orientation));
|
||||||
|
rvContributionsList.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
|
//Setting flicker animation of recycler view to false.
|
||||||
|
final ItemAnimator animator = rvContributionsList.getItemAnimator();
|
||||||
|
if (animator instanceof SimpleItemAnimator) {
|
||||||
|
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
contributionsListPresenter.setup();
|
||||||
* Process a touch event as part of a gesture that was claimed by returning true
|
contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), list -> {
|
||||||
* from a previous call to {@link #onInterceptTouchEvent}.
|
contributionsSize = list.size();
|
||||||
*
|
adapter.submitList(list);
|
||||||
* @param rv
|
callback.notifyDataSetChanged();
|
||||||
* @param e MotionEvent describing the touch event. All coordinates are in the
|
|
||||||
* RecyclerView's coordinate system.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
|
||||||
//required abstract method DO NOT DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a child of RecyclerView does not want RecyclerView and its ancestors
|
|
||||||
* to intercept touch events with {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
|
|
||||||
*
|
|
||||||
* @param disallowIntercept True if the child does not want the parent to intercept
|
|
||||||
* touch events.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
|
||||||
//required abstract method DO NOT DELETE
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getSpanCount(final int orientation) {
|
|
||||||
return orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
|
||||||
SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(final Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
// check orientation
|
|
||||||
fab_layout.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
|
||||||
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
|
|
||||||
rvContributionsList
|
|
||||||
.setLayoutManager(new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeAnimations() {
|
|
||||||
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
|
||||||
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
|
||||||
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
|
||||||
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setListeners() {
|
|
||||||
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
|
||||||
fabCamera.setOnClickListener(view -> {
|
|
||||||
controller.initiateCameraPick(getActivity());
|
|
||||||
animateFAB(isFabOpen);
|
|
||||||
});
|
|
||||||
fabGallery.setOnClickListener(view -> {
|
|
||||||
controller.initiateGalleryPick(getActivity(), true);
|
|
||||||
animateFAB(isFabOpen);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animateFAB(final boolean isFabOpen) {
|
|
||||||
this.isFabOpen = !isFabOpen;
|
|
||||||
if (fabPlus.isShown()) {
|
|
||||||
if (isFabOpen) {
|
|
||||||
fabPlus.startAnimation(rotate_backward);
|
|
||||||
fabCamera.startAnimation(fab_close);
|
|
||||||
fabGallery.startAnimation(fab_close);
|
|
||||||
fabCamera.hide();
|
|
||||||
fabGallery.hide();
|
|
||||||
} else {
|
|
||||||
fabPlus.startAnimation(rotate_forward);
|
|
||||||
fabCamera.startAnimation(fab_open);
|
|
||||||
fabGallery.startAnimation(fab_open);
|
|
||||||
fabCamera.show();
|
|
||||||
fabGallery.show();
|
|
||||||
}
|
|
||||||
this.isFabOpen = !isFabOpen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows welcome message if user has no contributions yet i.e. new user.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void showWelcomeTip(final boolean shouldShow) {
|
|
||||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Responsible to set progress bar invisible and visible
|
|
||||||
*
|
|
||||||
* @param shouldShow True when contributions list should be hidden.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void showProgress(final boolean shouldShow) {
|
|
||||||
progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showNoContributionsUI(final boolean shouldShow) {
|
|
||||||
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList
|
|
||||||
.getLayoutManager();
|
|
||||||
outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onViewStateRestored(savedInstanceState);
|
|
||||||
if (null != savedInstanceState) {
|
|
||||||
final Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE);
|
|
||||||
rvContributionsList.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void retryUpload(final Contribution contribution) {
|
|
||||||
if (null != callback) {//Just being safe, ideally they won't be called when detached
|
|
||||||
callback.retryUpload(contribution);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteUpload(final Contribution contribution) {
|
|
||||||
contributionsListPresenter.deleteUpload(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void openMediaDetail(final int position, boolean isWikipediaButtonDisplayed) {
|
|
||||||
if (null != callback) {//Just being safe, ideally they won't be called when detached
|
|
||||||
callback.showDetail(position, isWikipediaButtonDisplayed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle callback for wikipedia icon clicked
|
|
||||||
*
|
|
||||||
* @param contribution
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addImageToWikipedia(Contribution contribution) {
|
|
||||||
DialogUtil.showAlertDialog(getActivity(),
|
|
||||||
getString(R.string.add_picture_to_wikipedia_article_title),
|
|
||||||
String.format(getString(R.string.add_picture_to_wikipedia_article_desc),
|
|
||||||
Locale.getDefault().getDisplayLanguage()),
|
|
||||||
() -> {
|
|
||||||
showAddImageToWikipediaInstructions(contribution);
|
|
||||||
}, () -> {
|
|
||||||
// do nothing
|
|
||||||
});
|
});
|
||||||
}
|
rvContributionsList.setAdapter(adapter);
|
||||||
|
adapter.registerAdapterDataObserver(new AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
|
super.onItemRangeInserted(positionStart, itemCount);
|
||||||
|
if (itemCount > 0 && positionStart == 0) {
|
||||||
|
if (adapter.getContributionForPosition(positionStart) != null) {
|
||||||
|
rvContributionsList
|
||||||
|
.scrollToPosition(0);//Newly upload items are always added to the top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pauses the current upload
|
* Called whenever items in the list have changed
|
||||||
* @param contribution
|
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void pauseUpload(Contribution contribution) {
|
public void onItemRangeChanged(final int positionStart, final int itemCount) {
|
||||||
ViewUtil.showShortToast(getContext(), R.string.pausing_upload);
|
super.onItemRangeChanged(positionStart, itemCount);
|
||||||
callback.pauseUpload(contribution);
|
callback.viewPagerNotifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
//Fab close on touch outside (Scrolling or taping on item triggers this action).
|
||||||
* Resumes the current upload
|
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() {
|
||||||
* @param contribution
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void resumeUpload(Contribution contribution) {
|
|
||||||
ViewUtil.showShortToast(getContext(), R.string.resuming_upload);
|
|
||||||
callback.retryUpload(contribution);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display confirmation dialog with instructions when the user tries to add image to wikipedia
|
* Silently observe and/or take over touch events sent to the RecyclerView before
|
||||||
*
|
* they are handled by either the RecyclerView itself or its child views.
|
||||||
* @param contribution
|
*/
|
||||||
*/
|
@Override
|
||||||
private void showAddImageToWikipediaInstructions(Contribution contribution) {
|
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||||
FragmentManager fragmentManager = getFragmentManager();
|
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
||||||
WikipediaInstructionsDialogFragment fragment = WikipediaInstructionsDialogFragment
|
if (isFabOpen) {
|
||||||
.newInstance(contribution);
|
animateFAB(isFabOpen);
|
||||||
fragment.setCallback(this::onConfirmClicked);
|
}
|
||||||
fragment.show(fragmentManager, "WikimediaFragment");
|
}
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a touch event as part of a gesture that was claimed by returning true
|
||||||
|
* from a previous call to {@link #onInterceptTouchEvent}.
|
||||||
|
*
|
||||||
|
* @param rv
|
||||||
|
* @param e MotionEvent describing the touch event. All coordinates are in the
|
||||||
|
* RecyclerView's coordinate system.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||||
|
//required abstract method DO NOT DELETE
|
||||||
|
}
|
||||||
|
|
||||||
public Media getMediaAtPosition(final int i) {
|
/**
|
||||||
if(adapter.getContributionForPosition(i) != null) {
|
* Called when a child of RecyclerView does not want RecyclerView and its ancestors
|
||||||
return adapter.getContributionForPosition(i).getMedia();
|
* to intercept touch events with {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
|
||||||
}
|
*
|
||||||
return null;
|
* @param disallowIntercept True if the child does not want the parent to intercept
|
||||||
}
|
* touch events.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||||
|
//required abstract method DO NOT DELETE
|
||||||
|
}
|
||||||
|
|
||||||
public int getTotalMediaCount() {
|
});
|
||||||
return contributionsSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open the editor for the language Wikipedia
|
|
||||||
*
|
|
||||||
* @param contribution
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) {
|
|
||||||
if (copyWikicode) {
|
|
||||||
String wikicode = contribution.getMedia().getWikiCode();
|
|
||||||
Utils.copy("wikicode", wikicode, getContext());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String url =
|
private int getSpanCount(final int orientation) {
|
||||||
languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace()
|
return orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||||
.getWikipediaPageTitle();
|
SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
|
||||||
Utils.handleWebUrl(getContext(), Uri.parse(url));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Integer getContributionStateAt(int position) {
|
@Override
|
||||||
return adapter.getContributionForPosition(position).getState();
|
public void onConfigurationChanged(final Configuration newConfig) {
|
||||||
}
|
super.onConfigurationChanged(newConfig);
|
||||||
|
// check orientation
|
||||||
|
fab_layout.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||||
|
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
|
||||||
|
rvContributionsList
|
||||||
|
.setLayoutManager(
|
||||||
|
new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation)));
|
||||||
|
}
|
||||||
|
|
||||||
public interface Callback {
|
private void initializeAnimations() {
|
||||||
|
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open);
|
||||||
|
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close);
|
||||||
|
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward);
|
||||||
|
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
|
||||||
|
}
|
||||||
|
|
||||||
void notifyDataSetChanged();
|
private void setListeners() {
|
||||||
|
fabPlus.setOnClickListener(view -> animateFAB(isFabOpen));
|
||||||
|
fabCamera.setOnClickListener(view -> {
|
||||||
|
controller.initiateCameraPick(getActivity());
|
||||||
|
animateFAB(isFabOpen);
|
||||||
|
});
|
||||||
|
fabGallery.setOnClickListener(view -> {
|
||||||
|
controller.initiateGalleryPick(getActivity(), true);
|
||||||
|
animateFAB(isFabOpen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void retryUpload(Contribution contribution);
|
private void animateFAB(final boolean isFabOpen) {
|
||||||
|
this.isFabOpen = !isFabOpen;
|
||||||
|
if (fabPlus.isShown()) {
|
||||||
|
if (isFabOpen) {
|
||||||
|
fabPlus.startAnimation(rotate_backward);
|
||||||
|
fabCamera.startAnimation(fab_close);
|
||||||
|
fabGallery.startAnimation(fab_close);
|
||||||
|
fabCamera.hide();
|
||||||
|
fabGallery.hide();
|
||||||
|
} else {
|
||||||
|
fabPlus.startAnimation(rotate_forward);
|
||||||
|
fabCamera.startAnimation(fab_open);
|
||||||
|
fabGallery.startAnimation(fab_open);
|
||||||
|
fabCamera.show();
|
||||||
|
fabGallery.show();
|
||||||
|
}
|
||||||
|
this.isFabOpen = !isFabOpen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void showDetail(int position, boolean isWikipediaButtonDisplayed);
|
/**
|
||||||
|
* Shows welcome message if user has no contributions yet i.e. new user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showWelcomeTip(final boolean shouldShow) {
|
||||||
|
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
void pauseUpload(Contribution contribution);
|
/**
|
||||||
|
* Responsible to set progress bar invisible and visible
|
||||||
|
*
|
||||||
|
* @param shouldShow True when contributions list should be hidden.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void showProgress(final boolean shouldShow) {
|
||||||
|
progressBar.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
// Notify the viewpager that number of items have changed.
|
@Override
|
||||||
void viewPagerNotifyDataSetChanged();
|
public void showNoContributionsUI(final boolean shouldShow) {
|
||||||
}
|
noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList
|
||||||
|
.getLayoutManager();
|
||||||
|
outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewStateRestored(savedInstanceState);
|
||||||
|
if (null != savedInstanceState) {
|
||||||
|
final Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE);
|
||||||
|
rvContributionsList.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void retryUpload(final Contribution contribution) {
|
||||||
|
if (null != callback) {//Just being safe, ideally they won't be called when detached
|
||||||
|
callback.retryUpload(contribution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteUpload(final Contribution contribution) {
|
||||||
|
contributionsListPresenter.deleteUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void openMediaDetail(final int position, boolean isWikipediaButtonDisplayed) {
|
||||||
|
if (null != callback) {//Just being safe, ideally they won't be called when detached
|
||||||
|
callback.showDetail(position, isWikipediaButtonDisplayed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle callback for wikipedia icon clicked
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addImageToWikipedia(Contribution contribution) {
|
||||||
|
DialogUtil.showAlertDialog(getActivity(),
|
||||||
|
getString(R.string.add_picture_to_wikipedia_article_title),
|
||||||
|
String.format(getString(R.string.add_picture_to_wikipedia_article_desc),
|
||||||
|
Locale.getDefault().getDisplayLanguage()),
|
||||||
|
() -> {
|
||||||
|
showAddImageToWikipediaInstructions(contribution);
|
||||||
|
}, () -> {
|
||||||
|
// do nothing
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the current upload
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void pauseUpload(Contribution contribution) {
|
||||||
|
ViewUtil.showShortToast(getContext(), R.string.pausing_upload);
|
||||||
|
callback.pauseUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the current upload
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void resumeUpload(Contribution contribution) {
|
||||||
|
ViewUtil.showShortToast(getContext(), R.string.resuming_upload);
|
||||||
|
callback.retryUpload(contribution);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display confirmation dialog with instructions when the user tries to add image to wikipedia
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
private void showAddImageToWikipediaInstructions(Contribution contribution) {
|
||||||
|
FragmentManager fragmentManager = getFragmentManager();
|
||||||
|
WikipediaInstructionsDialogFragment fragment = WikipediaInstructionsDialogFragment
|
||||||
|
.newInstance(contribution);
|
||||||
|
fragment.setCallback(this::onConfirmClicked);
|
||||||
|
fragment.show(fragmentManager, "WikimediaFragment");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Media getMediaAtPosition(final int i) {
|
||||||
|
if (adapter.getContributionForPosition(i) != null) {
|
||||||
|
return adapter.getContributionForPosition(i).getMedia();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
return contributionsSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the editor for the language Wikipedia
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) {
|
||||||
|
if (copyWikicode) {
|
||||||
|
String wikicode = contribution.getMedia().getWikiCode();
|
||||||
|
Utils.copy("wikicode", wikicode, getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
final String url =
|
||||||
|
languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace()
|
||||||
|
.getWikipediaPageTitle();
|
||||||
|
Utils.handleWebUrl(getContext(), Uri.parse(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getContributionStateAt(int position) {
|
||||||
|
return adapter.getContributionForPosition(position).getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
|
||||||
|
void notifyDataSetChanged();
|
||||||
|
|
||||||
|
void retryUpload(Contribution contribution);
|
||||||
|
|
||||||
|
void showDetail(int position, boolean isWikipediaButtonDisplayed);
|
||||||
|
|
||||||
|
void pauseUpload(Contribution contribution);
|
||||||
|
|
||||||
|
// Notify the viewpager that number of items have changed.
|
||||||
|
void viewPagerNotifyDataSetChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,57 +15,58 @@ import javax.inject.Named;
|
||||||
*/
|
*/
|
||||||
public class ContributionsListPresenter implements UserActionListener {
|
public class ContributionsListPresenter implements UserActionListener {
|
||||||
|
|
||||||
private final ContributionBoundaryCallback contributionBoundaryCallback;
|
private final ContributionBoundaryCallback contributionBoundaryCallback;
|
||||||
private final ContributionsRepository repository;
|
private final ContributionsRepository repository;
|
||||||
private final Scheduler ioThreadScheduler;
|
private final Scheduler ioThreadScheduler;
|
||||||
|
|
||||||
private final CompositeDisposable compositeDisposable;
|
private final CompositeDisposable compositeDisposable;
|
||||||
|
|
||||||
LiveData<PagedList<Contribution>> contributionList;
|
LiveData<PagedList<Contribution>> contributionList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
ContributionsListPresenter(
|
ContributionsListPresenter(
|
||||||
final ContributionBoundaryCallback contributionBoundaryCallback,
|
final ContributionBoundaryCallback contributionBoundaryCallback,
|
||||||
final ContributionsRepository repository,
|
final ContributionsRepository repository,
|
||||||
@Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) {
|
@Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) {
|
||||||
this.contributionBoundaryCallback = contributionBoundaryCallback;
|
this.contributionBoundaryCallback = contributionBoundaryCallback;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.ioThreadScheduler = ioThreadScheduler;
|
this.ioThreadScheduler = ioThreadScheduler;
|
||||||
compositeDisposable = new CompositeDisposable();
|
compositeDisposable = new CompositeDisposable();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttachView(final ContributionsListContract.View view) {
|
public void onAttachView(final ContributionsListContract.View view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the paged list. This method sets the configuration for paged list and ties it up with the
|
* Setup the paged list. This method sets the configuration for paged list and ties it up with
|
||||||
* live data object. This method can be tweaked to update the lazy loading behavior of the
|
* the live data object. This method can be tweaked to update the lazy loading behavior of the
|
||||||
* contributions list
|
* contributions list
|
||||||
*/
|
*/
|
||||||
void setup() {
|
void setup() {
|
||||||
final PagedList.Config pagedListConfig =
|
final PagedList.Config pagedListConfig =
|
||||||
(new PagedList.Config.Builder())
|
(new PagedList.Config.Builder())
|
||||||
.setPrefetchDistance(50)
|
.setPrefetchDistance(50)
|
||||||
.setPageSize(10).build();
|
.setPageSize(10).build();
|
||||||
contributionList = (new LivePagedListBuilder(repository.fetchContributions(), pagedListConfig)
|
contributionList = (new LivePagedListBuilder(repository.fetchContributions(),
|
||||||
.setBoundaryCallback(contributionBoundaryCallback)).build();
|
pagedListConfig)
|
||||||
}
|
.setBoundaryCallback(contributionBoundaryCallback)).build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDetachView() {
|
public void onDetachView() {
|
||||||
compositeDisposable.clear();
|
compositeDisposable.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a failed contribution from the local db
|
* Delete a failed contribution from the local db
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void deleteUpload(final Contribution contribution) {
|
public void deleteUpload(final Contribution contribution) {
|
||||||
compositeDisposable.add(repository
|
compositeDisposable.add(repository
|
||||||
.deleteContributionFromDB(contribution)
|
.deleteContributionFromDB(contribution)
|
||||||
.subscribeOn(ioThreadScheduler)
|
.subscribeOn(ioThreadScheduler)
|
||||||
.subscribe());
|
.subscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
|
||||||
* The database for accessing the respective DAOs
|
* The database for accessing the respective DAOs
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Database(entities = [Contribution::class,Depicts::class], version = 8, exportSchema = false)
|
@Database(entities = [Contribution::class, Depicts::class], version = 8, exportSchema = false)
|
||||||
@TypeConverters(Converters::class)
|
@TypeConverters(Converters::class)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
abstract fun contributionDao(): ContributionDao
|
abstract fun contributionDao(): ContributionDao
|
||||||
abstract fun DepictsDao (): DepictsDao;
|
abstract fun DepictsDao(): DepictsDao;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,181 +23,183 @@ import fr.free.nrw.commons.navtab.NavTab;
|
||||||
public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
|
public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
|
||||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||||
|
|
||||||
private MediaDetailPagerFragment mediaDetails;
|
private MediaDetailPagerFragment mediaDetails;
|
||||||
private CategoriesMediaFragment listFragment;
|
private CategoriesMediaFragment listFragment;
|
||||||
|
|
||||||
@BindView(R.id.explore_container)
|
@BindView(R.id.explore_container)
|
||||||
FrameLayout container;
|
FrameLayout container;
|
||||||
|
|
||||||
public ExploreListRootFragment(){
|
public ExploreListRootFragment() {
|
||||||
//empty constructor necessary otherwise crashes on recreate
|
//empty constructor necessary otherwise crashes on recreate
|
||||||
}
|
|
||||||
|
|
||||||
public ExploreListRootFragment(Bundle bundle) {
|
|
||||||
String title = bundle.getString("categoryName");
|
|
||||||
listFragment = new CategoriesMediaFragment();
|
|
||||||
Bundle featuredArguments = new Bundle();
|
|
||||||
featuredArguments.putString("categoryName", title);
|
|
||||||
listFragment.setArguments(featuredArguments);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
|
|
||||||
@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
|
|
||||||
ButterKnife.bind(this, view);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onViewCreated(view, savedInstanceState);
|
|
||||||
if(savedInstanceState == null) {
|
|
||||||
setFragment(listFragment, mediaDetails);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
public ExploreListRootFragment(Bundle bundle) {
|
||||||
if (fragment.isAdded() && otherFragment != null) {
|
String title = bundle.getString("categoryName");
|
||||||
getChildFragmentManager()
|
listFragment = new CategoriesMediaFragment();
|
||||||
.beginTransaction()
|
Bundle featuredArguments = new Bundle();
|
||||||
.hide(otherFragment)
|
featuredArguments.putString("categoryName", title);
|
||||||
.show( fragment)
|
listFragment.setArguments(featuredArguments);
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
} else if (fragment.isAdded() && otherFragment == null) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.show( fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
} else if (!fragment.isAdded() && otherFragment != null ) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.hide(otherFragment)
|
|
||||||
.add(R.id.explore_container, fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
} else if (!fragment.isAdded()) {
|
|
||||||
getChildFragmentManager()
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.explore_container, fragment)
|
|
||||||
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
|
||||||
.commit();
|
|
||||||
getChildFragmentManager().executePendingTransactions();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void removeFragment(Fragment fragment) {
|
@Nullable
|
||||||
getChildFragmentManager()
|
@Override
|
||||||
.beginTransaction()
|
public View onCreateView(@NonNull final LayoutInflater inflater,
|
||||||
.remove(fragment)
|
@Nullable final ViewGroup container,
|
||||||
.commit();
|
@Nullable final Bundle savedInstanceState) {
|
||||||
getChildFragmentManager().executePendingTransactions();
|
super.onCreate(savedInstanceState);
|
||||||
}
|
View view = inflater.inflate(R.layout.fragment_featured_root, container, false);
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
@Override
|
return view;
|
||||||
public void onAttach(final Context context) {
|
|
||||||
super.onAttach(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMediaClicked(int position) {
|
|
||||||
container.setVisibility(View.VISIBLE);
|
|
||||||
((ExploreFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
|
||||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
|
||||||
setFragment(mediaDetails, listFragment);
|
|
||||||
mediaDetails.showImage(position);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
|
||||||
*
|
|
||||||
* @param i It is the index of which media object is to be returned which is same as current
|
|
||||||
* index of viewPager.
|
|
||||||
* @return Media Object
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Media getMediaAtPosition(int i) {
|
|
||||||
if (listFragment != null) {
|
|
||||||
return listFragment.getMediaAtPosition(i);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
|
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||||
* same number of media items as that of media elements in adapter.
|
super.onViewCreated(view, savedInstanceState);
|
||||||
*
|
if (savedInstanceState == null) {
|
||||||
* @return Total Media count in the adapter
|
setFragment(listFragment, mediaDetails);
|
||||||
*/
|
}
|
||||||
@Override
|
|
||||||
public int getTotalMediaCount() {
|
|
||||||
if (listFragment!=null) {
|
|
||||||
return listFragment.getTotalMediaCount();
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||||
public Integer getContributionStateAt(int position) {
|
if (fragment.isAdded() && otherFragment != null) {
|
||||||
return null;
|
getChildFragmentManager()
|
||||||
}
|
.beginTransaction()
|
||||||
|
.hide(otherFragment)
|
||||||
/**
|
.show(fragment)
|
||||||
* Reload media detail fragment once media is nominated
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
*
|
.commit();
|
||||||
* @param index item position that has been nominated
|
getChildFragmentManager().executePendingTransactions();
|
||||||
*/
|
} else if (fragment.isAdded() && otherFragment == null) {
|
||||||
@Override
|
getChildFragmentManager()
|
||||||
public void refreshNominatedMedia(int index) {
|
.beginTransaction()
|
||||||
if(mediaDetails != null && !listFragment.isVisible()) {
|
.show(fragment)
|
||||||
removeFragment(mediaDetails);
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
onMediaClicked(index);
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
} else if (!fragment.isAdded() && otherFragment != null) {
|
||||||
|
getChildFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.hide(otherFragment)
|
||||||
|
.add(R.id.explore_container, fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
} else if (!fragment.isAdded()) {
|
||||||
|
getChildFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.explore_container, fragment)
|
||||||
|
.addToBackStack("CONTRIBUTION_LIST_FRAGMENT_TAG")
|
||||||
|
.commit();
|
||||||
|
getChildFragmentManager().executePendingTransactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void removeFragment(Fragment fragment) {
|
||||||
* This method is called on success of API call for featured images or mobile uploads. The
|
getChildFragmentManager()
|
||||||
* viewpager will notified that number of items have changed.
|
.beginTransaction()
|
||||||
*/
|
.remove(fragment)
|
||||||
@Override
|
.commit();
|
||||||
public void viewPagerNotifyDataSetChanged() {
|
getChildFragmentManager().executePendingTransactions();
|
||||||
if (mediaDetails != null) {
|
|
||||||
mediaDetails.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Performs back pressed action on the fragment.
|
public void onAttach(final Context context) {
|
||||||
* Return true if the event was handled by the mediaDetails otherwise returns false.
|
super.onAttach(context);
|
||||||
* @return
|
}
|
||||||
*/
|
|
||||||
public boolean backPressed() {
|
@Override
|
||||||
if (null != mediaDetails && mediaDetails.isVisible()) {
|
public void onMediaClicked(int position) {
|
||||||
// todo add get list fragment
|
container.setVisibility(View.VISIBLE);
|
||||||
if (mediaDetails.backButtonClicked()) {
|
((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||||
// MediaDetails handled the event no further action required.
|
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||||
return true;
|
setFragment(mediaDetails, listFragment);
|
||||||
} else {
|
mediaDetails.showImage(position);
|
||||||
((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.VISIBLE);
|
}
|
||||||
removeFragment(mediaDetails);
|
|
||||||
((ExploreFragment) getParentFragment()).setScroll(true);
|
/**
|
||||||
setFragment(listFragment, mediaDetails);
|
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||||
|
*
|
||||||
|
* @param i It is the index of which media object is to be returned which is same as current
|
||||||
|
* index of viewPager.
|
||||||
|
* @return Media Object
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Media getMediaAtPosition(int i) {
|
||||||
|
if (listFragment != null) {
|
||||||
|
return listFragment.getMediaAtPosition(i);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on from getCount of MediaDetailPagerFragment The viewpager will contain
|
||||||
|
* same number of media items as that of media elements in adapter.
|
||||||
|
*
|
||||||
|
* @return Total Media count in the adapter
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getTotalMediaCount() {
|
||||||
|
if (listFragment != null) {
|
||||||
|
return listFragment.getTotalMediaCount();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getContributionStateAt(int position) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reload media detail fragment once media is nominated
|
||||||
|
*
|
||||||
|
* @param index item position that has been nominated
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void refreshNominatedMedia(int index) {
|
||||||
|
if (mediaDetails != null && !listFragment.isVisible()) {
|
||||||
|
removeFragment(mediaDetails);
|
||||||
|
onMediaClicked(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called on success of API call for featured images or mobile uploads. The
|
||||||
|
* viewpager will notified that number of items have changed.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void viewPagerNotifyDataSetChanged() {
|
||||||
|
if (mediaDetails != null) {
|
||||||
|
mediaDetails.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs back pressed action on the fragment. Return true if the event was handled by the
|
||||||
|
* mediaDetails otherwise returns false.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean backPressed() {
|
||||||
|
if (null != mediaDetails && mediaDetails.isVisible()) {
|
||||||
|
// todo add get list fragment
|
||||||
|
if (mediaDetails.backButtonClicked()) {
|
||||||
|
// MediaDetails handled the event no further action required.
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
((ExploreFragment) getParentFragment()).tabLayout.setVisibility(View.VISIBLE);
|
||||||
|
removeFragment(mediaDetails);
|
||||||
|
((ExploreFragment) getParentFragment()).setScroll(true);
|
||||||
|
setFragment(listFragment, mediaDetails);
|
||||||
|
((MainActivity) getActivity()).showTabs();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||||
|
}
|
||||||
((MainActivity) getActivity()).showTabs();
|
((MainActivity) getActivity()).showTabs();
|
||||||
return true;
|
return false;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
|
||||||
}
|
}
|
||||||
((MainActivity) getActivity()).showTabs();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,64 +6,61 @@ import android.view.MotionEvent;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ParentViewPager
|
* ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
|
||||||
* A custom viewPager whose scrolling can be enabled and disabled.
|
*/
|
||||||
*/
|
|
||||||
public class ParentViewPager extends ViewPager {
|
public class ParentViewPager extends ViewPager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
|
* Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
|
||||||
*/
|
*/
|
||||||
private boolean canScroll = true;
|
private boolean canScroll = true;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructors
|
* Default constructors
|
||||||
*/
|
*/
|
||||||
public ParentViewPager(Context context) {
|
public ParentViewPager(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentViewPager(Context context, AttributeSet attrs) {
|
public ParentViewPager(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setter method for canScroll.
|
* Setter method for canScroll.
|
||||||
*/
|
*/
|
||||||
public void setCanScroll(boolean canScroll) {
|
public void setCanScroll(boolean canScroll) {
|
||||||
this.canScroll = canScroll;
|
this.canScroll = canScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter method for canScroll.
|
* Getter method for canScroll.
|
||||||
*/
|
*/
|
||||||
public boolean isCanScroll() {
|
public boolean isCanScroll() {
|
||||||
return canScroll;
|
return canScroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method that prevents scrolling if canScroll is set to false.
|
* Method that prevents scrolling if canScroll is set to false.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouchEvent(MotionEvent ev) {
|
public boolean onTouchEvent(MotionEvent ev) {
|
||||||
return canScroll && super.onTouchEvent(ev);
|
return canScroll && super.onTouchEvent(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A facilitator method that allows parent to intercept touch events before its children.
|
|
||||||
* thus making it possible to prevent swiping parent on child end.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
|
||||||
return canScroll && super.onInterceptTouchEvent(ev);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A facilitator method that allows parent to intercept touch events before its children. thus
|
||||||
|
* making it possible to prevent swiping parent on child end.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
return canScroll && super.onInterceptTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,11 @@ import fr.free.nrw.commons.explore.SearchActivity;
|
||||||
* Displays the recent searches screen.
|
* Displays the recent searches screen.
|
||||||
*/
|
*/
|
||||||
public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
@Inject RecentSearchesDao recentSearchesDao;
|
|
||||||
@BindView(R.id.recent_searches_list) ListView recentSearchesList;
|
@Inject
|
||||||
|
RecentSearchesDao recentSearchesDao;
|
||||||
|
@BindView(R.id.recent_searches_list)
|
||||||
|
ListView recentSearchesList;
|
||||||
List<String> recentSearches;
|
List<String> recentSearches;
|
||||||
ArrayAdapter adapter;
|
ArrayAdapter adapter;
|
||||||
@BindView(R.id.recent_searches_delete_button)
|
@BindView(R.id.recent_searches_delete_button)
|
||||||
|
|
@ -38,16 +41,16 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View rootView = inflater.inflate(R.layout.fragment_search_history, container, false);
|
View rootView = inflater.inflate(R.layout.fragment_search_history, container, false);
|
||||||
ButterKnife.bind(this, rootView);
|
ButterKnife.bind(this, rootView);
|
||||||
recentSearches = recentSearchesDao.recentSearches(10);
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
|
|
||||||
if(recentSearches.isEmpty()) {
|
if (recentSearches.isEmpty()) {
|
||||||
recent_searches_delete_button.setVisibility(View.GONE);
|
recent_searches_delete_button.setVisibility(View.GONE);
|
||||||
recent_searches_text_view.setText(R.string.no_recent_searches);
|
recent_searches_text_view.setText(R.string.no_recent_searches);
|
||||||
}
|
}
|
||||||
|
|
||||||
recent_searches_delete_button.setOnClickListener(v -> {
|
recent_searches_delete_button.setOnClickListener(v -> {
|
||||||
new AlertDialog.Builder(getContext())
|
new AlertDialog.Builder(getContext())
|
||||||
.setMessage(getString(R.string.delete_recent_searches_dialog))
|
.setMessage(getString(R.string.delete_recent_searches_dialog))
|
||||||
|
|
@ -55,9 +58,11 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
recentSearchesDao.deleteAll();
|
recentSearchesDao.deleteAll();
|
||||||
recent_searches_delete_button.setVisibility(View.GONE);
|
recent_searches_delete_button.setVisibility(View.GONE);
|
||||||
recent_searches_text_view.setText(R.string.no_recent_searches);
|
recent_searches_text_view.setText(R.string.no_recent_searches);
|
||||||
Toast.makeText(getContext(),getString(R.string.search_history_deleted),Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), getString(R.string.search_history_deleted),
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
recentSearches = recentSearchesDao.recentSearches(10);
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches, recentSearches);
|
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches,
|
||||||
|
recentSearches);
|
||||||
recentSearchesList.setAdapter(adapter);
|
recentSearchesList.setAdapter(adapter);
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
|
|
@ -67,24 +72,26 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
adapter = new ArrayAdapter<>(requireContext(), R.layout.item_recent_searches, recentSearches);
|
adapter = new ArrayAdapter<>(requireContext(), R.layout.item_recent_searches,
|
||||||
|
recentSearches);
|
||||||
recentSearchesList.setAdapter(adapter);
|
recentSearchesList.setAdapter(adapter);
|
||||||
recentSearchesList.setOnItemClickListener((parent, view, position, id) -> (
|
recentSearchesList.setOnItemClickListener((parent, view, position, id) -> (
|
||||||
(SearchActivity)getContext()).updateText(recentSearches.get(position)));
|
(SearchActivity) getContext()).updateText(recentSearches.get(position)));
|
||||||
recentSearchesList.setOnItemLongClickListener((parent, view, position, id) -> {
|
recentSearchesList.setOnItemLongClickListener((parent, view, position, id) -> {
|
||||||
new AlertDialog.Builder(getContext())
|
new AlertDialog.Builder(getContext())
|
||||||
.setMessage(R.string.delete_search_dialog)
|
.setMessage(R.string.delete_search_dialog)
|
||||||
.setPositiveButton(getString(R.string.delete).toUpperCase(),((dialog, which) -> {
|
.setPositiveButton(getString(R.string.delete).toUpperCase(), ((dialog, which) -> {
|
||||||
recentSearchesDao.delete(recentSearchesDao.find(recentSearches.get(position)));
|
recentSearchesDao.delete(recentSearchesDao.find(recentSearches.get(position)));
|
||||||
recentSearches = recentSearchesDao.recentSearches(10);
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches, recentSearches);
|
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches,
|
||||||
recentSearchesList.setAdapter(adapter);
|
recentSearches);
|
||||||
adapter.notifyDataSetChanged();
|
recentSearchesList.setAdapter(adapter);
|
||||||
dialog.dismiss();
|
adapter.notifyDataSetChanged();
|
||||||
}))
|
dialog.dismiss();
|
||||||
.setNegativeButton(android.R.string.cancel,null)
|
}))
|
||||||
.create()
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
.show();
|
.create()
|
||||||
|
.show();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
updateRecentSearches();
|
updateRecentSearches();
|
||||||
|
|
@ -92,8 +99,8 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called on back press of activity
|
* This method is called on back press of activity so we are updating the list from database to
|
||||||
* so we are updating the list from database to refresh the recent searches list.
|
* refresh the recent searches list.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
|
|
@ -108,7 +115,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
||||||
recentSearches = recentSearchesDao.recentSearches(10);
|
recentSearches = recentSearchesDao.recentSearches(10);
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
|
|
||||||
if(!recentSearches.isEmpty()) {
|
if (!recentSearches.isEmpty()) {
|
||||||
recent_searches_delete_button.setVisibility(View.VISIBLE);
|
recent_searches_delete_button.setVisibility(View.VISIBLE);
|
||||||
recent_searches_text_view.setText(R.string.search_recent_header);
|
recent_searches_text_view.setText(R.string.search_recent_header);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,195 +35,199 @@ import timber.log.Timber;
|
||||||
public class CustomOkHttpNetworkFetcher
|
public class CustomOkHttpNetworkFetcher
|
||||||
extends BaseNetworkFetcher<CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState> {
|
extends BaseNetworkFetcher<CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState> {
|
||||||
|
|
||||||
private static final String QUEUE_TIME = "queue_time";
|
private static final String QUEUE_TIME = "queue_time";
|
||||||
private static final String FETCH_TIME = "fetch_time";
|
private static final String FETCH_TIME = "fetch_time";
|
||||||
private static final String TOTAL_TIME = "total_time";
|
private static final String TOTAL_TIME = "total_time";
|
||||||
private static final String IMAGE_SIZE = "image_size";
|
private static final String IMAGE_SIZE = "image_size";
|
||||||
private final Call.Factory mCallFactory;
|
private final Call.Factory mCallFactory;
|
||||||
private final @Nullable
|
private final @Nullable
|
||||||
CacheControl mCacheControl;
|
CacheControl mCacheControl;
|
||||||
private Executor mCancellationExecutor;
|
private Executor mCancellationExecutor;
|
||||||
private JsonKvStore defaultKvStore;
|
private JsonKvStore defaultKvStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param okHttpClient client to use
|
* @param okHttpClient client to use
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public CustomOkHttpNetworkFetcher(OkHttpClient okHttpClient,
|
public CustomOkHttpNetworkFetcher(OkHttpClient okHttpClient,
|
||||||
@Named("default_preferences") JsonKvStore defaultKvStore) {
|
@Named("default_preferences") JsonKvStore defaultKvStore) {
|
||||||
this(okHttpClient, okHttpClient.dispatcher().executorService(), defaultKvStore);
|
this(okHttpClient, okHttpClient.dispatcher().executorService(), defaultKvStore);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
|
||||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
|
||||||
* cancellation is requested from the UI Thread
|
|
||||||
*/
|
|
||||||
public CustomOkHttpNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor,
|
|
||||||
JsonKvStore defaultKvStore) {
|
|
||||||
this(callFactory, cancellationExecutor, defaultKvStore, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
|
||||||
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
|
||||||
* cancellation is requested from the UI Thread
|
|
||||||
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
|
|
||||||
*/
|
|
||||||
public CustomOkHttpNetworkFetcher(
|
|
||||||
Call.Factory callFactory, Executor cancellationExecutor, JsonKvStore defaultKvStore,
|
|
||||||
boolean disableOkHttpCache) {
|
|
||||||
this.defaultKvStore = defaultKvStore;
|
|
||||||
mCallFactory = callFactory;
|
|
||||||
mCancellationExecutor = cancellationExecutor;
|
|
||||||
mCacheControl = disableOkHttpCache ? new CacheControl.Builder().noStore().build() : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OkHttpNetworkFetchState createFetchState(
|
|
||||||
Consumer<EncodedImage> consumer, ProducerContext context) {
|
|
||||||
return new OkHttpNetworkFetchState(consumer, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void fetch(
|
|
||||||
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
|
|
||||||
fetchState.submitTime = SystemClock.elapsedRealtime();
|
|
||||||
final Uri uri = fetchState.getUri();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (defaultKvStore
|
|
||||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
|
|
||||||
Timber.d("Skipping loading of image as limited connection mode is enabled");
|
|
||||||
callback.onFailure(
|
|
||||||
new Exception("Failing image request as limited connection mode is enabled"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
|
|
||||||
|
|
||||||
if (mCacheControl != null) {
|
|
||||||
requestBuilder.cacheControl(mCacheControl);
|
|
||||||
}
|
|
||||||
|
|
||||||
final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
|
|
||||||
if (bytesRange != null) {
|
|
||||||
requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchWithRequest(fetchState, callback, requestBuilder.build());
|
|
||||||
} catch (Exception e) {
|
|
||||||
// handle error while creating the request
|
|
||||||
callback.onFailure(e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||||
}
|
* cancellation is requested from the UI Thread
|
||||||
|
*/
|
||||||
|
public CustomOkHttpNetworkFetcher(Call.Factory callFactory, Executor cancellationExecutor,
|
||||||
|
JsonKvStore defaultKvStore) {
|
||||||
|
this(callFactory, cancellationExecutor, defaultKvStore, true);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
* @param callFactory custom {@link Call.Factory} for fetching image from the network
|
||||||
Map<String, String> extraMap = new HashMap<>(4);
|
* @param cancellationExecutor executor on which fetching cancellation is performed if
|
||||||
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
|
* cancellation is requested from the UI Thread
|
||||||
extraMap.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
|
* @param disableOkHttpCache true if network requests should not be cached by OkHttp
|
||||||
extraMap.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
|
*/
|
||||||
extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
|
public CustomOkHttpNetworkFetcher(
|
||||||
return extraMap;
|
Call.Factory callFactory, Executor cancellationExecutor, JsonKvStore defaultKvStore,
|
||||||
}
|
boolean disableOkHttpCache) {
|
||||||
|
this.defaultKvStore = defaultKvStore;
|
||||||
|
mCallFactory = callFactory;
|
||||||
|
mCancellationExecutor = cancellationExecutor;
|
||||||
|
mCacheControl = disableOkHttpCache ? new CacheControl.Builder().noStore().build() : null;
|
||||||
|
}
|
||||||
|
|
||||||
protected void fetchWithRequest(
|
@Override
|
||||||
final OkHttpNetworkFetchState fetchState,
|
public OkHttpNetworkFetchState createFetchState(
|
||||||
final NetworkFetcher.Callback callback,
|
Consumer<EncodedImage> consumer, ProducerContext context) {
|
||||||
final Request request) {
|
return new OkHttpNetworkFetchState(consumer, context);
|
||||||
final Call call = mCallFactory.newCall(request);
|
}
|
||||||
|
|
||||||
fetchState
|
@Override
|
||||||
.getContext()
|
public void fetch(
|
||||||
.addCallbacks(
|
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
|
||||||
new BaseProducerContextCallbacks() {
|
fetchState.submitTime = SystemClock.elapsedRealtime();
|
||||||
@Override
|
final Uri uri = fetchState.getUri();
|
||||||
public void onCancellationRequested() {
|
|
||||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
|
||||||
call.cancel();
|
|
||||||
} else {
|
|
||||||
mCancellationExecutor.execute(
|
|
||||||
new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
call.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
call.enqueue(
|
try {
|
||||||
new okhttp3.Callback() {
|
if (defaultKvStore
|
||||||
@Override
|
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
|
||||||
public void onResponse(Call call, Response response) throws IOException {
|
Timber.d("Skipping loading of image as limited connection mode is enabled");
|
||||||
fetchState.responseTime = SystemClock.elapsedRealtime();
|
callback.onFailure(
|
||||||
final ResponseBody body = response.body();
|
new Exception("Failing image request as limited connection mode is enabled"));
|
||||||
try {
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
handleException(
|
|
||||||
call, new IOException("Unexpected HTTP code " + response), callback);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
BytesRange responseRange =
|
|
||||||
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
|
||||||
if (responseRange != null
|
|
||||||
&& !(responseRange.from == 0
|
|
||||||
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
|
||||||
// Only treat as a partial image if the range is not all of the content
|
|
||||||
fetchState.setResponseBytesRange(responseRange);
|
|
||||||
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
|
||||||
}
|
|
||||||
|
|
||||||
long contentLength = body.contentLength();
|
|
||||||
if (contentLength < 0) {
|
|
||||||
contentLength = 0;
|
|
||||||
}
|
|
||||||
callback.onResponse(body.byteStream(), (int) contentLength);
|
|
||||||
} catch (Exception e) {
|
|
||||||
handleException(call, e, callback);
|
|
||||||
} finally {
|
|
||||||
body.close();
|
|
||||||
}
|
}
|
||||||
}
|
final Request.Builder requestBuilder = new Request.Builder().url(uri.toString()).get();
|
||||||
|
|
||||||
@Override
|
if (mCacheControl != null) {
|
||||||
public void onFailure(Call call, IOException e) {
|
requestBuilder.cacheControl(mCacheControl);
|
||||||
handleException(call, e, callback);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
final BytesRange bytesRange = fetchState.getContext().getImageRequest().getBytesRange();
|
||||||
* Handles exceptions.
|
if (bytesRange != null) {
|
||||||
*
|
requestBuilder.addHeader("Range", bytesRange.toHttpRangeHeaderValue());
|
||||||
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught after
|
}
|
||||||
* request cancellation, then the exception is interpreted as successful cancellation and
|
|
||||||
* onCancellation is called. Otherwise onFailure is called.
|
fetchWithRequest(fetchState, callback, requestBuilder.build());
|
||||||
*/
|
} catch (Exception e) {
|
||||||
private void handleException(final Call call, final Exception e, final Callback callback) {
|
// handle error while creating the request
|
||||||
if (call.isCanceled()) {
|
callback.onFailure(e);
|
||||||
callback.onCancellation();
|
}
|
||||||
} else {
|
|
||||||
callback.onFailure(e);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static class OkHttpNetworkFetchState extends FetchState {
|
@Override
|
||||||
|
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||||
public long submitTime;
|
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||||
public long responseTime;
|
}
|
||||||
public long fetchCompleteTime;
|
|
||||||
|
@Override
|
||||||
public OkHttpNetworkFetchState(
|
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||||
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
|
Map<String, String> extraMap = new HashMap<>(4);
|
||||||
super(consumer, producerContext);
|
extraMap.put(QUEUE_TIME, Long.toString(fetchState.responseTime - fetchState.submitTime));
|
||||||
|
extraMap
|
||||||
|
.put(FETCH_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.responseTime));
|
||||||
|
extraMap
|
||||||
|
.put(TOTAL_TIME, Long.toString(fetchState.fetchCompleteTime - fetchState.submitTime));
|
||||||
|
extraMap.put(IMAGE_SIZE, Integer.toString(byteSize));
|
||||||
|
return extraMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fetchWithRequest(
|
||||||
|
final OkHttpNetworkFetchState fetchState,
|
||||||
|
final NetworkFetcher.Callback callback,
|
||||||
|
final Request request) {
|
||||||
|
final Call call = mCallFactory.newCall(request);
|
||||||
|
|
||||||
|
fetchState
|
||||||
|
.getContext()
|
||||||
|
.addCallbacks(
|
||||||
|
new BaseProducerContextCallbacks() {
|
||||||
|
@Override
|
||||||
|
public void onCancellationRequested() {
|
||||||
|
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||||
|
call.cancel();
|
||||||
|
} else {
|
||||||
|
mCancellationExecutor.execute(
|
||||||
|
new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
call.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
call.enqueue(
|
||||||
|
new okhttp3.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call call, Response response) throws IOException {
|
||||||
|
fetchState.responseTime = SystemClock.elapsedRealtime();
|
||||||
|
final ResponseBody body = response.body();
|
||||||
|
try {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
handleException(
|
||||||
|
call, new IOException("Unexpected HTTP code " + response),
|
||||||
|
callback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BytesRange responseRange =
|
||||||
|
BytesRange.fromContentRangeHeader(response.header("Content-Range"));
|
||||||
|
if (responseRange != null
|
||||||
|
&& !(responseRange.from == 0
|
||||||
|
&& responseRange.to == BytesRange.TO_END_OF_CONTENT)) {
|
||||||
|
// Only treat as a partial image if the range is not all of the content
|
||||||
|
fetchState.setResponseBytesRange(responseRange);
|
||||||
|
fetchState.setOnNewResultStatusFlags(Consumer.IS_PARTIAL_RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
long contentLength = body.contentLength();
|
||||||
|
if (contentLength < 0) {
|
||||||
|
contentLength = 0;
|
||||||
|
}
|
||||||
|
callback.onResponse(body.byteStream(), (int) contentLength);
|
||||||
|
} catch (Exception e) {
|
||||||
|
handleException(call, e, callback);
|
||||||
|
} finally {
|
||||||
|
body.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call call, IOException e) {
|
||||||
|
handleException(call, e, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles exceptions.
|
||||||
|
*
|
||||||
|
* <p>OkHttp notifies callers of cancellations via an IOException. If IOException is caught
|
||||||
|
* after
|
||||||
|
* request cancellation, then the exception is interpreted as successful cancellation and
|
||||||
|
* onCancellation is called. Otherwise onFailure is called.
|
||||||
|
*/
|
||||||
|
private void handleException(final Call call, final Exception e, final Callback callback) {
|
||||||
|
if (call.isCanceled()) {
|
||||||
|
callback.onCancellation();
|
||||||
|
} else {
|
||||||
|
callback.onFailure(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class OkHttpNetworkFetchState extends FetchState {
|
||||||
|
|
||||||
|
public long submitTime;
|
||||||
|
public long responseTime;
|
||||||
|
public long fetchCompleteTime;
|
||||||
|
|
||||||
|
public OkHttpNetworkFetchState(
|
||||||
|
Consumer<EncodedImage> consumer, ProducerContext producerContext) {
|
||||||
|
super(consumer, producerContext);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,319 +42,325 @@ import timber.log.Timber;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class OkHttpJsonApiClient {
|
public class OkHttpJsonApiClient {
|
||||||
|
|
||||||
private final OkHttpClient okHttpClient;
|
private final OkHttpClient okHttpClient;
|
||||||
private final DepictsClient depictsClient;
|
private final DepictsClient depictsClient;
|
||||||
private final HttpUrl wikiMediaToolforgeUrl;
|
private final HttpUrl wikiMediaToolforgeUrl;
|
||||||
private final HttpUrl wikiMediaTestToolforgeUrl;
|
private final HttpUrl wikiMediaTestToolforgeUrl;
|
||||||
private final String sparqlQueryUrl;
|
private final String sparqlQueryUrl;
|
||||||
private final String campaignsUrl;
|
private final String campaignsUrl;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||||
DepictsClient depictsClient,
|
DepictsClient depictsClient,
|
||||||
HttpUrl wikiMediaToolforgeUrl,
|
HttpUrl wikiMediaToolforgeUrl,
|
||||||
HttpUrl wikiMediaTestToolforgeUrl,
|
HttpUrl wikiMediaTestToolforgeUrl,
|
||||||
String sparqlQueryUrl,
|
String sparqlQueryUrl,
|
||||||
String campaignsUrl,
|
String campaignsUrl,
|
||||||
Gson gson) {
|
Gson gson) {
|
||||||
this.okHttpClient = okHttpClient;
|
this.okHttpClient = okHttpClient;
|
||||||
this.depictsClient = depictsClient;
|
this.depictsClient = depictsClient;
|
||||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||||
this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
|
this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
|
||||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||||
this.campaignsUrl = campaignsUrl;
|
this.campaignsUrl = campaignsUrl;
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The method will gradually calls the leaderboard API and fetches the leaderboard
|
|
||||||
* @param userName username of leaderboard user
|
|
||||||
* @param duration duration for leaderboard
|
|
||||||
* @param category category for leaderboard
|
|
||||||
* @param limit page size limit for list
|
|
||||||
* @param offset offset for the list
|
|
||||||
* @return LeaderboardResponse object
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Observable<LeaderboardResponse> getLeaderboard(String userName, String duration, String category, String limit, String offset) {
|
|
||||||
final String fetchLeaderboardUrlTemplate = wikiMediaTestToolforgeUrl
|
|
||||||
+ LEADERBOARD_END_POINT;
|
|
||||||
String url = String.format(Locale.ENGLISH,
|
|
||||||
fetchLeaderboardUrlTemplate,
|
|
||||||
userName,
|
|
||||||
duration,
|
|
||||||
category,
|
|
||||||
limit,
|
|
||||||
offset);
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
|
||||||
urlBuilder.addQueryParameter("user", userName);
|
|
||||||
urlBuilder.addQueryParameter("duration", duration);
|
|
||||||
urlBuilder.addQueryParameter("category", category);
|
|
||||||
urlBuilder.addQueryParameter("limit", limit);
|
|
||||||
urlBuilder.addQueryParameter("offset", offset);
|
|
||||||
Timber.i("Url %s", urlBuilder.toString());
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.toString())
|
|
||||||
.build();
|
|
||||||
return Observable.fromCallable(() -> {
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return new LeaderboardResponse();
|
|
||||||
}
|
|
||||||
Timber.d("Response for leaderboard is %s", json);
|
|
||||||
try {
|
|
||||||
return gson.fromJson(json, LeaderboardResponse.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new LeaderboardResponse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new LeaderboardResponse();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method will update the leaderboard user avatar
|
|
||||||
* @param username username to update
|
|
||||||
* @param avatar url of the new avatar
|
|
||||||
* @return UpdateAvatarResponse object
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Single<UpdateAvatarResponse> setAvatar(String username, String avatar) {
|
|
||||||
final String urlTemplate = wikiMediaTestToolforgeUrl
|
|
||||||
+ UPDATE_AVATAR_END_POINT;
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
String url = String.format(Locale.ENGLISH,
|
|
||||||
urlTemplate,
|
|
||||||
username,
|
|
||||||
avatar);
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
|
||||||
urlBuilder.addQueryParameter("user", username);
|
|
||||||
urlBuilder.addQueryParameter("avatar", avatar);
|
|
||||||
Timber.i("Url %s", urlBuilder.toString());
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.toString())
|
|
||||||
.build();
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return gson.fromJson(json, UpdateAvatarResponse.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new UpdateAvatarResponse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Single<Integer> getUploadCount(String userName) {
|
|
||||||
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
|
||||||
urlBuilder
|
|
||||||
.addPathSegments("uploadsbyuser.py")
|
|
||||||
.addQueryParameter("user", userName);
|
|
||||||
|
|
||||||
if (ConfigUtils.isBetaFlavour()) {
|
|
||||||
urlBuilder.addQueryParameter("labs", "commonswiki");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
/**
|
||||||
.url(urlBuilder.build())
|
* The method will gradually calls the leaderboard API and fetches the leaderboard
|
||||||
.build();
|
*
|
||||||
|
* @param userName username of leaderboard user
|
||||||
return Single.fromCallable(() -> {
|
* @param duration duration for leaderboard
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
* @param category category for leaderboard
|
||||||
if (response != null && response.isSuccessful()) {
|
* @param limit page size limit for list
|
||||||
ResponseBody responseBody = response.body();
|
* @param offset offset for the list
|
||||||
if (null != responseBody) {
|
* @return LeaderboardResponse object
|
||||||
String responseBodyString = responseBody.string().trim();
|
*/
|
||||||
if (!TextUtils.isEmpty(responseBodyString)) {
|
@NonNull
|
||||||
try {
|
public Observable<LeaderboardResponse> getLeaderboard(String userName, String duration,
|
||||||
return Integer.parseInt(responseBodyString);
|
String category, String limit, String offset) {
|
||||||
} catch (NumberFormatException e) {
|
final String fetchLeaderboardUrlTemplate = wikiMediaTestToolforgeUrl
|
||||||
Timber.e(e);
|
+ LEADERBOARD_END_POINT;
|
||||||
|
String url = String.format(Locale.ENGLISH,
|
||||||
|
fetchLeaderboardUrlTemplate,
|
||||||
|
userName,
|
||||||
|
duration,
|
||||||
|
category,
|
||||||
|
limit,
|
||||||
|
offset);
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", userName);
|
||||||
|
urlBuilder.addQueryParameter("duration", duration);
|
||||||
|
urlBuilder.addQueryParameter("category", category);
|
||||||
|
urlBuilder.addQueryParameter("limit", limit);
|
||||||
|
urlBuilder.addQueryParameter("offset", offset);
|
||||||
|
Timber.i("Url %s", urlBuilder.toString());
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
return Observable.fromCallable(() -> {
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return new LeaderboardResponse();
|
||||||
|
}
|
||||||
|
Timber.d("Response for leaderboard is %s", json);
|
||||||
|
try {
|
||||||
|
return gson.fromJson(json, LeaderboardResponse.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new LeaderboardResponse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return new LeaderboardResponse();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Single<Integer> getWikidataEdits(String userName) {
|
|
||||||
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
|
||||||
urlBuilder
|
|
||||||
.addPathSegments("wikidataedits.py")
|
|
||||||
.addQueryParameter("user", userName);
|
|
||||||
|
|
||||||
if (ConfigUtils.isBetaFlavour()) {
|
|
||||||
urlBuilder.addQueryParameter("labs", "commonswiki");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
/**
|
||||||
.url(urlBuilder.build())
|
* This method will update the leaderboard user avatar
|
||||||
.build();
|
*
|
||||||
|
* @param username username to update
|
||||||
|
* @param avatar url of the new avatar
|
||||||
|
* @return UpdateAvatarResponse object
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Single<UpdateAvatarResponse> setAvatar(String username, String avatar) {
|
||||||
|
final String urlTemplate = wikiMediaTestToolforgeUrl
|
||||||
|
+ UPDATE_AVATAR_END_POINT;
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
String url = String.format(Locale.ENGLISH,
|
||||||
|
urlTemplate,
|
||||||
|
username,
|
||||||
|
avatar);
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", username);
|
||||||
|
urlBuilder.addQueryParameter("avatar", avatar);
|
||||||
|
Timber.i("Url %s", urlBuilder.toString());
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return gson.fromJson(json, UpdateAvatarResponse.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new UpdateAvatarResponse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Single.fromCallable(() -> {
|
@NonNull
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
public Single<Integer> getUploadCount(String userName) {
|
||||||
if (response != null &&
|
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
||||||
response.isSuccessful() && response.body() != null) {
|
urlBuilder
|
||||||
String json = response.body().string();
|
.addPathSegments("uploadsbyuser.py")
|
||||||
if (json == null) {
|
.addQueryParameter("user", userName);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
GetWikidataEditCountResponse countResponse = gson
|
|
||||||
.fromJson(json, GetWikidataEditCountResponse.class);
|
|
||||||
if (null != countResponse) {
|
|
||||||
return countResponse.getWikidataEditCount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (ConfigUtils.isBetaFlavour()) {
|
||||||
* This takes userName as input, which is then used to fetch the feedback/achievements statistics
|
urlBuilder.addQueryParameter("labs", "commonswiki");
|
||||||
* using OkHttp and JavaRx. This function return JSONObject
|
|
||||||
*
|
|
||||||
* @param userName MediaWiki user name
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Single<FeedbackResponse> getAchievements(String userName) {
|
|
||||||
final String fetchAchievementUrlTemplate =
|
|
||||||
wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
|
|
||||||
: "/feedback.py");
|
|
||||||
return Single.fromCallable(() -> {
|
|
||||||
String url = String.format(
|
|
||||||
Locale.ENGLISH,
|
|
||||||
fetchAchievementUrlTemplate,
|
|
||||||
userName);
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
|
||||||
urlBuilder.addQueryParameter("user", userName);
|
|
||||||
Request request = new Request.Builder()
|
|
||||||
.url(urlBuilder.toString())
|
|
||||||
.build();
|
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
|
||||||
String json = response.body().string();
|
|
||||||
if (json == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Timber.d("Response for achievements is %s", json);
|
|
||||||
try {
|
|
||||||
return gson.fromJson(json, FeedbackResponse.class);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
}
|
return Single.fromCallable(() -> {
|
||||||
return null;
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
});
|
if (response != null && response.isSuccessful()) {
|
||||||
}
|
ResponseBody responseBody = response.body();
|
||||||
|
if (null != responseBody) {
|
||||||
|
String responseBodyString = responseBody.string().trim();
|
||||||
|
if (!TextUtils.isEmpty(responseBodyString)) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(responseBodyString);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Timber.e(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public Observable<List<Place>> getNearbyPlaces(LatLng cur, String language, double radius) throws IOException {
|
@NonNull
|
||||||
|
public Single<Integer> getWikidataEdits(String userName) {
|
||||||
|
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
||||||
|
urlBuilder
|
||||||
|
.addPathSegments("wikidataedits.py")
|
||||||
|
.addQueryParameter("user", userName);
|
||||||
|
|
||||||
|
if (ConfigUtils.isBetaFlavour()) {
|
||||||
|
urlBuilder.addQueryParameter("labs", "commonswiki");
|
||||||
|
}
|
||||||
|
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null &&
|
||||||
|
response.isSuccessful() && response.body() != null) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
GetWikidataEditCountResponse countResponse = gson
|
||||||
|
.fromJson(json, GetWikidataEditCountResponse.class);
|
||||||
|
if (null != countResponse) {
|
||||||
|
return countResponse.getWikidataEditCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This takes userName as input, which is then used to fetch the feedback/achievements
|
||||||
|
* statistics using OkHttp and JavaRx. This function return JSONObject
|
||||||
|
*
|
||||||
|
* @param userName MediaWiki user name
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Single<FeedbackResponse> getAchievements(String userName) {
|
||||||
|
final String fetchAchievementUrlTemplate =
|
||||||
|
wikiMediaToolforgeUrl + (ConfigUtils.isBetaFlavour() ? "/feedback.py?labs=commonswiki"
|
||||||
|
: "/feedback.py");
|
||||||
|
return Single.fromCallable(() -> {
|
||||||
|
String url = String.format(
|
||||||
|
Locale.ENGLISH,
|
||||||
|
fetchAchievementUrlTemplate,
|
||||||
|
userName);
|
||||||
|
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
|
||||||
|
urlBuilder.addQueryParameter("user", userName);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(urlBuilder.toString())
|
||||||
|
.build();
|
||||||
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
|
String json = response.body().string();
|
||||||
|
if (json == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Timber.d("Response for achievements is %s", json);
|
||||||
|
try {
|
||||||
|
return gson.fromJson(json, FeedbackResponse.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new FeedbackResponse(0, 0, 0, new FeaturedImages(0, 0), 0, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<List<Place>> getNearbyPlaces(LatLng cur, String language, double radius)
|
||||||
|
throws IOException {
|
||||||
String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
|
String wikidataQuery = FileUtils.readFromResource("/queries/nearby_query.rq");
|
||||||
String query = wikidataQuery
|
String query = wikidataQuery
|
||||||
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||||
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
||||||
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
||||||
.replace("${LANG}", language);
|
.replace("${LANG}", language);
|
||||||
|
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl
|
HttpUrl.Builder urlBuilder = HttpUrl
|
||||||
.parse(sparqlQueryUrl)
|
.parse(sparqlQueryUrl)
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("query", query)
|
.addQueryParameter("query", query)
|
||||||
.addQueryParameter("format", "json");
|
.addQueryParameter("format", "json");
|
||||||
|
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(urlBuilder.build())
|
.url(urlBuilder.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return Observable.fromCallable(() -> {
|
return Observable.fromCallable(() -> {
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
||||||
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
||||||
List<Place> places = new ArrayList<>();
|
List<Place> places = new ArrayList<>();
|
||||||
for (NearbyResultItem item : bindings) {
|
for (NearbyResultItem item : bindings) {
|
||||||
places.add(Place.from(item));
|
places.add(Place.from(item));
|
||||||
}
|
}
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||||
* bridge -> suspended bridge, aqueduct, etc
|
* bridge -> suspended bridge, aqueduct, etc
|
||||||
*/
|
*/
|
||||||
public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition,
|
public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition,
|
||||||
int limit) throws IOException {
|
int limit) throws IOException {
|
||||||
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,"/queries/subclasses_query.rq"));
|
return depictedItemsFrom(
|
||||||
}
|
sparqlQuery(qid, startPosition, limit, "/queries/subclasses_query.rq"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||||
* bridge -> suspended bridge, aqueduct, etc
|
* bridge -> suspended bridge, aqueduct, etc
|
||||||
*/
|
*/
|
||||||
public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition,
|
public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition,
|
||||||
int limit) throws IOException {
|
int limit) throws IOException {
|
||||||
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
|
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
|
||||||
"/queries/parentclasses_query.rq"));
|
"/queries/parentclasses_query.rq"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Single<List<DepictedItem>> depictedItemsFrom(Request request) {
|
private Single<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||||
return depictsClient.toDepictions(Single.fromCallable(() -> {
|
return depictsClient.toDepictions(Single.fromCallable(() -> {
|
||||||
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||||
return gson.fromJson(body.string(), SparqlResponse.class);
|
return gson.fromJson(body.string(), SparqlResponse.class);
|
||||||
}
|
}
|
||||||
}).doOnError(Timber::e));
|
}).doOnError(Timber::e));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Request sparqlQuery(String qid, int startPosition, int limit, String fileName) throws IOException {
|
private Request sparqlQuery(String qid, int startPosition, int limit, String fileName)
|
||||||
String query = FileUtils.readFromResource(fileName)
|
throws IOException {
|
||||||
.replace("${QID}", qid)
|
String query = FileUtils.readFromResource(fileName)
|
||||||
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
|
.replace("${QID}", qid)
|
||||||
.replace("${LIMIT}",""+ limit)
|
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
|
||||||
.replace("${OFFSET}",""+ startPosition);
|
.replace("${LIMIT}", "" + limit)
|
||||||
HttpUrl.Builder urlBuilder = HttpUrl
|
.replace("${OFFSET}", "" + startPosition);
|
||||||
.parse(sparqlQueryUrl)
|
HttpUrl.Builder urlBuilder = HttpUrl
|
||||||
.newBuilder()
|
.parse(sparqlQueryUrl)
|
||||||
.addQueryParameter("query", query)
|
.newBuilder()
|
||||||
.addQueryParameter("format", "json");
|
.addQueryParameter("query", query)
|
||||||
return new Request.Builder()
|
.addQueryParameter("format", "json");
|
||||||
.url(urlBuilder.build())
|
return new Request.Builder()
|
||||||
.build();
|
.url(urlBuilder.build())
|
||||||
}
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public Single<CampaignResponseDTO> getCampaigns() {
|
public Single<CampaignResponseDTO> getCampaigns() {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
Request request = new Request.Builder().url(campaignsUrl)
|
Request request = new Request.Builder().url(campaignsUrl)
|
||||||
.build();
|
.build();
|
||||||
Response response = okHttpClient.newCall(request).execute();
|
Response response = okHttpClient.newCall(request).execute();
|
||||||
if (response != null && response.body() != null && response.isSuccessful()) {
|
if (response != null && response.body() != null && response.isSuccessful()) {
|
||||||
String json = response.body().string();
|
String json = response.body().string();
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return gson.fromJson(json, CampaignResponseDTO.class);
|
return gson.fromJson(json, CampaignResponseDTO.class);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,82 +16,81 @@ import org.wikipedia.model.EnumCodeMap;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public enum NavTab implements EnumCode {
|
public enum NavTab implements EnumCode {
|
||||||
CONTRIBUTIONS(R.string.contributions_fragment, R.drawable.ic_baseline_person_24) {
|
CONTRIBUTIONS(R.string.contributions_fragment, R.drawable.ic_baseline_person_24) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return ContributionsFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NEARBY(R.string.nearby_fragment, R.drawable.ic_location_on_black_24dp) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return NearbyParentFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return ExploreFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return BookmarkFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final EnumCodeMap<NavTab> MAP = new EnumCodeMap<>(NavTab.class);
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private final int text;
|
||||||
|
@DrawableRes
|
||||||
|
private final int icon;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
public static NavTab of(int code) {
|
||||||
public Fragment newInstance() {
|
return MAP.get(code);
|
||||||
return ContributionsFragment.newInstance();
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
NEARBY(R.string.nearby_fragment, R.drawable.ic_location_on_black_24dp){
|
public static int size() {
|
||||||
|
return MAP.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
public int text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
public int icon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public abstract Fragment newInstance();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment newInstance() {
|
public int code() {
|
||||||
return NearbyParentFragment.newInstance();
|
// This enumeration is not marshalled so tying declaration order to presentation order is
|
||||||
|
// convenient and consistent.
|
||||||
|
return ordinal();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
|
NavTab(@StringRes int text, @DrawableRes int icon) {
|
||||||
@NonNull
|
this.text = text;
|
||||||
@Override
|
this.icon = icon;
|
||||||
public Fragment newInstance() {
|
|
||||||
return ExploreFragment.newInstance();
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Fragment newInstance() {
|
|
||||||
return BookmarkFragment.newInstance();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Fragment newInstance() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final EnumCodeMap<NavTab> MAP = new EnumCodeMap<>(NavTab.class);
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
private final int text;
|
|
||||||
@DrawableRes
|
|
||||||
private final int icon;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static NavTab of(int code) {
|
|
||||||
return MAP.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int size() {
|
|
||||||
return MAP.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
public int text() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DrawableRes
|
|
||||||
public int icon() {
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public abstract Fragment newInstance();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int code() {
|
|
||||||
// This enumeration is not marshalled so tying declaration order to presentation order is
|
|
||||||
// convenient and consistent.
|
|
||||||
return ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
NavTab(@StringRes int text, @DrawableRes int icon) {
|
|
||||||
this.text = text;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,28 +8,31 @@ import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
|
|
||||||
public class NavTabFragmentPagerAdapter extends FragmentPagerAdapter {
|
public class NavTabFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||||
private Fragment currentFragment;
|
|
||||||
|
|
||||||
public NavTabFragmentPagerAdapter(FragmentManager mgr) {
|
private Fragment currentFragment;
|
||||||
super(mgr);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
public NavTabFragmentPagerAdapter(FragmentManager mgr) {
|
||||||
public Fragment getCurrentFragment() {
|
super(mgr);
|
||||||
return currentFragment;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override public Fragment getItem(int pos) {
|
@Nullable
|
||||||
return NavTab.of(pos).newInstance();
|
public Fragment getCurrentFragment() {
|
||||||
}
|
return currentFragment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public int getCount() {
|
@Override
|
||||||
return NavTab.size();
|
public Fragment getItem(int pos) {
|
||||||
}
|
return NavTab.of(pos).newInstance();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
public int getCount() {
|
||||||
currentFragment = ((Fragment) object);
|
return NavTab.size();
|
||||||
super.setPrimaryItem(container, position, object);
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||||
|
currentFragment = ((Fragment) object);
|
||||||
|
super.setPrimaryItem(container, position, object);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,32 @@ import fr.free.nrw.commons.contributions.MainActivity;
|
||||||
|
|
||||||
public class NavTabLayout extends BottomNavigationView {
|
public class NavTabLayout extends BottomNavigationView {
|
||||||
|
|
||||||
public NavTabLayout(Context context) {
|
public NavTabLayout(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
setTabViews();
|
setTabViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
public NavTabLayout(Context context, AttributeSet attrs) {
|
public NavTabLayout(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
setTabViews();
|
setTabViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
setTabViews();
|
setTabViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTabViews() {
|
private void setTabViews() {
|
||||||
if (((MainActivity)getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
|
if (((MainActivity) getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
|
||||||
for (int i = 0; i < NavTabLoggedOut.size(); i++) {
|
for (int i = 0; i < NavTabLoggedOut.size(); i++) {
|
||||||
NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
|
NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
|
||||||
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < NavTab.size(); i++) {
|
for (int i = 0; i < NavTab.size(); i++) {
|
||||||
NavTab navTab = NavTab.of(i);
|
NavTab navTab = NavTab.of(i);
|
||||||
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,66 +13,67 @@ import org.wikipedia.model.EnumCodeMap;
|
||||||
|
|
||||||
public enum NavTabLoggedOut implements EnumCode {
|
public enum NavTabLoggedOut implements EnumCode {
|
||||||
|
|
||||||
EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
|
EXPLORE(R.string.navigation_item_explore, R.drawable.ic_globe) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return ExploreFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return BookmarkFragment.newInstance();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment newInstance() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final EnumCodeMap<NavTabLoggedOut> MAP = new EnumCodeMap<>(
|
||||||
|
NavTabLoggedOut.class);
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
private final int text;
|
||||||
|
@DrawableRes
|
||||||
|
private final int icon;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
public static NavTabLoggedOut of(int code) {
|
||||||
public Fragment newInstance() {
|
return MAP.get(code);
|
||||||
return ExploreFragment.newInstance();
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
FAVORITES(R.string.favorites, R.drawable.ic_round_star_border_24px) {
|
public static int size() {
|
||||||
|
return MAP.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
public int text() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@DrawableRes
|
||||||
|
public int icon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
public abstract Fragment newInstance();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment newInstance() {
|
public int code() {
|
||||||
return BookmarkFragment.newInstance();
|
// This enumeration is not marshalled so tying declaration order to presentation order is
|
||||||
|
// convenient and consistent.
|
||||||
|
return ordinal();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
MORE(R.string.more, R.drawable.ic_menu_black_24dp) {
|
NavTabLoggedOut(@StringRes int text, @DrawableRes int icon) {
|
||||||
@NonNull
|
this.text = text;
|
||||||
@Override
|
this.icon = icon;
|
||||||
public Fragment newInstance() {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
private static final EnumCodeMap<NavTabLoggedOut> MAP = new EnumCodeMap<>(NavTabLoggedOut.class);
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
private final int text;
|
|
||||||
@DrawableRes
|
|
||||||
private final int icon;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static NavTabLoggedOut of(int code) {
|
|
||||||
return MAP.get(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int size() {
|
|
||||||
return MAP.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
public int text() {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@DrawableRes
|
|
||||||
public int icon() {
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public abstract Fragment newInstance();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int code() {
|
|
||||||
// This enumeration is not marshalled so tying declaration order to presentation order is
|
|
||||||
// convenient and consistent.
|
|
||||||
return ordinal();
|
|
||||||
}
|
|
||||||
|
|
||||||
NavTabLoggedOut(@StringRes int text, @DrawableRes int icon) {
|
|
||||||
this.text = text;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,65 +20,65 @@ import timber.log.Timber;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class FileUtilsWrapper {
|
public class FileUtilsWrapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public FileUtilsWrapper() {
|
public FileUtilsWrapper() {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileExt(String fileName) {
|
|
||||||
return FileUtils.getFileExt(fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSHA1(InputStream is) {
|
|
||||||
return FileUtils.getSHA1(is);
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException {
|
|
||||||
return FileUtils.getFileInputStream(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getGeolocationOfFile(String filePath) {
|
|
||||||
return FileUtils.getGeolocationOfFile(filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Takes a file as input and returns an Observable of files with the specified chunk size
|
|
||||||
*/
|
|
||||||
public List<File> getFileChunks(Context context, File file, final int chunkSize)
|
|
||||||
throws IOException {
|
|
||||||
final byte[] buffer = new byte[chunkSize];
|
|
||||||
|
|
||||||
//try-with-resources to ensure closing stream
|
|
||||||
try (final FileInputStream fis = new FileInputStream(file);
|
|
||||||
final BufferedInputStream bis = new BufferedInputStream(fis)) {
|
|
||||||
final List<File> buffers = new ArrayList<>();
|
|
||||||
int size;
|
|
||||||
while ((size = bis.read(buffer)) > 0) {
|
|
||||||
buffers.add(writeToFile(context, Arrays.copyOf(buffer, size), file.getName(),
|
|
||||||
getFileExt(file.getName())));
|
|
||||||
}
|
|
||||||
return buffers;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public String getFileExt(String fileName) {
|
||||||
* Create a temp file containing the passed byte data.
|
return FileUtils.getFileExt(fileName);
|
||||||
*/
|
}
|
||||||
private File writeToFile(Context context, final byte[] data, final String fileName,
|
|
||||||
String fileExtension)
|
public String getSHA1(InputStream is) {
|
||||||
throws IOException {
|
return FileUtils.getSHA1(is);
|
||||||
final File file = File.createTempFile(fileName, fileExtension, context.getCacheDir());
|
}
|
||||||
try {
|
|
||||||
if (!file.exists()) {
|
public FileInputStream getFileInputStream(String filePath) throws FileNotFoundException {
|
||||||
file.createNewFile();
|
return FileUtils.getFileInputStream(filePath);
|
||||||
}
|
}
|
||||||
final FileOutputStream fos = new FileOutputStream(file);
|
|
||||||
fos.write(data);
|
public String getGeolocationOfFile(String filePath) {
|
||||||
fos.close();
|
return FileUtils.getGeolocationOfFile(filePath);
|
||||||
} catch (final Exception throwable) {
|
}
|
||||||
Timber.e(throwable, "Failed to create file");
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a file as input and returns an Observable of files with the specified chunk size
|
||||||
|
*/
|
||||||
|
public List<File> getFileChunks(Context context, File file, final int chunkSize)
|
||||||
|
throws IOException {
|
||||||
|
final byte[] buffer = new byte[chunkSize];
|
||||||
|
|
||||||
|
//try-with-resources to ensure closing stream
|
||||||
|
try (final FileInputStream fis = new FileInputStream(file);
|
||||||
|
final BufferedInputStream bis = new BufferedInputStream(fis)) {
|
||||||
|
final List<File> buffers = new ArrayList<>();
|
||||||
|
int size;
|
||||||
|
while ((size = bis.read(buffer)) > 0) {
|
||||||
|
buffers.add(writeToFile(context, Arrays.copyOf(buffer, size), file.getName(),
|
||||||
|
getFileExt(file.getName())));
|
||||||
|
}
|
||||||
|
return buffers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a temp file containing the passed byte data.
|
||||||
|
*/
|
||||||
|
private File writeToFile(Context context, final byte[] data, final String fileName,
|
||||||
|
String fileExtension)
|
||||||
|
throws IOException {
|
||||||
|
final File file = File.createTempFile(fileName, fileExtension, context.getCacheDir());
|
||||||
|
try {
|
||||||
|
if (!file.exists()) {
|
||||||
|
file.createNewFile();
|
||||||
|
}
|
||||||
|
final FileOutputStream fos = new FileOutputStream(file);
|
||||||
|
fos.write(data);
|
||||||
|
fos.close();
|
||||||
|
} catch (final Exception throwable) {
|
||||||
|
Timber.e(throwable, "Failed to create file");
|
||||||
|
}
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ImageProcessingService {
|
public class ImageProcessingService {
|
||||||
|
|
||||||
private final FileUtilsWrapper fileUtilsWrapper;
|
private final FileUtilsWrapper fileUtilsWrapper;
|
||||||
private final ImageUtilsWrapper imageUtilsWrapper;
|
private final ImageUtilsWrapper imageUtilsWrapper;
|
||||||
private final ReadFBMD readFBMD;
|
private final ReadFBMD readFBMD;
|
||||||
|
|
@ -30,9 +31,9 @@ public class ImageProcessingService {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
||||||
ImageUtilsWrapper imageUtilsWrapper,
|
ImageUtilsWrapper imageUtilsWrapper,
|
||||||
ReadFBMD readFBMD, EXIFReader EXIFReader,
|
ReadFBMD readFBMD, EXIFReader EXIFReader,
|
||||||
MediaClient mediaClient, Context context) {
|
MediaClient mediaClient, Context context) {
|
||||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||||
this.readFBMD = readFBMD;
|
this.readFBMD = readFBMD;
|
||||||
|
|
@ -41,33 +42,34 @@ public class ImageProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||||
* geolocation for image - check for valid title
|
* geolocation for image - check for valid title
|
||||||
*/
|
*/
|
||||||
Single<Integer> validateImage(UploadItem uploadItem) {
|
Single<Integer> validateImage(UploadItem uploadItem) {
|
||||||
int currentImageQuality = uploadItem.getImageQuality();
|
int currentImageQuality = uploadItem.getImageQuality();
|
||||||
Timber.d("Current image quality is %d", currentImageQuality);
|
Timber.d("Current image quality is %d", currentImageQuality);
|
||||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
}
|
|
||||||
Timber.d("Checking the validity of image");
|
|
||||||
String filePath = uploadItem.getMediaUri().getPath();
|
|
||||||
|
|
||||||
return Single.zip(
|
|
||||||
checkDuplicateImage(filePath),
|
|
||||||
checkImageGeoLocation(uploadItem.getPlace(), filePath),
|
|
||||||
checkDarkImage(filePath),
|
|
||||||
validateItemTitle(uploadItem),
|
|
||||||
checkFBMD(filePath),
|
|
||||||
checkEXIF(filePath),
|
|
||||||
(duplicateImage, wrongGeoLocation, darkImage, itemTitle, fbmd, exif) -> {
|
|
||||||
Timber.d("duplicate: %d, geo: %d, dark: %d, title: %d" + "fbmd:" + fbmd + "exif:" + exif,
|
|
||||||
duplicateImage, wrongGeoLocation, darkImage, itemTitle);
|
|
||||||
return duplicateImage | wrongGeoLocation | darkImage | itemTitle | fbmd | exif;
|
|
||||||
}
|
}
|
||||||
);
|
Timber.d("Checking the validity of image");
|
||||||
}
|
String filePath = uploadItem.getMediaUri().getPath();
|
||||||
|
|
||||||
|
return Single.zip(
|
||||||
|
checkDuplicateImage(filePath),
|
||||||
|
checkImageGeoLocation(uploadItem.getPlace(), filePath),
|
||||||
|
checkDarkImage(filePath),
|
||||||
|
validateItemTitle(uploadItem),
|
||||||
|
checkFBMD(filePath),
|
||||||
|
checkEXIF(filePath),
|
||||||
|
(duplicateImage, wrongGeoLocation, darkImage, itemTitle, fbmd, exif) -> {
|
||||||
|
Timber.d("duplicate: %d, geo: %d, dark: %d, title: %d" + "fbmd:" + fbmd + "exif:"
|
||||||
|
+ exif,
|
||||||
|
duplicateImage, wrongGeoLocation, darkImage, itemTitle);
|
||||||
|
return duplicateImage | wrongGeoLocation | darkImage | itemTitle | fbmd | exif;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
* We want to discourage users from uploading images to Commons that were taken from Facebook.
|
||||||
|
|
@ -79,10 +81,10 @@ public class ImageProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We try to minimize uploads from the Commons app that might be copyright violations.
|
* We try to minimize uploads from the Commons app that might be copyright violations. If an
|
||||||
* If an image does not have any Exif metadata, then it was likely downloaded from the internet,
|
* image does not have any Exif metadata, then it was likely downloaded from the internet, and
|
||||||
* and is probably not an original work by the user. We detect these kinds of images by looking
|
* is probably not an original work by the user. We detect these kinds of images by looking for
|
||||||
* for the presence of some basic Exif metadata.
|
* the presence of some basic Exif metadata.
|
||||||
*/
|
*/
|
||||||
private Single<Integer> checkEXIF(String filepath) {
|
private Single<Integer> checkEXIF(String filepath) {
|
||||||
return EXIFReader.processMetadata(filepath);
|
return EXIFReader.processMetadata(filepath);
|
||||||
|
|
@ -90,9 +92,7 @@ public class ImageProcessingService {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks item caption
|
* Checks item caption - empty caption - existing caption
|
||||||
* - empty caption
|
|
||||||
* - existing caption
|
|
||||||
*
|
*
|
||||||
* @param uploadItem
|
* @param uploadItem
|
||||||
* @return
|
* @return
|
||||||
|
|
@ -105,11 +105,11 @@ public class ImageProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
||||||
.map(doesFileExist -> {
|
.map(doesFileExist -> {
|
||||||
Timber.d("Result for valid title is %s", doesFileExist);
|
Timber.d("Result for valid title is %s", doesFileExist);
|
||||||
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io());
|
.subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,13 +121,13 @@ public class ImageProcessingService {
|
||||||
private Single<Integer> checkDuplicateImage(String filePath) {
|
private Single<Integer> checkDuplicateImage(String filePath) {
|
||||||
Timber.d("Checking for duplicate image %s", filePath);
|
Timber.d("Checking for duplicate image %s", filePath);
|
||||||
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
|
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
|
||||||
.map(fileUtilsWrapper::getSHA1)
|
.map(fileUtilsWrapper::getSHA1)
|
||||||
.flatMap(mediaClient::checkFileExistsUsingSha)
|
.flatMap(mediaClient::checkFileExistsUsingSha)
|
||||||
.map(b -> {
|
.map(b -> {
|
||||||
Timber.d("Result for duplicate image %s", b);
|
Timber.d("Result for duplicate image %s", b);
|
||||||
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.io());
|
.subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -142,8 +142,8 @@ public class ImageProcessingService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for image geolocation
|
* Checks for image geolocation returns IMAGE_OK if the place is null or if the file doesn't
|
||||||
* returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
|
* contain a geolocation
|
||||||
*
|
*
|
||||||
* @param filePath file to be checked
|
* @param filePath file to be checked
|
||||||
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
||||||
|
|
@ -154,14 +154,15 @@ public class ImageProcessingService {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
}
|
}
|
||||||
return Single.fromCallable(() -> filePath)
|
return Single.fromCallable(() -> filePath)
|
||||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
.map(fileUtilsWrapper::getGeolocationOfFile)
|
||||||
.flatMap(geoLocation -> {
|
.flatMap(geoLocation -> {
|
||||||
if (StringUtils.isBlank(geoLocation)) {
|
if (StringUtils.isBlank(geoLocation)) {
|
||||||
return Single.just(ImageUtils.IMAGE_OK);
|
return Single.just(ImageUtils.IMAGE_OK);
|
||||||
}
|
}
|
||||||
return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
return imageUtilsWrapper
|
||||||
})
|
.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
||||||
.subscribeOn(Schedulers.io());
|
})
|
||||||
|
.subscribeOn(Schedulers.io());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,96 +16,96 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
class PageContentsCreator {
|
class PageContentsCreator {
|
||||||
|
|
||||||
//{{According to Exif data|2009-01-09}}
|
//{{According to Exif data|2009-01-09}}
|
||||||
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
||||||
|
|
||||||
//2009-01-09 → 9 January 2009
|
//2009-01-09 → 9 January 2009
|
||||||
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PageContentsCreator(Context context) {
|
public PageContentsCreator(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
|
||||||
|
|
||||||
public String createFrom(Contribution contribution) {
|
|
||||||
StringBuilder buffer = new StringBuilder();
|
|
||||||
final Media media = contribution.getMedia();
|
|
||||||
buffer
|
|
||||||
.append("== {{int:filedesc}} ==\n")
|
|
||||||
.append("{{Information\n")
|
|
||||||
.append("|description=").append(media.getFallbackDescription()).append("\n")
|
|
||||||
.append("|source=").append("{{own}}\n")
|
|
||||||
.append("|author=[[User:").append(media.getAuthor()).append("|")
|
|
||||||
.append(media.getAuthor()).append("]]\n");
|
|
||||||
|
|
||||||
String templatizedCreatedDate = getTemplatizedCreatedDate(
|
|
||||||
contribution.getDateCreated(), contribution.getDateCreatedSource());
|
|
||||||
if (!StringUtils.isBlank(templatizedCreatedDate)) {
|
|
||||||
buffer.append("|date=").append(templatizedCreatedDate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.append("}}").append("\n");
|
public String createFrom(Contribution contribution) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
final Media media = contribution.getMedia();
|
||||||
|
buffer
|
||||||
|
.append("== {{int:filedesc}} ==\n")
|
||||||
|
.append("{{Information\n")
|
||||||
|
.append("|description=").append(media.getFallbackDescription()).append("\n")
|
||||||
|
.append("|source=").append("{{own}}\n")
|
||||||
|
.append("|author=[[User:").append(media.getAuthor()).append("|")
|
||||||
|
.append(media.getAuthor()).append("]]\n");
|
||||||
|
|
||||||
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
|
String templatizedCreatedDate = getTemplatizedCreatedDate(
|
||||||
final String decimalCoords = contribution.getDecimalCoords();
|
contribution.getDateCreated(), contribution.getDateCreatedSource());
|
||||||
if (decimalCoords != null) {
|
if (!StringUtils.isBlank(templatizedCreatedDate)) {
|
||||||
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
buffer.append("|date=").append(templatizedCreatedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append("}}").append("\n");
|
||||||
|
|
||||||
|
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
|
||||||
|
final String decimalCoords = contribution.getDecimalCoords();
|
||||||
|
if (decimalCoords != null) {
|
||||||
|
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append("== {{int:license-header}} ==\n")
|
||||||
|
.append(licenseTemplateFor(media.getLicense())).append("\n\n")
|
||||||
|
.append("{{Uploaded from Mobile|platform=Android|version=")
|
||||||
|
.append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
|
||||||
|
final List<String> categories = media.getCategories();
|
||||||
|
if (categories != null && categories.size() != 0) {
|
||||||
|
for (int i = 0; i < categories.size(); i++) {
|
||||||
|
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer.append("{{subst:unc}}");
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.append("== {{int:license-header}} ==\n")
|
/**
|
||||||
.append(licenseTemplateFor(media.getLicense())).append("\n\n")
|
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
|
||||||
.append("{{Uploaded from Mobile|platform=Android|version=")
|
*
|
||||||
.append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
|
* @param dateCreated
|
||||||
final List<String> categories = media.getCategories();
|
* @param dateCreatedSource
|
||||||
if (categories != null && categories.size() != 0) {
|
* @return
|
||||||
for (int i = 0; i < categories.size(); i++) {
|
*/
|
||||||
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");
|
private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) {
|
||||||
}
|
if (dateCreated != null) {
|
||||||
} else {
|
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
buffer.append("{{subst:unc}}");
|
return String.format(Locale.ENGLISH,
|
||||||
}
|
isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE,
|
||||||
return buffer.toString();
|
dateFormat.format(dateCreated)
|
||||||
}
|
) + "\n";
|
||||||
|
}
|
||||||
/**
|
return "";
|
||||||
* Returns upload date in either TEMPLATE_DATE_ACC_TO_EXIF or TEMPLATE_DATA_OTHER_SOURCE
|
|
||||||
*
|
|
||||||
* @param dateCreated
|
|
||||||
* @param dateCreatedSource
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private String getTemplatizedCreatedDate(Date dateCreated, String dateCreatedSource) {
|
|
||||||
if (dateCreated != null) {
|
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
|
|
||||||
return String.format(Locale.ENGLISH,
|
|
||||||
isExif(dateCreatedSource) ? TEMPLATE_DATE_ACC_TO_EXIF : TEMPLATE_DATA_OTHER_SOURCE,
|
|
||||||
dateFormat.format(dateCreated)
|
|
||||||
) + "\n";
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isExif(String dateCreatedSource) {
|
|
||||||
return DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private String licenseTemplateFor(String license) {
|
|
||||||
switch (license) {
|
|
||||||
case Licenses.CC_BY_3:
|
|
||||||
return "{{self|cc-by-3.0}}";
|
|
||||||
case Licenses.CC_BY_4:
|
|
||||||
return "{{self|cc-by-4.0}}";
|
|
||||||
case Licenses.CC_BY_SA_3:
|
|
||||||
return "{{self|cc-by-sa-3.0}}";
|
|
||||||
case Licenses.CC_BY_SA_4:
|
|
||||||
return "{{self|cc-by-sa-4.0}}";
|
|
||||||
case Licenses.CC0:
|
|
||||||
return "{{self|cc-zero}}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException("Unrecognized license value: " + license);
|
private boolean isExif(String dateCreatedSource) {
|
||||||
}
|
return DateTimeWithSource.EXIF_SOURCE.equals(dateCreatedSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private String licenseTemplateFor(String license) {
|
||||||
|
switch (license) {
|
||||||
|
case Licenses.CC_BY_3:
|
||||||
|
return "{{self|cc-by-3.0}}";
|
||||||
|
case Licenses.CC_BY_4:
|
||||||
|
return "{{self|cc-by-4.0}}";
|
||||||
|
case Licenses.CC_BY_SA_3:
|
||||||
|
return "{{self|cc-by-sa-3.0}}";
|
||||||
|
case Licenses.CC_BY_SA_4:
|
||||||
|
return "{{self|cc-by-sa-4.0}}";
|
||||||
|
case Licenses.CC0:
|
||||||
|
return "{{self|cc-zero}}";
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,34 +15,34 @@ import javax.inject.Singleton;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ReadFBMD {
|
public class ReadFBMD {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ReadFBMD() {
|
public ReadFBMD() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> processMetadata(String path) {
|
public Single<Integer> processMetadata(String path) {
|
||||||
return Single.fromCallable(() -> {
|
return Single.fromCallable(() -> {
|
||||||
try {
|
try {
|
||||||
int psBlockOffset;
|
int psBlockOffset;
|
||||||
int fbmdOffset;
|
int fbmdOffset;
|
||||||
|
|
||||||
try (FileInputStream fs = new FileInputStream(path)) {
|
try (FileInputStream fs = new FileInputStream(path)) {
|
||||||
byte[] bytes = new byte[4096];
|
byte[] bytes = new byte[4096];
|
||||||
fs.read(bytes);
|
fs.read(bytes);
|
||||||
fs.close();
|
fs.close();
|
||||||
String fileStr = new String(bytes);
|
String fileStr = new String(bytes);
|
||||||
psBlockOffset = fileStr.indexOf("8BIM");
|
psBlockOffset = fileStr.indexOf("8BIM");
|
||||||
fbmdOffset = fileStr.indexOf("FBMD");
|
fbmdOffset = fileStr.indexOf("FBMD");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (psBlockOffset > 0 && fbmdOffset > 0
|
if (psBlockOffset > 0 && fbmdOffset > 0
|
||||||
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
||||||
return ImageUtils.FILE_FBMD;
|
return ImageUtils.FILE_FBMD;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
return ImageUtils.IMAGE_OK;
|
return ImageUtils.IMAGE_OK;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,199 +35,204 @@ import timber.log.Timber;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class UploadClient {
|
public class UploadClient {
|
||||||
|
|
||||||
private final int CHUNK_SIZE = 512 * 1024; // 512 KB
|
private final int CHUNK_SIZE = 512 * 1024; // 512 KB
|
||||||
|
|
||||||
//This is maximum duration for which a stash is persisted on MediaWiki
|
//This is maximum duration for which a stash is persisted on MediaWiki
|
||||||
// https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
|
// https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
|
||||||
private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
|
private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
|
||||||
|
|
||||||
private final UploadInterface uploadInterface;
|
private final UploadInterface uploadInterface;
|
||||||
private final CsrfTokenClient csrfTokenClient;
|
private final CsrfTokenClient csrfTokenClient;
|
||||||
private final PageContentsCreator pageContentsCreator;
|
private final PageContentsCreator pageContentsCreator;
|
||||||
private final FileUtilsWrapper fileUtilsWrapper;
|
private final FileUtilsWrapper fileUtilsWrapper;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public UploadClient(final UploadInterface uploadInterface,
|
public UploadClient(final UploadInterface uploadInterface,
|
||||||
@Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
|
@Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
|
||||||
final PageContentsCreator pageContentsCreator,
|
final PageContentsCreator pageContentsCreator,
|
||||||
final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
|
final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
|
||||||
this.uploadInterface = uploadInterface;
|
this.uploadInterface = uploadInterface;
|
||||||
this.csrfTokenClient = csrfTokenClient;
|
this.csrfTokenClient = csrfTokenClient;
|
||||||
this.pageContentsCreator = pageContentsCreator;
|
this.pageContentsCreator = pageContentsCreator;
|
||||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload file to stash in chunks of specified size. Uploading files in chunks will make handling
|
|
||||||
* of large files easier. Also, it will be useful in supporting pause/resume of uploads
|
|
||||||
*/
|
|
||||||
public Observable<StashUploadResult> uploadFileToStash(
|
|
||||||
final Context context, final String filename, final Contribution contribution,
|
|
||||||
final NotificationUpdateProgressListener notificationUpdater) throws IOException {
|
|
||||||
if (contribution.getChunkInfo() != null
|
|
||||||
&& contribution.getChunkInfo().getTotalChunks() == contribution.getChunkInfo()
|
|
||||||
.getIndexOfNextChunkToUpload()) {
|
|
||||||
return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
|
|
||||||
contribution.getChunkInfo().getUploadResult().getFilekey()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
|
/**
|
||||||
|
* Upload file to stash in chunks of specified size. Uploading files in chunks will make
|
||||||
|
* handling of large files easier. Also, it will be useful in supporting pause/resume of
|
||||||
|
* uploads
|
||||||
|
*/
|
||||||
|
public Observable<StashUploadResult> uploadFileToStash(
|
||||||
|
final Context context, final String filename, final Contribution contribution,
|
||||||
|
final NotificationUpdateProgressListener notificationUpdater) throws IOException {
|
||||||
|
if (contribution.getChunkInfo() != null
|
||||||
|
&& contribution.getChunkInfo().getTotalChunks() == contribution.getChunkInfo()
|
||||||
|
.getIndexOfNextChunkToUpload()) {
|
||||||
|
return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
|
||||||
|
contribution.getChunkInfo().getUploadResult().getFilekey()));
|
||||||
|
}
|
||||||
|
|
||||||
final File file = new File(contribution.getLocalUri().getPath());
|
CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
|
||||||
final List<File> fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
|
|
||||||
|
|
||||||
final int totalChunks = fileChunks.size();
|
final File file = new File(contribution.getLocalUri().getPath());
|
||||||
|
final List<File> fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
|
||||||
|
|
||||||
final MediaType mediaType = MediaType
|
final int totalChunks = fileChunks.size();
|
||||||
.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
|
|
||||||
|
|
||||||
final AtomicReference<ChunkInfo> chunkInfo = new AtomicReference<>();
|
final MediaType mediaType = MediaType
|
||||||
if (isStashValid(contribution)) {
|
.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
|
||||||
chunkInfo.set(contribution.getChunkInfo());
|
|
||||||
|
|
||||||
Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
|
final AtomicReference<ChunkInfo> chunkInfo = new AtomicReference<>();
|
||||||
contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
|
if (isStashValid(contribution)) {
|
||||||
contribution.getChunkInfo().getTotalChunks());
|
chunkInfo.set(contribution.getChunkInfo());
|
||||||
}
|
|
||||||
|
|
||||||
final AtomicInteger index = new AtomicInteger();
|
Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
|
||||||
final AtomicBoolean failures = new AtomicBoolean();
|
contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
|
||||||
|
contribution.getChunkInfo().getTotalChunks());
|
||||||
|
}
|
||||||
|
|
||||||
compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
|
final AtomicInteger index = new AtomicInteger();
|
||||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
final AtomicBoolean failures = new AtomicBoolean();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunkInfo.get() != null && index.get() < chunkInfo.get().getIndexOfNextChunkToUpload()) {
|
compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
|
||||||
index.incrementAndGet();
|
if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
||||||
Timber.d("Chunk: Increment and return: %s", index.get());
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
index.getAndIncrement();
|
|
||||||
final int offset =
|
|
||||||
chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getOffset() : 0;
|
|
||||||
|
|
||||||
Timber.d("Chunk: Sending Chunk number: %s, offset: %s", index.get(), offset);
|
|
||||||
final String filekey =
|
|
||||||
chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getFilekey() : null;
|
|
||||||
|
|
||||||
final RequestBody requestBody = RequestBody
|
|
||||||
.create(mediaType, chunkFile);
|
|
||||||
final CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody,
|
|
||||||
notificationUpdater::onProgress, offset,
|
|
||||||
file.length());
|
|
||||||
|
|
||||||
compositeDisposable.add(uploadChunkToStash(filename,
|
|
||||||
file.length(),
|
|
||||||
offset,
|
|
||||||
filekey,
|
|
||||||
countingRequestBody).subscribe(uploadResult -> {
|
|
||||||
Timber.d("Chunk: Received Chunk number: %s, offset: %s", index.get(),
|
|
||||||
uploadResult.getOffset());
|
|
||||||
chunkInfo.set(
|
|
||||||
new ChunkInfo(uploadResult, index.get(), totalChunks));
|
|
||||||
notificationUpdater.onChunkUploaded(contribution, chunkInfo.get());
|
|
||||||
}, throwable -> {
|
|
||||||
Timber.e(throwable, "Received error in chunk upload");
|
|
||||||
failures.set(true);
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId())) {
|
|
||||||
Timber.d("Upload stash paused %s", contribution.getPageId());
|
|
||||||
return Observable.just(new StashUploadResult(StashUploadState.PAUSED, null));
|
|
||||||
} else if (failures.get()) {
|
|
||||||
Timber.d("Upload stash contains failures %s", contribution.getPageId());
|
|
||||||
return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
|
|
||||||
} else if (chunkInfo.get() != null) {
|
|
||||||
Timber.d("Upload stash success %s", contribution.getPageId());
|
|
||||||
return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
|
|
||||||
chunkInfo.get().getUploadResult().getFilekey()));
|
|
||||||
} else {
|
|
||||||
Timber.d("Upload stash failed %s", contribution.getPageId());
|
|
||||||
return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stash is valid for 6 hours. This function checks the validity of stash
|
|
||||||
* @param contribution
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private boolean isStashValid(Contribution contribution) {
|
|
||||||
return contribution.getChunkInfo() != null &&
|
|
||||||
contribution.getDateModified()
|
|
||||||
.after(new Date(System.currentTimeMillis() - MAX_CHUNK_AGE));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a file chunk to stash
|
|
||||||
*
|
|
||||||
* @param filename The name of the file being uploaded
|
|
||||||
* @param fileSize The total size of the file
|
|
||||||
* @param offset The offset returned by the previous chunk upload
|
|
||||||
* @param fileKey The filekey returned by the previous chunk upload
|
|
||||||
* @param countingRequestBody Request body with chunk file
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
Observable<UploadResult> uploadChunkToStash(final String filename,
|
|
||||||
final long fileSize,
|
|
||||||
final long offset,
|
|
||||||
final String fileKey,
|
|
||||||
final CountingRequestBody countingRequestBody) {
|
|
||||||
final MultipartBody.Part filePart;
|
|
||||||
try {
|
|
||||||
filePart = MultipartBody.Part
|
|
||||||
.createFormData("chunk", URLEncoder.encode(filename, "utf-8"), countingRequestBody);
|
|
||||||
|
|
||||||
return uploadInterface.uploadFileToStash(toRequestBody(filename),
|
|
||||||
toRequestBody(String.valueOf(fileSize)),
|
|
||||||
toRequestBody(String.valueOf(offset)),
|
|
||||||
toRequestBody(fileKey),
|
|
||||||
toRequestBody(csrfTokenClient.getTokenBlocking()),
|
|
||||||
filePart)
|
|
||||||
.map(UploadResponse::getUpload);
|
|
||||||
} catch (final Throwable throwable) {
|
|
||||||
Timber.e(throwable, "Failed to upload chunk to stash");
|
|
||||||
return Observable.error(throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts string value to request body
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private RequestBody toRequestBody(@Nullable final String value) {
|
|
||||||
return value == null ? null : RequestBody.create(okhttp3.MultipartBody.FORM, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Observable<UploadResult> uploadFileFromStash(
|
|
||||||
final Contribution contribution,
|
|
||||||
final String uniqueFileName,
|
|
||||||
final String fileKey) {
|
|
||||||
try {
|
|
||||||
return uploadInterface
|
|
||||||
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
|
|
||||||
pageContentsCreator.createFrom(contribution),
|
|
||||||
CommonsApplication.DEFAULT_EDIT_SUMMARY,
|
|
||||||
uniqueFileName,
|
|
||||||
fileKey).map(uploadResponse -> {
|
|
||||||
UploadResponse uploadResult = gson.fromJson(uploadResponse, UploadResponse.class);
|
|
||||||
if (uploadResult.getUpload() == null) {
|
|
||||||
final MwException exception = gson.fromJson(uploadResponse, MwException.class);
|
|
||||||
Timber.e(exception, "Error in uploading file from stash");
|
|
||||||
throw new RuntimeException(exception.getErrorCode());
|
|
||||||
}
|
}
|
||||||
return uploadResult.getUpload();
|
|
||||||
});
|
if (chunkInfo.get() != null && index.get() < chunkInfo.get()
|
||||||
} catch (final Throwable throwable) {
|
.getIndexOfNextChunkToUpload()) {
|
||||||
Timber.e(throwable, "Exception occurred in uploading file from stash");
|
index.incrementAndGet();
|
||||||
return Observable.error(throwable);
|
Timber.d("Chunk: Increment and return: %s", index.get());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
index.getAndIncrement();
|
||||||
|
final int offset =
|
||||||
|
chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getOffset() : 0;
|
||||||
|
|
||||||
|
Timber.d("Chunk: Sending Chunk number: %s, offset: %s", index.get(), offset);
|
||||||
|
final String filekey =
|
||||||
|
chunkInfo.get() != null ? chunkInfo.get().getUploadResult().getFilekey() : null;
|
||||||
|
|
||||||
|
final RequestBody requestBody = RequestBody
|
||||||
|
.create(mediaType, chunkFile);
|
||||||
|
final CountingRequestBody countingRequestBody = new CountingRequestBody(requestBody,
|
||||||
|
notificationUpdater::onProgress, offset,
|
||||||
|
file.length());
|
||||||
|
|
||||||
|
compositeDisposable.add(uploadChunkToStash(filename,
|
||||||
|
file.length(),
|
||||||
|
offset,
|
||||||
|
filekey,
|
||||||
|
countingRequestBody).subscribe(uploadResult -> {
|
||||||
|
Timber.d("Chunk: Received Chunk number: %s, offset: %s", index.get(),
|
||||||
|
uploadResult.getOffset());
|
||||||
|
chunkInfo.set(
|
||||||
|
new ChunkInfo(uploadResult, index.get(), totalChunks));
|
||||||
|
notificationUpdater.onChunkUploaded(contribution, chunkInfo.get());
|
||||||
|
}, throwable -> {
|
||||||
|
Timber.e(throwable, "Received error in chunk upload");
|
||||||
|
failures.set(true);
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (CommonsApplication.pauseUploads.get(contribution.getPageId())) {
|
||||||
|
Timber.d("Upload stash paused %s", contribution.getPageId());
|
||||||
|
return Observable.just(new StashUploadResult(StashUploadState.PAUSED, null));
|
||||||
|
} else if (failures.get()) {
|
||||||
|
Timber.d("Upload stash contains failures %s", contribution.getPageId());
|
||||||
|
return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
|
||||||
|
} else if (chunkInfo.get() != null) {
|
||||||
|
Timber.d("Upload stash success %s", contribution.getPageId());
|
||||||
|
return Observable.just(new StashUploadResult(StashUploadState.SUCCESS,
|
||||||
|
chunkInfo.get().getUploadResult().getFilekey()));
|
||||||
|
} else {
|
||||||
|
Timber.d("Upload stash failed %s", contribution.getPageId());
|
||||||
|
return Observable.just(new StashUploadResult(StashUploadState.FAILED, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stash is valid for 6 hours. This function checks the validity of stash
|
||||||
|
*
|
||||||
|
* @param contribution
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private boolean isStashValid(Contribution contribution) {
|
||||||
|
return contribution.getChunkInfo() != null &&
|
||||||
|
contribution.getDateModified()
|
||||||
|
.after(new Date(System.currentTimeMillis() - MAX_CHUNK_AGE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a file chunk to stash
|
||||||
|
*
|
||||||
|
* @param filename The name of the file being uploaded
|
||||||
|
* @param fileSize The total size of the file
|
||||||
|
* @param offset The offset returned by the previous chunk upload
|
||||||
|
* @param fileKey The filekey returned by the previous chunk upload
|
||||||
|
* @param countingRequestBody Request body with chunk file
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
Observable<UploadResult> uploadChunkToStash(final String filename,
|
||||||
|
final long fileSize,
|
||||||
|
final long offset,
|
||||||
|
final String fileKey,
|
||||||
|
final CountingRequestBody countingRequestBody) {
|
||||||
|
final MultipartBody.Part filePart;
|
||||||
|
try {
|
||||||
|
filePart = MultipartBody.Part
|
||||||
|
.createFormData("chunk", URLEncoder.encode(filename, "utf-8"), countingRequestBody);
|
||||||
|
|
||||||
|
return uploadInterface.uploadFileToStash(toRequestBody(filename),
|
||||||
|
toRequestBody(String.valueOf(fileSize)),
|
||||||
|
toRequestBody(String.valueOf(offset)),
|
||||||
|
toRequestBody(fileKey),
|
||||||
|
toRequestBody(csrfTokenClient.getTokenBlocking()),
|
||||||
|
filePart)
|
||||||
|
.map(UploadResponse::getUpload);
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Failed to upload chunk to stash");
|
||||||
|
return Observable.error(throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts string value to request body
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private RequestBody toRequestBody(@Nullable final String value) {
|
||||||
|
return value == null ? null : RequestBody.create(okhttp3.MultipartBody.FORM, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Observable<UploadResult> uploadFileFromStash(
|
||||||
|
final Contribution contribution,
|
||||||
|
final String uniqueFileName,
|
||||||
|
final String fileKey) {
|
||||||
|
try {
|
||||||
|
return uploadInterface
|
||||||
|
.uploadFileFromStash(csrfTokenClient.getTokenBlocking(),
|
||||||
|
pageContentsCreator.createFrom(contribution),
|
||||||
|
CommonsApplication.DEFAULT_EDIT_SUMMARY,
|
||||||
|
uniqueFileName,
|
||||||
|
fileKey).map(uploadResponse -> {
|
||||||
|
UploadResponse uploadResult = gson
|
||||||
|
.fromJson(uploadResponse, UploadResponse.class);
|
||||||
|
if (uploadResult.getUpload() == null) {
|
||||||
|
final MwException exception = gson
|
||||||
|
.fromJson(uploadResponse, MwException.class);
|
||||||
|
Timber.e(exception, "Error in uploading file from stash");
|
||||||
|
throw new RuntimeException(exception.getErrorCode());
|
||||||
|
}
|
||||||
|
return uploadResult.getUpload();
|
||||||
|
});
|
||||||
|
} catch (final Throwable throwable) {
|
||||||
|
Timber.e(throwable, "Exception occurred in uploading file from stash");
|
||||||
|
return Observable.error(throwable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,110 +14,111 @@ import java.util.List;
|
||||||
|
|
||||||
public class UploadItem {
|
public class UploadItem {
|
||||||
|
|
||||||
private final Uri mediaUri;
|
private final Uri mediaUri;
|
||||||
private final String mimeType;
|
private final String mimeType;
|
||||||
private ImageCoordinates gpsCoords;
|
private ImageCoordinates gpsCoords;
|
||||||
private List<UploadMediaDetail> uploadMediaDetails;
|
private List<UploadMediaDetail> uploadMediaDetails;
|
||||||
private Place place;
|
private Place place;
|
||||||
private final long createdTimestamp;
|
private final long createdTimestamp;
|
||||||
private final String createdTimestampSource;
|
private final String createdTimestampSource;
|
||||||
private final BehaviorSubject<Integer> imageQuality;
|
private final BehaviorSubject<Integer> imageQuality;
|
||||||
private boolean hasInvalidLocation;
|
private boolean hasInvalidLocation;
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
@SuppressLint("CheckResult")
|
||||||
UploadItem(final Uri mediaUri,
|
UploadItem(final Uri mediaUri,
|
||||||
final String mimeType,
|
final String mimeType,
|
||||||
final ImageCoordinates gpsCoords,
|
final ImageCoordinates gpsCoords,
|
||||||
final Place place,
|
final Place place,
|
||||||
final long createdTimestamp,
|
final long createdTimestamp,
|
||||||
final String createdTimestampSource) {
|
final String createdTimestampSource) {
|
||||||
this.createdTimestampSource = createdTimestampSource;
|
this.createdTimestampSource = createdTimestampSource;
|
||||||
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
|
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
|
||||||
this.place = place;
|
this.place = place;
|
||||||
this.mediaUri = mediaUri;
|
this.mediaUri = mediaUri;
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
this.gpsCoords = gpsCoords;
|
this.gpsCoords = gpsCoords;
|
||||||
this.createdTimestamp = createdTimestamp;
|
this.createdTimestamp = createdTimestamp;
|
||||||
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
||||||
}
|
|
||||||
|
|
||||||
public String getCreatedTimestampSource() {
|
|
||||||
return createdTimestampSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ImageCoordinates getGpsCoords() {
|
|
||||||
return gpsCoords;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<UploadMediaDetail> getUploadMediaDetails() {
|
|
||||||
return uploadMediaDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getCreatedTimestamp() {
|
|
||||||
return createdTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getMediaUri() {
|
|
||||||
return mediaUri;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getImageQuality() {
|
|
||||||
return imageQuality.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setImageQuality(final int imageQuality) {
|
|
||||||
this.imageQuality.onNext(imageQuality);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the corresponding place to the uploadItem
|
|
||||||
* @param place geolocated Wikidata item
|
|
||||||
*/
|
|
||||||
public void setPlace(Place place) {
|
|
||||||
this.place = place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Place getPlace() {
|
|
||||||
return place;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
|
|
||||||
this.uploadMediaDetails = uploadMediaDetails;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable final Object obj) {
|
|
||||||
if (!(obj instanceof UploadItem)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
|
|
||||||
|
|
||||||
}
|
public String getCreatedTimestampSource() {
|
||||||
|
return createdTimestampSource;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
public ImageCoordinates getGpsCoords() {
|
||||||
public int hashCode() {
|
return gpsCoords;
|
||||||
return mediaUri.hashCode();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public List<UploadMediaDetail> getUploadMediaDetails() {
|
||||||
* Choose a filename for the media. Currently, the caption is used as a filename. If several
|
return uploadMediaDetails;
|
||||||
* languages have been entered, the first language is used.
|
}
|
||||||
*/
|
|
||||||
public String getFileName() {
|
|
||||||
return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
|
|
||||||
MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setGpsCoords(final ImageCoordinates gpsCoords) {
|
public long getCreatedTimestamp() {
|
||||||
this.gpsCoords = gpsCoords;
|
return createdTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHasInvalidLocation(boolean hasInvalidLocation) {
|
public Uri getMediaUri() {
|
||||||
this.hasInvalidLocation=hasInvalidLocation;
|
return mediaUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasInvalidLocation() {
|
public int getImageQuality() {
|
||||||
return hasInvalidLocation;
|
return imageQuality.getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setImageQuality(final int imageQuality) {
|
||||||
|
this.imageQuality.onNext(imageQuality);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the corresponding place to the uploadItem
|
||||||
|
*
|
||||||
|
* @param place geolocated Wikidata item
|
||||||
|
*/
|
||||||
|
public void setPlace(Place place) {
|
||||||
|
this.place = place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Place getPlace() {
|
||||||
|
return place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediaDetails(final List<UploadMediaDetail> uploadMediaDetails) {
|
||||||
|
this.uploadMediaDetails = uploadMediaDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable final Object obj) {
|
||||||
|
if (!(obj instanceof UploadItem)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mediaUri.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Choose a filename for the media. Currently, the caption is used as a filename. If several
|
||||||
|
* languages have been entered, the first language is used.
|
||||||
|
*/
|
||||||
|
public String getFileName() {
|
||||||
|
return Utils.fixExtension(uploadMediaDetails.get(0).getCaptionText(),
|
||||||
|
MimeTypeMapWrapper.getExtensionFromMimeType(mimeType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGpsCoords(final ImageCoordinates gpsCoords) {
|
||||||
|
this.gpsCoords = gpsCoords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHasInvalidLocation(boolean hasInvalidLocation) {
|
||||||
|
this.hasInvalidLocation = hasInvalidLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasInvalidLocation() {
|
||||||
|
return hasInvalidLocation;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
|
||||||
public class ActivityUtils {
|
public class ActivityUtils {
|
||||||
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
|
|
||||||
Intent intent = new Intent(context, cls);
|
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
|
||||||
for (int flag: flags) {
|
Intent intent = new Intent(context, cls);
|
||||||
intent.addFlags(flag);
|
for (int flag : flags) {
|
||||||
|
intent.addFlags(flag);
|
||||||
|
}
|
||||||
|
context.startActivity(intent);
|
||||||
}
|
}
|
||||||
context.startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,22 @@ import javax.inject.Singleton;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class ImageUtilsWrapper {
|
public class ImageUtilsWrapper {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ImageUtilsWrapper() {
|
public ImageUtilsWrapper() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||||
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||||
.subscribeOn(Schedulers.computation());
|
.subscribeOn(Schedulers.computation());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
|
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
|
||||||
LatLng latLng) {
|
LatLng latLng) {
|
||||||
return Single.fromCallable(
|
return Single.fromCallable(
|
||||||
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
|
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
|
||||||
.subscribeOn(Schedulers.computation())
|
.subscribeOn(Schedulers.computation())
|
||||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
|
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
|
||||||
: ImageUtils.IMAGE_OK);
|
: ImageUtils.IMAGE_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,43 +19,44 @@ import timber.log.Timber;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class WikiBaseClient {
|
public class WikiBaseClient {
|
||||||
|
|
||||||
private final WikiBaseInterface wikiBaseInterface;
|
private final WikiBaseInterface wikiBaseInterface;
|
||||||
private final CsrfTokenClient csrfTokenClient;
|
private final CsrfTokenClient csrfTokenClient;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
|
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
|
||||||
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
||||||
this.wikiBaseInterface = wikiBaseInterface;
|
this.wikiBaseInterface = wikiBaseInterface;
|
||||||
this.csrfTokenClient = csrfTokenClient;
|
this.csrfTokenClient = csrfTokenClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
|
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
|
||||||
return csrfToken()
|
return csrfToken()
|
||||||
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
|
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
|
||||||
.map(response -> (response.getSuccessVal() == 1)));
|
.map(response -> (response.getSuccessVal() == 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
|
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
|
||||||
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
|
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
|
||||||
.map(response -> (long) (response.query().pages().get(0).pageId()));
|
.map(response -> (long) (response.query().pages().get(0).pageId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
|
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
|
||||||
String languageCode, String captionValue) {
|
String languageCode, String captionValue) {
|
||||||
return csrfToken()
|
return csrfToken()
|
||||||
.switchMap(editToken -> wikiBaseInterface
|
.switchMap(editToken -> wikiBaseInterface
|
||||||
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, captionValue));
|
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode,
|
||||||
|
captionValue));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Observable<String> csrfToken() {
|
private Observable<String> csrfToken() {
|
||||||
return Observable.fromCallable(() -> {
|
return Observable.fromCallable(() -> {
|
||||||
try {
|
try {
|
||||||
return csrfTokenClient.getTokenBlocking();
|
return csrfTokenClient.getTokenBlocking();
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
Timber.e(throwable);
|
Timber.e(throwable);
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,32 +15,33 @@ import org.wikipedia.wikidata.Statement_partial;
|
||||||
public class WikidataClient {
|
public class WikidataClient {
|
||||||
|
|
||||||
|
|
||||||
private final WikidataInterface wikidataInterface;
|
private final WikidataInterface wikidataInterface;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
|
public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
|
||||||
this.wikidataInterface = wikidataInterface;
|
this.wikidataInterface = wikidataInterface;
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create wikidata claim to add P18 value
|
* Create wikidata claim to add P18 value
|
||||||
*
|
*
|
||||||
* @return revisionID of the edit
|
* @return revisionID of the edit
|
||||||
*/
|
*/
|
||||||
Observable<Long> setClaim(Statement_partial claim, String tags) {
|
Observable<Long> setClaim(Statement_partial claim, String tags) {
|
||||||
return getCsrfToken()
|
return getCsrfToken()
|
||||||
.flatMap(csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
|
.flatMap(
|
||||||
.map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
|
csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
|
||||||
}
|
.map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get csrf token for wikidata edit
|
* Get csrf token for wikidata edit
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
private Observable<String> getCsrfToken() {
|
private Observable<String> getCsrfToken() {
|
||||||
return wikidataInterface.getCsrfToken()
|
return wikidataInterface.getCsrfToken()
|
||||||
.map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
|
.map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,169 +44,172 @@ import timber.log.Timber;
|
||||||
@Singleton
|
@Singleton
|
||||||
public class WikidataEditService {
|
public class WikidataEditService {
|
||||||
|
|
||||||
public static final String COMMONS_APP_TAG = "wikimedia-commons-app";
|
public static final String COMMONS_APP_TAG = "wikimedia-commons-app";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final WikidataEditListener wikidataEditListener;
|
private final WikidataEditListener wikidataEditListener;
|
||||||
private final JsonKvStore directKvStore;
|
private final JsonKvStore directKvStore;
|
||||||
private final WikiBaseClient wikiBaseClient;
|
private final WikiBaseClient wikiBaseClient;
|
||||||
private final WikidataClient wikidataClient;
|
private final WikidataClient wikidataClient;
|
||||||
private final Gson gson;
|
private final Gson gson;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WikidataEditService(final Context context,
|
public WikidataEditService(final Context context,
|
||||||
final WikidataEditListener wikidataEditListener,
|
final WikidataEditListener wikidataEditListener,
|
||||||
@Named("default_preferences") final JsonKvStore directKvStore,
|
@Named("default_preferences") final JsonKvStore directKvStore,
|
||||||
final WikiBaseClient wikiBaseClient,
|
final WikiBaseClient wikiBaseClient,
|
||||||
final WikidataClient wikidataClient, final Gson gson) {
|
final WikidataClient wikidataClient, final Gson gson) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.wikidataEditListener = wikidataEditListener;
|
this.wikidataEditListener = wikidataEditListener;
|
||||||
this.directKvStore = directKvStore;
|
this.directKvStore = directKvStore;
|
||||||
this.wikiBaseClient = wikiBaseClient;
|
this.wikiBaseClient = wikiBaseClient;
|
||||||
this.wikidataClient = wikidataClient;
|
this.wikidataClient = wikidataClient;
|
||||||
this.gson = gson;
|
this.gson = gson;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Edits the wikibase entity by adding DEPICTS property. Adding DEPICTS property requires call to
|
|
||||||
* the wikibase API to set tag against the entity.
|
|
||||||
*/
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private Observable<Boolean> addDepictsProperty(final String fileEntityId,
|
|
||||||
final WikidataItem depictedItem) {
|
|
||||||
|
|
||||||
final EditClaim data = editClaim(
|
|
||||||
ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10)
|
|
||||||
: depictedItem.getId()
|
|
||||||
);
|
|
||||||
|
|
||||||
return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(data))
|
|
||||||
.doOnNext(success -> {
|
|
||||||
if (success) {
|
|
||||||
Timber.d("DEPICTS property was set successfully for %s", fileEntityId);
|
|
||||||
} else {
|
|
||||||
Timber.d("Unable to set DEPICTS property for %s", fileEntityId);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.doOnError(throwable -> {
|
|
||||||
Timber.e(throwable, "Error occurred while setting DEPICTS property");
|
|
||||||
ViewUtil.showLongToast(context, throwable.toString());
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io());
|
|
||||||
}
|
|
||||||
|
|
||||||
private EditClaim editClaim(final String entityId) {
|
|
||||||
return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a success toast when the edit is made successfully
|
|
||||||
*/
|
|
||||||
private void showSuccessToast(final String wikiItemName) {
|
|
||||||
final String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
|
|
||||||
final String successMessage = String
|
|
||||||
.format(Locale.getDefault(), successStringTemplate, wikiItemName);
|
|
||||||
ViewUtil.showLongToast(context, successMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds label to Wikidata using the fileEntityId and the edit token, obtained from
|
|
||||||
* csrfTokenClient
|
|
||||||
*
|
|
||||||
* @param fileEntityId
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
private Observable<Boolean> addCaption(final long fileEntityId, final String languageCode,
|
|
||||||
final String captionValue) {
|
|
||||||
return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue)
|
|
||||||
.doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse))
|
|
||||||
.doOnError(throwable -> {
|
|
||||||
Timber.e(throwable, "Error occurred while setting Captions");
|
|
||||||
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
|
||||||
})
|
|
||||||
.map(mwPostResponse -> mwPostResponse != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onAddCaptionResponse(Long fileEntityId, MwPostResponse response) {
|
|
||||||
if (response != null) {
|
|
||||||
Timber.d("Caption successfully set, revision id = %s", response);
|
|
||||||
} else {
|
|
||||||
Timber.d("Error occurred while setting Captions, fileEntityId = %s", fileEntityId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long createClaim(@Nullable final WikidataPlace wikidataPlace, final String fileName, final
|
|
||||||
Map<String, String> captions) {
|
|
||||||
if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) {
|
|
||||||
Timber
|
|
||||||
.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return addImageAndMediaLegends(wikidataPlace, fileName, captions);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long addImageAndMediaLegends(final WikidataItem wikidataItem, final String fileName,
|
|
||||||
final Map<String, String> captions) {
|
|
||||||
final Snak_partial p18 = new Snak_partial("value", WikidataProperties.IMAGE.getPropertyName(),
|
|
||||||
new ValueString(fileName.replace("File:", "")));
|
|
||||||
|
|
||||||
final List<Snak_partial> snaks = new ArrayList<>();
|
|
||||||
for (final Map.Entry<String, String> entry : captions.entrySet()) {
|
|
||||||
snaks.add(new Snak_partial("value",
|
|
||||||
WikidataProperties.MEDIA_LEGENDS.getPropertyName(), new DataValue.MonoLingualText(
|
|
||||||
new WikiBaseMonolingualTextValue(entry.getValue(), entry.getKey()))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String id = wikidataItem.getId() + "$" + UUID.randomUUID().toString();
|
/**
|
||||||
final Statement_partial claim = new Statement_partial(p18, "statement", "normal", id,
|
* Edits the wikibase entity by adding DEPICTS property. Adding DEPICTS property requires call
|
||||||
Collections.singletonMap(WikidataProperties.MEDIA_LEGENDS.getPropertyName(), snaks),
|
* to the wikibase API to set tag against the entity.
|
||||||
Arrays.asList(WikidataProperties.MEDIA_LEGENDS.getPropertyName()));
|
*/
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private Observable<Boolean> addDepictsProperty(final String fileEntityId,
|
||||||
|
final WikidataItem depictedItem) {
|
||||||
|
|
||||||
return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle();
|
final EditClaim data = editClaim(
|
||||||
}
|
ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10)
|
||||||
|
: depictedItem.getId()
|
||||||
public void handleImageClaimResult(final WikidataItem wikidataItem, final Long revisionId) {
|
|
||||||
if (revisionId != null) {
|
|
||||||
if (wikidataEditListener != null) {
|
|
||||||
wikidataEditListener.onSuccessfulWikidataEdit();
|
|
||||||
}
|
|
||||||
showSuccessToast(wikidataItem.getName());
|
|
||||||
} else {
|
|
||||||
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem);
|
|
||||||
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable addDepictionsAndCaptions(final UploadResult uploadResult, final Contribution contribution) {
|
|
||||||
return wikiBaseClient.getFileEntityId(uploadResult)
|
|
||||||
.doOnError(throwable -> {
|
|
||||||
Timber.e(throwable, "Error occurred while getting EntityID to set DEPICTS property");
|
|
||||||
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
|
||||||
})
|
|
||||||
.switchMap(fileEntityId -> {
|
|
||||||
if (fileEntityId != null) {
|
|
||||||
Timber.d("EntityId for image was received successfully: %s", fileEntityId);
|
|
||||||
return Observable.concat(
|
|
||||||
depictionEdits(contribution, fileEntityId),
|
|
||||||
captionEdits(contribution, fileEntityId)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Timber.d("Error acquiring EntityId for image: %s", uploadResult);
|
|
||||||
return Observable.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Boolean> captionEdits(Contribution contribution, Long fileEntityId) {
|
return wikiBaseClient.postEditEntity(PAGE_ID_PREFIX + fileEntityId, gson.toJson(data))
|
||||||
return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
|
.doOnNext(success -> {
|
||||||
.concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
|
if (success) {
|
||||||
}
|
Timber.d("DEPICTS property was set successfully for %s", fileEntityId);
|
||||||
|
} else {
|
||||||
|
Timber.d("Unable to set DEPICTS property for %s", fileEntityId);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.doOnError(throwable -> {
|
||||||
|
Timber.e(throwable, "Error occurred while setting DEPICTS property");
|
||||||
|
ViewUtil.showLongToast(context, throwable.toString());
|
||||||
|
})
|
||||||
|
.subscribeOn(Schedulers.io());
|
||||||
|
}
|
||||||
|
|
||||||
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
|
private EditClaim editClaim(final String entityId) {
|
||||||
return Observable.fromIterable(contribution.getDepictedItems())
|
return EditClaim.from(entityId, WikidataProperties.DEPICTS.getPropertyName());
|
||||||
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Show a success toast when the edit is made successfully
|
||||||
|
*/
|
||||||
|
private void showSuccessToast(final String wikiItemName) {
|
||||||
|
final String successStringTemplate = context.getString(R.string.successful_wikidata_edit);
|
||||||
|
final String successMessage = String
|
||||||
|
.format(Locale.getDefault(), successStringTemplate, wikiItemName);
|
||||||
|
ViewUtil.showLongToast(context, successMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds label to Wikidata using the fileEntityId and the edit token, obtained from
|
||||||
|
* csrfTokenClient
|
||||||
|
*
|
||||||
|
* @param fileEntityId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
private Observable<Boolean> addCaption(final long fileEntityId, final String languageCode,
|
||||||
|
final String captionValue) {
|
||||||
|
return wikiBaseClient.addLabelstoWikidata(fileEntityId, languageCode, captionValue)
|
||||||
|
.doOnNext(mwPostResponse -> onAddCaptionResponse(fileEntityId, mwPostResponse))
|
||||||
|
.doOnError(throwable -> {
|
||||||
|
Timber.e(throwable, "Error occurred while setting Captions");
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
||||||
|
})
|
||||||
|
.map(mwPostResponse -> mwPostResponse != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onAddCaptionResponse(Long fileEntityId, MwPostResponse response) {
|
||||||
|
if (response != null) {
|
||||||
|
Timber.d("Caption successfully set, revision id = %s", response);
|
||||||
|
} else {
|
||||||
|
Timber.d("Error occurred while setting Captions, fileEntityId = %s", fileEntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long createClaim(@Nullable final WikidataPlace wikidataPlace, final String fileName,
|
||||||
|
final Map<String, String> captions) {
|
||||||
|
if (!(directKvStore.getBoolean("Picture_Has_Correct_Location", true))) {
|
||||||
|
Timber
|
||||||
|
.d("Image location and nearby place location mismatched, so Wikidata item won't be edited");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return addImageAndMediaLegends(wikidataPlace, fileName, captions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long addImageAndMediaLegends(final WikidataItem wikidataItem, final String fileName,
|
||||||
|
final Map<String, String> captions) {
|
||||||
|
final Snak_partial p18 = new Snak_partial("value",
|
||||||
|
WikidataProperties.IMAGE.getPropertyName(),
|
||||||
|
new ValueString(fileName.replace("File:", "")));
|
||||||
|
|
||||||
|
final List<Snak_partial> snaks = new ArrayList<>();
|
||||||
|
for (final Map.Entry<String, String> entry : captions.entrySet()) {
|
||||||
|
snaks.add(new Snak_partial("value",
|
||||||
|
WikidataProperties.MEDIA_LEGENDS.getPropertyName(), new DataValue.MonoLingualText(
|
||||||
|
new WikiBaseMonolingualTextValue(entry.getValue(), entry.getKey()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
final String id = wikidataItem.getId() + "$" + UUID.randomUUID().toString();
|
||||||
|
final Statement_partial claim = new Statement_partial(p18, "statement", "normal", id,
|
||||||
|
Collections.singletonMap(WikidataProperties.MEDIA_LEGENDS.getPropertyName(), snaks),
|
||||||
|
Arrays.asList(WikidataProperties.MEDIA_LEGENDS.getPropertyName()));
|
||||||
|
|
||||||
|
return wikidataClient.setClaim(claim, COMMONS_APP_TAG).blockingSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleImageClaimResult(final WikidataItem wikidataItem, final Long revisionId) {
|
||||||
|
if (revisionId != null) {
|
||||||
|
if (wikidataEditListener != null) {
|
||||||
|
wikidataEditListener.onSuccessfulWikidataEdit();
|
||||||
|
}
|
||||||
|
showSuccessToast(wikidataItem.getName());
|
||||||
|
} else {
|
||||||
|
Timber.d("Unable to make wiki data edit for entity %s", wikidataItem);
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable addDepictionsAndCaptions(final UploadResult uploadResult,
|
||||||
|
final Contribution contribution) {
|
||||||
|
return wikiBaseClient.getFileEntityId(uploadResult)
|
||||||
|
.doOnError(throwable -> {
|
||||||
|
Timber
|
||||||
|
.e(throwable, "Error occurred while getting EntityID to set DEPICTS property");
|
||||||
|
ViewUtil.showLongToast(context, context.getString(R.string.wikidata_edit_failure));
|
||||||
|
})
|
||||||
|
.switchMap(fileEntityId -> {
|
||||||
|
if (fileEntityId != null) {
|
||||||
|
Timber.d("EntityId for image was received successfully: %s", fileEntityId);
|
||||||
|
return Observable.concat(
|
||||||
|
depictionEdits(contribution, fileEntityId),
|
||||||
|
captionEdits(contribution, fileEntityId)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Timber.d("Error acquiring EntityId for image: %s", uploadResult);
|
||||||
|
return Observable.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observable<Boolean> captionEdits(Contribution contribution, Long fileEntityId) {
|
||||||
|
return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
|
||||||
|
.concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
|
||||||
|
return Observable.fromIterable(contribution.getDepictedItems())
|
||||||
|
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ import org.robolectric.annotation.Implements;
|
||||||
@Implements(ActionBar.class)
|
@Implements(ActionBar.class)
|
||||||
public class ShadowActionBar {
|
public class ShadowActionBar {
|
||||||
|
|
||||||
private boolean showHomeAsUp;
|
private boolean showHomeAsUp;
|
||||||
|
|
||||||
public boolean getShowHomeAsUp() {
|
public boolean getShowHomeAsUp() {
|
||||||
return showHomeAsUp;
|
return showHomeAsUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Implementation
|
@Implementation
|
||||||
void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
|
void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
|
||||||
this.showHomeAsUp = showHomeAsUp;
|
this.showHomeAsUp = showHomeAsUp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,60 +12,60 @@ import org.wikipedia.login.LoginResult;
|
||||||
|
|
||||||
public class TestAppAdapter extends AppAdapter {
|
public class TestAppAdapter extends AppAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getMediaWikiBaseUrl() {
|
public String getMediaWikiBaseUrl() {
|
||||||
return Service.WIKIPEDIA_URL;
|
return Service.WIKIPEDIA_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getRestbaseUriFormat() {
|
public String getRestbaseUriFormat() {
|
||||||
return "%1$s://%2$s/api/rest_v1/";
|
return "%1$s://%2$s/api/rest_v1/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
|
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
|
||||||
return new OkHttpClient.Builder()
|
return new OkHttpClient.Builder()
|
||||||
.addInterceptor(new UnsuccessfulResponseInterceptor())
|
.addInterceptor(new UnsuccessfulResponseInterceptor())
|
||||||
.addInterceptor(new TestStubInterceptor())
|
.addInterceptor(new TestStubInterceptor())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getDesiredLeadImageDp() {
|
public int getDesiredLeadImageDp() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoggedIn() {
|
public boolean isLoggedIn() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUserName() {
|
public String getUserName() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateAccount(@NonNull LoginResult result) {
|
public void updateAccount(@NonNull LoginResult result) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SharedPreferenceCookieManager getCookies() {
|
public SharedPreferenceCookieManager getCookies() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
|
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean logErrorsInsteadOfCrashing() {
|
public boolean logErrorsInsteadOfCrashing() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue