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:
Vivek Maskara 2020-09-25 05:57:22 -07:00 committed by GitHub
parent 59ee7b8df2
commit 66f6e2e648
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 389 additions and 15 deletions

View file

@ -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 {

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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 {

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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()))

View file

@ -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);
}

View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View 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"/>

View file

@ -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"

View file

@ -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[&#169; <a href="https://www.mapbox.com/about/maps/">Mapbox</a> &#169; <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>

View file

@ -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
*/