mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-11-04 00:33:55 +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