mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +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
|
|
@ -63,38 +63,39 @@ import org.wikipedia.language.AppLanguageLookUpTable;
|
|||
import timber.log.Timber;
|
||||
|
||||
@AcraCore(
|
||||
buildConfigClass = BuildConfig.class,
|
||||
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
||||
reportFormat = StringFormat.KEY_VALUE_LIST,
|
||||
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL, STACK_TRACE}
|
||||
buildConfigClass = BuildConfig.class,
|
||||
resReportSendSuccessToast = R.string.crash_dialog_ok_toast,
|
||||
reportFormat = StringFormat.KEY_VALUE_LIST,
|
||||
reportContent = {USER_COMMENT, APP_VERSION_CODE, APP_VERSION_NAME, ANDROID_VERSION, PHONE_MODEL,
|
||||
STACK_TRACE}
|
||||
)
|
||||
|
||||
@AcraMailSender(
|
||||
mailTo = "commons-app-android-private@googlegroups.com",
|
||||
reportAsFile = false
|
||||
mailTo = "commons-app-android-private@googlegroups.com",
|
||||
reportAsFile = false
|
||||
)
|
||||
|
||||
@AcraDialog(
|
||||
resTheme = R.style.Theme_AppCompat_Dialog,
|
||||
resText = R.string.crash_dialog_text,
|
||||
resTitle = R.string.crash_dialog_title,
|
||||
resCommentPrompt = R.string.crash_dialog_comment_prompt
|
||||
resTheme = R.style.Theme_AppCompat_Dialog,
|
||||
resText = R.string.crash_dialog_text,
|
||||
resTitle = R.string.crash_dialog_title,
|
||||
resCommentPrompt = R.string.crash_dialog_comment_prompt
|
||||
)
|
||||
|
||||
public class CommonsApplication extends MultiDexApplication {
|
||||
|
||||
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
DBOpenHelper dbOpenHelper;
|
||||
public static final String IS_LIMITED_CONNECTION_MODE_ENABLED = "is_limited_connection_mode_enabled";
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
@Inject
|
||||
DBOpenHelper dbOpenHelper;
|
||||
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
JsonKvStore defaultPrefs;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
JsonKvStore defaultPrefs;
|
||||
|
||||
@Inject
|
||||
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
||||
@Inject
|
||||
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
||||
|
||||
/**
|
||||
* Constants begin
|
||||
|
|
@ -118,23 +119,26 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
private RefWatcher refWatcher;
|
||||
|
||||
private static CommonsApplication INSTANCE;
|
||||
|
||||
public static CommonsApplication getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
private AppLanguageLookUpTable languageLookUpTable;
|
||||
|
||||
public AppLanguageLookUpTable getLanguageLookUpTable() {
|
||||
return languageLookUpTable;
|
||||
}
|
||||
|
||||
@Inject ContributionDao contributionDao;
|
||||
@Inject
|
||||
ContributionDao contributionDao;
|
||||
|
||||
/**
|
||||
* In memory list of contributios whose uploads ahve been paused by the user
|
||||
*/
|
||||
public static Map<String, Boolean> pauseUploads = new HashMap<>();
|
||||
/**
|
||||
* In memory list of contributios whose uploads ahve been paused by the user
|
||||
*/
|
||||
public static Map<String, Boolean> pauseUploads = new HashMap<>();
|
||||
|
||||
/**
|
||||
/**
|
||||
* Used to declare and initialize various components and dependencies
|
||||
*/
|
||||
@Override
|
||||
|
|
@ -146,15 +150,14 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
Mapbox.getInstance(this, getString(R.string.mapbox_commons_app_token));
|
||||
|
||||
ApplicationlessInjection
|
||||
.getInstance(this)
|
||||
.getCommonsApplicationComponent()
|
||||
.inject(this);
|
||||
.getInstance(this)
|
||||
.getCommonsApplicationComponent()
|
||||
.inject(this);
|
||||
|
||||
AppAdapter.set(new CommonsAppAdapter(sessionManager, defaultPrefs));
|
||||
|
||||
initTimber();
|
||||
|
||||
|
||||
if (!defaultPrefs.getBoolean("has_user_manually_removed_location")) {
|
||||
Set<String> defaultExifTagsSet = defaultPrefs.getStringSet(Prefs.MANAGED_EXIF_TAGS);
|
||||
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
|
||||
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
|
||||
.setNetworkFetcher(customOkHttpNetworkFetcher)
|
||||
.setDownsampleEnabled(true)
|
||||
.build();
|
||||
.setNetworkFetcher(customOkHttpNetworkFetcher)
|
||||
.setDownsampleEnabled(true)
|
||||
.build();
|
||||
try {
|
||||
Fresco.initialize(this, config);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -192,46 +195,44 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
}
|
||||
|
||||
/**
|
||||
* Plants debug and file logging tree.
|
||||
* Timber lets you plant your own logging trees.
|
||||
*
|
||||
* Plants debug and file logging tree. Timber lets you plant your own logging trees.
|
||||
*/
|
||||
private void initTimber() {
|
||||
boolean isBeta = ConfigUtils.isBetaFlavour();
|
||||
String logFileName =
|
||||
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
|
||||
String logDirectory = LogUtils.getLogDirectory();
|
||||
//Delete stale logs if they have exceeded the specified size
|
||||
deleteStaleLogs(logFileName, logDirectory);
|
||||
boolean isBeta = ConfigUtils.isBetaFlavour();
|
||||
String logFileName =
|
||||
isBeta ? "CommonsBetaAppLogs" : "CommonsAppLogs";
|
||||
String logDirectory = LogUtils.getLogDirectory();
|
||||
//Delete stale logs if they have exceeded the specified size
|
||||
deleteStaleLogs(logFileName, logDirectory);
|
||||
|
||||
FileLoggingTree tree = new FileLoggingTree(
|
||||
Log.VERBOSE,
|
||||
logFileName,
|
||||
logDirectory,
|
||||
1000,
|
||||
getFileLoggingThreadPool());
|
||||
FileLoggingTree tree = new FileLoggingTree(
|
||||
Log.VERBOSE,
|
||||
logFileName,
|
||||
logDirectory,
|
||||
1000,
|
||||
getFileLoggingThreadPool());
|
||||
|
||||
Timber.plant(tree);
|
||||
Timber.plant(new Timber.DebugTree());
|
||||
Timber.plant(tree);
|
||||
Timber.plant(new Timber.DebugTree());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the logs zip file at the specified directory and file locations specified in the
|
||||
* params
|
||||
*
|
||||
* @param logFileName
|
||||
* @param logDirectory
|
||||
*/
|
||||
private void deleteStaleLogs(String logFileName, String logDirectory) {
|
||||
try {
|
||||
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
|
||||
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
|
||||
file.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e(e);
|
||||
/**
|
||||
* Deletes the logs zip file at the specified directory and file locations specified in the
|
||||
* params
|
||||
*
|
||||
* @param logFileName
|
||||
* @param logDirectory
|
||||
*/
|
||||
private void deleteStaleLogs(String logFileName, String logDirectory) {
|
||||
try {
|
||||
File file = new File(logDirectory + "/zip/" + logFileName + ".zip");
|
||||
if (file.exists() && file.getTotalSpace() > 1000000) {// In Kbs
|
||||
file.delete();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isRoboUnitTest() {
|
||||
return "robolectric".equals(Build.FINGERPRINT);
|
||||
|
|
@ -239,30 +240,35 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
|
||||
private ThreadPoolService getFileLoggingThreadPool() {
|
||||
return new ThreadPoolService.Builder("file-logging-thread")
|
||||
.setPriority(Process.THREAD_PRIORITY_LOWEST)
|
||||
.setPoolSize(1)
|
||||
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
||||
.build();
|
||||
.setPriority(Process.THREAD_PRIORITY_LOWEST)
|
||||
.setPoolSize(1)
|
||||
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void createNotificationChannel(@NonNull Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationChannel channel = manager.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
|
||||
NotificationManager manager = (NotificationManager) context
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationChannel channel = manager
|
||||
.getNotificationChannel(NOTIFICATION_CHANNEL_ID_ALL);
|
||||
if (channel == null) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @return instance of LeakCanary
|
||||
*/
|
||||
protected RefWatcher setupLeakCanary() {
|
||||
|
|
@ -272,7 +278,7 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
return LeakCanary.install(this);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Provides a way to get member refWatcher
|
||||
*
|
||||
* @param context Application context
|
||||
|
|
@ -285,7 +291,8 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
|
||||
/**
|
||||
* clears data of current application
|
||||
* @param context Application context
|
||||
*
|
||||
* @param context Application context
|
||||
* @param logoutListener Implementation of interface LogoutListener
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
|
|
@ -302,13 +309,13 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
}
|
||||
|
||||
sessionManager.logout()
|
||||
.andThen(Completable.fromAction(() ->{
|
||||
Timber.d("All accounts have been removed");
|
||||
clearImageCache();
|
||||
//TODO: fix preference manager
|
||||
defaultPrefs.clearAll();
|
||||
defaultPrefs.putBoolean("firstrun", false);
|
||||
updateAllDatabases();
|
||||
.andThen(Completable.fromAction(() -> {
|
||||
Timber.d("All accounts have been removed");
|
||||
clearImageCache();
|
||||
//TODO: fix preference manager
|
||||
defaultPrefs.clearAll();
|
||||
defaultPrefs.putBoolean("firstrun", false);
|
||||
updateAllDatabases();
|
||||
}
|
||||
))
|
||||
.subscribeOn(Schedulers.io())
|
||||
|
|
@ -332,12 +339,13 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
|
||||
|
||||
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 {
|
||||
contributionDao.deleteAll();
|
||||
contributionDao.deleteAll();
|
||||
} catch (SQLiteException e) {
|
||||
Timber.e(e);
|
||||
Timber.e(e);
|
||||
}
|
||||
BookmarkPicturesDao.Table.onDelete(db);
|
||||
BookmarkLocationsDao.Table.onDelete(db);
|
||||
|
|
@ -348,6 +356,7 @@ public class CommonsApplication extends MultiDexApplication {
|
|||
* Interface used to get log-out events
|
||||
*/
|
||||
public interface LogoutListener {
|
||||
|
||||
void onLogoutComplete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,45 +10,48 @@ import com.mapbox.mapboxsdk.camera.CameraPosition;
|
|||
*/
|
||||
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() {
|
||||
intent = new Intent();
|
||||
public static CameraPosition getCameraPosition(final Intent data) {
|
||||
return data.getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
public static class IntentBuilder {
|
||||
|
||||
/**
|
||||
* Gets and sets the activity
|
||||
* @param activity Activity
|
||||
* @return Intent
|
||||
*/
|
||||
public Intent build(final Activity activity) {
|
||||
intent.setClass(activity, LocationPickerActivity.class);
|
||||
return intent;
|
||||
private final Intent intent;
|
||||
|
||||
/**
|
||||
* Creates a new builder that creates an intent to launch the place picker activity.
|
||||
*/
|
||||
public IntentBuilder() {
|
||||
intent = new 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,
|
||||
OnCameraMoveStartedListener, OnCameraIdleListener, Observer<CameraPosition> {
|
||||
|
||||
/**
|
||||
* cameraPosition : position of picker
|
||||
*/
|
||||
private CameraPosition cameraPosition;
|
||||
/**
|
||||
* markerImage : picker image
|
||||
*/
|
||||
private ImageView markerImage;
|
||||
/**
|
||||
* mapboxMap : map
|
||||
*/
|
||||
private MapboxMap mapboxMap;
|
||||
/**
|
||||
* mapView : view of the map
|
||||
*/
|
||||
private MapView mapView;
|
||||
/**
|
||||
* tvAttribution : credit
|
||||
*/
|
||||
private AppCompatTextView tvAttribution;
|
||||
/**
|
||||
* cameraPosition : position of picker
|
||||
*/
|
||||
private CameraPosition cameraPosition;
|
||||
/**
|
||||
* markerImage : picker image
|
||||
*/
|
||||
private ImageView markerImage;
|
||||
/**
|
||||
* mapboxMap : map
|
||||
*/
|
||||
private MapboxMap mapboxMap;
|
||||
/**
|
||||
* mapView : view of the map
|
||||
*/
|
||||
private MapView mapView;
|
||||
/**
|
||||
* tvAttribution : credit
|
||||
*/
|
||||
private AppCompatTextView tvAttribution;
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@Override
|
||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
setContentView(R.layout.activity_location_picker);
|
||||
getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
}
|
||||
setContentView(R.layout.activity_location_picker);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
cameraPosition = getIntent().getParcelableExtra(LocationPickerConstants.MAP_CAMERA_POSITION);
|
||||
if (savedInstanceState == null) {
|
||||
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);
|
||||
viewModel.getResult().observe(this, this);
|
||||
|
||||
bindViews();
|
||||
addBackButtonListener();
|
||||
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);
|
||||
|
||||
/**
|
||||
* For showing credits
|
||||
*/
|
||||
private void addCredits() {
|
||||
tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
|
||||
tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* Clicking back button destroy locationPickerActivity
|
||||
*/
|
||||
private void addBackButtonListener() {
|
||||
final ImageView backButton = findViewById(R.id.mapbox_place_picker_toolbar_back_button);
|
||||
backButton.setOnClickListener(view -> finish());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
cameraPosition = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the preferable location
|
||||
*/
|
||||
private void addPlaceSelectedButton() {
|
||||
final FloatingActionButton placeSelectedButton = findViewById(R.id.location_chosen_button);
|
||||
placeSelectedButton.setOnClickListener(view -> placeSelected());
|
||||
}
|
||||
/**
|
||||
* Binds the listeners
|
||||
*/
|
||||
private void bindListeners() {
|
||||
mapboxMap.addOnCameraMoveStartedListener(
|
||||
this);
|
||||
mapboxMap.addOnCameraIdleListener(
|
||||
this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
/**
|
||||
* Gets toolbar color
|
||||
*/
|
||||
private void getToolbarUI() {
|
||||
final ConstraintLayout toolbar = findViewById(R.id.location_picker_toolbar);
|
||||
toolbar.setBackgroundColor(getResources().getColor(R.color.primaryColor));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
mapView.onStart();
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mapView.onResume();
|
||||
}
|
||||
/**
|
||||
* move the location to the current media coordinates
|
||||
*/
|
||||
private void adjustCameraBasedOnOptions() {
|
||||
mapboxMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
mapView.onPause();
|
||||
}
|
||||
/**
|
||||
* Enables location components
|
||||
*
|
||||
* @param loadedMapStyle Style
|
||||
*/
|
||||
@SuppressWarnings({"MissingPermission"})
|
||||
private void enableLocationComponent(@NonNull final Style loadedMapStyle) {
|
||||
final UiSettings uiSettings = mapboxMap.getUiSettings();
|
||||
uiSettings.setAttributionEnabled(false);
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
mapView.onStop();
|
||||
}
|
||||
// Check if permissions are enabled and if not request
|
||||
if (PermissionsManager.areLocationPermissionsGranted(this)) {
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(final @NotNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
mapView.onSaveInstanceState(outState);
|
||||
}
|
||||
// Get an instance of the component
|
||||
final LocationComponent locationComponent = mapboxMap.getLocationComponent();
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mapView.onDestroy();
|
||||
}
|
||||
// Activate with options
|
||||
locationComponent.activateLocationComponent(
|
||||
LocationComponentActivationOptions.builder(this, loadedMapStyle).build());
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
super.onLowMemory();
|
||||
mapView.onLowMemory();
|
||||
}
|
||||
// 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
|
||||
*
|
||||
* @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 static final String MAP_CAMERA_POSITION
|
||||
= "location.picker.cameraPosition";
|
||||
public static final String MAP_CAMERA_POSITION
|
||||
= "location.picker.cameraPosition";
|
||||
|
||||
private LocationPickerConstants() {
|
||||
}
|
||||
private LocationPickerConstants() {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,45 +16,48 @@ import timber.log.Timber;
|
|||
*/
|
||||
public class LocationPickerViewModel extends AndroidViewModel implements Callback<CameraPosition> {
|
||||
|
||||
/**
|
||||
* Wrapping CameraPosition with MutableLiveData
|
||||
*/
|
||||
private final MutableLiveData<CameraPosition> result = new MutableLiveData<>();
|
||||
/**
|
||||
* Wrapping CameraPosition with MutableLiveData
|
||||
*/
|
||||
private final MutableLiveData<CameraPosition> result = new MutableLiveData<>();
|
||||
|
||||
/**
|
||||
* Constructor for this class
|
||||
* @param application 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;
|
||||
/**
|
||||
* Constructor for this class
|
||||
*
|
||||
* @param application Application
|
||||
*/
|
||||
public LocationPickerViewModel(@NonNull final Application application) {
|
||||
super(application);
|
||||
}
|
||||
result.setValue(response.body());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) {
|
||||
Timber.e(t);
|
||||
}
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets live CameraPosition
|
||||
* @return MutableLiveData<CameraPosition>
|
||||
*/
|
||||
public MutableLiveData<CameraPosition> getResult() {
|
||||
return result;
|
||||
}
|
||||
@Override
|
||||
public void onFailure(final @NotNull Call<CameraPosition> call, final @NotNull Throwable t) {
|
||||
Timber.e(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private FragmentManager supportFragmentManager;
|
||||
private BookmarksPagerAdapter adapter;
|
||||
@BindView(R.id.viewPagerBookmarks)
|
||||
ParentViewPager viewPager;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.fragmentContainer)
|
||||
FrameLayout fragmentContainer;
|
||||
private FragmentManager supportFragmentManager;
|
||||
private BookmarksPagerAdapter adapter;
|
||||
@BindView(R.id.viewPagerBookmarks)
|
||||
ParentViewPager viewPager;
|
||||
@BindView(R.id.tab_layout)
|
||||
TabLayout tabLayout;
|
||||
@BindView(R.id.fragmentContainer)
|
||||
FrameLayout fragmentContainer;
|
||||
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
/**
|
||||
* To check if the user is loggedIn or not.
|
||||
*/
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public
|
||||
JsonKvStore applicationKvStore;
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
/**
|
||||
* To check if the user is loggedIn or not.
|
||||
*/
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public
|
||||
JsonKvStore applicationKvStore;
|
||||
|
||||
@NonNull
|
||||
public static BookmarkFragment newInstance() {
|
||||
BookmarkFragment fragment = new BookmarkFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
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);
|
||||
@NonNull
|
||||
public static BookmarkFragment newInstance() {
|
||||
BookmarkFragment fragment = new BookmarkFragment();
|
||||
fragment.setRetainInstance(true);
|
||||
return fragment;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void onBackPressed() {
|
||||
if(((BookmarkListRootFragment)(adapter.getItem(tabLayout.getSelectedTabPosition()))).backPressed()) {
|
||||
// The event is handled internally by the adapter , no further action required.
|
||||
return;
|
||||
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 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
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
AdapterView.OnItemClickListener, CategoryImagesCallback{
|
||||
AdapterView.OnItemClickListener, CategoryImagesCallback {
|
||||
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
//private BookmarkPicturesFragment bookmarkPicturesFragment;
|
||||
private BookmarkLocationsFragment bookmarkLocationsFragment;
|
||||
public Fragment listFragment;
|
||||
private BookmarksPagerAdapter bookmarksPagerAdapter;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
//private BookmarkPicturesFragment bookmarkPicturesFragment;
|
||||
private BookmarkLocationsFragment bookmarkLocationsFragment;
|
||||
public Fragment listFragment;
|
||||
private BookmarksPagerAdapter bookmarksPagerAdapter;
|
||||
|
||||
@BindView(R.id.explore_container)
|
||||
FrameLayout container;
|
||||
@BindView(R.id.explore_container)
|
||||
FrameLayout container;
|
||||
|
||||
public BookmarkListRootFragment(){
|
||||
//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();
|
||||
public BookmarkListRootFragment() {
|
||||
//empty constructor necessary otherwise crashes on recreate
|
||||
}
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
listFragment.setArguments(featuredArguments);
|
||||
this.bookmarksPagerAdapter = bookmarksPagerAdapter;
|
||||
}
|
||||
|
||||
@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 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;
|
||||
}
|
||||
}
|
||||
|
||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||
if (fragment.isAdded() && otherFragment != null) {
|
||||
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();
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFragment(Fragment fragment) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
@Override
|
||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
setFragment(listFragment, mediaDetails);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(final Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||
if (fragment.isAdded() && otherFragment != null) {
|
||||
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 onMediaClicked(int position) {
|
||||
Log.d("deneme8","on media clicked");
|
||||
public void removeFragment(Fragment fragment) {
|
||||
getChildFragmentManager()
|
||||
.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);
|
||||
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true, position);
|
||||
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
|
||||
* same number of media items as that of media elements in adapter.
|
||||
*
|
||||
* @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;
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
// todo add get list fragment
|
||||
((BookmarkFragment) getParentFragment()).setupTabLayout();
|
||||
ArrayList<Integer> removed=mediaDetails.getRemovedItems();
|
||||
removeFragment(mediaDetails);
|
||||
((BookmarkFragment) getParentFragment()).setScroll(true);
|
||||
setFragment(listFragment, mediaDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (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();
|
||||
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();
|
||||
}
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Log.d("deneme8","on media clicked");
|
||||
container.setVisibility(View.VISIBLE);
|
||||
((BookmarkFragment)getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, listFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Log.d("deneme8", "on media clicked");
|
||||
container.setVisibility(View.VISIBLE);
|
||||
((BookmarkFragment) getParentFragment()).tabLayout.setVisibility(View.GONE);
|
||||
mediaDetails = new MediaDetailPagerFragment(false, true);
|
||||
((BookmarkFragment) getParentFragment()).setScroll(false);
|
||||
setFragment(mediaDetails, listFragment);
|
||||
mediaDetails.showImage(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,61 +18,61 @@ import java.util.List;
|
|||
@Dao
|
||||
public abstract class ContributionDao {
|
||||
|
||||
@Query("SELECT * FROM contribution order by media_dateUploaded DESC")
|
||||
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
||||
@Query("SELECT * FROM contribution order by media_dateUploaded DESC")
|
||||
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract void saveSynchronous(Contribution contribution);
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract void saveSynchronous(Contribution contribution);
|
||||
|
||||
public Completable save(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> {
|
||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||
saveSynchronous(contribution);
|
||||
});
|
||||
}
|
||||
public Completable save(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> {
|
||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||
saveSynchronous(contribution);
|
||||
});
|
||||
}
|
||||
|
||||
@Transaction
|
||||
public void deleteAndSaveContribution(final Contribution oldContribution,
|
||||
final Contribution newContribution) {
|
||||
deleteSynchronous(oldContribution);
|
||||
saveSynchronous(newContribution);
|
||||
}
|
||||
@Transaction
|
||||
public void deleteAndSaveContribution(final Contribution oldContribution,
|
||||
final Contribution newContribution) {
|
||||
deleteSynchronous(oldContribution);
|
||||
saveSynchronous(newContribution);
|
||||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
public abstract Single<List<Long>> save(List<Contribution> contribution);
|
||||
|
||||
@Delete
|
||||
public abstract void deleteSynchronous(Contribution contribution);
|
||||
@Delete
|
||||
public abstract void deleteSynchronous(Contribution contribution);
|
||||
|
||||
public Completable delete(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteSynchronous(contribution));
|
||||
}
|
||||
public Completable delete(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteSynchronous(contribution));
|
||||
}
|
||||
|
||||
@Query("SELECT * from contribution WHERE media_filename=:fileName")
|
||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||
@Query("SELECT * from contribution WHERE media_filename=:fileName")
|
||||
public abstract List<Contribution> getContributionWithTitle(String fileName);
|
||||
|
||||
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
||||
public abstract Contribution getContribution(String pageId);
|
||||
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
||||
public abstract Contribution getContribution(String pageId);
|
||||
|
||||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
||||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
||||
|
||||
@Query("SELECT COUNT(*) from contribution WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> getPendingUploads(int[] toUpdateStates);
|
||||
@Query("SELECT COUNT(*) from contribution WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> getPendingUploads(int[] toUpdateStates);
|
||||
|
||||
@Query("Delete FROM contribution")
|
||||
public abstract void deleteAll() throws SQLiteException;
|
||||
@Query("Delete FROM contribution")
|
||||
public abstract void deleteAll() throws SQLiteException;
|
||||
|
||||
@Update
|
||||
public abstract void updateSynchronous(Contribution contribution);
|
||||
@Update
|
||||
public abstract void updateSynchronous(Contribution contribution);
|
||||
|
||||
public Completable update(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> {
|
||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||
updateSynchronous(contribution);
|
||||
});
|
||||
}
|
||||
public Completable update(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> {
|
||||
contribution.setDateModified(Calendar.getInstance().getTime());
|
||||
updateSynchronous(contribution);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,244 +24,243 @@ import io.reactivex.schedulers.Schedulers;
|
|||
|
||||
public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final Callback callback;
|
||||
@BindView(R.id.contributionImage)
|
||||
SimpleDraweeView imageView;
|
||||
@BindView(R.id.contributionTitle)
|
||||
TextView titleView;
|
||||
@BindView(R.id.authorView)
|
||||
TextView authorView;
|
||||
@BindView(R.id.contributionState)
|
||||
TextView stateView;
|
||||
@BindView(R.id.contributionSequenceNumber)
|
||||
TextView seqNumView;
|
||||
@BindView(R.id.contributionProgress)
|
||||
ProgressBar progressView;
|
||||
@BindView(R.id.image_options)
|
||||
RelativeLayout imageOptions;
|
||||
@BindView(R.id.wikipediaButton)
|
||||
ImageButton addToWikipediaButton;
|
||||
@BindView(R.id.retryButton)
|
||||
ImageButton retryButton;
|
||||
@BindView(R.id.cancelButton)
|
||||
ImageButton cancelButton;
|
||||
@BindView(R.id.pauseResumeButton)
|
||||
ImageButton pauseResumeButton;
|
||||
private final Callback callback;
|
||||
@BindView(R.id.contributionImage)
|
||||
SimpleDraweeView imageView;
|
||||
@BindView(R.id.contributionTitle)
|
||||
TextView titleView;
|
||||
@BindView(R.id.authorView)
|
||||
TextView authorView;
|
||||
@BindView(R.id.contributionState)
|
||||
TextView stateView;
|
||||
@BindView(R.id.contributionSequenceNumber)
|
||||
TextView seqNumView;
|
||||
@BindView(R.id.contributionProgress)
|
||||
ProgressBar progressView;
|
||||
@BindView(R.id.image_options)
|
||||
RelativeLayout imageOptions;
|
||||
@BindView(R.id.wikipediaButton)
|
||||
ImageButton addToWikipediaButton;
|
||||
@BindView(R.id.retryButton)
|
||||
ImageButton retryButton;
|
||||
@BindView(R.id.cancelButton)
|
||||
ImageButton cancelButton;
|
||||
@BindView(R.id.pauseResumeButton)
|
||||
ImageButton pauseResumeButton;
|
||||
|
||||
|
||||
private int position;
|
||||
private Contribution contribution;
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final MediaClient mediaClient;
|
||||
private boolean isWikipediaButtonDisplayed;
|
||||
private int position;
|
||||
private Contribution contribution;
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final MediaClient mediaClient;
|
||||
private boolean isWikipediaButtonDisplayed;
|
||||
|
||||
ContributionViewHolder(final View parent, final Callback callback,
|
||||
final MediaClient mediaClient) {
|
||||
super(parent);
|
||||
this.mediaClient = mediaClient;
|
||||
ButterKnife.bind(this, parent);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void init(final int position, final Contribution contribution) {
|
||||
|
||||
//handling crashes when the contribution is null.
|
||||
if( null == contribution) {
|
||||
return;
|
||||
ContributionViewHolder(final View parent, final Callback callback,
|
||||
final MediaClient mediaClient) {
|
||||
super(parent);
|
||||
this.mediaClient = mediaClient;
|
||||
ButterKnife.bind(this, parent);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
this.contribution = contribution;
|
||||
this.position = position;
|
||||
titleView.setText(contribution.getMedia().getMostRelevantCaption());
|
||||
authorView.setText(contribution.getMedia().getAuthor());
|
||||
public void init(final int position, final Contribution contribution) {
|
||||
|
||||
//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));
|
||||
//handling crashes when the contribution is null.
|
||||
if (null == contribution) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Contribution.STATE_PAUSED:
|
||||
stateView.setVisibility(View.VISIBLE);
|
||||
stateView.setText(R.string.paused);
|
||||
|
||||
this.contribution = contribution;
|
||||
this.position = position;
|
||||
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();
|
||||
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;
|
||||
/**
|
||||
* Update pause/resume button to show pause state
|
||||
*/
|
||||
private void setPaused() {
|
||||
pauseResumeButton.setImageResource(R.drawable.pause_icon);
|
||||
pauseResumeButton.setTag(R.string.pause);
|
||||
}
|
||||
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);
|
||||
/**
|
||||
* Update pause/resume button to show resume state
|
||||
*/
|
||||
private void setResume() {
|
||||
pauseResumeButton.setImageResource(R.drawable.play_icon);
|
||||
pauseResumeButton.setTag(R.string.resume);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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,
|
||||
WikipediaInstructionsDialogFragment.Callback {
|
||||
|
||||
private static final String RV_STATE = "rv_scroll_state";
|
||||
private static final String RV_STATE = "rv_scroll_state";
|
||||
|
||||
@BindView(R.id.contributionsList)
|
||||
RecyclerView rvContributionsList;
|
||||
@BindView(R.id.loadingContributionsProgressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.fab_plus)
|
||||
FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera)
|
||||
FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_gallery)
|
||||
FloatingActionButton fabGallery;
|
||||
@BindView(R.id.noContributionsYet)
|
||||
TextView noContributionsYet;
|
||||
@BindView(R.id.fab_layout)
|
||||
LinearLayout fab_layout;
|
||||
@BindView(R.id.contributionsList)
|
||||
RecyclerView rvContributionsList;
|
||||
@BindView(R.id.loadingContributionsProgressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.fab_plus)
|
||||
FloatingActionButton fabPlus;
|
||||
@BindView(R.id.fab_camera)
|
||||
FloatingActionButton fabCamera;
|
||||
@BindView(R.id.fab_gallery)
|
||||
FloatingActionButton fabGallery;
|
||||
@BindView(R.id.noContributionsYet)
|
||||
TextView noContributionsYet;
|
||||
@BindView(R.id.fab_layout)
|
||||
LinearLayout fab_layout;
|
||||
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
@Inject
|
||||
ContributionController controller;
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
||||
@Inject
|
||||
WikiSite languageWikipediaSite;
|
||||
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
||||
@Inject
|
||||
WikiSite languageWikipediaSite;
|
||||
|
||||
@Inject
|
||||
ContributionsListPresenter contributionsListPresenter;
|
||||
@Inject
|
||||
ContributionsListPresenter contributionsListPresenter;
|
||||
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
private Animation rotate_backward;
|
||||
private Animation fab_close;
|
||||
private Animation fab_open;
|
||||
private Animation rotate_forward;
|
||||
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_PORTRAIT = 1;
|
||||
private final int SPAN_COUNT_LANDSCAPE = 3;
|
||||
private final int SPAN_COUNT_PORTRAIT = 1;
|
||||
|
||||
private int contributionsSize;
|
||||
private int contributionsSize;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
contributionsListPresenter.onAttachView(this);
|
||||
initAdapter();
|
||||
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);
|
||||
@Override
|
||||
public View onCreateView(
|
||||
final LayoutInflater inflater, @Nullable final ViewGroup container,
|
||||
@Nullable final Bundle savedInstanceState) {
|
||||
final View view = inflater.inflate(R.layout.fragment_contributions_list, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
contributionsListPresenter.onAttachView(this);
|
||||
initAdapter();
|
||||
return view;
|
||||
}
|
||||
|
||||
contributionsListPresenter.setup();
|
||||
contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), list -> {
|
||||
contributionsSize = list.size();
|
||||
adapter.submitList(list);
|
||||
callback.notifyDataSetChanged();
|
||||
});
|
||||
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
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
if (getParentFragment() != null && getParentFragment() instanceof ContributionsFragment) {
|
||||
callback = ((ContributionsFragment) getParentFragment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever items in the list have changed
|
||||
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
|
||||
*/
|
||||
@Override
|
||||
public void onItemRangeChanged(final int positionStart, final int itemCount) {
|
||||
super.onItemRangeChanged(positionStart, itemCount);
|
||||
callback.viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
callback = null;//To avoid possible memory leak
|
||||
}
|
||||
|
||||
//Fab close on touch outside (Scrolling or taping on item triggers this action).
|
||||
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() {
|
||||
private void initAdapter() {
|
||||
adapter = new ContributionsListAdapter(this, mediaClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently observe and/or take over touch events sent to the RecyclerView before
|
||||
* they are handled by either the RecyclerView itself or its child views.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (isFabOpen) {
|
||||
animateFAB(isFabOpen);
|
||||
}
|
||||
@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);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a touch event as part of a gesture that was claimed by returning true
|
||||
* from a previous call to {@link #onInterceptTouchEvent}.
|
||||
*
|
||||
* @param rv
|
||||
* @param e MotionEvent describing the touch event. All coordinates are in the
|
||||
* RecyclerView's coordinate system.
|
||||
*/
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
//required abstract method DO NOT DELETE
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a child of RecyclerView does not want RecyclerView and its ancestors
|
||||
* to intercept touch events with {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
|
||||
*
|
||||
* @param disallowIntercept True if the child does not want the parent to intercept
|
||||
* touch events.
|
||||
*/
|
||||
@Override
|
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
|
||||
//required abstract method DO NOT DELETE
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
contributionsListPresenter.setup();
|
||||
contributionsListPresenter.contributionList.observe(this.getViewLifecycleOwner(), list -> {
|
||||
contributionsSize = list.size();
|
||||
adapter.submitList(list);
|
||||
callback.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
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
|
||||
* @param contribution
|
||||
*/
|
||||
@Override
|
||||
public void pauseUpload(Contribution contribution) {
|
||||
ViewUtil.showShortToast(getContext(), R.string.pausing_upload);
|
||||
callback.pauseUpload(contribution);
|
||||
}
|
||||
/**
|
||||
* Called whenever items in the list have changed
|
||||
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
|
||||
*/
|
||||
@Override
|
||||
public void onItemRangeChanged(final int positionStart, final int itemCount) {
|
||||
super.onItemRangeChanged(positionStart, itemCount);
|
||||
callback.viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Resumes the current upload
|
||||
* @param contribution
|
||||
*/
|
||||
@Override
|
||||
public void resumeUpload(Contribution contribution) {
|
||||
ViewUtil.showShortToast(getContext(), R.string.resuming_upload);
|
||||
callback.retryUpload(contribution);
|
||||
}
|
||||
//Fab close on touch outside (Scrolling or taping on item triggers this action).
|
||||
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() {
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
/**
|
||||
* Silently observe and/or take over touch events sent to the RecyclerView before
|
||||
* they are handled by either the RecyclerView itself or its child views.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (isFabOpen) {
|
||||
animateFAB(isFabOpen);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a touch event as part of a gesture that was claimed by returning true
|
||||
* from a previous call to {@link #onInterceptTouchEvent}.
|
||||
*
|
||||
* @param rv
|
||||
* @param e MotionEvent describing the touch event. All coordinates are in the
|
||||
* RecyclerView's coordinate system.
|
||||
*/
|
||||
@Override
|
||||
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
|
||||
//required abstract method DO NOT DELETE
|
||||
}
|
||||
|
||||
public Media getMediaAtPosition(final int i) {
|
||||
if(adapter.getContributionForPosition(i) != null) {
|
||||
return adapter.getContributionForPosition(i).getMedia();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
private int getSpanCount(final int orientation) {
|
||||
return orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||
SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
|
||||
}
|
||||
|
||||
public Integer getContributionStateAt(int position) {
|
||||
return adapter.getContributionForPosition(position).getState();
|
||||
}
|
||||
@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)));
|
||||
}
|
||||
|
||||
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.
|
||||
void viewPagerNotifyDataSetChanged();
|
||||
}
|
||||
@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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
private final ContributionBoundaryCallback contributionBoundaryCallback;
|
||||
private final ContributionsRepository repository;
|
||||
private final Scheduler ioThreadScheduler;
|
||||
private final ContributionBoundaryCallback contributionBoundaryCallback;
|
||||
private final ContributionsRepository repository;
|
||||
private final Scheduler ioThreadScheduler;
|
||||
|
||||
private final CompositeDisposable compositeDisposable;
|
||||
private final CompositeDisposable compositeDisposable;
|
||||
|
||||
LiveData<PagedList<Contribution>> contributionList;
|
||||
LiveData<PagedList<Contribution>> contributionList;
|
||||
|
||||
@Inject
|
||||
ContributionsListPresenter(
|
||||
final ContributionBoundaryCallback contributionBoundaryCallback,
|
||||
final ContributionsRepository repository,
|
||||
@Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) {
|
||||
this.contributionBoundaryCallback = contributionBoundaryCallback;
|
||||
this.repository = repository;
|
||||
this.ioThreadScheduler = ioThreadScheduler;
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
@Inject
|
||||
ContributionsListPresenter(
|
||||
final ContributionBoundaryCallback contributionBoundaryCallback,
|
||||
final ContributionsRepository repository,
|
||||
@Named(CommonsApplicationModule.IO_THREAD) final Scheduler ioThreadScheduler) {
|
||||
this.contributionBoundaryCallback = contributionBoundaryCallback;
|
||||
this.repository = repository;
|
||||
this.ioThreadScheduler = ioThreadScheduler;
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachView(final ContributionsListContract.View view) {
|
||||
}
|
||||
@Override
|
||||
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
|
||||
* live data object. This method can be tweaked to update the lazy loading behavior of the
|
||||
* contributions list
|
||||
*/
|
||||
void setup() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
.setPrefetchDistance(50)
|
||||
.setPageSize(10).build();
|
||||
contributionList = (new LivePagedListBuilder(repository.fetchContributions(), pagedListConfig)
|
||||
.setBoundaryCallback(contributionBoundaryCallback)).build();
|
||||
}
|
||||
/**
|
||||
* Setup the paged list. This method sets the configuration for paged list and ties it up with
|
||||
* the live data object. This method can be tweaked to update the lazy loading behavior of the
|
||||
* contributions list
|
||||
*/
|
||||
void setup() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
.setPrefetchDistance(50)
|
||||
.setPageSize(10).build();
|
||||
contributionList = (new LivePagedListBuilder(repository.fetchContributions(),
|
||||
pagedListConfig)
|
||||
.setBoundaryCallback(contributionBoundaryCallback)).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
@Override
|
||||
public void onDetachView() {
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a failed contribution from the local db
|
||||
*/
|
||||
@Override
|
||||
public void deleteUpload(final Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.deleteContributionFromDB(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
/**
|
||||
* Delete a failed contribution from the local db
|
||||
*/
|
||||
@Override
|
||||
public void deleteUpload(final Contribution contribution) {
|
||||
compositeDisposable.add(repository
|
||||
.deleteContributionFromDB(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
|
|||
* 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)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun contributionDao(): ContributionDao
|
||||
abstract fun DepictsDao (): DepictsDao;
|
||||
abstract fun contributionDao(): ContributionDao
|
||||
abstract fun DepictsDao(): DepictsDao;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,181 +23,183 @@ import fr.free.nrw.commons.navtab.NavTab;
|
|||
public class ExploreListRootFragment extends CommonsDaggerSupportFragment implements
|
||||
MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback {
|
||||
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategoriesMediaFragment listFragment;
|
||||
private MediaDetailPagerFragment mediaDetails;
|
||||
private CategoriesMediaFragment listFragment;
|
||||
|
||||
@BindView(R.id.explore_container)
|
||||
FrameLayout container;
|
||||
@BindView(R.id.explore_container)
|
||||
FrameLayout container;
|
||||
|
||||
public ExploreListRootFragment(){
|
||||
//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 ExploreListRootFragment() {
|
||||
//empty constructor necessary otherwise crashes on recreate
|
||||
}
|
||||
}
|
||||
|
||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||
if (fragment.isAdded() && otherFragment != null) {
|
||||
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();
|
||||
public ExploreListRootFragment(Bundle bundle) {
|
||||
String title = bundle.getString("categoryName");
|
||||
listFragment = new CategoriesMediaFragment();
|
||||
Bundle featuredArguments = new Bundle();
|
||||
featuredArguments.putString("categoryName", title);
|
||||
listFragment.setArguments(featuredArguments);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFragment(Fragment fragment) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
if (savedInstanceState == null) {
|
||||
setFragment(listFragment, mediaDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
public void setFragment(Fragment fragment, Fragment otherFragment) {
|
||||
if (fragment.isAdded() && otherFragment != null) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void removeFragment(Fragment fragment) {
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.remove(fragment)
|
||||
.commit();
|
||||
getChildFragmentManager().executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
((MainActivity) getActivity()).setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
return false;
|
||||
}
|
||||
((MainActivity) getActivity()).showTabs();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,64 +6,61 @@ import android.view.MotionEvent;
|
|||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
/**
|
||||
* ParentViewPager
|
||||
* A custom viewPager whose scrolling can be enabled and disabled.
|
||||
*/
|
||||
* ParentViewPager A custom viewPager whose scrolling can be enabled and disabled.
|
||||
*/
|
||||
public class ParentViewPager extends ViewPager {
|
||||
|
||||
/**
|
||||
* Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
|
||||
*/
|
||||
private boolean canScroll = true;
|
||||
/**
|
||||
* Boolean variable that stores the current state of pager scroll i.e(enabled or disabled)
|
||||
*/
|
||||
private boolean canScroll = true;
|
||||
|
||||
|
||||
/**
|
||||
* Default constructors
|
||||
*/
|
||||
public ParentViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
/**
|
||||
* Default constructors
|
||||
*/
|
||||
public ParentViewPager(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public ParentViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ParentViewPager(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter method for canScroll.
|
||||
*/
|
||||
public void setCanScroll(boolean canScroll) {
|
||||
this.canScroll = canScroll;
|
||||
}
|
||||
/**
|
||||
* Setter method for canScroll.
|
||||
*/
|
||||
public void setCanScroll(boolean canScroll) {
|
||||
this.canScroll = canScroll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter method for canScroll.
|
||||
*/
|
||||
public boolean isCanScroll() {
|
||||
return canScroll;
|
||||
}
|
||||
/**
|
||||
* Getter method for canScroll.
|
||||
*/
|
||||
public boolean isCanScroll() {
|
||||
return canScroll;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Method that prevents scrolling if canScroll is set to false.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A facilitator method that allows parent to intercept touch events before its children.
|
||||
* thus making it possible to prevent swiping parent on child end.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
/**
|
||||
* Method that prevents scrolling if canScroll is set to false.
|
||||
*/
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A facilitator method that allows parent to intercept touch events before its children. thus
|
||||
* making it possible to prevent swiping parent on child end.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return canScroll && super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,8 +27,11 @@ import fr.free.nrw.commons.explore.SearchActivity;
|
|||
* Displays the recent searches screen.
|
||||
*/
|
||||
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;
|
||||
ArrayAdapter adapter;
|
||||
@BindView(R.id.recent_searches_delete_button)
|
||||
|
|
@ -38,16 +41,16 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
|||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_search_history, container, false);
|
||||
ButterKnife.bind(this, rootView);
|
||||
recentSearches = recentSearchesDao.recentSearches(10);
|
||||
|
||||
if(recentSearches.isEmpty()) {
|
||||
if (recentSearches.isEmpty()) {
|
||||
recent_searches_delete_button.setVisibility(View.GONE);
|
||||
recent_searches_text_view.setText(R.string.no_recent_searches);
|
||||
}
|
||||
|
||||
|
||||
recent_searches_delete_button.setOnClickListener(v -> {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(getString(R.string.delete_recent_searches_dialog))
|
||||
|
|
@ -55,9 +58,11 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
|||
recentSearchesDao.deleteAll();
|
||||
recent_searches_delete_button.setVisibility(View.GONE);
|
||||
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);
|
||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches, recentSearches);
|
||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches,
|
||||
recentSearches);
|
||||
recentSearchesList.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
dialog.dismiss();
|
||||
|
|
@ -67,24 +72,26 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
|||
.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.setOnItemClickListener((parent, view, position, id) -> (
|
||||
(SearchActivity)getContext()).updateText(recentSearches.get(position)));
|
||||
(SearchActivity) getContext()).updateText(recentSearches.get(position)));
|
||||
recentSearchesList.setOnItemLongClickListener((parent, view, position, id) -> {
|
||||
new AlertDialog.Builder(getContext())
|
||||
.setMessage(R.string.delete_search_dialog)
|
||||
.setPositiveButton(getString(R.string.delete).toUpperCase(),((dialog, which) -> {
|
||||
recentSearchesDao.delete(recentSearchesDao.find(recentSearches.get(position)));
|
||||
recentSearches = recentSearchesDao.recentSearches(10);
|
||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches, recentSearches);
|
||||
recentSearchesList.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
dialog.dismiss();
|
||||
}))
|
||||
.setNegativeButton(android.R.string.cancel,null)
|
||||
.create()
|
||||
.show();
|
||||
.setMessage(R.string.delete_search_dialog)
|
||||
.setPositiveButton(getString(R.string.delete).toUpperCase(), ((dialog, which) -> {
|
||||
recentSearchesDao.delete(recentSearchesDao.find(recentSearches.get(position)));
|
||||
recentSearches = recentSearchesDao.recentSearches(10);
|
||||
adapter = new ArrayAdapter<>(getContext(), R.layout.item_recent_searches,
|
||||
recentSearches);
|
||||
recentSearchesList.setAdapter(adapter);
|
||||
adapter.notifyDataSetChanged();
|
||||
dialog.dismiss();
|
||||
}))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
.show();
|
||||
return true;
|
||||
});
|
||||
updateRecentSearches();
|
||||
|
|
@ -92,8 +99,8 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
|||
}
|
||||
|
||||
/**
|
||||
* This method is called on back press of activity
|
||||
* so we are updating the list from database to refresh the recent searches list.
|
||||
* This method is called on back press of activity so we are updating the list from database to
|
||||
* refresh the recent searches list.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
|
|
@ -108,7 +115,7 @@ public class RecentSearchesFragment extends CommonsDaggerSupportFragment {
|
|||
recentSearches = recentSearchesDao.recentSearches(10);
|
||||
adapter.notifyDataSetChanged();
|
||||
|
||||
if(!recentSearches.isEmpty()) {
|
||||
if (!recentSearches.isEmpty()) {
|
||||
recent_searches_delete_button.setVisibility(View.VISIBLE);
|
||||
recent_searches_text_view.setText(R.string.search_recent_header);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,195 +35,199 @@ import timber.log.Timber;
|
|||
public class CustomOkHttpNetworkFetcher
|
||||
extends BaseNetworkFetcher<CustomOkHttpNetworkFetcher.OkHttpNetworkFetchState> {
|
||||
|
||||
private static final String QUEUE_TIME = "queue_time";
|
||||
private static final String FETCH_TIME = "fetch_time";
|
||||
private static final String TOTAL_TIME = "total_time";
|
||||
private static final String IMAGE_SIZE = "image_size";
|
||||
private final Call.Factory mCallFactory;
|
||||
private final @Nullable
|
||||
CacheControl mCacheControl;
|
||||
private Executor mCancellationExecutor;
|
||||
private JsonKvStore defaultKvStore;
|
||||
private static final String QUEUE_TIME = "queue_time";
|
||||
private static final String FETCH_TIME = "fetch_time";
|
||||
private static final String TOTAL_TIME = "total_time";
|
||||
private static final String IMAGE_SIZE = "image_size";
|
||||
private final Call.Factory mCallFactory;
|
||||
private final @Nullable
|
||||
CacheControl mCacheControl;
|
||||
private Executor mCancellationExecutor;
|
||||
private JsonKvStore defaultKvStore;
|
||||
|
||||
/**
|
||||
* @param okHttpClient client to use
|
||||
*/
|
||||
@Inject
|
||||
public CustomOkHttpNetworkFetcher(OkHttpClient okHttpClient,
|
||||
@Named("default_preferences") JsonKvStore 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);
|
||||
/**
|
||||
* @param okHttpClient client to use
|
||||
*/
|
||||
@Inject
|
||||
public CustomOkHttpNetworkFetcher(OkHttpClient okHttpClient,
|
||||
@Named("default_preferences") JsonKvStore defaultKvStore) {
|
||||
this(okHttpClient, okHttpClient.dispatcher().executorService(), defaultKvStore);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
Map<String, String> extraMap = new HashMap<>(4);
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
protected void fetchWithRequest(
|
||||
final OkHttpNetworkFetchState fetchState,
|
||||
final NetworkFetcher.Callback callback,
|
||||
final Request request) {
|
||||
final Call call = mCallFactory.newCall(request);
|
||||
@Override
|
||||
public OkHttpNetworkFetchState createFetchState(
|
||||
Consumer<EncodedImage> consumer, ProducerContext context) {
|
||||
return new OkHttpNetworkFetchState(consumer, context);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void fetch(
|
||||
final OkHttpNetworkFetchState fetchState, final NetworkFetcher.Callback callback) {
|
||||
fetchState.submitTime = SystemClock.elapsedRealtime();
|
||||
final Uri uri = fetchState.getUri();
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
public void onFailure(Call call, IOException e) {
|
||||
handleException(call, e, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mCacheControl != null) {
|
||||
requestBuilder.cacheControl(mCacheControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@Override
|
||||
public void onFetchCompletion(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
fetchState.fetchCompleteTime = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getExtraMap(OkHttpNetworkFetchState fetchState, int byteSize) {
|
||||
Map<String, String> extraMap = new HashMap<>(4);
|
||||
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
|
||||
public class OkHttpJsonApiClient {
|
||||
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final DepictsClient depictsClient;
|
||||
private final HttpUrl wikiMediaToolforgeUrl;
|
||||
private final HttpUrl wikiMediaTestToolforgeUrl;
|
||||
private final String sparqlQueryUrl;
|
||||
private final String campaignsUrl;
|
||||
private final Gson gson;
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final DepictsClient depictsClient;
|
||||
private final HttpUrl wikiMediaToolforgeUrl;
|
||||
private final HttpUrl wikiMediaTestToolforgeUrl;
|
||||
private final String sparqlQueryUrl;
|
||||
private final String campaignsUrl;
|
||||
private final Gson gson;
|
||||
|
||||
|
||||
@Inject
|
||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||
DepictsClient depictsClient,
|
||||
HttpUrl wikiMediaToolforgeUrl,
|
||||
HttpUrl wikiMediaTestToolforgeUrl,
|
||||
String sparqlQueryUrl,
|
||||
String campaignsUrl,
|
||||
Gson gson) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.depictsClient = depictsClient;
|
||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||
this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
|
||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||
this.campaignsUrl = campaignsUrl;
|
||||
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");
|
||||
@Inject
|
||||
public OkHttpJsonApiClient(OkHttpClient okHttpClient,
|
||||
DepictsClient depictsClient,
|
||||
HttpUrl wikiMediaToolforgeUrl,
|
||||
HttpUrl wikiMediaTestToolforgeUrl,
|
||||
String sparqlQueryUrl,
|
||||
String campaignsUrl,
|
||||
Gson gson) {
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.depictsClient = depictsClient;
|
||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||
this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl;
|
||||
this.sparqlQueryUrl = sparqlQueryUrl;
|
||||
this.campaignsUrl = campaignsUrl;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
|
||||
return Single.fromCallable(() -> {
|
||||
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);
|
||||
/**
|
||||
* 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 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");
|
||||
return new LeaderboardResponse();
|
||||
});
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
@NonNull
|
||||
public Single<Integer> getUploadCount(String userName) {
|
||||
HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder();
|
||||
urlBuilder
|
||||
.addPathSegments("uploadsbyuser.py")
|
||||
.addQueryParameter("user", userName);
|
||||
|
||||
/**
|
||||
* 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, "");
|
||||
if (ConfigUtils.isBetaFlavour()) {
|
||||
urlBuilder.addQueryParameter("labs", "commonswiki");
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return Single.fromCallable(() -> {
|
||||
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 query = wikidataQuery
|
||||
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
||||
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
||||
.replace("${LANG}", language);
|
||||
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.getLatitude()))
|
||||
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.getLongitude()))
|
||||
.replace("${LANG}", language);
|
||||
|
||||
HttpUrl.Builder urlBuilder = HttpUrl
|
||||
.parse(sparqlQueryUrl)
|
||||
.newBuilder()
|
||||
.addQueryParameter("query", query)
|
||||
.addQueryParameter("format", "json");
|
||||
HttpUrl.Builder urlBuilder = HttpUrl
|
||||
.parse(sparqlQueryUrl)
|
||||
.newBuilder()
|
||||
.addQueryParameter("query", query)
|
||||
.addQueryParameter("format", "json");
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.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 ArrayList<>();
|
||||
}
|
||||
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
||||
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
||||
List<Place> places = new ArrayList<>();
|
||||
for (NearbyResultItem item : bindings) {
|
||||
places.add(Place.from(item));
|
||||
}
|
||||
return places;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
});
|
||||
}
|
||||
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 ArrayList<>();
|
||||
}
|
||||
NearbyResponse nearbyResponse = gson.fromJson(json, NearbyResponse.class);
|
||||
List<NearbyResultItem> bindings = nearbyResponse.getResults().getBindings();
|
||||
List<Place> places = new ArrayList<>();
|
||||
for (NearbyResultItem item : bindings) {
|
||||
places.add(Place.from(item));
|
||||
}
|
||||
return places;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition,
|
||||
int limit) throws IOException {
|
||||
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:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Single<List<DepictedItem>> getChildDepictions(String qid, int startPosition,
|
||||
int limit) throws IOException {
|
||||
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:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition,
|
||||
int limit) throws IOException {
|
||||
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
|
||||
"/queries/parentclasses_query.rq"));
|
||||
}
|
||||
/**
|
||||
* Get the QIDs of all Wikidata items that are subclasses of the given Wikidata item. Example:
|
||||
* bridge -> suspended bridge, aqueduct, etc
|
||||
*/
|
||||
public Single<List<DepictedItem>> getParentDepictions(String qid, int startPosition,
|
||||
int limit) throws IOException {
|
||||
return depictedItemsFrom(sparqlQuery(qid, startPosition, limit,
|
||||
"/queries/parentclasses_query.rq"));
|
||||
}
|
||||
|
||||
private Single<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||
return depictsClient.toDepictions(Single.fromCallable(() -> {
|
||||
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||
return gson.fromJson(body.string(), SparqlResponse.class);
|
||||
}
|
||||
}).doOnError(Timber::e));
|
||||
}
|
||||
private Single<List<DepictedItem>> depictedItemsFrom(Request request) {
|
||||
return depictsClient.toDepictions(Single.fromCallable(() -> {
|
||||
try (ResponseBody body = okHttpClient.newCall(request).execute().body()) {
|
||||
return gson.fromJson(body.string(), SparqlResponse.class);
|
||||
}
|
||||
}).doOnError(Timber::e));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Request sparqlQuery(String qid, int startPosition, int limit, String fileName) throws IOException {
|
||||
String query = FileUtils.readFromResource(fileName)
|
||||
.replace("${QID}", qid)
|
||||
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
|
||||
.replace("${LIMIT}",""+ limit)
|
||||
.replace("${OFFSET}",""+ startPosition);
|
||||
HttpUrl.Builder urlBuilder = HttpUrl
|
||||
.parse(sparqlQueryUrl)
|
||||
.newBuilder()
|
||||
.addQueryParameter("query", query)
|
||||
.addQueryParameter("format", "json");
|
||||
return new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
}
|
||||
@NotNull
|
||||
private Request sparqlQuery(String qid, int startPosition, int limit, String fileName)
|
||||
throws IOException {
|
||||
String query = FileUtils.readFromResource(fileName)
|
||||
.replace("${QID}", qid)
|
||||
.replace("${LANG}", "\"" + Locale.getDefault().getLanguage() + "\"")
|
||||
.replace("${LIMIT}", "" + limit)
|
||||
.replace("${OFFSET}", "" + startPosition);
|
||||
HttpUrl.Builder urlBuilder = HttpUrl
|
||||
.parse(sparqlQueryUrl)
|
||||
.newBuilder()
|
||||
.addQueryParameter("query", query)
|
||||
.addQueryParameter("format", "json");
|
||||
return new Request.Builder()
|
||||
.url(urlBuilder.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
public Single<CampaignResponseDTO> getCampaigns() {
|
||||
return Single.fromCallable(() -> {
|
||||
Request request = new Request.Builder().url(campaignsUrl)
|
||||
.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;
|
||||
}
|
||||
return gson.fromJson(json, CampaignResponseDTO.class);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
public Single<CampaignResponseDTO> getCampaigns() {
|
||||
return Single.fromCallable(() -> {
|
||||
Request request = new Request.Builder().url(campaignsUrl)
|
||||
.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;
|
||||
}
|
||||
return gson.fromJson(json, CampaignResponseDTO.class);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,82 +16,81 @@ import org.wikipedia.model.EnumCodeMap;
|
|||
import fr.free.nrw.commons.R;
|
||||
|
||||
|
||||
|
||||
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
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return ContributionsFragment.newInstance();
|
||||
public static NavTab of(int code) {
|
||||
return MAP.get(code);
|
||||
}
|
||||
},
|
||||
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
|
||||
public abstract Fragment newInstance();
|
||||
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return NearbyParentFragment.newInstance();
|
||||
public int code() {
|
||||
// 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) {
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return ExploreFragment.newInstance();
|
||||
|
||||
NavTab(@StringRes int text, @DrawableRes int icon) {
|
||||
this.text = text;
|
||||
this.icon = icon;
|
||||
}
|
||||
},
|
||||
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;
|
||||
|
||||
public class NavTabFragmentPagerAdapter extends FragmentPagerAdapter {
|
||||
private Fragment currentFragment;
|
||||
|
||||
public NavTabFragmentPagerAdapter(FragmentManager mgr) {
|
||||
super(mgr);
|
||||
}
|
||||
private Fragment currentFragment;
|
||||
|
||||
@Nullable
|
||||
public Fragment getCurrentFragment() {
|
||||
return currentFragment;
|
||||
}
|
||||
public NavTabFragmentPagerAdapter(FragmentManager mgr) {
|
||||
super(mgr);
|
||||
}
|
||||
|
||||
@Override public Fragment getItem(int pos) {
|
||||
return NavTab.of(pos).newInstance();
|
||||
}
|
||||
@Nullable
|
||||
public Fragment getCurrentFragment() {
|
||||
return currentFragment;
|
||||
}
|
||||
|
||||
@Override public int getCount() {
|
||||
return NavTab.size();
|
||||
}
|
||||
@Override
|
||||
public Fragment getItem(int pos) {
|
||||
return NavTab.of(pos).newInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(ViewGroup container, int position, Object object) {
|
||||
currentFragment = ((Fragment) object);
|
||||
super.setPrimaryItem(container, position, object);
|
||||
}
|
||||
@Override
|
||||
public int getCount() {
|
||||
return NavTab.size();
|
||||
}
|
||||
|
||||
@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 NavTabLayout(Context context) {
|
||||
super(context);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
public NavTabLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
private void setTabViews() {
|
||||
if (((MainActivity)getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
|
||||
for (int i = 0; i < NavTabLoggedOut.size(); i++) {
|
||||
NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
|
||||
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < NavTab.size(); i++) {
|
||||
NavTab navTab = NavTab.of(i);
|
||||
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
||||
}
|
||||
public NavTabLayout(Context context) {
|
||||
super(context);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
public NavTabLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
public NavTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
setTabViews();
|
||||
}
|
||||
|
||||
private void setTabViews() {
|
||||
if (((MainActivity) getContext()).applicationKvStore.getBoolean("login_skipped") == true) {
|
||||
for (int i = 0; i < NavTabLoggedOut.size(); i++) {
|
||||
NavTabLoggedOut navTab = NavTabLoggedOut.of(i);
|
||||
getMenu().add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon());
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < NavTab.size(); i++) {
|
||||
NavTab navTab = NavTab.of(i);
|
||||
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 {
|
||||
|
||||
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
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return ExploreFragment.newInstance();
|
||||
public static NavTabLoggedOut of(int code) {
|
||||
return MAP.get(code);
|
||||
}
|
||||
},
|
||||
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
|
||||
public abstract Fragment newInstance();
|
||||
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return BookmarkFragment.newInstance();
|
||||
public int code() {
|
||||
// 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) {
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment newInstance() {
|
||||
return null;
|
||||
|
||||
NavTabLoggedOut(@StringRes int text, @DrawableRes int icon) {
|
||||
this.text = text;
|
||||
this.icon = icon;
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
public class FileUtilsWrapper {
|
||||
|
||||
@Inject
|
||||
public FileUtilsWrapper() {
|
||||
@Inject
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
public class ImageProcessingService {
|
||||
|
||||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final ImageUtilsWrapper imageUtilsWrapper;
|
||||
private final ReadFBMD readFBMD;
|
||||
|
|
@ -30,9 +31,9 @@ public class ImageProcessingService {
|
|||
|
||||
@Inject
|
||||
public ImageProcessingService(FileUtilsWrapper fileUtilsWrapper,
|
||||
ImageUtilsWrapper imageUtilsWrapper,
|
||||
ReadFBMD readFBMD, EXIFReader EXIFReader,
|
||||
MediaClient mediaClient, Context context) {
|
||||
ImageUtilsWrapper imageUtilsWrapper,
|
||||
ReadFBMD readFBMD, EXIFReader EXIFReader,
|
||||
MediaClient mediaClient, Context context) {
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.imageUtilsWrapper = imageUtilsWrapper;
|
||||
this.readFBMD = readFBMD;
|
||||
|
|
@ -41,33 +42,34 @@ public class ImageProcessingService {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||
* geolocation for image - check for valid title
|
||||
*/
|
||||
Single<Integer> validateImage(UploadItem uploadItem) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||
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;
|
||||
/**
|
||||
* Check image quality before upload - checks duplicate image - checks dark image - checks
|
||||
* geolocation for image - check for valid title
|
||||
*/
|
||||
Single<Integer> validateImage(UploadItem uploadItem) {
|
||||
int currentImageQuality = uploadItem.getImageQuality();
|
||||
Timber.d("Current image quality is %d", currentImageQuality);
|
||||
if (currentImageQuality == ImageUtils.IMAGE_KEEP) {
|
||||
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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* If an image does not have any Exif metadata, then it was likely downloaded from the internet,
|
||||
* and is probably not an original work by the user. We detect these kinds of images by looking
|
||||
* for the presence of some basic Exif metadata.
|
||||
* We try to minimize uploads from the Commons app that might be copyright violations. If an
|
||||
* image does not have any Exif metadata, then it was likely downloaded from the internet, and
|
||||
* is probably not an original work by the user. We detect these kinds of images by looking for
|
||||
* the presence of some basic Exif metadata.
|
||||
*/
|
||||
private Single<Integer> checkEXIF(String filepath) {
|
||||
return EXIFReader.processMetadata(filepath);
|
||||
|
|
@ -90,9 +92,7 @@ public class ImageProcessingService {
|
|||
|
||||
|
||||
/**
|
||||
* Checks item caption
|
||||
* - empty caption
|
||||
* - existing caption
|
||||
* Checks item caption - empty caption - existing caption
|
||||
*
|
||||
* @param uploadItem
|
||||
* @return
|
||||
|
|
@ -105,11 +105,11 @@ public class ImageProcessingService {
|
|||
}
|
||||
|
||||
return mediaClient.checkPageExistsUsingTitle("File:" + uploadItem.getFileName())
|
||||
.map(doesFileExist -> {
|
||||
Timber.d("Result for valid title is %s", doesFileExist);
|
||||
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
.map(doesFileExist -> {
|
||||
Timber.d("Result for valid title is %s", doesFileExist);
|
||||
return doesFileExist ? FILE_NAME_EXISTS : IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -121,13 +121,13 @@ public class ImageProcessingService {
|
|||
private Single<Integer> checkDuplicateImage(String filePath) {
|
||||
Timber.d("Checking for duplicate image %s", filePath);
|
||||
return Single.fromCallable(() -> fileUtilsWrapper.getFileInputStream(filePath))
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.flatMap(mediaClient::checkFileExistsUsingSha)
|
||||
.map(b -> {
|
||||
Timber.d("Result for duplicate image %s", b);
|
||||
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
.map(fileUtilsWrapper::getSHA1)
|
||||
.flatMap(mediaClient::checkFileExistsUsingSha)
|
||||
.map(b -> {
|
||||
Timber.d("Result for duplicate image %s", b);
|
||||
return b ? ImageUtils.IMAGE_DUPLICATE : ImageUtils.IMAGE_OK;
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -142,8 +142,8 @@ public class ImageProcessingService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks for image geolocation
|
||||
* returns IMAGE_OK if the place is null or if the file doesn't contain a geolocation
|
||||
* Checks for image geolocation returns IMAGE_OK if the place is null or if the file doesn't
|
||||
* contain a geolocation
|
||||
*
|
||||
* @param filePath file to be checked
|
||||
* @return IMAGE_GEOLOCATION_DIFFERENT or IMAGE_OK
|
||||
|
|
@ -154,14 +154,15 @@ public class ImageProcessingService {
|
|||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
return Single.fromCallable(() -> filePath)
|
||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
||||
.flatMap(geoLocation -> {
|
||||
if (StringUtils.isBlank(geoLocation)) {
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
return imageUtilsWrapper.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
.map(fileUtilsWrapper::getGeolocationOfFile)
|
||||
.flatMap(geoLocation -> {
|
||||
if (StringUtils.isBlank(geoLocation)) {
|
||||
return Single.just(ImageUtils.IMAGE_OK);
|
||||
}
|
||||
return imageUtilsWrapper
|
||||
.checkImageGeolocationIsDifferent(geoLocation, place.getLocation());
|
||||
})
|
||||
.subscribeOn(Schedulers.io());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,96 +16,96 @@ import org.apache.commons.lang3.StringUtils;
|
|||
|
||||
class PageContentsCreator {
|
||||
|
||||
//{{According to Exif data|2009-01-09}}
|
||||
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
||||
//{{According to Exif data|2009-01-09}}
|
||||
private static final String TEMPLATE_DATE_ACC_TO_EXIF = "{{According to Exif data|%s}}";
|
||||
|
||||
//2009-01-09 → 9 January 2009
|
||||
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
||||
//2009-01-09 → 9 January 2009
|
||||
private static final String TEMPLATE_DATA_OTHER_SOURCE = "%s";
|
||||
|
||||
private final Context context;
|
||||
private final Context context;
|
||||
|
||||
@Inject
|
||||
public PageContentsCreator(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);
|
||||
@Inject
|
||||
public PageContentsCreator(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
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
|
||||
final String decimalCoords = contribution.getDecimalCoords();
|
||||
if (decimalCoords != null) {
|
||||
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
||||
String templatizedCreatedDate = getTemplatizedCreatedDate(
|
||||
contribution.getDateCreated(), contribution.getDateCreatedSource());
|
||||
if (!StringUtils.isBlank(templatizedCreatedDate)) {
|
||||
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")
|
||||
.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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}}";
|
||||
/**
|
||||
* 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 "";
|
||||
}
|
||||
|
||||
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
|
||||
public class ReadFBMD {
|
||||
|
||||
@Inject
|
||||
public ReadFBMD() {
|
||||
}
|
||||
@Inject
|
||||
public ReadFBMD() {
|
||||
}
|
||||
|
||||
public Single<Integer> processMetadata(String path) {
|
||||
return Single.fromCallable(() -> {
|
||||
try {
|
||||
int psBlockOffset;
|
||||
int fbmdOffset;
|
||||
public Single<Integer> processMetadata(String path) {
|
||||
return Single.fromCallable(() -> {
|
||||
try {
|
||||
int psBlockOffset;
|
||||
int fbmdOffset;
|
||||
|
||||
try (FileInputStream fs = new FileInputStream(path)) {
|
||||
byte[] bytes = new byte[4096];
|
||||
fs.read(bytes);
|
||||
fs.close();
|
||||
String fileStr = new String(bytes);
|
||||
psBlockOffset = fileStr.indexOf("8BIM");
|
||||
fbmdOffset = fileStr.indexOf("FBMD");
|
||||
}
|
||||
try (FileInputStream fs = new FileInputStream(path)) {
|
||||
byte[] bytes = new byte[4096];
|
||||
fs.read(bytes);
|
||||
fs.close();
|
||||
String fileStr = new String(bytes);
|
||||
psBlockOffset = fileStr.indexOf("8BIM");
|
||||
fbmdOffset = fileStr.indexOf("FBMD");
|
||||
}
|
||||
|
||||
if (psBlockOffset > 0 && fbmdOffset > 0
|
||||
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
||||
return ImageUtils.FILE_FBMD;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ImageUtils.IMAGE_OK;
|
||||
});
|
||||
}
|
||||
if (psBlockOffset > 0 && fbmdOffset > 0
|
||||
&& fbmdOffset > psBlockOffset && fbmdOffset - psBlockOffset < 0x80) {
|
||||
return ImageUtils.FILE_FBMD;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return ImageUtils.IMAGE_OK;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,199 +35,204 @@ import timber.log.Timber;
|
|||
@Singleton
|
||||
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
|
||||
// https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
|
||||
private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
|
||||
//This is maximum duration for which a stash is persisted on MediaWiki
|
||||
// https://www.mediawiki.org/wiki/Manual:$wgUploadStashMaxAge
|
||||
private final int MAX_CHUNK_AGE = 6 * 3600 * 1000; // 6 hours
|
||||
|
||||
private final UploadInterface uploadInterface;
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
private final PageContentsCreator pageContentsCreator;
|
||||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final Gson gson;
|
||||
private final UploadInterface uploadInterface;
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
private final PageContentsCreator pageContentsCreator;
|
||||
private final FileUtilsWrapper fileUtilsWrapper;
|
||||
private final Gson gson;
|
||||
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
@Inject
|
||||
public UploadClient(final UploadInterface uploadInterface,
|
||||
@Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
|
||||
final PageContentsCreator pageContentsCreator,
|
||||
final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
|
||||
this.uploadInterface = uploadInterface;
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
this.pageContentsCreator = pageContentsCreator;
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
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()));
|
||||
@Inject
|
||||
public UploadClient(final UploadInterface uploadInterface,
|
||||
@Named(NAMED_COMMONS_CSRF) final CsrfTokenClient csrfTokenClient,
|
||||
final PageContentsCreator pageContentsCreator,
|
||||
final FileUtilsWrapper fileUtilsWrapper, final Gson gson) {
|
||||
this.uploadInterface = uploadInterface;
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
this.pageContentsCreator = pageContentsCreator;
|
||||
this.fileUtilsWrapper = fileUtilsWrapper;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
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());
|
||||
final List<File> fileChunks = fileUtilsWrapper.getFileChunks(context, file, CHUNK_SIZE);
|
||||
CommonsApplication.pauseUploads.put(contribution.getPageId(), false);
|
||||
|
||||
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
|
||||
.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
|
||||
final int totalChunks = fileChunks.size();
|
||||
|
||||
final AtomicReference<ChunkInfo> chunkInfo = new AtomicReference<>();
|
||||
if (isStashValid(contribution)) {
|
||||
chunkInfo.set(contribution.getChunkInfo());
|
||||
final MediaType mediaType = MediaType
|
||||
.parse(FileUtils.getMimeType(context, Uri.parse(file.getPath())));
|
||||
|
||||
Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
|
||||
contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
|
||||
contribution.getChunkInfo().getTotalChunks());
|
||||
}
|
||||
final AtomicReference<ChunkInfo> chunkInfo = new AtomicReference<>();
|
||||
if (isStashValid(contribution)) {
|
||||
chunkInfo.set(contribution.getChunkInfo());
|
||||
|
||||
final AtomicInteger index = new AtomicInteger();
|
||||
final AtomicBoolean failures = new AtomicBoolean();
|
||||
Timber.d("Chunk: Next Chunk: %s, Total Chunks: %s",
|
||||
contribution.getChunkInfo().getIndexOfNextChunkToUpload(),
|
||||
contribution.getChunkInfo().getTotalChunks());
|
||||
}
|
||||
|
||||
compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
|
||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
||||
return;
|
||||
}
|
||||
final AtomicInteger index = new AtomicInteger();
|
||||
final AtomicBoolean failures = new AtomicBoolean();
|
||||
|
||||
if (chunkInfo.get() != null && index.get() < chunkInfo.get().getIndexOfNextChunkToUpload()) {
|
||||
index.incrementAndGet();
|
||||
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());
|
||||
compositeDisposable.add(Observable.fromIterable(fileChunks).forEach(chunkFile -> {
|
||||
if (CommonsApplication.pauseUploads.get(contribution.getPageId()) || failures.get()) {
|
||||
return;
|
||||
}
|
||||
return uploadResult.getUpload();
|
||||
});
|
||||
} catch (final Throwable throwable) {
|
||||
Timber.e(throwable, "Exception occurred in uploading file from stash");
|
||||
return Observable.error(throwable);
|
||||
|
||||
if (chunkInfo.get() != null && index.get() < chunkInfo.get()
|
||||
.getIndexOfNextChunkToUpload()) {
|
||||
index.incrementAndGet();
|
||||
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 {
|
||||
|
||||
private final Uri mediaUri;
|
||||
private final String mimeType;
|
||||
private ImageCoordinates gpsCoords;
|
||||
private List<UploadMediaDetail> uploadMediaDetails;
|
||||
private Place place;
|
||||
private final long createdTimestamp;
|
||||
private final String createdTimestampSource;
|
||||
private final BehaviorSubject<Integer> imageQuality;
|
||||
private boolean hasInvalidLocation;
|
||||
private final Uri mediaUri;
|
||||
private final String mimeType;
|
||||
private ImageCoordinates gpsCoords;
|
||||
private List<UploadMediaDetail> uploadMediaDetails;
|
||||
private Place place;
|
||||
private final long createdTimestamp;
|
||||
private final String createdTimestampSource;
|
||||
private final BehaviorSubject<Integer> imageQuality;
|
||||
private boolean hasInvalidLocation;
|
||||
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
UploadItem(final Uri mediaUri,
|
||||
final String mimeType,
|
||||
final ImageCoordinates gpsCoords,
|
||||
final Place place,
|
||||
final long createdTimestamp,
|
||||
final String createdTimestampSource) {
|
||||
this.createdTimestampSource = createdTimestampSource;
|
||||
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
|
||||
this.place = place;
|
||||
this.mediaUri = mediaUri;
|
||||
this.mimeType = mimeType;
|
||||
this.gpsCoords = gpsCoords;
|
||||
this.createdTimestamp = createdTimestamp;
|
||||
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;
|
||||
@SuppressLint("CheckResult")
|
||||
UploadItem(final Uri mediaUri,
|
||||
final String mimeType,
|
||||
final ImageCoordinates gpsCoords,
|
||||
final Place place,
|
||||
final long createdTimestamp,
|
||||
final String createdTimestampSource) {
|
||||
this.createdTimestampSource = createdTimestampSource;
|
||||
uploadMediaDetails = new ArrayList<>(Collections.singletonList(new UploadMediaDetail()));
|
||||
this.place = place;
|
||||
this.mediaUri = mediaUri;
|
||||
this.mimeType = mimeType;
|
||||
this.gpsCoords = gpsCoords;
|
||||
this.createdTimestamp = createdTimestamp;
|
||||
imageQuality = BehaviorSubject.createDefault(ImageUtils.IMAGE_WAIT);
|
||||
}
|
||||
return mediaUri.toString().contains(((UploadItem) (obj)).mediaUri.toString());
|
||||
|
||||
}
|
||||
public String getCreatedTimestampSource() {
|
||||
return createdTimestampSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mediaUri.hashCode();
|
||||
}
|
||||
public ImageCoordinates getGpsCoords() {
|
||||
return gpsCoords;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 List<UploadMediaDetail> getUploadMediaDetails() {
|
||||
return uploadMediaDetails;
|
||||
}
|
||||
|
||||
public void setGpsCoords(final ImageCoordinates gpsCoords) {
|
||||
this.gpsCoords = gpsCoords;
|
||||
}
|
||||
public long getCreatedTimestamp() {
|
||||
return createdTimestamp;
|
||||
}
|
||||
|
||||
public void setHasInvalidLocation(boolean hasInvalidLocation) {
|
||||
this.hasInvalidLocation=hasInvalidLocation;
|
||||
}
|
||||
public Uri getMediaUri() {
|
||||
return mediaUri;
|
||||
}
|
||||
|
||||
public boolean hasInvalidLocation() {
|
||||
return hasInvalidLocation;
|
||||
}
|
||||
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());
|
||||
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
public class ActivityUtils {
|
||||
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
|
||||
Intent intent = new Intent(context, cls);
|
||||
for (int flag: flags) {
|
||||
intent.addFlags(flag);
|
||||
|
||||
public static <T> void startActivityWithFlags(Context context, Class<T> cls, int... flags) {
|
||||
Intent intent = new Intent(context, cls);
|
||||
for (int flag : flags) {
|
||||
intent.addFlags(flag);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
}
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,22 +9,22 @@ import javax.inject.Singleton;
|
|||
@Singleton
|
||||
public class ImageUtilsWrapper {
|
||||
|
||||
@Inject
|
||||
public ImageUtilsWrapper() {
|
||||
@Inject
|
||||
public ImageUtilsWrapper() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||
.subscribeOn(Schedulers.computation());
|
||||
}
|
||||
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
|
||||
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
|
||||
.subscribeOn(Schedulers.computation());
|
||||
}
|
||||
|
||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
|
||||
LatLng latLng) {
|
||||
return Single.fromCallable(
|
||||
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
|
||||
: ImageUtils.IMAGE_OK);
|
||||
}
|
||||
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
|
||||
LatLng latLng) {
|
||||
return Single.fromCallable(
|
||||
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
|
||||
: ImageUtils.IMAGE_OK);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,43 +19,44 @@ import timber.log.Timber;
|
|||
@Singleton
|
||||
public class WikiBaseClient {
|
||||
|
||||
private final WikiBaseInterface wikiBaseInterface;
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
private final WikiBaseInterface wikiBaseInterface;
|
||||
private final CsrfTokenClient csrfTokenClient;
|
||||
|
||||
@Inject
|
||||
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
|
||||
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
||||
this.wikiBaseInterface = wikiBaseInterface;
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
}
|
||||
@Inject
|
||||
public WikiBaseClient(WikiBaseInterface wikiBaseInterface,
|
||||
@Named(NAMED_COMMONS_CSRF) CsrfTokenClient csrfTokenClient) {
|
||||
this.wikiBaseInterface = wikiBaseInterface;
|
||||
this.csrfTokenClient = csrfTokenClient;
|
||||
}
|
||||
|
||||
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
|
||||
return csrfToken()
|
||||
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
|
||||
.map(response -> (response.getSuccessVal() == 1)));
|
||||
}
|
||||
public Observable<Boolean> postEditEntity(String fileEntityId, String data) {
|
||||
return csrfToken()
|
||||
.switchMap(editToken -> wikiBaseInterface.postEditEntity(fileEntityId, editToken, data)
|
||||
.map(response -> (response.getSuccessVal() == 1)));
|
||||
}
|
||||
|
||||
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
|
||||
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
|
||||
.map(response -> (long) (response.query().pages().get(0).pageId()));
|
||||
}
|
||||
public Observable<Long> getFileEntityId(UploadResult uploadResult) {
|
||||
return wikiBaseInterface.getFileEntityId(uploadResult.createCanonicalFileName())
|
||||
.map(response -> (long) (response.query().pages().get(0).pageId()));
|
||||
}
|
||||
|
||||
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
|
||||
String languageCode, String captionValue) {
|
||||
return csrfToken()
|
||||
.switchMap(editToken -> wikiBaseInterface
|
||||
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode, captionValue));
|
||||
public Observable<MwPostResponse> addLabelstoWikidata(long fileEntityId,
|
||||
String languageCode, String captionValue) {
|
||||
return csrfToken()
|
||||
.switchMap(editToken -> wikiBaseInterface
|
||||
.addLabelstoWikidata(PAGE_ID_PREFIX + fileEntityId, editToken, languageCode,
|
||||
captionValue));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private Observable<String> csrfToken() {
|
||||
return Observable.fromCallable(() -> {
|
||||
try {
|
||||
return csrfTokenClient.getTokenBlocking();
|
||||
} catch (Throwable throwable) {
|
||||
Timber.e(throwable);
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
private Observable<String> csrfToken() {
|
||||
return Observable.fromCallable(() -> {
|
||||
try {
|
||||
return csrfTokenClient.getTokenBlocking();
|
||||
} catch (Throwable throwable) {
|
||||
Timber.e(throwable);
|
||||
return "";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,32 +15,33 @@ import org.wikipedia.wikidata.Statement_partial;
|
|||
public class WikidataClient {
|
||||
|
||||
|
||||
private final WikidataInterface wikidataInterface;
|
||||
private final Gson gson;
|
||||
private final WikidataInterface wikidataInterface;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
|
||||
this.wikidataInterface = wikidataInterface;
|
||||
this.gson = gson;
|
||||
}
|
||||
@Inject
|
||||
public WikidataClient(WikidataInterface wikidataInterface, final Gson gson) {
|
||||
this.wikidataInterface = wikidataInterface;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create wikidata claim to add P18 value
|
||||
*
|
||||
* @return revisionID of the edit
|
||||
*/
|
||||
Observable<Long> setClaim(Statement_partial claim, String tags) {
|
||||
return getCsrfToken()
|
||||
.flatMap(csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
|
||||
.map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
|
||||
}
|
||||
/**
|
||||
* Create wikidata claim to add P18 value
|
||||
*
|
||||
* @return revisionID of the edit
|
||||
*/
|
||||
Observable<Long> setClaim(Statement_partial claim, String tags) {
|
||||
return getCsrfToken()
|
||||
.flatMap(
|
||||
csrfToken -> wikidataInterface.postSetClaim(gson.toJson(claim), tags, csrfToken))
|
||||
.map(mwPostResponse -> mwPostResponse.getPageinfo().getLastrevid());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get csrf token for wikidata edit
|
||||
*/
|
||||
@NotNull
|
||||
private Observable<String> getCsrfToken() {
|
||||
return wikidataInterface.getCsrfToken()
|
||||
.map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
|
||||
}
|
||||
/**
|
||||
* Get csrf token for wikidata edit
|
||||
*/
|
||||
@NotNull
|
||||
private Observable<String> getCsrfToken() {
|
||||
return wikidataInterface.getCsrfToken()
|
||||
.map(mwQueryResponse -> mwQueryResponse.query().csrfToken());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,169 +44,172 @@ import timber.log.Timber;
|
|||
@Singleton
|
||||
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 WikidataEditListener wikidataEditListener;
|
||||
private final JsonKvStore directKvStore;
|
||||
private final WikiBaseClient wikiBaseClient;
|
||||
private final WikidataClient wikidataClient;
|
||||
private final Gson gson;
|
||||
private final Context context;
|
||||
private final WikidataEditListener wikidataEditListener;
|
||||
private final JsonKvStore directKvStore;
|
||||
private final WikiBaseClient wikiBaseClient;
|
||||
private final WikidataClient wikidataClient;
|
||||
private final Gson gson;
|
||||
|
||||
@Inject
|
||||
public WikidataEditService(final Context context,
|
||||
final WikidataEditListener wikidataEditListener,
|
||||
@Named("default_preferences") final JsonKvStore directKvStore,
|
||||
final WikiBaseClient wikiBaseClient,
|
||||
final WikidataClient wikidataClient, final Gson gson) {
|
||||
this.context = context;
|
||||
this.wikidataEditListener = wikidataEditListener;
|
||||
this.directKvStore = directKvStore;
|
||||
this.wikiBaseClient = wikiBaseClient;
|
||||
this.wikidataClient = wikidataClient;
|
||||
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()))));
|
||||
@Inject
|
||||
public WikidataEditService(final Context context,
|
||||
final WikidataEditListener wikidataEditListener,
|
||||
@Named("default_preferences") final JsonKvStore directKvStore,
|
||||
final WikiBaseClient wikiBaseClient,
|
||||
final WikidataClient wikidataClient, final Gson gson) {
|
||||
this.context = context;
|
||||
this.wikidataEditListener = wikidataEditListener;
|
||||
this.directKvStore = directKvStore;
|
||||
this.wikiBaseClient = wikiBaseClient;
|
||||
this.wikidataClient = wikidataClient;
|
||||
this.gson = gson;
|
||||
}
|
||||
|
||||
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()));
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
final EditClaim data = editClaim(
|
||||
ConfigUtils.isBetaFlavour() ? "Q10" // Wikipedia:Sandbox (Q10)
|
||||
: depictedItem.getId()
|
||||
);
|
||||
}
|
||||
|
||||
private Observable<Boolean> captionEdits(Contribution contribution, Long fileEntityId) {
|
||||
return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
|
||||
.concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
|
||||
}
|
||||
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 Observable<Boolean> depictionEdits(Contribution contribution, Long fileEntityId) {
|
||||
return Observable.fromIterable(contribution.getDepictedItems())
|
||||
.concatMap(wikidataItem -> addDepictsProperty(fileEntityId.toString(), wikidataItem));
|
||||
}
|
||||
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,
|
||||
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)
|
||||
public class ShadowActionBar {
|
||||
|
||||
private boolean showHomeAsUp;
|
||||
private boolean showHomeAsUp;
|
||||
|
||||
public boolean getShowHomeAsUp() {
|
||||
return showHomeAsUp;
|
||||
}
|
||||
public boolean getShowHomeAsUp() {
|
||||
return showHomeAsUp;
|
||||
}
|
||||
|
||||
@Implementation
|
||||
void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
|
||||
this.showHomeAsUp = showHomeAsUp;
|
||||
}
|
||||
@Implementation
|
||||
void setDisplayHomeAsUpEnabled(final boolean showHomeAsUp) {
|
||||
this.showHomeAsUp = showHomeAsUp;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,60 +12,60 @@ import org.wikipedia.login.LoginResult;
|
|||
|
||||
public class TestAppAdapter extends AppAdapter {
|
||||
|
||||
@Override
|
||||
public String getMediaWikiBaseUrl() {
|
||||
return Service.WIKIPEDIA_URL;
|
||||
}
|
||||
@Override
|
||||
public String getMediaWikiBaseUrl() {
|
||||
return Service.WIKIPEDIA_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRestbaseUriFormat() {
|
||||
return "%1$s://%2$s/api/rest_v1/";
|
||||
}
|
||||
@Override
|
||||
public String getRestbaseUriFormat() {
|
||||
return "%1$s://%2$s/api/rest_v1/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(new UnsuccessfulResponseInterceptor())
|
||||
.addInterceptor(new TestStubInterceptor())
|
||||
.build();
|
||||
}
|
||||
@Override
|
||||
public OkHttpClient getOkHttpClient(@NonNull WikiSite wikiSite) {
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(new UnsuccessfulResponseInterceptor())
|
||||
.addInterceptor(new TestStubInterceptor())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDesiredLeadImageDp() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getDesiredLeadImageDp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLoggedIn() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean isLoggedIn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAccount(@NonNull LoginResult result) {
|
||||
}
|
||||
@Override
|
||||
public void updateAccount(@NonNull LoginResult result) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedPreferenceCookieManager getCookies() {
|
||||
return null;
|
||||
}
|
||||
@Override
|
||||
public SharedPreferenceCookieManager getCookies() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
|
||||
}
|
||||
@Override
|
||||
public void setCookies(@NonNull SharedPreferenceCookieManager cookies) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean logErrorsInsteadOfCrashing() {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean logErrorsInsteadOfCrashing() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue