Enhancing Multi-Upload Functionality for Consistent Depiction Categorization (#5700)

* Add AlertDialog for categories and modularize receiveSharedItems

* Improve nearby-place search function for a multi-upload

Enhance the depiction consistency of a multi-upload by ensuring that it corresponds to a single place

* Add javadoc

* Update strings.xml

* Renamed setImageTobeUploaded to setImageToBeUploaded

* Make uploadIsOnPlace private & add a setter

* Rename uploadIsOnPlace to uploadIsOfAPlace

* Use singular when there is only one picture

* Add a 'Do not show again' checkbox on the dialog

* Update strings.xml

---------

Co-authored-by: Giannis Karyotakis <110292528+karyotakisg@users.noreply.github.com>
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Evangelos Talos 2024-05-02 14:12:32 +03:00 committed by GitHub
parent 6aa9303d0f
commit c178c5de41
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 210 additions and 99 deletions

View file

@ -142,6 +142,7 @@ public class MainActivity extends BaseActivity
} else {
if (applicationKvStore.getBoolean("firstrun", true)) {
applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false);
applicationKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", false);
}
if(savedInstanceState == null){
//starting a fresh fragment.

View file

@ -20,8 +20,11 @@ import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter;
@ -100,6 +103,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
private Place place;
private LatLng prevLocation;
private LatLng currLocation;
private static boolean uploadIsOfAPlace = false;
private boolean isInAppCameraUpload;
private List<UploadableFile> uploadableFiles = Collections.emptyList();
private int currentSelectedPosition = 0;
@ -123,10 +127,8 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
* when necessary. Initially, it is set to `true`, indicating that the permissions dialog
* should be displayed if permissions are missing and it is first time calling
* `checkStoragePermissions` method.
*
* This variable is used in the `checkStoragePermissions` method to determine whether to
* show a permissions dialog to the user if the required permissions are not granted.
*
* If `showPermissionsDialog` is set to `true` and the necessary permissions are missing,
* a permissions dialog will be displayed to request the required permissions. If set
* to `false`, the dialog won't be shown.
@ -438,6 +440,15 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
}
}
/**
* Sets the flag indicating whether the upload is of a specific place.
*
* @param uploadOfAPlace a boolean value indicating whether the upload is of place.
*/
public static void setUploadIsOfAPlace(boolean uploadOfAPlace) {
uploadIsOfAPlace = uploadOfAPlace;
}
private void receiveSharedItems() {
thumbnailsAdapter.context=this;
Intent intent = getIntent();
@ -452,8 +463,14 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
handleNullMedia();
} else {
//Show thumbnails
if (uploadableFiles.size()
> 1) {//If there is only file, no need to show the image thumbnails
if (uploadableFiles.size() > 1){
if(!defaultKvStore.getBoolean("hasAlreadyLaunchedCategoriesDialog")){//If there is only file, no need to show the image thumbnails
showAlertDialogForCategories();
}
if (uploadableFiles.size() > 3 &&
!defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")){
showAlertForBattery();
}
thumbnailsAdapter.setUploadableFiles(uploadableFiles);
} else {
binding.llContainerTopCard.setVisibility(View.GONE);
@ -467,76 +484,16 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
}
/* Suggest users to turn battery optimisation off when uploading more than a few files.
That's because we have noticed that many-files uploads have
a much higher probability of failing than uploads with less files.
Show the dialog for Android 6 and above as
the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS intent was added in API level 23
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (uploadableFiles.size() > 3
&& !defaultKvStore.getBoolean("hasAlreadyLaunchedBigMultiupload")) {
// When battery-optimisation dialog is shown don't show the image quality dialog
UploadMediaPresenter.isBatteryDialogShowing = true;
DialogUtil.showAlertDialog(
this,
getString(R.string.unrestricted_battery_mode),
getString(R.string.suggest_unrestricted_mode),
getString(R.string.title_activity_settings),
getString(R.string.cancel),
() -> {
/* Since opening the right settings page might be device dependent, using
https://github.com/WaseemSabir/BatteryPermissionHelper
directly appeared like a promising idea.
However, this simply closed the popup and did not make
the settings page appear on a Pixel as well as a Xiaomi device.
Used the standard intent instead of using this library as
it shows a list of all the apps on the device and allows users to
turn battery optimisation off.
*/
Intent batteryOptimisationSettingsIntent = new Intent(
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(batteryOptimisationSettingsIntent);
// calling checkImageQuality after battery dialog is interacted with
// so that 2 dialogs do not pop up simultaneously
presenter.checkImageQuality(0);
UploadMediaPresenter.isBatteryDialogShowing = false;
},
() -> {
presenter.checkImageQuality(0);
UploadMediaPresenter.isBatteryDialogShowing = false;
}
);
defaultKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", true);
}
}
for (UploadableFile uploadableFile : uploadableFiles) {
UploadMediaDetailFragment uploadMediaDetailFragment = new UploadMediaDetailFragment();
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
this, locationManager, null);
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
currLocation = locationManager.getLastLocation();
}
if (currLocation != null) {
float locationDifference = getLocationDifference(currLocation, prevLocation);
boolean isLocationTagUnchecked = isLocationTagUncheckedInTheSettings();
/* Remove location if the user has unchecked the Location EXIF tag in the
Manage EXIF Tags setting or turned "Record location for in-app shots" off.
Also, location information is discarded if the difference between
current location and location recorded just before capturing the image
is greater than 100 meters */
if (isLocationTagUnchecked || locationDifference > 100
|| !defaultKvStore.getBoolean("inAppCameraLocationPref")
|| !isInAppCameraUpload) {
currLocation = null;
}
}
if (!uploadIsOfAPlace) {
handleLocation();
uploadMediaDetailFragment.setImageToBeUploaded(uploadableFile, place, currLocation);
locationManager.unregisterLocationManager();
} else {
uploadMediaDetailFragment.setImageToBeUploaded(uploadableFile, place, currLocation);
}
UploadMediaDetailFragmentCallback uploadMediaDetailFragmentCallback = new UploadMediaDetailFragmentCallback() {
@Override
@ -930,4 +887,106 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
this::finish
);
}
/**
* If the user uploads more than 1 file informs that
* depictions/categories apply to all pictures of a multi upload.
* This method takes no arguments and does not return any value.
* It shows the AlertDialog and continues the flow of uploads.
*/
private void showAlertDialogForCategories() {
UploadMediaPresenter.isCategoriesDialogShowing = true;
// Inflate the custom layout
LayoutInflater inflater = getLayoutInflater();
View view = inflater.inflate(R.layout.activity_upload_categories_dialog, null);
CheckBox checkBox = view.findViewById(R.id.categories_checkbox);
// Create the alert dialog
AlertDialog alertDialog = new AlertDialog.Builder(this)
.setView(view)
.setTitle(getString(R.string.multiple_files_depiction_header))
.setMessage(getString(R.string.multiple_files_depiction))
.setPositiveButton("OK", (dialog, which) -> {
if (checkBox.isChecked()) {
// Save the user's choice to not show the dialog again
defaultKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", true);
}
presenter.checkImageQuality(0);
UploadMediaPresenter.isCategoriesDialogShowing = false;
})
.setNegativeButton("", null)
.create();
alertDialog.show();
}
/** Suggest users to turn battery optimisation off when uploading
* more than a few files. That's because we have noticed that
* many-files uploads have a much higher probability of failing
* than uploads with less files. Show the dialog for Android 6
* and above as the ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
* intent was added in API level 23
*/
private void showAlertForBattery(){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// When battery-optimisation dialog is shown don't show the image quality dialog
UploadMediaPresenter.isBatteryDialogShowing = true;
DialogUtil.showAlertDialog(
this,
getString(R.string.unrestricted_battery_mode),
getString(R.string.suggest_unrestricted_mode),
getString(R.string.title_activity_settings),
getString(R.string.cancel),
() -> {
/* Since opening the right settings page might be device dependent, using
https://github.com/WaseemSabir/BatteryPermissionHelper
directly appeared like a promising idea.
However, this simply closed the popup and did not make
the settings page appear on a Pixel as well as a Xiaomi device.
Used the standard intent instead of using this library as
it shows a list of all the apps on the device and allows users to
turn battery optimisation off.
*/
Intent batteryOptimisationSettingsIntent = new Intent(
Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(batteryOptimisationSettingsIntent);
// calling checkImageQuality after battery dialog is interacted with
// so that 2 dialogs do not pop up simultaneously
UploadMediaPresenter.isBatteryDialogShowing = false;
},
() -> {
UploadMediaPresenter.isBatteryDialogShowing = false;
}
);
defaultKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", true);
}
}
/**
* If the permission for Location is turned on and certain
* conditions are met, returns current location of the user.
*/
private void handleLocation(){
LocationPermissionsHelper locationPermissionsHelper = new LocationPermissionsHelper(
this, locationManager, null);
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
currLocation = locationManager.getLastLocation();
}
if (currLocation != null) {
float locationDifference = getLocationDifference(currLocation, prevLocation);
boolean isLocationTagUnchecked = isLocationTagUncheckedInTheSettings();
/* Remove location if the user has unchecked the Location EXIF tag in the
Manage EXIF Tags setting or turned "Record location for in-app shots" off.
Also, location information is discarded if the difference between
current location and location recorded just before capturing the image
is greater than 100 meters */
if (isLocationTagUnchecked || locationDifference > 100
|| !defaultKvStore.getBoolean("inAppCameraLocationPref")
|| !isInAppCameraUpload) {
currLocation = null;
}
}
}
}

View file

@ -374,8 +374,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace);
if (response) {
if (callback != null) {
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace,
indexOfFragment);
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace);
}
}
} else {
@ -395,20 +394,42 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
final View customLayout = getLayoutInflater().inflate(R.layout.custom_nearby_found, null);
ImageView nearbyFoundImage = customLayout.findViewById(R.id.nearbyItemImage);
nearbyFoundImage.setImageURI(uploadItem.getMediaUri());
final Activity activity = getActivity();
if (activity instanceof UploadActivity) {
final boolean isMultipleFilesSelected = ((UploadActivity) activity).getIsMultipleFilesSelected();
// Determine the message based on the selection status
String message;
if (isMultipleFilesSelected) {
// Use plural message if multiple files are selected
message = String.format(Locale.getDefault(),
getString(R.string.upload_nearby_place_found_description_plural),
place.getName());
} else {
// Use singular message if only one file is selected
message = String.format(Locale.getDefault(),
getString(R.string.upload_nearby_place_found_description_singular),
place.getName());
}
// Show the AlertDialog with the determined message
DialogUtil.showAlertDialog(getActivity(),
getString(R.string.upload_nearby_place_found_title),
String.format(Locale.getDefault(),
getString(R.string.upload_nearby_place_found_description),
place.getName()),
message,
() -> {
// Execute when user confirms the upload is of the specified place
UploadActivity.nearbyPopupAnswers.put(place, true);
presenter.onUserConfirmedUploadIsOfPlace(place, indexOfFragment);
presenter.onUserConfirmedUploadIsOfPlace(place);
},
() -> {
// Execute when user cancels the upload of the specified place
UploadActivity.nearbyPopupAnswers.put(place, false);
},
customLayout, true);
}
}
@Override
public void showProgress(boolean shouldShow) {
@ -440,8 +461,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
if (UploadActivity.nearbyPopupAnswers.containsKey(nearbyPlace)) {
final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace);
if (response) {
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace,
indexOfFragment);
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace);
}
} else {
showNearbyPlaceFound(nearbyPlace);

View file

@ -109,7 +109,7 @@ public interface UploadMediaDetailsContract {
void onEditButtonClicked(int indexInViewFlipper);
void onUserConfirmedUploadIsOfPlace(Place place, int uploadItemPosition);
void onUserConfirmedUploadIsOfPlace(Place place);
}
}

View file

@ -76,6 +76,8 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
*/
public static boolean isBatteryDialogShowing;
public static boolean isCategoriesDialogShowing;
@Inject
public UploadMediaPresenter(UploadRepository uploadRepository,
@Named("default_preferences") JsonKvStore defaultKVStore,
@ -329,17 +331,27 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
view.showEditActivity(repository.getUploads().get(indexInViewFlipper));
}
/**
* Updates the information regarding the specified place for uploads
* when the user confirms the suggested nearby place.
*
* @param place The place to be associated with the uploads.
*/
@Override
public void onUserConfirmedUploadIsOfPlace(Place place, int uploadItemPosition) {
final List<UploadMediaDetail> uploadMediaDetails = repository.getUploads()
.get(uploadItemPosition)
.getUploadMediaDetails();
UploadItem uploadItem = repository.getUploads()
.get(uploadItemPosition);
public void onUserConfirmedUploadIsOfPlace(Place place) {
final List<UploadItem> uploads = repository.getUploads();
for (UploadItem uploadItem : uploads) {
uploadItem.setPlace(place);
final List<UploadMediaDetail> uploadMediaDetails = uploadItem.getUploadMediaDetails();
// Update UploadMediaDetail object for this UploadItem
uploadMediaDetails.set(0, new UploadMediaDetail(place));
view.updateMediaDetails(uploadMediaDetails);
}
// Now that all UploadItems and their associated UploadMediaDetail objects have been updated,
// update the view with the modified media details of the first upload item
view.updateMediaDetails(uploads.get(0).getUploadMediaDetails());
UploadActivity.setUploadIsOfAPlace(true);
}
/**
* Calculates the image quality
@ -410,7 +422,7 @@ public class UploadMediaPresenter implements UserActionListener, SimilarImageInt
}
if (uploadItemIndex == 0) {
if (!isBatteryDialogShowing) {
if (!isBatteryDialogShowing && !isCategoriesDialogShowing) {
// if battery-optimisation dialog is not being shown, call checkImageQuality
checkImageQuality(uploadItem, uploadItemIndex);
} else {

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/upload_root_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<CheckBox
android:id="@+id/categories_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Don't show this message again" />
</LinearLayout>

View file

@ -610,7 +610,8 @@ Upload your first media by tapping on the add button.</string>
<string name="title_for_parent_classes">PARENT CLASSES</string>
<string name="upload_nearby_place_found_title">Nearby Place Found</string>
<string name="upload_nearby_place_found_description">Is this a photo of %1$s?</string>
<string name="upload_nearby_place_found_description_plural">Are these pictures of %1$s?</string>
<string name="upload_nearby_place_found_description_singular">Is this a picture of %1$s?</string>
<string name="title_app_shortcut_bookmark">Bookmarks</string>
<string name="title_app_shortcut_setting">Settings</string>
<string name="remove_bookmark">Removed from bookmarks</string>
@ -815,4 +816,6 @@ Upload your first media by tapping on the add button.</string>
<item quantity="one">%d image selected</item>
<item quantity="other">%d images selected</item>
</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>
</resources>