mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-11-01 23:33:54 +01:00
added javadoc/kdoc and fixed minor bug
This commit is contained in:
parent
9a3acdc3a0
commit
71c3e81fa3
21 changed files with 610 additions and 245 deletions
|
|
@ -127,14 +127,14 @@ public class ContributionController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog alerting the user about location services being off
|
||||
* and asking them to turn it on
|
||||
* Shows a dialog alerting the user about location services being off and asking them to turn it
|
||||
* on
|
||||
* TODO: Add a seperate callback in LocationPermissionsHelper for this.
|
||||
* Ref: https://github.com/commons-app/apps-android-commons/pull/5494/files#r1510553114
|
||||
*
|
||||
* @param activity Activity reference
|
||||
* @param activity Activity reference
|
||||
* @param dialogTextResource Resource id of text to be shown in dialog
|
||||
* @param toastTextResource Resource id of text to be shown in toast
|
||||
* @param toastTextResource Resource id of text to be shown in toast
|
||||
*/
|
||||
private void showLocationOffDialog(Activity activity, int dialogTextResource,
|
||||
int toastTextResource) {
|
||||
|
|
@ -320,6 +320,10 @@ public class ContributionController {
|
|||
return shareIntent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the contributions with the state "IN_PROGRESS", "QUEUED" and "PAUSED" and then it
|
||||
* populates the `pendingContributionList`.
|
||||
**/
|
||||
void getPendingContributions() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
|
|
@ -335,6 +339,10 @@ public class ContributionController {
|
|||
pendingContributionList = livePagedListBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the contributions with the state "FAILED" and populates the
|
||||
* `failedContributionList`.
|
||||
**/
|
||||
void getFailedContributions() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
|
|
@ -349,6 +357,10 @@ public class ContributionController {
|
|||
failedContributionList = livePagedListBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the contributions with the state "IN_PROGRESS", "QUEUED", "PAUSED" and "FAILED" and
|
||||
* then it populates the `failedAndPendingContributionList`.
|
||||
**/
|
||||
void getFailedAndPendingContributions() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
|
|
|
|||
|
|
@ -48,14 +48,27 @@ public abstract class ContributionDao {
|
|||
@Delete
|
||||
public abstract void deleteSynchronous(Contribution contribution);
|
||||
|
||||
/**
|
||||
* Deletes contributions with specific states from the database.
|
||||
*
|
||||
* @param states The states of the contributions to delete.
|
||||
* @throws SQLiteException If an SQLite error occurs.
|
||||
*/
|
||||
@Query("DELETE FROM contribution WHERE state IN (:states)")
|
||||
public abstract void deleteContributionsWithStatesSynchronous(List<Integer> states) throws SQLiteException;
|
||||
public abstract void deleteContributionsWithStatesSynchronous(List<Integer> states)
|
||||
throws SQLiteException;
|
||||
|
||||
public Completable delete(final Contribution contribution) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteSynchronous(contribution));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contributions with specific states from the database.
|
||||
*
|
||||
* @param states The states of the contributions to delete.
|
||||
* @return A Completable indicating the result of the operation.
|
||||
*/
|
||||
public Completable deleteContributionsWithStates(List<Integer> states) {
|
||||
return Completable
|
||||
.fromAction(() -> deleteContributionsWithStatesSynchronous(states));
|
||||
|
|
@ -70,10 +83,22 @@ public abstract class ContributionDao {
|
|||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||
public abstract Single<List<Contribution>> getContribution(List<Integer> states);
|
||||
|
||||
/**
|
||||
* Gets contributions with specific states in descending order by the date they were uploaded.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states.
|
||||
*/
|
||||
@Query("SELECT * from contribution WHERE state IN (:states) order by media_dateUploaded DESC")
|
||||
public abstract DataSource.Factory<Integer, Contribution> getContributions(
|
||||
List<Integer> states);
|
||||
|
||||
/**
|
||||
* Gets contributions with specific states in ascending order by the date the upload started.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states.
|
||||
*/
|
||||
@Query("SELECT * from contribution WHERE state IN (:states) order by dateUploadStarted ASC")
|
||||
public abstract DataSource.Factory<Integer, Contribution> getContributionsSortedByDateUploadStarted(
|
||||
List<Integer> states);
|
||||
|
|
@ -87,6 +112,12 @@ public abstract class ContributionDao {
|
|||
@Update
|
||||
public abstract void updateSynchronous(Contribution contribution);
|
||||
|
||||
/**
|
||||
* Updates the state of contributions with specific states.
|
||||
*
|
||||
* @param states The current states of the contributions to update.
|
||||
* @param newState The new state to set.
|
||||
*/
|
||||
@Query("UPDATE contribution SET state = :newState WHERE state IN (:states)")
|
||||
public abstract void updateContributionsState(List<Integer> states, int newState);
|
||||
|
||||
|
|
@ -98,6 +129,13 @@ public abstract class ContributionDao {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the state of contributions with specific states asynchronously.
|
||||
*
|
||||
* @param states The current states of the contributions to update.
|
||||
* @param newState The new state to set.
|
||||
* @return A Completable indicating the result of the operation.
|
||||
*/
|
||||
public Completable updateContributionsWithStates(List<Integer> states, int newState) {
|
||||
return Completable
|
||||
.fromAction(() -> {
|
||||
|
|
|
|||
|
|
@ -122,7 +122,8 @@ public class ContributionsFragment
|
|||
|
||||
public FragmentContributionsBinding binding;
|
||||
|
||||
@Inject ContributionsPresenter contributionsPresenter;
|
||||
@Inject
|
||||
ContributionsPresenter contributionsPresenter;
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
|
@ -159,20 +160,22 @@ public class ContributionsFragment
|
|||
areAllGranted = areAllGranted && b;
|
||||
}
|
||||
|
||||
if (areAllGranted) {
|
||||
onLocationPermissionGranted();
|
||||
} else {
|
||||
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
&& store.getBoolean("displayLocationPermissionForCardView", true)
|
||||
&& !store.getBoolean("doNotAskForLocationPermission", false)
|
||||
&& (((MainActivity) getActivity()).activeFragment == ActiveFragment.CONTRIBUTIONS)) {
|
||||
binding.cardViewNearby.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
|
||||
if (areAllGranted) {
|
||||
onLocationPermissionGranted();
|
||||
} else {
|
||||
displayYouWontSeeNearbyMessage();
|
||||
if (shouldShowRequestPermissionRationale(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
&& store.getBoolean("displayLocationPermissionForCardView", true)
|
||||
&& !store.getBoolean("doNotAskForLocationPermission", false)
|
||||
&& (((MainActivity) getActivity()).activeFragment
|
||||
== ActiveFragment.CONTRIBUTIONS)) {
|
||||
binding.cardViewNearby.permissionType = NearbyNotificationCardView.PermissionType.ENABLE_LOCATION_PERMISSION;
|
||||
} else {
|
||||
displayYouWontSeeNearbyMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@NonNull
|
||||
public static ContributionsFragment newInstance() {
|
||||
|
|
@ -210,11 +213,10 @@ public class ContributionsFragment
|
|||
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
// Do not ask for permission on activity start again
|
||||
store.putBoolean("displayLocationPermissionForCardView",false);
|
||||
store.putBoolean("displayLocationPermissionForCardView", false);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetailPagerFragment = (MediaDetailPagerFragment) getChildFragmentManager()
|
||||
.findFragmentByTag(MEDIA_DETAIL_PAGER_FRAGMENT_TAG);
|
||||
|
|
@ -224,7 +226,7 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
initFragments();
|
||||
if(!isUserProfile) {
|
||||
if (!isUserProfile) {
|
||||
upDateUploadCount();
|
||||
}
|
||||
if (shouldShowMediaDetailsFragment) {
|
||||
|
|
@ -269,10 +271,13 @@ public class ContributionsFragment
|
|||
notificationCount = notification.findViewById(R.id.notification_count_badge);
|
||||
MenuItem uploadMenuItem = menu.findItem(R.id.upload_tab);
|
||||
final View uploadMenuItemActionView = uploadMenuItem.getActionView();
|
||||
pendingUploadsCountTextView = uploadMenuItemActionView.findViewById(R.id.pending_uploads_count_badge);
|
||||
uploadsErrorTextView = uploadMenuItemActionView.findViewById(R.id.uploads_error_count_badge);
|
||||
pendingUploadsImageView = uploadMenuItemActionView.findViewById(R.id.pending_uploads_image_view);
|
||||
if (pendingUploadsImageView != null){
|
||||
pendingUploadsCountTextView = uploadMenuItemActionView.findViewById(
|
||||
R.id.pending_uploads_count_badge);
|
||||
uploadsErrorTextView = uploadMenuItemActionView.findViewById(
|
||||
R.id.uploads_error_count_badge);
|
||||
pendingUploadsImageView = uploadMenuItemActionView.findViewById(
|
||||
R.id.pending_uploads_image_view);
|
||||
if (pendingUploadsImageView != null) {
|
||||
pendingUploadsImageView.setOnClickListener(view -> {
|
||||
startActivity(new Intent(getContext(), UploadProgressActivity.class));
|
||||
});
|
||||
|
|
@ -301,13 +306,21 @@ public class ContributionsFragment
|
|||
throwable -> Timber.e(throwable, "Error occurred while loading notifications")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the upload icon based on the number of failed and pending
|
||||
* contributions.
|
||||
*/
|
||||
public void setUploadIconVisibility() {
|
||||
contributionController.getFailedAndPendingContributions();
|
||||
contributionController.failedAndPendingContributionList.observe(getViewLifecycleOwner(), list -> {
|
||||
updateUploadIcon(list.size());
|
||||
});
|
||||
contributionController.failedAndPendingContributionList.observe(getViewLifecycleOwner(),
|
||||
list -> {
|
||||
updateUploadIcon(list.size());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the count for the upload icon based on the number of pending and failed contributions.
|
||||
*/
|
||||
public void setUploadIconCount() {
|
||||
contributionController.getPendingContributions();
|
||||
contributionController.pendingContributionList.observe(getViewLifecycleOwner(),
|
||||
|
|
@ -379,7 +392,7 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
private void setupViewForMediaDetails() {
|
||||
if (binding!=null) {
|
||||
if (binding != null) {
|
||||
binding.campaignsView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -489,7 +502,7 @@ public class ContributionsFragment
|
|||
contributionsPresenter.onAttachView(this);
|
||||
locationManager.addLocationListener(this);
|
||||
|
||||
if (binding==null) {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -508,7 +521,8 @@ public class ContributionsFragment
|
|||
} catch (Exception e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
if (binding.cardViewNearby.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
if (binding.cardViewNearby.cardViewVisibilityState
|
||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
binding.cardViewNearby.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +532,7 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
// Notification Count and Campaigns should not be set, if it is used in User Profile
|
||||
if(!isUserProfile) {
|
||||
if (!isUserProfile) {
|
||||
setNotificationCount();
|
||||
fetchCampaigns();
|
||||
setUploadIconVisibility();
|
||||
|
|
@ -529,7 +543,8 @@ public class ContributionsFragment
|
|||
}
|
||||
|
||||
private void checkPermissionsAndShowNearbyCardView() {
|
||||
if (PermissionUtils.hasPermission(getActivity(), new String[]{Manifest.permission.ACCESS_FINE_LOCATION})) {
|
||||
if (PermissionUtils.hasPermission(getActivity(),
|
||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION})) {
|
||||
onLocationPermissionGranted();
|
||||
} else if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
&& store.getBoolean("displayLocationPermissionForCardView", true)
|
||||
|
|
@ -662,14 +677,14 @@ public class ContributionsFragment
|
|||
*/
|
||||
private void fetchCampaigns() {
|
||||
if (Utils.isMonumentsEnabled(new Date())) {
|
||||
if (binding!=null) {
|
||||
if (binding != null) {
|
||||
binding.campaignsView.setCampaign(wlmCampaign);
|
||||
binding.campaignsView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else if (store.getBoolean(CampaignView.CAMPAIGNS_DEFAULT_PREFERENCE, true)) {
|
||||
presenter.getCampaigns();
|
||||
} else {
|
||||
if (binding!=null) {
|
||||
if (binding != null) {
|
||||
binding.campaignsView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -683,7 +698,7 @@ public class ContributionsFragment
|
|||
@Override
|
||||
public void showCampaigns(Campaign campaign) {
|
||||
if (campaign != null && !isUserProfile) {
|
||||
if (binding!=null) {
|
||||
if (binding != null) {
|
||||
binding.campaignsView.setCampaign(campaign);
|
||||
}
|
||||
}
|
||||
|
|
@ -712,33 +727,49 @@ public class ContributionsFragment
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility and text of the pending uploads count TextView based on the given
|
||||
* count.
|
||||
*
|
||||
* @param pendingCount The number of pending uploads.
|
||||
*/
|
||||
public void updatePendingIcon(int pendingCount) {
|
||||
if (pendingUploadsCountTextView != null){
|
||||
if (pendingCount != 0){
|
||||
if (pendingUploadsCountTextView != null) {
|
||||
if (pendingCount != 0) {
|
||||
pendingUploadsCountTextView.setVisibility(View.VISIBLE);
|
||||
pendingUploadsCountTextView.setText(String.valueOf(pendingCount));
|
||||
}else {
|
||||
} else {
|
||||
pendingUploadsCountTextView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility and text of the error uploads TextView based on the given count.
|
||||
*
|
||||
* @param errorCount The number of error uploads.
|
||||
*/
|
||||
public void updateErrorIcon(int errorCount) {
|
||||
if (uploadsErrorTextView != null){
|
||||
if (errorCount != 0){
|
||||
if (uploadsErrorTextView != null) {
|
||||
if (errorCount != 0) {
|
||||
uploadsErrorTextView.setVisibility(View.VISIBLE);
|
||||
uploadsErrorTextView.setText(String.valueOf(errorCount));
|
||||
}else {
|
||||
} else {
|
||||
uploadsErrorTextView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility of the pending uploads ImageView based on the given count.
|
||||
*
|
||||
* @param count The number of pending uploads.
|
||||
*/
|
||||
public void updateUploadIcon(int count) {
|
||||
if (pendingUploadsImageView != null){
|
||||
if (count != 0){
|
||||
if (pendingUploadsImageView != null) {
|
||||
if (count != 0) {
|
||||
pendingUploadsImageView.setVisibility(View.VISIBLE);
|
||||
}else {
|
||||
} else {
|
||||
pendingUploadsImageView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
|
@ -779,7 +810,8 @@ public class ContributionsFragment
|
|||
public boolean backButtonClicked() {
|
||||
if (mediaDetailPagerFragment != null && mediaDetailPagerFragment.isVisible()) {
|
||||
if (store.getBoolean("displayNearbyCardView", true) && !isUserProfile) {
|
||||
if (binding.cardViewNearby.cardViewVisibilityState == NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
if (binding.cardViewNearby.cardViewVisibilityState
|
||||
== NearbyNotificationCardView.CardViewVisibilityState.READY) {
|
||||
binding.cardViewNearby.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -834,7 +866,7 @@ public class ContributionsFragment
|
|||
public void restartUpload(Contribution contribution) {
|
||||
contribution.setDateUploadStarted(Calendar.getInstance().getTime());
|
||||
if (contribution.getState() == Contribution.STATE_FAILED) {
|
||||
if (contribution.getErrorInfo() == null){
|
||||
if (contribution.getErrorInfo() == null) {
|
||||
contribution.setChunkInfo(null);
|
||||
contribution.setTransferred(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,6 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
private Animation rotate_forward;
|
||||
private Animation rotate_backward;
|
||||
private boolean isFabOpen;
|
||||
public int pendingUploadsCount = 0;
|
||||
public int uploadErrorCount = 0;
|
||||
@VisibleForTesting
|
||||
protected RecyclerView rvContributionsList;
|
||||
|
||||
|
|
@ -151,7 +149,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
contributionsListPresenter.onAttachView(this);
|
||||
binding.fabCustomGallery.setOnClickListener(v -> launchCustomSelector());
|
||||
binding.fabCustomGallery.setOnLongClickListener(view -> {
|
||||
ViewUtil.showShortToast(getContext(),R.string.custom_selector_title);
|
||||
ViewUtil.showShortToast(getContext(), R.string.custom_selector_title);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
|
@ -160,7 +158,8 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
binding.fabLayout.setVisibility(VISIBLE);
|
||||
} else {
|
||||
binding.tvContributionsOfUser.setVisibility(VISIBLE);
|
||||
binding.tvContributionsOfUser.setText(getString(R.string.contributions_of_user, userName));
|
||||
binding.tvContributionsOfUser.setText(
|
||||
getString(R.string.contributions_of_user, userName));
|
||||
binding.fabLayout.setVisibility(GONE);
|
||||
}
|
||||
|
||||
|
|
@ -305,8 +304,9 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
public void onConfigurationChanged(final Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// check orientation
|
||||
binding.fabLayout.setOrientation(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
|
||||
binding.fabLayout.setOrientation(
|
||||
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ?
|
||||
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
|
||||
rvContributionsList
|
||||
.setLayoutManager(
|
||||
new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation)));
|
||||
|
|
@ -326,7 +326,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
animateFAB(isFabOpen);
|
||||
});
|
||||
binding.fabCamera.setOnLongClickListener(view -> {
|
||||
ViewUtil.showShortToast(getContext(),R.string.add_contribution_from_camera);
|
||||
ViewUtil.showShortToast(getContext(), R.string.add_contribution_from_camera);
|
||||
return true;
|
||||
});
|
||||
binding.fabGallery.setOnClickListener(view -> {
|
||||
|
|
@ -334,7 +334,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
|
|||
animateFAB(isFabOpen);
|
||||
});
|
||||
binding.fabGallery.setOnLongClickListener(view -> {
|
||||
ViewUtil.showShortToast(getContext(),R.string.menu_from_gallery);
|
||||
ViewUtil.showShortToast(getContext(), R.string.menu_from_gallery);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,12 @@ class ContributionsLocalDataSource {
|
|||
return contributionDao.delete(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contributions with specific states.
|
||||
*
|
||||
* @param states The states of the contributions to delete.
|
||||
* @return A Completable indicating the result of the operation.
|
||||
*/
|
||||
public Completable deleteContributionsWithStates(List<Integer> states) {
|
||||
return contributionDao.deleteContributionsWithStates(states);
|
||||
}
|
||||
|
|
@ -72,10 +78,23 @@ class ContributionsLocalDataSource {
|
|||
return contributionDao.fetchContributions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches contributions with specific states.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states.
|
||||
*/
|
||||
public Factory<Integer, Contribution> getContributionsWithStates(List<Integer> states) {
|
||||
return contributionDao.getContributions(states);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches contributions with specific states sorted by the date the upload started.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states sorted by
|
||||
* date upload started.
|
||||
*/
|
||||
public Factory<Integer, Contribution> getContributionsWithStatesSortedByDateUploadStarted(
|
||||
List<Integer> states) {
|
||||
return contributionDao.getContributionsSortedByDateUploadStarted(states);
|
||||
|
|
|
|||
|
|
@ -34,13 +34,13 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
@Named(CommonsApplicationModule.IO_THREAD) Scheduler ioThreadScheduler) {
|
||||
this.contributionsRepository = repository;
|
||||
this.uploadRepository = uploadRepository;
|
||||
this.ioThreadScheduler=ioThreadScheduler;
|
||||
this.ioThreadScheduler = ioThreadScheduler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachView(ContributionsContract.View view) {
|
||||
this.view = view;
|
||||
compositeDisposable=new CompositeDisposable();
|
||||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -54,7 +54,12 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
return contributionsRepository.getContributionWithFileName(title);
|
||||
}
|
||||
|
||||
public void checkDuplicateImageAndRestartContribution(Contribution contribution){
|
||||
/**
|
||||
* Checks if a contribution is a duplicate and restarts the contribution process if it is not.
|
||||
*
|
||||
* @param contribution The contribution to check and potentially restart.
|
||||
*/
|
||||
public void checkDuplicateImageAndRestartContribution(Contribution contribution) {
|
||||
compositeDisposable.add(uploadRepository
|
||||
.checkDuplicateImage(contribution.getLocalUriPath().getPath())
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
|
|
@ -62,7 +67,7 @@ public class ContributionsPresenter implements UserActionListener {
|
|||
if (imageCheckResult == IMAGE_OK) {
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
saveContribution(contribution);
|
||||
}else {
|
||||
} else {
|
||||
Timber.e("Contribution already exists");
|
||||
compositeDisposable.add(contributionsRepository
|
||||
.deleteContributionFromDB(contribution)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class ContributionsRepository {
|
|||
|
||||
/**
|
||||
* Deletes a failed upload from DB
|
||||
*
|
||||
* @param contribution
|
||||
* @return
|
||||
*/
|
||||
|
|
@ -36,12 +37,19 @@ public class ContributionsRepository {
|
|||
return localDataSource.deleteContribution(contribution);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contributions from the database with specific states.
|
||||
*
|
||||
* @param states The states of the contributions to delete.
|
||||
* @return A Completable indicating the result of the operation.
|
||||
*/
|
||||
public Completable deleteContributionsFromDBWithStates(List<Integer> states) {
|
||||
return localDataSource.deleteContributionsWithStates(states);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get contribution object with title
|
||||
*
|
||||
* @param fileName
|
||||
* @return
|
||||
*/
|
||||
|
|
@ -53,11 +61,25 @@ public class ContributionsRepository {
|
|||
return localDataSource.getContributions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches contributions with specific states.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states.
|
||||
*/
|
||||
public Factory<Integer, Contribution> fetchContributionsWithStates(List<Integer> states) {
|
||||
return localDataSource.getContributionsWithStates(states);
|
||||
}
|
||||
|
||||
public Factory<Integer, Contribution> fetchContributionsWithStatesSortedByDateUploadStarted(List<Integer> states) {
|
||||
/**
|
||||
* Fetches contributions with specific states sorted by the date the upload started.
|
||||
*
|
||||
* @param states The states of the contributions to fetch.
|
||||
* @return A DataSource factory for paginated contributions with the specified states sorted by
|
||||
* date upload started.
|
||||
*/
|
||||
public Factory<Integer, Contribution> fetchContributionsWithStatesSortedByDateUploadStarted(
|
||||
List<Integer> states) {
|
||||
return localDataSource.getContributionsWithStatesSortedByDateUploadStarted(states);
|
||||
}
|
||||
|
||||
|
|
@ -65,19 +87,26 @@ public class ContributionsRepository {
|
|||
return localDataSource.saveContributions(contributions);
|
||||
}
|
||||
|
||||
public Completable save(Contribution contributions){
|
||||
public Completable save(Contribution contributions) {
|
||||
return localDataSource.saveContributions(contributions);
|
||||
}
|
||||
|
||||
public void set(String key, long value) {
|
||||
localDataSource.set(key,value);
|
||||
localDataSource.set(key, value);
|
||||
}
|
||||
|
||||
public Completable updateContribution(Contribution contribution) {
|
||||
return localDataSource.updateContribution(contribution);
|
||||
}
|
||||
|
||||
public Completable updateContributionWithStates(List<Integer> states, int newState) {
|
||||
/**
|
||||
* Updates the state of contributions with specific states.
|
||||
*
|
||||
* @param states The current states of the contributions to update.
|
||||
* @param newState The new state to set.
|
||||
* @return A Completable indicating the result of the operation.
|
||||
*/
|
||||
public Completable updateContributionsWithStates(List<Integer> states, int newState) {
|
||||
return localDataSource.updateContributionsWithStates(states, newState);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ import javax.inject.Inject;
|
|||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MainActivity extends BaseActivity
|
||||
public class MainActivity extends BaseActivity
|
||||
implements FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
@Inject
|
||||
|
|
@ -147,16 +147,16 @@ public class MainActivity extends BaseActivity
|
|||
applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false);
|
||||
applicationKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", false);
|
||||
}
|
||||
if(savedInstanceState == null){
|
||||
if (savedInstanceState == null) {
|
||||
//starting a fresh fragment.
|
||||
// Open Last opened screen if it is Contributions or Nearby, otherwise Contributions
|
||||
if(applicationKvStore.getBoolean("last_opened_nearby")){
|
||||
if (applicationKvStore.getBoolean("last_opened_nearby")) {
|
||||
setTitle(getString(R.string.nearby_fragment));
|
||||
showNearby();
|
||||
loadFragment(NearbyParentFragment.newInstance(),false);
|
||||
}else{
|
||||
loadFragment(NearbyParentFragment.newInstance(), false);
|
||||
} else {
|
||||
setTitle(getString(R.string.contributions_fragment));
|
||||
loadFragment(ContributionsFragment.newInstance(),false);
|
||||
loadFragment(ContributionsFragment.newInstance(), false);
|
||||
}
|
||||
}
|
||||
setUpPager();
|
||||
|
|
@ -168,7 +168,8 @@ public class MainActivity extends BaseActivity
|
|||
if (VERSION.SDK_INT >= VERSION_CODES.Q) {
|
||||
PermissionUtils.checkPermissionsAndPerformAction(
|
||||
this,
|
||||
() -> {},
|
||||
() -> {
|
||||
},
|
||||
R.string.media_location_permission_denied,
|
||||
R.string.add_location_manually,
|
||||
permission.ACCESS_MEDIA_LOCATION);
|
||||
|
|
@ -182,32 +183,33 @@ public class MainActivity extends BaseActivity
|
|||
}
|
||||
|
||||
private void setUpPager() {
|
||||
binding.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener(navListener = (item) -> {
|
||||
if (!item.getTitle().equals(getString(R.string.more))) {
|
||||
// do not change title for more fragment
|
||||
setTitle(item.getTitle());
|
||||
}
|
||||
// set last_opened_nearby true if item is nearby screen else set false
|
||||
applicationKvStore.putBoolean("last_opened_nearby",
|
||||
item.getTitle().equals(getString(R.string.nearby_fragment)));
|
||||
final Fragment fragment = NavTab.of(item.getOrder()).newInstance();
|
||||
return loadFragment(fragment, true);
|
||||
});
|
||||
binding.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener(
|
||||
navListener = (item) -> {
|
||||
if (!item.getTitle().equals(getString(R.string.more))) {
|
||||
// do not change title for more fragment
|
||||
setTitle(item.getTitle());
|
||||
}
|
||||
// set last_opened_nearby true if item is nearby screen else set false
|
||||
applicationKvStore.putBoolean("last_opened_nearby",
|
||||
item.getTitle().equals(getString(R.string.nearby_fragment)));
|
||||
final Fragment fragment = NavTab.of(item.getOrder()).newInstance();
|
||||
return loadFragment(fragment, true);
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpLoggedOutPager() {
|
||||
loadFragment(ExploreFragment.newInstance(),false);
|
||||
loadFragment(ExploreFragment.newInstance(), false);
|
||||
binding.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener(item -> {
|
||||
if (!item.getTitle().equals(getString(R.string.more))) {
|
||||
// do not change title for more fragment
|
||||
setTitle(item.getTitle());
|
||||
}
|
||||
Fragment fragment = NavTabLoggedOut.of(item.getOrder()).newInstance();
|
||||
return loadFragment(fragment,true);
|
||||
return loadFragment(fragment, true);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean loadFragment(Fragment fragment,boolean showBottom ) {
|
||||
private boolean loadFragment(Fragment fragment, boolean showBottom) {
|
||||
//showBottom so that we do not show the bottom tray again when constructing
|
||||
//from the saved instance state.
|
||||
if (fragment instanceof ContributionsFragment) {
|
||||
|
|
@ -237,7 +239,8 @@ public class MainActivity extends BaseActivity
|
|||
bookmarkFragment = (BookmarkFragment) fragment;
|
||||
activeFragment = ActiveFragment.BOOKMARK;
|
||||
} else if (fragment == null && showBottom) {
|
||||
if (applicationKvStore.getBoolean("login_skipped") == true) { // If logged out, more sheet is different
|
||||
if (applicationKvStore.getBoolean("login_skipped")
|
||||
== true) { // If logged out, more sheet is different
|
||||
MoreBottomSheetLoggedOutFragment bottomSheet = new MoreBottomSheetLoggedOutFragment();
|
||||
bottomSheet.show(getSupportFragmentManager(),
|
||||
"MoreBottomSheetLoggedOut");
|
||||
|
|
@ -267,28 +270,30 @@ public class MainActivity extends BaseActivity
|
|||
}
|
||||
|
||||
/**
|
||||
* Adds number of uploads next to tab text "Contributions" then it will look like
|
||||
* "Contributions (NUMBER)"
|
||||
* Adds number of uploads next to tab text "Contributions" then it will look like "Contributions
|
||||
* (NUMBER)"
|
||||
*
|
||||
* @param uploadCount
|
||||
*/
|
||||
public void setNumOfUploads(int uploadCount) {
|
||||
if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
|
||||
setTitle(getResources().getString(R.string.contributions_fragment) +" "+ (
|
||||
setTitle(getResources().getString(R.string.contributions_fragment) + " " + (
|
||||
!(uploadCount == 0) ?
|
||||
getResources()
|
||||
.getQuantityString(R.plurals.contributions_subtitle,
|
||||
uploadCount, uploadCount):getString(R.string.contributions_subtitle_zero)));
|
||||
getResources()
|
||||
.getQuantityString(R.plurals.contributions_subtitle,
|
||||
uploadCount, uploadCount)
|
||||
: getString(R.string.contributions_subtitle_zero)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume the uploads that got stuck because of the app being killed
|
||||
* or the device being rebooted.
|
||||
*
|
||||
* Resume the uploads that got stuck because of the app being killed or the device being
|
||||
* rebooted.
|
||||
* <p>
|
||||
* When the app is terminated or the device is restarted, contributions remain in the
|
||||
* 'STATE_IN_PROGRESS' state. This status persists and doesn't change during these events.
|
||||
* So, retrieving contributions labeled as 'STATE_IN_PROGRESS'
|
||||
* from the database will provide the list of uploads that appear as stuck on opening the app again
|
||||
* 'STATE_IN_PROGRESS' state. This status persists and doesn't change during these events. So,
|
||||
* retrieving contributions labeled as 'STATE_IN_PROGRESS' from the database will provide the
|
||||
* list of uploads that appear as stuck on opening the app again
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private void checkAndResumeStuckUploads() {
|
||||
|
|
@ -297,8 +302,8 @@ public class MainActivity extends BaseActivity
|
|||
.subscribeOn(Schedulers.io())
|
||||
.blockingGet();
|
||||
Timber.d("Resuming " + stuckUploads.size() + " uploads...");
|
||||
if(!stuckUploads.isEmpty()) {
|
||||
for(Contribution contribution: stuckUploads) {
|
||||
if (!stuckUploads.isEmpty()) {
|
||||
for (Contribution contribution : stuckUploads) {
|
||||
contribution.setState(Contribution.STATE_QUEUED);
|
||||
contribution.setDateUploadStarted(Calendar.getInstance().getTime());
|
||||
Completable.fromAction(() -> contributionDao.saveSynchronous(contribution))
|
||||
|
|
@ -327,24 +332,24 @@ public class MainActivity extends BaseActivity
|
|||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
String activeFragmentName = savedInstanceState.getString("activeFragment");
|
||||
if(activeFragmentName != null) {
|
||||
if (activeFragmentName != null) {
|
||||
restoreActiveFragment(activeFragmentName);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreActiveFragment(@NonNull String fragmentName) {
|
||||
if(fragmentName.equals(ActiveFragment.CONTRIBUTIONS.name())) {
|
||||
if (fragmentName.equals(ActiveFragment.CONTRIBUTIONS.name())) {
|
||||
setTitle(getString(R.string.contributions_fragment));
|
||||
loadFragment(ContributionsFragment.newInstance(),false);
|
||||
}else if(fragmentName.equals(ActiveFragment.NEARBY.name())) {
|
||||
loadFragment(ContributionsFragment.newInstance(), false);
|
||||
} else if (fragmentName.equals(ActiveFragment.NEARBY.name())) {
|
||||
setTitle(getString(R.string.nearby_fragment));
|
||||
loadFragment(NearbyParentFragment.newInstance(),false);
|
||||
}else if(fragmentName.equals(ActiveFragment.EXPLORE.name())) {
|
||||
loadFragment(NearbyParentFragment.newInstance(), false);
|
||||
} else if (fragmentName.equals(ActiveFragment.EXPLORE.name())) {
|
||||
setTitle(getString(R.string.navigation_item_explore));
|
||||
loadFragment(ExploreFragment.newInstance(),false);
|
||||
}else if(fragmentName.equals(ActiveFragment.BOOKMARK.name())) {
|
||||
loadFragment(ExploreFragment.newInstance(), false);
|
||||
} else if (fragmentName.equals(ActiveFragment.BOOKMARK.name())) {
|
||||
setTitle(getString(R.string.bookmarks));
|
||||
loadFragment(BookmarkFragment.newInstance(),false);
|
||||
loadFragment(BookmarkFragment.newInstance(), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -360,8 +365,9 @@ public class MainActivity extends BaseActivity
|
|||
// Means that nearby fragment is visible
|
||||
/* If function nearbyParentFragment.backButtonClick() returns false, it means that the bottomsheet is
|
||||
not expanded. So if the back button is pressed, then go back to the Contributions tab */
|
||||
if(!nearbyParentFragment.backButtonClicked()){
|
||||
getSupportFragmentManager().beginTransaction().remove(nearbyParentFragment).commit();
|
||||
if (!nearbyParentFragment.backButtonClicked()) {
|
||||
getSupportFragmentManager().beginTransaction().remove(nearbyParentFragment)
|
||||
.commit();
|
||||
setSelectedItemId(NavTab.CONTRIBUTIONS.code());
|
||||
}
|
||||
} else if (exploreFragment != null && activeFragment == ActiveFragment.EXPLORE) {
|
||||
|
|
@ -395,12 +401,16 @@ public class MainActivity extends BaseActivity
|
|||
getContribution(Collections.singletonList(Contribution.STATE_FAILED))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(failedUploads -> {
|
||||
for (Contribution contribution: failedUploads) {
|
||||
for (Contribution contribution : failedUploads) {
|
||||
contributionsFragment.retryUpload(contribution);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item selection in the options menu. This method is called when a user interacts with
|
||||
* the options menu in the Top Bar.
|
||||
*/
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
|
|
@ -418,17 +428,18 @@ public class MainActivity extends BaseActivity
|
|||
|
||||
public void centerMapToPlace(Place place) {
|
||||
setSelectedItemId(NavTab.NEARBY.code());
|
||||
nearbyParentFragment.setNearbyParentFragmentInstanceReadyCallback(new NearbyParentFragmentInstanceReadyCallback() {
|
||||
@Override
|
||||
public void onReady() {
|
||||
nearbyParentFragment.centerMapToPlace(place);
|
||||
}
|
||||
});
|
||||
nearbyParentFragment.setNearbyParentFragmentInstanceReadyCallback(
|
||||
new NearbyParentFragmentInstanceReadyCallback() {
|
||||
@Override
|
||||
public void onReady() {
|
||||
nearbyParentFragment.centerMapToPlace(place);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
Timber.d(data!=null?data.toString():"onActivityResult data is null");
|
||||
Timber.d(data != null ? data.toString() : "onActivityResult data is null");
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
controller.handleActivityResult(this, requestCode, resultCode, data);
|
||||
}
|
||||
|
|
@ -473,14 +484,15 @@ public class MainActivity extends BaseActivity
|
|||
/**
|
||||
* Load default language in onCreate from SharedPreferences
|
||||
*/
|
||||
private void loadLocale(){
|
||||
final SharedPreferences preferences = getSharedPreferences("Settings", Activity.MODE_PRIVATE);
|
||||
private void loadLocale() {
|
||||
final SharedPreferences preferences = getSharedPreferences("Settings",
|
||||
Activity.MODE_PRIVATE);
|
||||
final String language = preferences.getString("language", "");
|
||||
final SettingsFragment settingsFragment = new SettingsFragment();
|
||||
settingsFragment.setLocale(this, language);
|
||||
}
|
||||
|
||||
public NavTabLayout.OnNavigationItemSelectedListener getNavListener(){
|
||||
public NavTabLayout.OnNavigationItemSelectedListener getNavListener() {
|
||||
return navListener;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,19 @@ class FailedUploadsAdapter(callback: Callback) :
|
|||
PagedListAdapter<Contribution, FailedUploadsAdapter.ViewHolder>(ContributionDiffCallback()) {
|
||||
private var callback: Callback = callback
|
||||
|
||||
/**
|
||||
* Creates a new ViewHolder instance. Inflates the layout for each item in the list.
|
||||
*/
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view: View =
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.item_failed_upload, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds data to the provided ViewHolder. Sets up the item view with data from the
|
||||
* contribution at the specified position.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item: Contribution? = getItem(position)
|
||||
if (item != null) {
|
||||
|
|
@ -69,6 +76,9 @@ class FailedUploadsAdapter(callback: Callback) :
|
|||
holder.itemImage.setImageRequest(imageRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewHolder for the failed upload item. Holds references to the views for each item.
|
||||
*/
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var itemImage: com.facebook.drawee.view.SimpleDraweeView =
|
||||
itemView.findViewById(R.id.itemImage)
|
||||
|
|
@ -79,10 +89,18 @@ class FailedUploadsAdapter(callback: Callback) :
|
|||
var retryButton: ImageView = itemView.findViewById(R.id.retryButton)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID of the item at the specified position. Uses the pageId of the contribution
|
||||
* for unique identification.
|
||||
*/
|
||||
override fun getItemId(position: Int): Long {
|
||||
return getItem(position)?.pageId?.hashCode()?.toLong() ?: position.toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses DiffUtil to calculate the changes in the list
|
||||
* It has methods that check pageId and the content of the items to determine if its a new item
|
||||
*/
|
||||
class ContributionDiffCallback : DiffUtil.ItemCallback<Contribution>() {
|
||||
override fun areItemsTheSame(oldItem: Contribution, newItem: Contribution): Boolean {
|
||||
return oldItem.pageId.hashCode() == newItem.pageId.hashCode()
|
||||
|
|
@ -93,8 +111,22 @@ class FailedUploadsAdapter(callback: Callback) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface for handling actions related to failed uploads.
|
||||
*/
|
||||
interface Callback {
|
||||
/**
|
||||
* Deletes the failed upload item.
|
||||
*
|
||||
* @param contribution to be deleted.
|
||||
*/
|
||||
fun deleteUpload(contribution: Contribution?)
|
||||
|
||||
/**
|
||||
* Restarts the upload for the item at the specified index.
|
||||
*
|
||||
* @param index The position of the item in the list.
|
||||
*/
|
||||
fun restartUpload(index: Int)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,11 @@ import java.util.Locale
|
|||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [FailedUploadsFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
* Fragment for displaying a list of failed uploads in Upload Progress Activity. This fragment provides
|
||||
* functionality for the user to retry or cancel failed uploads.
|
||||
*/
|
||||
class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsContract.View,
|
||||
FailedUploadsAdapter.Callback {
|
||||
private var param1: String? = null
|
||||
private var param2: String? = null
|
||||
private val ARG_PARAM1 = "param1"
|
||||
private val ARG_PARAM2 = "param2"
|
||||
|
||||
@Inject
|
||||
lateinit var pendingUploadsPresenter: PendingUploadsPresenter
|
||||
|
|
@ -62,11 +57,6 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
param1 = it.getString(ARG_PARAM1)
|
||||
param2 = it.getString(ARG_PARAM2)
|
||||
}
|
||||
|
||||
//Now that we are allowing this fragment to be started for
|
||||
// any userName- we expect it to be passed as an argument
|
||||
if (arguments != null) {
|
||||
|
|
@ -97,6 +87,9 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
initRecyclerView()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the recycler view.
|
||||
*/
|
||||
fun initRecyclerView() {
|
||||
binding.failedUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
|
||||
binding.failedUploadsRecyclerView.adapter = adapter
|
||||
|
|
@ -124,17 +117,9 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(param1: String, param2: String) =
|
||||
FailedUploadsFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(ARG_PARAM1, param1)
|
||||
putString(ARG_PARAM2, param2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts all the failed uploads.
|
||||
*/
|
||||
fun restartUploads() {
|
||||
if (contributionsList != null) {
|
||||
pendingUploadsPresenter.restartUploads(
|
||||
|
|
@ -145,6 +130,9 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts a specific upload.
|
||||
*/
|
||||
override fun restartUpload(index: Int) {
|
||||
if (contributionsList != null) {
|
||||
pendingUploadsPresenter.restartUpload(
|
||||
|
|
@ -155,19 +143,22 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a specific upload after getting a confirmation from the user using Dialog.
|
||||
*/
|
||||
override fun deleteUpload(contribution: Contribution?) {
|
||||
DialogUtil.showAlertDialog(
|
||||
requireActivity(),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancelling_upload)
|
||||
requireActivity().getString(R.string.cancelling_upload)
|
||||
),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancel_upload_dialog)
|
||||
requireActivity().getString(R.string.cancel_upload_dialog)
|
||||
),
|
||||
String.format(Locale.getDefault(), getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), getString(R.string.no)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
|
||||
{
|
||||
ViewUtil.showShortToast(context, R.string.cancelling_upload)
|
||||
pendingUploadsPresenter.deleteUpload(
|
||||
|
|
@ -179,20 +170,23 @@ class FailedUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCont
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the uploads after getting a confirmation from the user using Dialog.
|
||||
*/
|
||||
fun deleteUploads() {
|
||||
if (contributionsList != null) {
|
||||
DialogUtil.showAlertDialog(
|
||||
requireActivity(),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancelling_all_the_uploads)
|
||||
requireActivity().getString(R.string.cancelling_all_the_uploads)
|
||||
),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)
|
||||
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)
|
||||
),
|
||||
String.format(Locale.getDefault(), getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), getString(R.string.no)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
|
||||
{
|
||||
ViewUtil.showShortToast(context, R.string.cancelling_upload)
|
||||
uploadProgressActivity.hidePendingIcons()
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
|
||||
data class PendingUploadItem(var title: String, var image: String, var queued : Boolean ,var error:String)
|
||||
|
|
@ -22,12 +22,19 @@ import java.io.File
|
|||
class PendingUploadsAdapter(private val callback: Callback) :
|
||||
PagedListAdapter<Contribution, PendingUploadsAdapter.ViewHolder>(ContributionDiffCallback()) {
|
||||
|
||||
/**
|
||||
* Creates a new ViewHolder instance. Inflates the layout for each item in the list.
|
||||
*/
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
val view: View = LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.item_pending_upload, parent, false)
|
||||
return ViewHolder(view)
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds data to the provided ViewHolder. Sets up the item view with data from the
|
||||
* contribution at the specified position utilizing payloads.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.isNotEmpty()) {
|
||||
when (val latestPayload = payloads.lastOrNull()) {
|
||||
|
|
@ -36,6 +43,7 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
latestPayload.total,
|
||||
getItem(position)!!.state
|
||||
)
|
||||
|
||||
is ContributionChangePayload.State -> holder.bindState(latestPayload.state)
|
||||
else -> onBindViewHolder(holder, position)
|
||||
}
|
||||
|
|
@ -44,6 +52,10 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds data to the provided ViewHolder. Sets up the item view with data from the
|
||||
* contribution at the specified position.
|
||||
*/
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val contribution = getItem(position)
|
||||
contribution?.let {
|
||||
|
|
@ -54,6 +66,9 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ViewHolder class for holding and binding item views.
|
||||
*/
|
||||
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var itemImage: com.facebook.drawee.view.SimpleDraweeView =
|
||||
itemView.findViewById(R.id.itemImage)
|
||||
|
|
@ -102,11 +117,11 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
errorTextView.visibility = View.VISIBLE
|
||||
itemProgress.visibility = View.GONE
|
||||
} else {
|
||||
if (state == Contribution.STATE_QUEUED || state == Contribution.STATE_PAUSED){
|
||||
if (state == Contribution.STATE_QUEUED || state == Contribution.STATE_PAUSED) {
|
||||
errorTextView.text = "Queued"
|
||||
errorTextView.visibility = View.VISIBLE
|
||||
itemProgress.visibility = View.GONE
|
||||
} else{
|
||||
} else {
|
||||
errorTextView.visibility = View.GONE
|
||||
itemProgress.visibility = View.VISIBLE
|
||||
if (transferred >= total) {
|
||||
|
|
@ -121,19 +136,49 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback interface for handling actions related to failed uploads.
|
||||
*/
|
||||
interface Callback {
|
||||
/**
|
||||
* Deletes the failed upload item.
|
||||
*
|
||||
* @param contribution to be deleted.
|
||||
*/
|
||||
fun deleteUpload(contribution: Contribution?)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses DiffUtil and payloads to calculate the changes in the list
|
||||
* It has methods that check pageId and the content of the items to determine if its a new item
|
||||
*/
|
||||
class ContributionDiffCallback : DiffUtil.ItemCallback<Contribution>() {
|
||||
/**
|
||||
* Checks if two items represent the same contribution.
|
||||
* @param oldItem The old contribution item.
|
||||
* @param newItem The new contribution item.
|
||||
* @return True if the items are the same, false otherwise.
|
||||
*/
|
||||
override fun areItemsTheSame(oldItem: Contribution, newItem: Contribution): Boolean {
|
||||
return oldItem.pageId.hashCode() == newItem.pageId.hashCode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the content of two items is the same.
|
||||
* @param oldItem The old contribution item.
|
||||
* @param newItem The new contribution item.
|
||||
* @return True if the contents are the same, false otherwise.
|
||||
*/
|
||||
override fun areContentsTheSame(oldItem: Contribution, newItem: Contribution): Boolean {
|
||||
return oldItem.transferred == newItem.transferred
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a payload representing the change between the old and new items.
|
||||
* @param oldItem The old contribution item.
|
||||
* @param newItem The new contribution item.
|
||||
* @return An object representing the change, or null if there are no changes.
|
||||
*/
|
||||
override fun getChangePayload(oldItem: Contribution, newItem: Contribution): Any? {
|
||||
return when {
|
||||
oldItem.transferred != newItem.transferred -> {
|
||||
|
|
@ -149,12 +194,30 @@ class PendingUploadsAdapter(private val callback: Callback) :
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique item ID for the contribution at the specified position.
|
||||
* @param position The position of the item.
|
||||
* @return The unique item ID.
|
||||
*/
|
||||
override fun getItemId(position: Int): Long {
|
||||
return getItem(position)?.pageId?.hashCode()?.toLong() ?: position.toLong()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sealed interface representing different types of changes to a contribution.
|
||||
*/
|
||||
private sealed interface ContributionChangePayload {
|
||||
/**
|
||||
* Represents a change in the progress of a contribution.
|
||||
* @param transferred The amount of data transferred.
|
||||
* @param total The total amount of data.
|
||||
*/
|
||||
data class Progress(val transferred: Long, val total: Long) : ContributionChangePayload
|
||||
|
||||
/**
|
||||
* Represents a change in the state of a contribution.
|
||||
* @param state The state of the contribution.
|
||||
*/
|
||||
data class State(val state: Int) : ContributionChangePayload
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,26 @@ import fr.free.nrw.commons.contributions.Contribution;
|
|||
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
|
||||
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract.View;
|
||||
|
||||
/**
|
||||
* The contract using which the PendingUploadsFragment or FailedUploadsFragment would communicate
|
||||
* with its PendingUploadsPresenter
|
||||
*/
|
||||
public class PendingUploadsContract {
|
||||
|
||||
/**
|
||||
* Interface representing the view for uploads.
|
||||
*/
|
||||
public interface View { }
|
||||
|
||||
/**
|
||||
* Interface representing the user actions related to uploads.
|
||||
*/
|
||||
public interface UserActionListener extends
|
||||
BasePresenter<fr.free.nrw.commons.upload.PendingUploadsContract.View> {
|
||||
|
||||
/**
|
||||
* Deletes a upload.
|
||||
*/
|
||||
void deleteUpload(Contribution contribution, Context context);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,18 +27,12 @@ import timber.log.Timber
|
|||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* A simple [Fragment] subclass.
|
||||
* Use the [PendingUploadsFragment.newInstance] factory method to
|
||||
* create an instance of this fragment.
|
||||
* Fragment for showing pending uploads in Upload Progress Activity. This fragment provides
|
||||
* functionality for the user to pause uploads.
|
||||
*/
|
||||
class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsContract.View,
|
||||
PendingUploadsAdapter.Callback {
|
||||
private var param1: String? = null
|
||||
private var param2: String? = null
|
||||
private val ARG_PARAM1 = "param1"
|
||||
private val ARG_PARAM2 = "param2"
|
||||
|
||||
@Inject
|
||||
lateinit var pendingUploadsPresenter: PendingUploadsPresenter
|
||||
|
|
@ -52,14 +46,6 @@ class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCon
|
|||
private var contributionsSize = 0
|
||||
var contributionsList = ArrayList<Contribution>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let {
|
||||
param1 = it.getString(ARG_PARAM1)
|
||||
param2 = it.getString(ARG_PARAM2)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
if (context is UploadProgressActivity) {
|
||||
|
|
@ -87,6 +73,9 @@ class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCon
|
|||
initRecyclerView()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the recycler view.
|
||||
*/
|
||||
fun initRecyclerView() {
|
||||
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
|
||||
binding.pendingUploadsRecyclerView.adapter = adapter
|
||||
|
|
@ -130,19 +119,22 @@ class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCon
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a specific upload after getting a confirmation from the user using Dialog.
|
||||
*/
|
||||
override fun deleteUpload(contribution: Contribution?) {
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancelling_upload)
|
||||
requireActivity().getString(R.string.cancelling_upload)
|
||||
),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancel_upload_dialog)
|
||||
requireActivity().getString(R.string.cancel_upload_dialog)
|
||||
),
|
||||
String.format(Locale.getDefault(), getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), getString(R.string.no)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
|
||||
{
|
||||
ViewUtil.showShortToast(context, R.string.cancelling_upload)
|
||||
pendingUploadsPresenter.deleteUpload(
|
||||
|
|
@ -154,17 +146,9 @@ class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCon
|
|||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(param1: String, param2: String) =
|
||||
PendingUploadsFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putString(ARG_PARAM1, param1)
|
||||
putString(ARG_PARAM2, param2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts all the paused uploads.
|
||||
*/
|
||||
fun restartUploads() {
|
||||
if (contributionsList != null) {
|
||||
pendingUploadsPresenter.restartUploads(
|
||||
|
|
@ -175,27 +159,29 @@ class PendingUploadsFragment : CommonsDaggerSupportFragment(), PendingUploadsCon
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses all the ongoing uploads.
|
||||
*/
|
||||
fun pauseUploads() {
|
||||
pendingUploadsPresenter.pauseUploads(
|
||||
listOf(Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS),
|
||||
Contribution.STATE_PAUSED
|
||||
)
|
||||
|
||||
pendingUploadsPresenter.pauseUploads()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all the uploads after getting a confirmation from the user using Dialog.
|
||||
*/
|
||||
fun deleteUploads() {
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.cancelling_all_the_uploads)
|
||||
requireActivity().getString(R.string.cancelling_all_the_uploads)
|
||||
),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)
|
||||
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads)
|
||||
),
|
||||
String.format(Locale.getDefault(), getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), getString(R.string.no)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
|
||||
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
|
||||
{
|
||||
ViewUtil.showShortToast(context, R.string.cancelling_upload)
|
||||
uploadProgressActivity.hidePendingIcons()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import fr.free.nrw.commons.upload.PendingUploadsContract.View;
|
|||
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
|
||||
import io.reactivex.Scheduler;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
|
|
@ -31,7 +32,7 @@ import javax.inject.Named;
|
|||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* The presenter class for Contributions
|
||||
* The presenter class for PendingUploadsFragment and FailedUploadsFragment
|
||||
*/
|
||||
public class PendingUploadsPresenter implements UserActionListener {
|
||||
|
||||
|
|
@ -61,11 +62,10 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Setups the paged list of Pending Uploads. 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 =
|
||||
|
|
@ -82,6 +82,11 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
totalContributionList = livePagedListBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups the paged list of Failed Uploads. 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 getFailedContributions() {
|
||||
final PagedList.Config pagedListConfig =
|
||||
(new PagedList.Config.Builder())
|
||||
|
|
@ -107,6 +112,12 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
contributionBoundaryCallback.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the specified upload (contribution) from the database.
|
||||
*
|
||||
* @param contribution The contribution object representing the upload to be deleted.
|
||||
* @param context The context in which the operation is being performed.
|
||||
*/
|
||||
@Override
|
||||
public void deleteUpload(final Contribution contribution, Context context) {
|
||||
compositeDisposable.add(contributionsRepository
|
||||
|
|
@ -115,14 +126,25 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
.subscribe());
|
||||
}
|
||||
|
||||
public void pauseUploads(List<Integer> states, int newState) {
|
||||
CommonsApplication.isPaused = true ;
|
||||
/**
|
||||
* Pauses all the uploads by changing the state of contributions from STATE_QUEUED and
|
||||
* STATE_IN_PROGRESS to STATE_PAUSED in the database.
|
||||
*/
|
||||
public void pauseUploads() {
|
||||
CommonsApplication.isPaused = true;
|
||||
compositeDisposable.add(contributionsRepository
|
||||
.updateContributionWithStates(states, newState)
|
||||
.updateContributionsWithStates(
|
||||
List.of(Contribution.STATE_QUEUED, Contribution.STATE_IN_PROGRESS),
|
||||
Contribution.STATE_PAUSED)
|
||||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes contributions from the database that match the specified states.
|
||||
*
|
||||
* @param states A list of integers representing the states of the contributions to be deleted.
|
||||
*/
|
||||
public void deleteUploads(List<Integer> states) {
|
||||
compositeDisposable.add(contributionsRepository
|
||||
.deleteContributionsFromDBWithStates(states)
|
||||
|
|
@ -130,6 +152,13 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
.subscribe());
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the uploads for the specified list of contributions starting from the given index.
|
||||
*
|
||||
* @param contributionList The list of contributions to be restarted.
|
||||
* @param index The starting index in the list from which to restart uploads.
|
||||
* @param context The context in which the operation is being performed.
|
||||
*/
|
||||
public void restartUploads(List<Contribution> contributionList, int index, Context context) {
|
||||
CommonsApplication.isPaused = false;
|
||||
if (index >= contributionList.size()) {
|
||||
|
|
@ -182,6 +211,13 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restarts the upload for the specified list of contributions for the given index.
|
||||
*
|
||||
* @param contributionList The list of contributions.
|
||||
* @param index The index in the list which to be restarted.
|
||||
* @param context The context in which the operation is being performed.
|
||||
*/
|
||||
public void restartUpload(List<Contribution> contributionList, int index, Context context) {
|
||||
CommonsApplication.isPaused = false;
|
||||
if (index >= contributionList.size()) {
|
||||
|
|
@ -190,7 +226,7 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
Contribution it = contributionList.get(index);
|
||||
if (it.getState() == Contribution.STATE_FAILED) {
|
||||
it.setDateUploadStarted(Calendar.getInstance().getTime());
|
||||
if (it.getErrorInfo() == null){
|
||||
if (it.getErrorInfo() == null) {
|
||||
it.setChunkInfo(null);
|
||||
it.setTransferred(0);
|
||||
}
|
||||
|
|
@ -205,7 +241,7 @@ public class PendingUploadsPresenter implements UserActionListener {
|
|||
.subscribeOn(ioThreadScheduler)
|
||||
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest(
|
||||
context, ExistingWorkPolicy.KEEP)));
|
||||
}else {
|
||||
} else {
|
||||
Timber.e("Contribution already exists");
|
||||
compositeDisposable.add(contributionsRepository
|
||||
.deleteContributionFromDB(it)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,12 @@ import io.reactivex.schedulers.Schedulers
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* Activity to manage the progress of uploads. It includes tabs to show pending and failed uploads,
|
||||
* and provides menu options to pause, resume, cancel, and retry uploads. Also, it contains ViewPager
|
||||
* which holds Pending Uploads Fragment and Failed Uploads Fragment to show list of pending and
|
||||
* failed uploads respectively.
|
||||
*/
|
||||
class UploadProgressActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityUploadProgressBinding
|
||||
|
|
@ -70,6 +75,11 @@ class UploadProgressActivity : BaseActivity() {
|
|||
setTabs()
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and sets up the tabs data by creating instances of `PendingUploadsFragment`
|
||||
* and `FailedUploadsFragment`, adds them to the `fragmentList`, and assigns corresponding
|
||||
* titles from resources to the `titleList`.
|
||||
*/
|
||||
fun setTabs() {
|
||||
pendingUploadsFragment = PendingUploadsFragment()
|
||||
failedUploadsFragment = FailedUploadsFragment()
|
||||
|
|
@ -83,7 +93,7 @@ class UploadProgressActivity : BaseActivity() {
|
|||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_uploads,menu)
|
||||
menuInflater.inflate(R.menu.menu_uploads, menu)
|
||||
this.menu = menu
|
||||
updateMenuItems(0)
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
|
|
@ -176,17 +186,28 @@ class UploadProgressActivity : BaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the menu icons related to pending uploads.
|
||||
*/
|
||||
fun hidePendingIcons() {
|
||||
isPendingIconsVisible = false
|
||||
updateMenuItems(binding.uploadProgressViewPager.currentItem)
|
||||
}
|
||||
|
||||
fun setPausedIcon(paused : Boolean){
|
||||
/**
|
||||
* Sets the paused state and updates the menu items accordingly.
|
||||
* @param paused A boolean indicating whether all the uploads are paused.
|
||||
*/
|
||||
fun setPausedIcon(paused: Boolean) {
|
||||
isPaused = paused
|
||||
updateMenuItems(binding.uploadProgressViewPager.currentItem)
|
||||
}
|
||||
|
||||
fun setErrorIconsVisibility(visible : Boolean){
|
||||
/**
|
||||
* Sets the visibility of the menu icons related to failed uploads.
|
||||
* @param visible A boolean indicating whether the error icons should be visible.
|
||||
*/
|
||||
fun setErrorIconsVisibility(visible: Boolean) {
|
||||
isErrorIconsVisisble = visible
|
||||
updateMenuItems(binding.uploadProgressViewPager.currentItem)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Build
|
||||
import android.os.Build.VERSION
|
||||
import android.os.Build.VERSION_CODES
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.multidex.BuildConfig
|
||||
|
|
@ -41,9 +39,6 @@ import fr.free.nrw.commons.upload.UploadResult
|
|||
import fr.free.nrw.commons.wikidata.WikidataEditService
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
|
@ -169,7 +164,7 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
|
|||
|
||||
override suspend fun doWork(): Result {
|
||||
try {
|
||||
var countUpload = 0
|
||||
var totalUploadsStarted = 0
|
||||
// Start a foreground service
|
||||
setForeground(createForegroundInfo())
|
||||
notificationManager = NotificationManagerCompat.from(appContext)
|
||||
|
|
@ -217,8 +212,8 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
|
|||
contribution.transferred = 0
|
||||
contribution.state = Contribution.STATE_IN_PROGRESS
|
||||
contributionDao.saveSynchronous(contribution)
|
||||
setProgressAsync(Data.Builder().putInt("progress", countUpload).build())
|
||||
countUpload++
|
||||
setProgressAsync(Data.Builder().putInt("progress", totalUploadsStarted).build())
|
||||
totalUploadsStarted++
|
||||
uploadContribution(contribution = contribution)
|
||||
}
|
||||
}
|
||||
|
|
@ -576,6 +571,9 @@ class UploadWorker(var appContext: Context, workerParams: WorkerParameters) :
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a notification for a failed contribution upload.
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private fun showErrorNotification(contribution: Contribution) {
|
||||
val displayTitle = contribution.media.displayTitle
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ class WorkRequestHelper {
|
|||
isUploadWorkerRunning = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the flag isUploadWorkerRunning to`false` allowing new worker to be started.
|
||||
*/
|
||||
fun markUploadWorkerAsStopped() {
|
||||
isUploadWorkerRunning = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -818,8 +818,6 @@ Upload your first media by tapping on the add button.</string>
|
|||
</plurals>
|
||||
<string name="multiple_files_depiction">Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads.</string>
|
||||
<string name="multiple_files_depiction_header">Note about multi-uploads</string>
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="nearby_wikitalk">Report a problem about this item to Wikidata</string>
|
||||
<string name="please_enter_some_comments">Please enter some comments</string>
|
||||
<string name="talk">Talk</string>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import androidx.loader.content.CursorLoader
|
|||
import androidx.loader.content.Loader
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
|
|
@ -24,6 +25,10 @@ import org.mockito.MockitoAnnotations
|
|||
class ContributionsPresenterTest {
|
||||
@Mock
|
||||
internal lateinit var repository: ContributionsRepository
|
||||
|
||||
@Mock
|
||||
internal lateinit var uploadRepository: UploadRepository
|
||||
|
||||
@Mock
|
||||
internal lateinit var view: ContributionsContract.View
|
||||
|
||||
|
|
@ -37,9 +42,11 @@ class ContributionsPresenterTest {
|
|||
|
||||
lateinit var liveData: LiveData<List<Contribution>>
|
||||
|
||||
@Rule @JvmField var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
@Rule
|
||||
@JvmField
|
||||
var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
lateinit var scheduler : TestScheduler
|
||||
lateinit var scheduler: TestScheduler
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
|
|
@ -48,24 +55,23 @@ class ContributionsPresenterTest {
|
|||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
scheduler=TestScheduler()
|
||||
scheduler = TestScheduler()
|
||||
cursor = Mockito.mock(Cursor::class.java)
|
||||
contribution = Mockito.mock(Contribution::class.java)
|
||||
contributionsPresenter = ContributionsPresenter(repository, scheduler)
|
||||
contributionsPresenter = ContributionsPresenter(repository, uploadRepository, scheduler)
|
||||
loader = Mockito.mock(CursorLoader::class.java)
|
||||
contributionsPresenter.onAttachView(view)
|
||||
liveData=MutableLiveData()
|
||||
liveData = MutableLiveData()
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetch contribution with filename
|
||||
*/
|
||||
@Test
|
||||
fun testGetContributionWithFileName(){
|
||||
fun testGetContributionWithFileName() {
|
||||
contributionsPresenter.getContributionsWithTitle("ashish")
|
||||
verify(repository).getContributionWithFileName("ashish")
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ import fr.free.nrw.commons.TestCommonsApplication
|
|||
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
|
||||
import fr.free.nrw.commons.contributions.ChunkInfo
|
||||
import fr.free.nrw.commons.contributions.Contribution
|
||||
import fr.free.nrw.commons.contributions.ContributionDao
|
||||
import fr.free.nrw.commons.upload.UploadClient.TimeProvider
|
||||
import fr.free.nrw.commons.upload.worker.UploadWorker
|
||||
import fr.free.nrw.commons.wikidata.mwapi.MwException
|
||||
|
|
@ -44,8 +45,17 @@ class UploadClientTest {
|
|||
private val pageContentsCreator = mock<PageContentsCreator>()
|
||||
private val fileUtilsWrapper = mock<FileUtilsWrapper>()
|
||||
private val gson = mock<Gson>()
|
||||
private val contributionDao = mock<ContributionDao> { }
|
||||
private val timeProvider = mock<TimeProvider>()
|
||||
private val uploadClient = UploadClient(uploadInterface, csrfTokenClient, pageContentsCreator, fileUtilsWrapper, gson, timeProvider)
|
||||
private val uploadClient = UploadClient(
|
||||
uploadInterface,
|
||||
csrfTokenClient,
|
||||
pageContentsCreator,
|
||||
fileUtilsWrapper,
|
||||
gson,
|
||||
timeProvider,
|
||||
contributionDao
|
||||
)
|
||||
|
||||
private val expectedChunkSize = 512 * 1024
|
||||
private val testToken = "test-token"
|
||||
|
|
@ -67,7 +77,15 @@ class UploadClientTest {
|
|||
@Test
|
||||
fun testUploadFileFromStash_NoErrors() {
|
||||
whenever(gson.fromJson(uploadJson, UploadResponse::class.java)).thenReturn(uploadResponse)
|
||||
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey)).thenReturn(Observable.just(uploadJson))
|
||||
whenever(
|
||||
uploadInterface.uploadFileFromStash(
|
||||
testToken,
|
||||
createdContent,
|
||||
DEFAULT_EDIT_SUMMARY,
|
||||
filename,
|
||||
filekey
|
||||
)
|
||||
).thenReturn(Observable.just(uploadJson))
|
||||
|
||||
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
|
||||
|
||||
|
|
@ -83,7 +101,15 @@ class UploadClientTest {
|
|||
|
||||
whenever(gson.fromJson(uploadJson, UploadResponse::class.java)).thenReturn(errorResponse)
|
||||
whenever(gson.fromJson(uploadJson, MwException::class.java)).thenReturn(uploadException)
|
||||
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey)).thenReturn(Observable.just(uploadJson))
|
||||
whenever(
|
||||
uploadInterface.uploadFileFromStash(
|
||||
testToken,
|
||||
createdContent,
|
||||
DEFAULT_EDIT_SUMMARY,
|
||||
filename,
|
||||
filekey
|
||||
)
|
||||
).thenReturn(Observable.just(uploadJson))
|
||||
|
||||
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
|
||||
|
||||
|
|
@ -94,7 +120,15 @@ class UploadClientTest {
|
|||
@Test
|
||||
fun testUploadFileFromStash_Failure() {
|
||||
val exception = Exception("test")
|
||||
whenever(uploadInterface.uploadFileFromStash(testToken, createdContent, DEFAULT_EDIT_SUMMARY, filename, filekey))
|
||||
whenever(
|
||||
uploadInterface.uploadFileFromStash(
|
||||
testToken,
|
||||
createdContent,
|
||||
DEFAULT_EDIT_SUMMARY,
|
||||
filename,
|
||||
filekey
|
||||
)
|
||||
)
|
||||
.thenReturn(Observable.error(exception))
|
||||
|
||||
val result = uploadClient.uploadFileFromStash(contribution, filename, filekey).test()
|
||||
|
|
@ -107,7 +141,8 @@ class UploadClientTest {
|
|||
fun testUploadChunkToStash_Success() {
|
||||
val fileContent = "content"
|
||||
val requestBody: RequestBody = fileContent.toRequestBody("text/plain".toMediaType())
|
||||
val countingRequestBody = CountingRequestBody(requestBody, mock(), 0, fileContent.length.toLong())
|
||||
val countingRequestBody =
|
||||
CountingRequestBody(requestBody, mock(), 0, fileContent.length.toLong())
|
||||
|
||||
val filenameCaptor: KArgumentCaptor<RequestBody> = argumentCaptor<RequestBody>()
|
||||
val totalFileSizeCaptor = argumentCaptor<RequestBody>()
|
||||
|
|
@ -116,12 +151,15 @@ class UploadClientTest {
|
|||
val tokenCaptor = argumentCaptor<RequestBody>()
|
||||
val fileCaptor = argumentCaptor<MultipartBody.Part>()
|
||||
|
||||
whenever(uploadInterface.uploadFileToStash(
|
||||
filenameCaptor.capture(), totalFileSizeCaptor.capture(), offsetCaptor.capture(),
|
||||
fileKeyCaptor.capture(), tokenCaptor.capture(), fileCaptor.capture()
|
||||
)).thenReturn(Observable.just(uploadResponse))
|
||||
whenever(
|
||||
uploadInterface.uploadFileToStash(
|
||||
filenameCaptor.capture(), totalFileSizeCaptor.capture(), offsetCaptor.capture(),
|
||||
fileKeyCaptor.capture(), tokenCaptor.capture(), fileCaptor.capture()
|
||||
)
|
||||
).thenReturn(Observable.just(uploadResponse))
|
||||
|
||||
val result = uploadClient.uploadChunkToStash(filename, 100, 10, filekey, countingRequestBody).test()
|
||||
val result =
|
||||
uploadClient.uploadChunkToStash(filename, 100, 10, filekey, countingRequestBody).test()
|
||||
|
||||
result.assertNoErrors()
|
||||
assertSame(uploadResult, result.values()[0])
|
||||
|
|
@ -164,7 +202,12 @@ class UploadClientTest {
|
|||
whenever(contribution.isCompleted()).thenReturn(false)
|
||||
whenever(contribution.fileKey).thenReturn(filekey)
|
||||
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
|
||||
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(emptyList())
|
||||
whenever(
|
||||
fileUtilsWrapper.getFileChunks(
|
||||
anyOrNull<File>(),
|
||||
eq(expectedChunkSize)
|
||||
)
|
||||
).thenReturn(emptyList())
|
||||
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
|
||||
result.assertNoErrors()
|
||||
verify(contribution, times(1))
|
||||
|
|
@ -185,7 +228,12 @@ class UploadClientTest {
|
|||
whenever(contribution.isCompleted()).thenReturn(false)
|
||||
whenever(contribution.fileKey).thenReturn(filekey)
|
||||
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
|
||||
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(emptyList())
|
||||
whenever(
|
||||
fileUtilsWrapper.getFileChunks(
|
||||
anyOrNull<File>(),
|
||||
eq(expectedChunkSize)
|
||||
)
|
||||
).thenReturn(emptyList())
|
||||
|
||||
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
|
||||
|
||||
|
|
@ -201,8 +249,22 @@ class UploadClientTest {
|
|||
whenever(contribution.isCompleted()).thenReturn(false)
|
||||
whenever(contribution.fileKey).thenReturn(filekey)
|
||||
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
|
||||
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(listOf(mockFile))
|
||||
whenever(uploadInterface.uploadFileToStash(any(), any(), any(), any(), any(), any())).thenReturn(Observable.just(uploadResponse))
|
||||
whenever(
|
||||
fileUtilsWrapper.getFileChunks(
|
||||
anyOrNull<File>(),
|
||||
eq(expectedChunkSize)
|
||||
)
|
||||
).thenReturn(listOf(mockFile))
|
||||
whenever(
|
||||
uploadInterface.uploadFileToStash(
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
any(),
|
||||
any()
|
||||
)
|
||||
).thenReturn(Observable.just(uploadResponse))
|
||||
|
||||
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
|
||||
|
||||
|
|
@ -228,10 +290,19 @@ class UploadClientTest {
|
|||
whenever(contribution.fileKey).thenReturn(filekey)
|
||||
|
||||
whenever(fileUtilsWrapper.getMimeType(anyOrNull<File>())).thenReturn("image/png")
|
||||
whenever(fileUtilsWrapper.getFileChunks(anyOrNull<File>(), eq(expectedChunkSize))).thenReturn(listOf(mockFile))
|
||||
whenever(
|
||||
fileUtilsWrapper.getFileChunks(
|
||||
anyOrNull<File>(),
|
||||
eq(expectedChunkSize)
|
||||
)
|
||||
).thenReturn(listOf(mockFile))
|
||||
|
||||
whenever(uploadInterface.uploadFileToStash(anyOrNull(), anyOrNull(), anyOrNull(),
|
||||
anyOrNull(), anyOrNull(), anyOrNull())).thenReturn(Observable.just(uploadResponse))
|
||||
whenever(
|
||||
uploadInterface.uploadFileToStash(
|
||||
anyOrNull(), anyOrNull(), anyOrNull(),
|
||||
anyOrNull(), anyOrNull(), anyOrNull()
|
||||
)
|
||||
).thenReturn(Observable.just(uploadResponse))
|
||||
|
||||
val result = uploadClient.uploadFileToStash(filename, contribution, mock()).test()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue