mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
With changes for limited connection mode (#3934)
* With changed for limited connection mode * Java docs * With minor fix * Fix cosmetic issues * Fix ANR * Add Unit test
This commit is contained in:
parent
59ee7b8df2
commit
66f6e2e648
15 changed files with 389 additions and 15 deletions
|
|
@ -36,6 +36,7 @@ import fr.free.nrw.commons.di.ApplicationlessInjection;
|
|||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.logging.FileLoggingTree;
|
||||
import fr.free.nrw.commons.logging.LogUtils;
|
||||
import fr.free.nrw.commons.media.CustomOkHttpNetworkFetcher;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.FileUtils;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
|
|
@ -77,10 +78,19 @@ import timber.log.Timber;
|
|||
)
|
||||
|
||||
public class CommonsApplication extends MultiDexApplication {
|
||||
@Inject SessionManager sessionManager;
|
||||
@Inject DBOpenHelper dbOpenHelper;
|
||||
|
||||
@Inject @Named("default_preferences") JsonKvStore defaultPrefs;
|
||||
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
|
||||
CustomOkHttpNetworkFetcher customOkHttpNetworkFetcher;
|
||||
|
||||
/**
|
||||
* Constants begin
|
||||
|
|
@ -147,6 +157,7 @@ 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();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ data class Contribution constructor(
|
|||
const val STATE_QUEUED = 2
|
||||
const val STATE_IN_PROGRESS = 3
|
||||
const val STATE_PAUSED = 4
|
||||
const val STATE_QUEUED_LIMITED_CONNECTION_MODE=5
|
||||
|
||||
/**
|
||||
* Formatting captions to the Wikibase format for sending labels
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@ public abstract class ContributionDao {
|
|||
@Query("SELECT * from contribution WHERE pageId=:pageId")
|
||||
public abstract Contribution getContribution(String pageId);
|
||||
|
||||
@Query("SELECT * from contribution WHERE state=:state")
|
||||
public abstract Single<List<Contribution>> getContribution(int state);
|
||||
|
||||
@Query("UPDATE contribution SET state=:state WHERE state in (:toUpdateStates)")
|
||||
public abstract Single<Integer> updateStates(int state, int[] toUpdateStates);
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,11 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
this.contribution = contribution;
|
||||
this.position = position;
|
||||
titleView.setText(contribution.getMedia().getMostRelevantCaption());
|
||||
|
||||
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)) {
|
||||
|
|
@ -88,6 +93,7 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -474,7 +474,7 @@ public class ContributionsFragment
|
|||
@Override
|
||||
public void retryUpload(Contribution contribution) {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED && null != uploadService) {
|
||||
if (contribution.getState() == STATE_FAILED || contribution.getState() == STATE_PAUSED || contribution.getState()==Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE && null != uploadService) {
|
||||
uploadService.queue(contribution);
|
||||
Timber.d("Restarting for %s", contribution.toString());
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package fr.free.nrw.commons.contributions;
|
|||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
|
@ -10,6 +12,7 @@ import android.view.MenuInflater;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.GravityCompat;
|
||||
|
|
@ -21,6 +24,7 @@ import androidx.viewpager.widget.ViewPager;
|
|||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||
|
|
@ -33,6 +37,7 @@ import fr.free.nrw.commons.quiz.QuizChecker;
|
|||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.List;
|
||||
|
|
@ -56,6 +61,8 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
NotificationController notificationController;
|
||||
@Inject
|
||||
QuizChecker quizChecker;
|
||||
@Inject
|
||||
ViewUtilWrapper viewUtilWrapper;
|
||||
|
||||
|
||||
public ContributionsActivityPagerAdapter contributionsActivityPagerAdapter;
|
||||
|
|
@ -70,6 +77,7 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
private TextView notificationCount;
|
||||
private NearbyParentFragment nearbyParentFragment;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_contributions);
|
||||
|
|
@ -280,9 +288,25 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
this.menu = menu;
|
||||
updateMenuItem();
|
||||
setNotificationCount();
|
||||
|
||||
updateLimitedConnectionToggle(menu);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateLimitedConnectionToggle(Menu menu) {
|
||||
MenuItem checkable = menu.findItem(R.id.toggle_limited_connection_mode);
|
||||
boolean isEnabled = defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false);
|
||||
|
||||
checkable.setChecked(isEnabled);
|
||||
final Switch switchToggleLimitedConnectionMode = checkable.getActionView()
|
||||
.findViewById(R.id.switch_toggle_limited_connection_mode);
|
||||
switchToggleLimitedConnectionMode.setChecked(isEnabled);
|
||||
switchToggleLimitedConnectionMode.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> toggleLimitedConnectionMode());
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private void setNotificationCount() {
|
||||
compositeDisposable.add(notificationController.getNotifications(false)
|
||||
|
|
@ -339,6 +363,27 @@ public class MainActivity extends NavigationBaseActivity implements FragmentMana
|
|||
}
|
||||
}
|
||||
|
||||
private void toggleLimitedConnectionMode() {
|
||||
defaultKvStore.putBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
!defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false));
|
||||
if (defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
|
||||
viewUtilWrapper
|
||||
.showShortToast(getBaseContext(), getString(R.string.limited_connection_enabled));
|
||||
} else {
|
||||
Intent intent = new Intent(this, UploadService.class);
|
||||
intent.setAction(UploadService.PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS);
|
||||
if (VERSION.SDK_INT >= VERSION_CODES.O) {
|
||||
startForegroundService(intent);
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
viewUtilWrapper
|
||||
.showShortToast(getBaseContext(), getString(R.string.limited_connection_disabled));
|
||||
}
|
||||
}
|
||||
|
||||
public class ContributionsActivityPagerAdapter extends FragmentPagerAdapter {
|
||||
FragmentManager fragmentManager;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,229 @@
|
|||
package fr.free.nrw.commons.media;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.imagepipeline.common.BytesRange;
|
||||
import com.facebook.imagepipeline.image.EncodedImage;
|
||||
import com.facebook.imagepipeline.producers.BaseNetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.BaseProducerContextCallbacks;
|
||||
import com.facebook.imagepipeline.producers.Consumer;
|
||||
import com.facebook.imagepipeline.producers.FetchState;
|
||||
import com.facebook.imagepipeline.producers.NetworkFetcher;
|
||||
import com.facebook.imagepipeline.producers.ProducerContext;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import okhttp3.CacheControl;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import timber.log.Timber;
|
||||
|
||||
// Custom implementation of Fresco's Network fetcher to skip downloading of images when limited connection mode is enabled
|
||||
// https://github.com/facebook/fresco/blob/master/imagepipeline-backends/imagepipeline-okhttp3/src/main/java/com/facebook/imagepipeline/backends/okhttp3/OkHttpNetworkFetcher.java
|
||||
@Singleton
|
||||
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;
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,6 +429,13 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment implements
|
|||
* - when the high resolution image is available, it replaces the low resolution image
|
||||
*/
|
||||
private void setupImageView() {
|
||||
|
||||
image.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
||||
image.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
||||
|
||||
imageLandscape.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder);
|
||||
imageLandscape.getHierarchy().setFailureImage(R.drawable.image_placeholder);
|
||||
|
||||
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||
.setLowResImageRequest(ImageRequest.fromUri(media.getThumbUrl()))
|
||||
.setImageRequest(ImageRequest.fromUri(media.getImageUrl()))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,21 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.repository.UploadRepository;
|
||||
import io.reactivex.Observer;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -29,13 +28,16 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
|||
UploadContract.View.class.getClassLoader(),
|
||||
new Class[]{UploadContract.View.class}, (proxy, method, methodArgs) -> null);
|
||||
private final UploadRepository repository;
|
||||
private final JsonKvStore defaultKvStore;
|
||||
private UploadContract.View view = DUMMY;
|
||||
|
||||
private CompositeDisposable compositeDisposable;
|
||||
|
||||
@Inject
|
||||
UploadPresenter(UploadRepository uploadRepository) {
|
||||
UploadPresenter(UploadRepository uploadRepository,
|
||||
@Named("default_preferences") JsonKvStore defaultKvStore) {
|
||||
this.repository = uploadRepository;
|
||||
this.defaultKvStore = defaultKvStore;
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +56,14 @@ public class UploadPresenter implements UploadContract.UserActionListener {
|
|||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
view.showProgress(false);
|
||||
view.showMessage(R.string.uploading_started);
|
||||
if (defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
false)) {
|
||||
view.showMessage(R.string.uploading_queued);
|
||||
} else {
|
||||
view.showMessage(R.string.uploading_started);
|
||||
}
|
||||
|
||||
compositeDisposable.add(d);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,13 +21,17 @@ import fr.free.nrw.commons.contributions.ContributionDao;
|
|||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.di.CommonsApplicationModule;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerService;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.media.MediaClient;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import fr.free.nrw.commons.wikidata.WikidataEditService;
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.ObservableSource;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.functions.Consumer;
|
||||
import io.reactivex.functions.Function;
|
||||
import io.reactivex.processors.PublishProcessor;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.io.IOException;
|
||||
|
|
@ -50,6 +54,7 @@ public class UploadService extends CommonsDaggerService {
|
|||
.asList("uploadstash-file-not-found", "stashfailed", "verification-error", "chunk-too-small");
|
||||
|
||||
public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload";
|
||||
public static final String PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS = EXTRA_PREFIX + "process_limited_connection_mode_uploads";
|
||||
public static final String EXTRA_FILES = EXTRA_PREFIX + ".files";
|
||||
@Inject
|
||||
WikidataEditService wikidataEditService;
|
||||
|
|
@ -67,6 +72,9 @@ public class UploadService extends CommonsDaggerService {
|
|||
@Inject
|
||||
@Named(CommonsApplicationModule.IO_THREAD)
|
||||
Scheduler ioThreadScheduler;
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
public JsonKvStore defaultKvStore;
|
||||
|
||||
private NotificationManagerCompat notificationManager;
|
||||
private NotificationCompat.Builder curNotification;
|
||||
|
|
@ -203,11 +211,21 @@ public class UploadService extends CommonsDaggerService {
|
|||
private boolean freshStart = true;
|
||||
|
||||
public void queue(Contribution contribution) {
|
||||
if (defaultKvStore
|
||||
.getBoolean(CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED, false)) {
|
||||
contribution.setState(Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE);
|
||||
contributionDao.save(contribution)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe();
|
||||
return;
|
||||
}
|
||||
contributionsToUpload.offer(contribution);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS,
|
||||
curNotification.setContentText(getText(R.string.starting_uploads)).build());
|
||||
if (ACTION_START_SERVICE.equals(intent.getAction()) && freshStart) {
|
||||
compositeDisposable.add(contributionDao.updateStates(Contribution.STATE_FAILED,
|
||||
new int[]{Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS})
|
||||
|
|
@ -215,7 +233,14 @@ public class UploadService extends CommonsDaggerService {
|
|||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
freshStart = false;
|
||||
}
|
||||
} else if (PROCESS_PENDING_LIMITED_CONNECTION_MODE_UPLOADS.equals(intent.getAction())) {
|
||||
contributionDao.getContribution(Contribution.STATE_QUEUED_LIMITED_CONNECTION_MODE)
|
||||
.flatMapObservable(
|
||||
(Function<List<Contribution>, ObservableSource<Contribution>>) contributions -> Observable.fromIterable(contributions))
|
||||
.concatMapCompletable(contribution -> Completable.fromAction(() -> queue(contribution)))
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe();
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
app/src/main/res/drawable/image_placeholder.png
Normal file
BIN
app/src/main/res/drawable/image_placeholder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.5 KiB |
5
app/src/main/res/layout/menu_switch.xml
Normal file
5
app/src/main/res/layout/menu_switch.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Switch xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/switch_toggle_limited_connection_mode"/>
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item android:id="@+id/toggle_limited_connection_mode"
|
||||
android:title="@string/limited_connection"
|
||||
app:showAsAction="always"
|
||||
android:checkable="true"
|
||||
app:actionLayout="@layout/menu_switch"
|
||||
/>
|
||||
<item android:id="@+id/notifications"
|
||||
android:title="@string/notifications"
|
||||
app:showAsAction="ifRoom|withText"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<item quantity="one">(%1$d)</item>
|
||||
<item quantity="other">(%1$d)</item>
|
||||
</plurals>
|
||||
<string name="starting_uploads"> Starting Uploads</string>
|
||||
<plurals name="starting_multiple_uploads">
|
||||
<item quantity="one">Starting %1$d upload</item>
|
||||
<item quantity="other">Starting %1$d uploads</item>
|
||||
|
|
@ -54,6 +55,7 @@
|
|||
<string name="upload_failed">File not found. Please try another file.</string>
|
||||
<string name="authentication_failed">Authentication failed, please login again</string>
|
||||
<string name="uploading_started">Upload started!</string>
|
||||
<string name="uploading_queued">Upload queued (limited connection mode enabled)</string>
|
||||
<string name="upload_completed_notification_title">%1$s uploaded!</string>
|
||||
<string name="upload_completed_notification_text">Tap to view your upload</string>
|
||||
<string name="upload_progress_notification_title_start">Starting %1$s upload</string>
|
||||
|
|
@ -692,4 +694,7 @@ Upload your first media by tapping on the add button.</string>
|
|||
<string name="mapbox_telemetry">Telemetry Opt Out</string>
|
||||
<string name="telemetry_opt_out_summary">Send anonymized location and usage data to Mapbox when using Nearby feature</string>
|
||||
<string name="map_attribution" translatable="false"><![CDATA[© <a href="https://www.mapbox.com/about/maps/">Mapbox</a> © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> <a href="https://www.mapbox.com/map-feedback/">Improve this map</a>]]></string>
|
||||
<string name="limited_connection_enabled">Limited connection mode enabled!</string>
|
||||
<string name="limited_connection_disabled">Limited connection mode disabled. Pending uploads will resume now.</string>
|
||||
<string name="limited_connection">Limited Connection</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import fr.free.nrw.commons.CommonsApplication
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import io.reactivex.Observable
|
||||
import org.junit.Before
|
||||
|
|
@ -27,6 +29,9 @@ class UploadPresenterTest {
|
|||
@Mock
|
||||
lateinit var contribution: Contribution
|
||||
|
||||
@Mock
|
||||
lateinit var defaultKvStore: JsonKvStore
|
||||
|
||||
@Mock
|
||||
private lateinit var uploadableFile: UploadableFile
|
||||
|
||||
|
|
@ -65,6 +70,22 @@ class UploadPresenterTest {
|
|||
verify(repository).buildContributions()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleSubmitTestUserLoggedInAndLimitedConnectionOn() {
|
||||
`when`(
|
||||
defaultKvStore
|
||||
.getBoolean(
|
||||
CommonsApplication.IS_LIMITED_CONNECTION_MODE_ENABLED,
|
||||
false
|
||||
)).thenReturn(true)
|
||||
`when`(view.isLoggedIn).thenReturn(true)
|
||||
uploadPresenter.handleSubmit()
|
||||
verify(view).isLoggedIn
|
||||
verify(view).showProgress(true)
|
||||
verify(repository).buildContributions()
|
||||
verify(repository).buildContributions()
|
||||
}
|
||||
|
||||
/**
|
||||
* unit test case for method UploadPresenter.handleSubmit
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue