mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 21:33:53 +01:00
Convert UploadMediaDetailFragment to kotlin
This commit is contained in:
parent
69f804438e
commit
b9c2d79fe7
8 changed files with 933 additions and 1010 deletions
|
|
@ -41,8 +41,8 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper
|
|||
import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback
|
||||
import fr.free.nrw.commons.location.LocationServiceManager
|
||||
import fr.free.nrw.commons.theme.BaseActivity
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_LOCATION
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
|
||||
import fr.free.nrw.commons.utils.DialogUtil
|
||||
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
|
|
|||
|
|
@ -511,11 +511,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
|
|||
* ensuring that the thumbnail change is reflected in the UI.
|
||||
*
|
||||
* @param index The index of the UploadableFile to be updated.
|
||||
* @param filepath The file path of the new thumbnail image.
|
||||
* @param uri The file path of the new thumbnail image.
|
||||
*/
|
||||
override fun changeThumbnail(index: Int, filepath: String) {
|
||||
override fun changeThumbnail(index: Int, uri: String) {
|
||||
uploadableFiles.removeAt(index)
|
||||
uploadableFiles.add(index, UploadableFile(File(filepath)))
|
||||
uploadableFiles.add(index, UploadableFile(File(uri)))
|
||||
binding.rvThumbnails.adapter!!.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
|
@ -544,9 +544,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
|
|||
|
||||
if (isFragmentsSaved) {
|
||||
val fragment = fragments!![0] as UploadMediaDetailFragment?
|
||||
fragment!!.setCallback(uploadMediaDetailFragmentCallback)
|
||||
fragment!!.fragmentCallback = uploadMediaDetailFragmentCallback
|
||||
} else {
|
||||
uploadMediaDetailFragment.setCallback(uploadMediaDetailFragmentCallback)
|
||||
uploadMediaDetailFragment.fragmentCallback = uploadMediaDetailFragmentCallback
|
||||
fragments!!.add(uploadMediaDetailFragment)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,924 +0,0 @@
|
|||
package fr.free.nrw.commons.upload.mediaDetails;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.speech.RecognizerIntent;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
import androidx.activity.result.ActivityResult;
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import fr.free.nrw.commons.CameraPosition;
|
||||
import fr.free.nrw.commons.locationpicker.LocationPicker;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.MainActivity;
|
||||
import fr.free.nrw.commons.databinding.FragmentUploadMediaDetailFragmentBinding;
|
||||
import fr.free.nrw.commons.edit.EditActivity;
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile;
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore;
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Place;
|
||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.ImageCoordinates;
|
||||
import fr.free.nrw.commons.upload.SimilarImageDialogFragment;
|
||||
import fr.free.nrw.commons.upload.UploadActivity;
|
||||
import fr.free.nrw.commons.upload.UploadBaseFragment;
|
||||
import fr.free.nrw.commons.upload.UploadItem;
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail;
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter;
|
||||
import fr.free.nrw.commons.utils.ActivityUtils;
|
||||
import fr.free.nrw.commons.utils.DialogUtil;
|
||||
import fr.free.nrw.commons.utils.ImageUtils;
|
||||
import fr.free.nrw.commons.utils.NetworkUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
||||
UploadMediaDetailsContract.View, UploadMediaDetailAdapter.EventListener {
|
||||
|
||||
private UploadMediaDetailAdapter uploadMediaDetailAdapter;
|
||||
|
||||
private final ActivityResultLauncher<Intent> startForResult = registerForActivityResult(
|
||||
new StartActivityForResult(), result -> {
|
||||
onCameraPosition(result);
|
||||
});
|
||||
|
||||
private final ActivityResultLauncher<Intent> startForEditActivityResult = registerForActivityResult(
|
||||
new StartActivityForResult(), result -> {
|
||||
onEditActivityResult(result);
|
||||
}
|
||||
);
|
||||
|
||||
private final ActivityResultLauncher<Intent> voiceInputResultLauncher = registerForActivityResult(
|
||||
new StartActivityForResult(), result -> {
|
||||
onVoiceInput(result);
|
||||
}
|
||||
);
|
||||
|
||||
public static Activity activity ;
|
||||
|
||||
private int indexOfFragment;
|
||||
|
||||
/**
|
||||
* A key for applicationKvStore. By this key we can retrieve the location of last UploadItem ex.
|
||||
* 12.3433,54.78897 from applicationKvStore.
|
||||
*/
|
||||
public static final String LAST_LOCATION = "last_location_while_uploading";
|
||||
public static final String LAST_ZOOM = "last_zoom_level_while_uploading";
|
||||
|
||||
|
||||
public static final String UPLOADABLE_FILE = "uploadable_file";
|
||||
|
||||
public static final String UPLOAD_MEDIA_DETAILS = "upload_media_detail_adapter";
|
||||
|
||||
/**
|
||||
* True when user removes location from the current image
|
||||
*/
|
||||
private boolean hasUserRemovedLocation;
|
||||
|
||||
|
||||
@Inject
|
||||
UploadMediaDetailsContract.UserActionListener presenter;
|
||||
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
JsonKvStore defaultKvStore;
|
||||
|
||||
@Inject
|
||||
RecentLanguagesDao recentLanguagesDao;
|
||||
|
||||
private UploadableFile uploadableFile;
|
||||
private Place place;
|
||||
|
||||
private boolean isExpanded = true;
|
||||
|
||||
/**
|
||||
* True if location is added via the "missing location" popup dialog (which appears after
|
||||
* tapping "Next" if the picture has no geographical coordinates).
|
||||
*/
|
||||
private boolean isMissingLocationDialog;
|
||||
|
||||
/**
|
||||
* showNearbyFound will be true, if any nearby location found that needs pictures and the nearby
|
||||
* popup is yet to be shown Used to show and check if the nearby found popup is already shown
|
||||
*/
|
||||
private boolean showNearbyFound;
|
||||
|
||||
/**
|
||||
* nearbyPlace holds the detail of nearby place that need pictures, if any found
|
||||
*/
|
||||
private Place nearbyPlace;
|
||||
private UploadItem uploadItem;
|
||||
/**
|
||||
* inAppPictureLocation: use location recorded while using the in-app camera if device camera
|
||||
* does not record it in the EXIF
|
||||
*/
|
||||
private LatLng inAppPictureLocation;
|
||||
/**
|
||||
* editableUploadItem : Storing the upload item before going to update the coordinates
|
||||
*/
|
||||
private UploadItem editableUploadItem;
|
||||
|
||||
private BasicKvStore basicKvStore;
|
||||
|
||||
private final String keyForShowingAlertDialog = "isNoNetworkAlertDialogShowing";
|
||||
|
||||
private UploadMediaDetailFragmentCallback callback;
|
||||
|
||||
private FragmentUploadMediaDetailFragmentBinding binding;
|
||||
|
||||
public void setCallback(UploadMediaDetailFragmentCallback callback) {
|
||||
this.callback = callback;
|
||||
UploadMediaPresenter.Companion.setPresenterCallback(callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
|
||||
if(savedInstanceState!=null && uploadableFile==null) {
|
||||
uploadableFile = savedInstanceState.getParcelable(UPLOADABLE_FILE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void setImageToBeUploaded(UploadableFile uploadableFile, Place place,
|
||||
LatLng inAppPictureLocation) {
|
||||
this.uploadableFile = uploadableFile;
|
||||
this.place = place;
|
||||
this.inAppPictureLocation = inAppPictureLocation;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
binding = FragmentUploadMediaDetailFragmentBinding.inflate(inflater, container, false);
|
||||
return binding.getRoot();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
activity = requireActivity();
|
||||
basicKvStore = new BasicKvStore(activity, "CurrentUploadImageQualities");
|
||||
|
||||
if (callback != null) {
|
||||
indexOfFragment = callback.getIndexInViewFlipper(this);
|
||||
initializeFragment();
|
||||
}
|
||||
|
||||
if(savedInstanceState!=null){
|
||||
if(uploadMediaDetailAdapter.getItems().size()==0 && callback != null){
|
||||
uploadMediaDetailAdapter.setItems(savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS));
|
||||
presenter.setUploadMediaDetails(uploadMediaDetailAdapter.getItems(),
|
||||
indexOfFragment);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if(!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
|
||||
ActivityUtils.startActivityWithFlags(
|
||||
getActivity(), MainActivity.class, Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void initializeFragment() {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
binding.tvTitle.setText(getString(R.string.step_count, (indexOfFragment + 1),
|
||||
callback.getTotalNumberOfSteps(), getString(R.string.media_detail_step_title)));
|
||||
binding.tooltip.setOnClickListener(
|
||||
v -> showInfoAlert(R.string.media_detail_step_title, R.string.media_details_tooltip));
|
||||
initPresenter();
|
||||
presenter.receiveImage(uploadableFile, place, inAppPictureLocation);
|
||||
initRecyclerView();
|
||||
|
||||
if (indexOfFragment == 0) {
|
||||
binding.btnPrevious.setEnabled(false);
|
||||
binding.btnPrevious.setAlpha(0.5f);
|
||||
} else {
|
||||
binding.btnPrevious.setEnabled(true);
|
||||
binding.btnPrevious.setAlpha(1.0f);
|
||||
}
|
||||
|
||||
// If the image EXIF data contains the location, show the map icon with a green tick
|
||||
if (inAppPictureLocation != null ||
|
||||
(uploadableFile != null && uploadableFile.hasLocation())) {
|
||||
Drawable mapTick = getResources().getDrawable(R.drawable.ic_map_available_20dp);
|
||||
binding.locationImageView.setImageDrawable(mapTick);
|
||||
binding.locationTextView.setText(R.string.edit_location);
|
||||
} else {
|
||||
// Otherwise, show the map icon with a red question mark
|
||||
Drawable mapQuestionMark =
|
||||
getResources().getDrawable(R.drawable.ic_map_not_available_20dp);
|
||||
binding.locationImageView.setImageDrawable(mapQuestionMark);
|
||||
binding.locationTextView.setText(R.string.add_location);
|
||||
}
|
||||
|
||||
//If this is the last media, we have nothing to copy, lets not show the button
|
||||
if (indexOfFragment == callback.getTotalNumberOfSteps() - 4) {
|
||||
binding.btnCopySubsequentMedia.setVisibility(View.GONE);
|
||||
} else {
|
||||
binding.btnCopySubsequentMedia.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
binding.btnNext.setOnClickListener(v -> onNextButtonClicked());
|
||||
binding.btnPrevious.setOnClickListener(v -> onPreviousButtonClicked());
|
||||
binding.llEditImage.setOnClickListener(v -> onEditButtonClicked());
|
||||
binding.llContainerTitle.setOnClickListener(v -> onLlContainerTitleClicked());
|
||||
binding.llLocationStatus.setOnClickListener(v -> onIbMapClicked());
|
||||
binding.btnCopySubsequentMedia.setOnClickListener(v -> onButtonCopyTitleDescToSubsequentMedia());
|
||||
|
||||
|
||||
attachImageViewScaleChangeListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the scale change listener to the image view
|
||||
*/
|
||||
private void attachImageViewScaleChangeListener() {
|
||||
binding.backgroundImage.setOnScaleChangeListener(
|
||||
(scaleFactor, focusX, focusY) -> {
|
||||
//Whenever the uses plays with the image, lets collapse the media detail container
|
||||
//only if it is not already collapsed, which resolves flickering of arrow
|
||||
if (isExpanded) {
|
||||
expandCollapseLlMediaDetail(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* attach the presenter with the view
|
||||
*/
|
||||
private void initPresenter() {
|
||||
presenter.onAttachView(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* init the description recycler veiw and caption recyclerview
|
||||
*/
|
||||
private void initRecyclerView() {
|
||||
uploadMediaDetailAdapter = new UploadMediaDetailAdapter(this,
|
||||
defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, ""), recentLanguagesDao, voiceInputResultLauncher);
|
||||
uploadMediaDetailAdapter.setCallback(this::showInfoAlert);
|
||||
uploadMediaDetailAdapter.setEventListener(this);
|
||||
binding.rvDescriptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.rvDescriptions.setAdapter(uploadMediaDetailAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* show dialog with info
|
||||
* @param titleStringID
|
||||
* @param messageStringId
|
||||
*/
|
||||
private void showInfoAlert(int titleStringID, int messageStringId) {
|
||||
DialogUtil.showAlertDialog(getActivity(), getString(titleStringID),
|
||||
getString(messageStringId), getString(android.R.string.ok), null);
|
||||
}
|
||||
|
||||
|
||||
public void onNextButtonClicked() {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
presenter.displayLocDialog(indexOfFragment, inAppPictureLocation, hasUserRemovedLocation);
|
||||
}
|
||||
|
||||
public void onPreviousButtonClicked() {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
callback.onPreviousButtonClicked(indexOfFragment);
|
||||
}
|
||||
|
||||
public void onEditButtonClicked() {
|
||||
presenter.onEditButtonClicked(indexOfFragment);
|
||||
}
|
||||
@Override
|
||||
public void showSimilarImageFragment(String originalFilePath, String possibleFilePath,
|
||||
ImageCoordinates similarImageCoordinates) {
|
||||
BasicKvStore basicKvStore = new BasicKvStore(getActivity(), "IsAnyImageCancelled");
|
||||
if (!basicKvStore.getBoolean("IsAnyImageCancelled", false)) {
|
||||
SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment();
|
||||
newFragment.setCancelable(false);
|
||||
newFragment.setCallback(new SimilarImageDialogFragment.Callback() {
|
||||
@Override
|
||||
public void onPositiveResponse() {
|
||||
Timber.d("positive response from similar image fragment");
|
||||
presenter.useSimilarPictureCoordinates(similarImageCoordinates,
|
||||
indexOfFragment);
|
||||
|
||||
// set the description text when user selects to use coordinate from the other image
|
||||
// which was taken within 120s
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/4700
|
||||
uploadMediaDetailAdapter.getItems().get(0).setDescriptionText(
|
||||
getString(R.string.similar_coordinate_description_auto_set));
|
||||
updateMediaDetails(uploadMediaDetailAdapter.getItems());
|
||||
|
||||
// Replace the 'Add location' button with 'Edit location' button when user clicks
|
||||
// yes in similar image dialog
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/5669
|
||||
Drawable mapTick = getResources().getDrawable(R.drawable.ic_map_available_20dp);
|
||||
binding.locationImageView.setImageDrawable(mapTick);
|
||||
binding.locationTextView.setText(R.string.edit_location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeResponse() {
|
||||
Timber.d("negative response from similar image fragment");
|
||||
}
|
||||
});
|
||||
Bundle args = new Bundle();
|
||||
args.putString("originalImagePath", originalFilePath);
|
||||
args.putString("possibleImagePath", possibleFilePath);
|
||||
newFragment.setArguments(args);
|
||||
newFragment.show(getChildFragmentManager(), "dialog");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageProcessed(@NotNull UploadItem uploadItem) {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
binding.backgroundImage.setImageURI(uploadItem.getMediaUri());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets variables to Show popup if any nearby location needing pictures matches uploadable picture's GPS location
|
||||
* @param uploadItem
|
||||
* @param place
|
||||
*/
|
||||
@Override
|
||||
public void onNearbyPlaceFound(
|
||||
@NotNull UploadItem uploadItem, @org.jetbrains.annotations.Nullable Place place) {
|
||||
nearbyPlace = place;
|
||||
this.uploadItem = uploadItem;
|
||||
showNearbyFound = true;
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
if (indexOfFragment == 0) {
|
||||
if (UploadActivity.nearbyPopupAnswers.containsKey(nearbyPlace)) {
|
||||
final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace);
|
||||
if (response) {
|
||||
if (callback != null) {
|
||||
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showNearbyPlaceFound(nearbyPlace);
|
||||
}
|
||||
showNearbyFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows nearby place found popup
|
||||
* @param place
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
// To avoid the unwanted lint warning that string 'upload_nearby_place_found_description' is not of a valid format
|
||||
private void showNearbyPlaceFound(Place place) {
|
||||
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).isMultipleFilesSelected();
|
||||
|
||||
// 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),
|
||||
message,
|
||||
() -> {
|
||||
// Execute when user confirms the upload is of the specified place
|
||||
UploadActivity.nearbyPopupAnswers.put(place, true);
|
||||
presenter.onUserConfirmedUploadIsOfPlace(place, indexOfFragment);
|
||||
},
|
||||
() -> {
|
||||
// Execute when user cancels the upload of the specified place
|
||||
UploadActivity.nearbyPopupAnswers.put(place, false);
|
||||
},
|
||||
customLayout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showProgress(boolean shouldShow) {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
callback.showProgress(shouldShow);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onImageValidationSuccess() {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
callback.onNextButtonClicked(indexOfFragment);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets called whenever the next/previous button is pressed
|
||||
*/
|
||||
@Override
|
||||
public void onBecameVisible() {
|
||||
super.onBecameVisible();
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
presenter.fetchTitleAndDescription(indexOfFragment);
|
||||
if (showNearbyFound) {
|
||||
if (UploadActivity.nearbyPopupAnswers.containsKey(nearbyPlace)) {
|
||||
final boolean response = UploadActivity.nearbyPopupAnswers.get(nearbyPlace);
|
||||
if (response) {
|
||||
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment);
|
||||
}
|
||||
} else {
|
||||
showNearbyPlaceFound(nearbyPlace);
|
||||
}
|
||||
showNearbyFound = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(int stringResourceId, int colorResourceId) {
|
||||
ViewUtil.showLongToast(getContext(), stringResourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String message, int colorResourceId) {
|
||||
ViewUtil.showLongToast(getContext(), message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showDuplicatePicturePopup(@NotNull UploadItem uploadItem) {
|
||||
if (defaultKvStore.getBoolean("showDuplicatePicturePopup", true)) {
|
||||
String uploadTitleFormat = getString(R.string.upload_title_duplicate);
|
||||
View checkBoxView = View
|
||||
.inflate(getActivity(), R.layout.nearby_permission_dialog, null);
|
||||
CheckBox checkBox = (CheckBox) checkBoxView.findViewById(R.id.never_ask_again);
|
||||
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
defaultKvStore.putBoolean("showDuplicatePicturePopup", false);
|
||||
}
|
||||
});
|
||||
DialogUtil.showAlertDialog(requireActivity(),
|
||||
getString(R.string.duplicate_file_name),
|
||||
String.format(Locale.getDefault(),
|
||||
uploadTitleFormat,
|
||||
uploadItem.getFilename()),
|
||||
getString(R.string.upload),
|
||||
getString(R.string.cancel),
|
||||
() -> {
|
||||
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||
onImageValidationSuccess();
|
||||
}, null,
|
||||
checkBoxView);
|
||||
} else {
|
||||
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||
onImageValidationSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog alerting the user that internet connection is required for upload process
|
||||
* Does nothing if there is network connectivity and then the user presses okay
|
||||
*/
|
||||
@Override
|
||||
public void showConnectionErrorPopupForCaptionCheck() {
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
getString(R.string.upload_connection_error_alert_title),
|
||||
getString(R.string.upload_connection_error_alert_detail),
|
||||
getString(R.string.ok),
|
||||
getString(R.string.cancel_upload),
|
||||
() -> {
|
||||
if (!NetworkUtils.isInternetConnectionEstablished(activity)) {
|
||||
showConnectionErrorPopupForCaptionCheck();
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
activity.finish();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog alerting the user that internet connection is required for upload process
|
||||
* Recalls UploadMediaPresenter.getImageQuality for all the next upload items,
|
||||
* if there is network connectivity and then the user presses okay
|
||||
*/
|
||||
@Override
|
||||
public void showConnectionErrorPopup() {
|
||||
try {
|
||||
boolean FLAG_ALERT_DIALOG_SHOWING = basicKvStore.getBoolean(
|
||||
keyForShowingAlertDialog, false);
|
||||
if (!FLAG_ALERT_DIALOG_SHOWING) {
|
||||
basicKvStore.putBoolean(keyForShowingAlertDialog, true);
|
||||
DialogUtil.showAlertDialog(getActivity(),
|
||||
getString(R.string.upload_connection_error_alert_title),
|
||||
getString(R.string.upload_connection_error_alert_detail),
|
||||
getString(R.string.ok),
|
||||
getString(R.string.cancel_upload),
|
||||
() -> {
|
||||
basicKvStore.putBoolean(keyForShowingAlertDialog, false);
|
||||
if (NetworkUtils.isInternetConnectionEstablished(activity)) {
|
||||
int sizeOfUploads = basicKvStore.getInt(
|
||||
UploadActivity.keyForCurrentUploadImagesSize);
|
||||
for (int i = indexOfFragment; i < sizeOfUploads; i++) {
|
||||
presenter.getImageQuality(i, inAppPictureLocation, activity);
|
||||
}
|
||||
} else {
|
||||
showConnectionErrorPopup();
|
||||
}
|
||||
},
|
||||
() -> {
|
||||
basicKvStore.putBoolean(keyForShowingAlertDialog, false);
|
||||
activity.finish();
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showExternalMap(@NotNull final UploadItem uploadItem) {
|
||||
goToLocationPickerActivity(uploadItem);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the image editing activity to edit the specified UploadItem.
|
||||
*
|
||||
* @param uploadItem The UploadItem to be edited.
|
||||
*
|
||||
* This method is called to start the image editing activity for a specific UploadItem.
|
||||
* It sets the UploadItem as the currently editable item, creates an intent to launch the
|
||||
* EditActivity, and passes the image file path as an extra in the intent. The activity
|
||||
* is started using resultLauncher that handles the result in respective callback.
|
||||
*/
|
||||
@Override
|
||||
public void showEditActivity(@NotNull UploadItem uploadItem) {
|
||||
editableUploadItem = uploadItem;
|
||||
Intent intent = new Intent(getContext(), EditActivity.class);
|
||||
intent.putExtra("image", uploadableFile.getFilePath().toString());
|
||||
startForEditActivityResult.launch(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Location picker activity. Show the location first then user can modify it by clicking
|
||||
* modify location button.
|
||||
* @param uploadItem current upload item
|
||||
*/
|
||||
private void goToLocationPickerActivity(final UploadItem uploadItem) {
|
||||
|
||||
editableUploadItem = uploadItem;
|
||||
double defaultLatitude = 37.773972;
|
||||
double defaultLongitude = -122.431297;
|
||||
double defaultZoom = 16.0;
|
||||
|
||||
final Intent locationPickerIntent;
|
||||
|
||||
/* Retrieve image location from EXIF if present or
|
||||
check if user has provided location while using the in-app camera.
|
||||
Use location of last UploadItem if none of them is available */
|
||||
if (uploadItem.getGpsCoords() != null && uploadItem.getGpsCoords()
|
||||
.getDecLatitude() != 0.0 && uploadItem.getGpsCoords().getDecLongitude() != 0.0) {
|
||||
defaultLatitude = uploadItem.getGpsCoords()
|
||||
.getDecLatitude();
|
||||
defaultLongitude = uploadItem.getGpsCoords().getDecLongitude();
|
||||
defaultZoom = uploadItem.getGpsCoords().getZoomLevel();
|
||||
|
||||
locationPickerIntent = new LocationPicker.IntentBuilder()
|
||||
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
|
||||
.activityKey("UploadActivity")
|
||||
.build(getActivity());
|
||||
} else {
|
||||
if (defaultKvStore.getString(LAST_LOCATION) != null) {
|
||||
final String[] locationLatLng
|
||||
= defaultKvStore.getString(LAST_LOCATION).split(",");
|
||||
defaultLatitude = Double.parseDouble(locationLatLng[0]);
|
||||
defaultLongitude = Double.parseDouble(locationLatLng[1]);
|
||||
}
|
||||
if (defaultKvStore.getString(LAST_ZOOM) != null) {
|
||||
defaultZoom = Double.parseDouble(defaultKvStore.getString(LAST_ZOOM));
|
||||
}
|
||||
|
||||
locationPickerIntent = new LocationPicker.IntentBuilder()
|
||||
.defaultLocation(new CameraPosition(defaultLatitude,defaultLongitude,defaultZoom))
|
||||
.activityKey("NoLocationUploadActivity")
|
||||
.build(getActivity());
|
||||
}
|
||||
startForResult.launch(locationPickerIntent);
|
||||
}
|
||||
|
||||
private void onCameraPosition(ActivityResult result){
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
|
||||
assert result.getData() != null;
|
||||
final CameraPosition cameraPosition = LocationPicker.getCameraPosition(result.getData());
|
||||
|
||||
if (cameraPosition != null) {
|
||||
|
||||
final String latitude = String.valueOf(cameraPosition.getLatitude());
|
||||
final String longitude = String.valueOf(cameraPosition.getLongitude());
|
||||
final double zoom = cameraPosition.getZoom();
|
||||
|
||||
editLocation(latitude, longitude, zoom);
|
||||
// If isMissingLocationDialog is true, it means that the user has already tapped the
|
||||
// "Next" button, so go directly to the next step.
|
||||
if (isMissingLocationDialog) {
|
||||
isMissingLocationDialog = false;
|
||||
onNextButtonClicked();
|
||||
}
|
||||
} else {
|
||||
// If camera position is null means location is removed by the user
|
||||
removeLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onVoiceInput(ActivityResult result) {
|
||||
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
||||
ArrayList<String> resultData = result.getData().getStringArrayListExtra(
|
||||
RecognizerIntent.EXTRA_RESULTS);
|
||||
uploadMediaDetailAdapter.handleSpeechResult(resultData.get(0));
|
||||
}else {
|
||||
Timber.e("Error %s", result.getResultCode());
|
||||
}
|
||||
}
|
||||
|
||||
private void onEditActivityResult(ActivityResult result){
|
||||
if (result.getResultCode() == RESULT_OK) {
|
||||
String path = result.getData().getStringExtra("editedImageFilePath");
|
||||
|
||||
if (Objects.equals(result, "Error")) {
|
||||
Timber.e("Error in rotating image");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (binding != null){
|
||||
binding.backgroundImage.setImageURI(Uri.fromFile(new File(path)));
|
||||
}
|
||||
editableUploadItem.setContentAndMediaUri(Uri.fromFile(new File(path)));
|
||||
callback.changeThumbnail(indexOfFragment,
|
||||
path);
|
||||
} catch (Exception e) {
|
||||
Timber.e(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the location data from the image, by setting them to null
|
||||
*/
|
||||
public void removeLocation() {
|
||||
editableUploadItem.getGpsCoords().setDecimalCoords(null);
|
||||
try {
|
||||
ExifInterface sourceExif = new ExifInterface(uploadableFile.getFilePath());
|
||||
String[] exifTags = {
|
||||
ExifInterface.TAG_GPS_LATITUDE,
|
||||
ExifInterface.TAG_GPS_LATITUDE_REF,
|
||||
ExifInterface.TAG_GPS_LONGITUDE,
|
||||
ExifInterface.TAG_GPS_LONGITUDE_REF,
|
||||
};
|
||||
|
||||
for (String tag : exifTags) {
|
||||
sourceExif.setAttribute(tag, null);
|
||||
}
|
||||
sourceExif.saveAttributes();
|
||||
|
||||
Drawable mapQuestion = getResources().getDrawable(R.drawable.ic_map_not_available_20dp);
|
||||
|
||||
if (binding != null) {
|
||||
binding.locationImageView.setImageDrawable(mapQuestion);
|
||||
binding.locationTextView.setText(R.string.add_location);
|
||||
}
|
||||
|
||||
editableUploadItem.getGpsCoords().setDecLatitude(0.0);
|
||||
editableUploadItem.getGpsCoords().setDecLongitude(0.0);
|
||||
editableUploadItem.getGpsCoords().setImageCoordsExists(false);
|
||||
hasUserRemovedLocation = true;
|
||||
|
||||
Toast.makeText(getContext(), getString(R.string.location_removed), Toast.LENGTH_LONG)
|
||||
.show();
|
||||
} catch (Exception e) {
|
||||
Timber.d(e);
|
||||
Toast.makeText(getContext(), "Location could not be removed due to internal error",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the old coordinates with new one
|
||||
* @param latitude new latitude
|
||||
* @param longitude new longitude
|
||||
*/
|
||||
public void editLocation(final String latitude, final String longitude, final double zoom) {
|
||||
|
||||
editableUploadItem.getGpsCoords().setDecLatitude(Double.parseDouble(latitude));
|
||||
editableUploadItem.getGpsCoords().setDecLongitude(Double.parseDouble(longitude));
|
||||
editableUploadItem.getGpsCoords().setDecimalCoords(latitude + "|" + longitude);
|
||||
editableUploadItem.getGpsCoords().setImageCoordsExists(true);
|
||||
editableUploadItem.getGpsCoords().setZoomLevel(zoom);
|
||||
|
||||
// Replace the map icon using the one with a green tick
|
||||
Drawable mapTick = getResources().getDrawable(R.drawable.ic_map_available_20dp);
|
||||
|
||||
if (binding != null) {
|
||||
binding.locationImageView.setImageDrawable(mapTick);
|
||||
binding.locationTextView.setText(R.string.edit_location);
|
||||
}
|
||||
|
||||
Toast.makeText(getContext(), getString(R.string.location_updated), Toast.LENGTH_LONG).show();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMediaDetails(@NotNull List<UploadMediaDetail> uploadMediaDetails) {
|
||||
uploadMediaDetailAdapter.setItems(uploadMediaDetails);
|
||||
showNearbyFound =
|
||||
showNearbyFound && (
|
||||
uploadMediaDetails == null || uploadMediaDetails.isEmpty()
|
||||
|| listContainsEmptyDetails(
|
||||
uploadMediaDetails));
|
||||
}
|
||||
|
||||
/**
|
||||
* if the media details that come in here are empty
|
||||
* (empty caption AND empty description, with caption being the decider here)
|
||||
* this method allows usage of nearby place caption and description if any
|
||||
* else it takes the media details saved in prior for this picture
|
||||
* @param uploadMediaDetails saved media details,
|
||||
* ex: in case when "copy to subsequent media" button is clicked
|
||||
* for a previous image
|
||||
* @return boolean whether the details are empty or not
|
||||
*/
|
||||
private boolean listContainsEmptyDetails(List<UploadMediaDetail> uploadMediaDetails) {
|
||||
for (UploadMediaDetail uploadDetail: uploadMediaDetails) {
|
||||
if (!TextUtils.isEmpty(uploadDetail.getCaptionText()) && !TextUtils.isEmpty(uploadDetail.getDescriptionText())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing dialog for adding location
|
||||
*
|
||||
* @param onSkipClicked proceed for verifying image quality
|
||||
*/
|
||||
@Override
|
||||
public void displayAddLocationDialog(@NotNull final Runnable onSkipClicked) {
|
||||
isMissingLocationDialog = true;
|
||||
DialogUtil.showAlertDialog(requireActivity(),
|
||||
getString(R.string.no_location_found_title),
|
||||
getString(R.string.no_location_found_message),
|
||||
getString(R.string.add_location),
|
||||
getString(R.string.skip_login),
|
||||
this::onIbMapClicked,
|
||||
onSkipClicked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
presenter.onDetachView();
|
||||
}
|
||||
|
||||
public void onLlContainerTitleClicked() {
|
||||
expandCollapseLlMediaDetail(!isExpanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* show hide media detail based on
|
||||
* @param shouldExpand
|
||||
*/
|
||||
private void expandCollapseLlMediaDetail(boolean shouldExpand){
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
binding.llContainerMediaDetail.setVisibility(shouldExpand ? View.VISIBLE : View.GONE);
|
||||
isExpanded = !isExpanded;
|
||||
binding.ibExpandCollapse.setRotation(binding.ibExpandCollapse.getRotation() + 180);
|
||||
}
|
||||
|
||||
public void onIbMapClicked() {
|
||||
if (callback == null) {
|
||||
return;
|
||||
}
|
||||
presenter.onMapIconClicked(indexOfFragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPrimaryCaptionTextChange(boolean isNotEmpty) {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
binding.btnCopySubsequentMedia.setEnabled(isNotEmpty);
|
||||
binding.btnCopySubsequentMedia.setClickable(isNotEmpty);
|
||||
binding.btnCopySubsequentMedia.setAlpha(isNotEmpty ? 1.0f : 0.5f);
|
||||
binding.btnNext.setEnabled(isNotEmpty);
|
||||
binding.btnNext.setClickable(isNotEmpty);
|
||||
binding.btnNext.setAlpha(isNotEmpty ? 1.0f : 0.5f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new language item to RecyclerView
|
||||
*/
|
||||
@Override
|
||||
public void addLanguage() {
|
||||
UploadMediaDetail uploadMediaDetail = new UploadMediaDetail();
|
||||
uploadMediaDetail.setManuallyAdded(true);//This was manually added by the user
|
||||
uploadMediaDetailAdapter.addDescription(uploadMediaDetail);
|
||||
binding.rvDescriptions.smoothScrollToPosition(uploadMediaDetailAdapter.getItemCount()-1);
|
||||
}
|
||||
|
||||
public interface UploadMediaDetailFragmentCallback extends Callback {
|
||||
|
||||
void deletePictureAtIndex(int index);
|
||||
|
||||
void changeThumbnail(int index, String uri);
|
||||
}
|
||||
|
||||
|
||||
public void onButtonCopyTitleDescToSubsequentMedia(){
|
||||
presenter.copyTitleAndDescriptionToSubsequentMedia(indexOfFragment);
|
||||
Toast.makeText(getContext(), getResources().getString(R.string.copied_successfully), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(final Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
if(uploadableFile!=null){
|
||||
outState.putParcelable(UPLOADABLE_FILE,uploadableFile);
|
||||
}
|
||||
if(uploadMediaDetailAdapter!=null){
|
||||
outState.putParcelableArrayList(UPLOAD_MEDIA_DETAILS,
|
||||
(ArrayList<? extends Parcelable>) uploadMediaDetailAdapter.getItems());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,904 @@
|
|||
package fr.free.nrw.commons.upload.mediaDetails
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.speech.RecognizerIntent
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import fr.free.nrw.commons.CameraPosition
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.contributions.MainActivity
|
||||
import fr.free.nrw.commons.databinding.FragmentUploadMediaDetailFragmentBinding
|
||||
import fr.free.nrw.commons.edit.EditActivity
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.locationpicker.LocationPicker
|
||||
import fr.free.nrw.commons.locationpicker.LocationPicker.getCameraPosition
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
|
||||
import fr.free.nrw.commons.settings.Prefs
|
||||
import fr.free.nrw.commons.upload.ImageCoordinates
|
||||
import fr.free.nrw.commons.upload.SimilarImageDialogFragment
|
||||
import fr.free.nrw.commons.upload.UploadActivity
|
||||
import fr.free.nrw.commons.upload.UploadBaseFragment
|
||||
import fr.free.nrw.commons.upload.UploadItem
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetail
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaPresenter.Companion.presenterCallback
|
||||
import fr.free.nrw.commons.utils.ActivityUtils.startActivityWithFlags
|
||||
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||
import fr.free.nrw.commons.utils.ImageUtils
|
||||
import fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK
|
||||
import fr.free.nrw.commons.utils.ImageUtils.getErrorMessageForResult
|
||||
import fr.free.nrw.commons.utils.NetworkUtils.isInternetConnectionEstablished
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
import java.util.Locale
|
||||
import java.util.Objects
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
class UploadMediaDetailFragment : UploadBaseFragment(), UploadMediaDetailsContract.View,
|
||||
UploadMediaDetailAdapter.EventListener {
|
||||
|
||||
private val startForResult = registerForActivityResult<Intent, ActivityResult>(
|
||||
ActivityResultContracts.StartActivityForResult(), ::onCameraPosition)
|
||||
|
||||
private val startForEditActivityResult = registerForActivityResult<Intent, ActivityResult>(
|
||||
ActivityResultContracts.StartActivityForResult(), ::onEditActivityResult)
|
||||
|
||||
private val voiceInputResultLauncher = registerForActivityResult<Intent, ActivityResult>(
|
||||
ActivityResultContracts.StartActivityForResult(), ::onVoiceInput)
|
||||
|
||||
@Inject
|
||||
lateinit var presenter: UploadMediaDetailsContract.UserActionListener
|
||||
|
||||
@Inject
|
||||
@field:Named("default_preferences")
|
||||
lateinit var defaultKvStore: JsonKvStore
|
||||
|
||||
@Inject
|
||||
lateinit var recentLanguagesDao: RecentLanguagesDao
|
||||
|
||||
/**
|
||||
* True when user removes location from the current image
|
||||
*/
|
||||
var hasUserRemovedLocation = false
|
||||
|
||||
/**
|
||||
* True if location is added via the "missing location" popup dialog (which appears after
|
||||
* tapping "Next" if the picture has no geographical coordinates).
|
||||
*/
|
||||
private var isMissingLocationDialog = false
|
||||
|
||||
/**
|
||||
* showNearbyFound will be true, if any nearby location found that needs pictures and the nearby
|
||||
* popup is yet to be shown Used to show and check if the nearby found popup is already shown
|
||||
*/
|
||||
private var showNearbyFound = false
|
||||
|
||||
/**
|
||||
* nearbyPlace holds the detail of nearby place that need pictures, if any found
|
||||
*/
|
||||
private var nearbyPlace: Place? = null
|
||||
private var uploadItem: UploadItem? = null
|
||||
|
||||
/**
|
||||
* inAppPictureLocation: use location recorded while using the in-app camera if device camera
|
||||
* does not record it in the EXIF
|
||||
*/
|
||||
var inAppPictureLocation: LatLng? = null
|
||||
|
||||
/**
|
||||
* editableUploadItem : Storing the upload item before going to update the coordinates
|
||||
*/
|
||||
private var editableUploadItem: UploadItem? = null
|
||||
|
||||
private var _binding: FragmentUploadMediaDetailFragmentBinding? = null
|
||||
private val binding: FragmentUploadMediaDetailFragmentBinding get() = _binding!!
|
||||
|
||||
private var basicKvStore: BasicKvStore? = null
|
||||
private val keyForShowingAlertDialog = "isNoNetworkAlertDialogShowing"
|
||||
private var uploadableFile: UploadableFile? = null
|
||||
private var place: Place? = null
|
||||
private lateinit var uploadMediaDetailAdapter: UploadMediaDetailAdapter
|
||||
var indexOfFragment = 0
|
||||
var isExpanded = true
|
||||
var fragmentCallback: UploadMediaDetailFragmentCallback? = null
|
||||
set(value) {
|
||||
field = value
|
||||
UploadMediaPresenter.presenterCallback = value
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null && uploadableFile == null) {
|
||||
uploadableFile = savedInstanceState.getParcelable(UPLOADABLE_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
fun setImageToBeUploaded(
|
||||
uploadableFile: UploadableFile?, place: Place?, inAppPictureLocation: LatLng?
|
||||
) {
|
||||
this.uploadableFile = uploadableFile
|
||||
this.place = place
|
||||
this.inAppPictureLocation = inAppPictureLocation
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentUploadMediaDetailFragmentBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
basicKvStore = BasicKvStore(requireActivity(), "CurrentUploadImageQualities")
|
||||
|
||||
if (fragmentCallback != null) {
|
||||
indexOfFragment = fragmentCallback!!.getIndexInViewFlipper(this)
|
||||
initializeFragment()
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (uploadMediaDetailAdapter.items.isEmpty() && fragmentCallback != null) {
|
||||
uploadMediaDetailAdapter.items = savedInstanceState.getParcelableArrayList(UPLOAD_MEDIA_DETAILS)!!
|
||||
presenter.setUploadMediaDetails(uploadMediaDetailAdapter.items, indexOfFragment)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (!presenter.getImageQuality(indexOfFragment, inAppPictureLocation, requireActivity())) {
|
||||
startActivityWithFlags(
|
||||
requireActivity(),
|
||||
MainActivity::class.java,
|
||||
Intent.FLAG_ACTIVITY_CLEAR_TOP,
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||
)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeFragment() {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
binding.tvTitle.text = getString(
|
||||
R.string.step_count, (indexOfFragment + 1),
|
||||
fragmentCallback!!.totalNumberOfSteps, getString(R.string.media_detail_step_title)
|
||||
)
|
||||
binding.tooltip.setOnClickListener {
|
||||
showInfoAlert(
|
||||
R.string.media_detail_step_title,
|
||||
R.string.media_details_tooltip
|
||||
)
|
||||
}
|
||||
presenter.onAttachView(this)
|
||||
presenter.receiveImage(uploadableFile, place, inAppPictureLocation)
|
||||
initRecyclerView()
|
||||
|
||||
with (binding){
|
||||
if (indexOfFragment == 0) {
|
||||
btnPrevious.isEnabled = false
|
||||
btnPrevious.alpha = 0.5f
|
||||
} else {
|
||||
btnPrevious.isEnabled = true
|
||||
btnPrevious.alpha = 1.0f
|
||||
}
|
||||
|
||||
// If the image EXIF data contains the location, show the map icon with a green tick
|
||||
if (inAppPictureLocation != null || (uploadableFile != null && uploadableFile!!.hasLocation())) {
|
||||
val mapTick =
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_available_20dp)
|
||||
locationImageView.setImageDrawable(mapTick)
|
||||
locationTextView.setText(R.string.edit_location)
|
||||
} else {
|
||||
// Otherwise, show the map icon with a red question mark
|
||||
val mapQuestionMark = ContextCompat.getDrawable(
|
||||
requireContext(),
|
||||
R.drawable.ic_map_not_available_20dp
|
||||
)
|
||||
locationImageView.setImageDrawable(mapQuestionMark)
|
||||
locationTextView.setText(R.string.add_location)
|
||||
}
|
||||
|
||||
//If this is the last media, we have nothing to copy, lets not show the button
|
||||
btnCopySubsequentMedia.visibility =
|
||||
if (indexOfFragment == fragmentCallback!!.totalNumberOfSteps - 4) {
|
||||
View.GONE
|
||||
} else {
|
||||
View.VISIBLE
|
||||
}
|
||||
|
||||
btnNext.setOnClickListener { presenter.displayLocDialog(indexOfFragment, inAppPictureLocation, hasUserRemovedLocation) }
|
||||
btnPrevious.setOnClickListener { fragmentCallback?.onPreviousButtonClicked(indexOfFragment) }
|
||||
llEditImage.setOnClickListener { presenter.onEditButtonClicked(indexOfFragment) }
|
||||
llContainerTitle.setOnClickListener { expandCollapseLlMediaDetail(!isExpanded) }
|
||||
llLocationStatus.setOnClickListener { presenter.onMapIconClicked(indexOfFragment) }
|
||||
btnCopySubsequentMedia.setOnClickListener { onButtonCopyTitleDescToSubsequentMedia() }
|
||||
}
|
||||
|
||||
attachImageViewScaleChangeListener()
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the scale change listener to the image view
|
||||
*/
|
||||
private fun attachImageViewScaleChangeListener() {
|
||||
binding.backgroundImage.setOnScaleChangeListener { _: Float, _: Float, _: Float ->
|
||||
//Whenever the uses plays with the image, lets collapse the media detail container
|
||||
//only if it is not already collapsed, which resolves flickering of arrow
|
||||
if (isExpanded) {
|
||||
expandCollapseLlMediaDetail(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* init the description recycler veiw and caption recyclerview
|
||||
*/
|
||||
private fun initRecyclerView() {
|
||||
uploadMediaDetailAdapter = UploadMediaDetailAdapter(
|
||||
this,
|
||||
defaultKvStore.getString(Prefs.DESCRIPTION_LANGUAGE, "")!!,
|
||||
recentLanguagesDao, voiceInputResultLauncher
|
||||
)
|
||||
uploadMediaDetailAdapter.callback =
|
||||
UploadMediaDetailAdapter.Callback { titleStringID: Int, messageStringId: Int ->
|
||||
showInfoAlert(titleStringID, messageStringId)
|
||||
}
|
||||
uploadMediaDetailAdapter.eventListener = this
|
||||
binding.rvDescriptions.layoutManager = LinearLayoutManager(context)
|
||||
binding.rvDescriptions.adapter = uploadMediaDetailAdapter
|
||||
}
|
||||
|
||||
private fun showInfoAlert(titleStringID: Int, messageStringId: Int) {
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(titleStringID),
|
||||
getString(messageStringId),
|
||||
getString(android.R.string.ok),
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
override fun showSimilarImageFragment(
|
||||
originalFilePath: String?, possibleFilePath: String?,
|
||||
similarImageCoordinates: ImageCoordinates?
|
||||
) {
|
||||
val basicKvStore = BasicKvStore(requireActivity(), "IsAnyImageCancelled")
|
||||
if (!basicKvStore.getBoolean("IsAnyImageCancelled", false)) {
|
||||
val newFragment = SimilarImageDialogFragment()
|
||||
newFragment.isCancelable = false
|
||||
newFragment.callback = object : SimilarImageDialogFragment.Callback {
|
||||
override fun onPositiveResponse() {
|
||||
Timber.d("positive response from similar image fragment")
|
||||
presenter.useSimilarPictureCoordinates(
|
||||
similarImageCoordinates!!,
|
||||
indexOfFragment
|
||||
)
|
||||
|
||||
// set the description text when user selects to use coordinate from the other image
|
||||
// which was taken within 120s
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/4700
|
||||
uploadMediaDetailAdapter.items[0].descriptionText =
|
||||
getString(R.string.similar_coordinate_description_auto_set)
|
||||
updateMediaDetails(uploadMediaDetailAdapter.items)
|
||||
|
||||
// Replace the 'Add location' button with 'Edit location' button when user clicks
|
||||
// yes in similar image dialog
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/5669
|
||||
val mapTick = ContextCompat.getDrawable(
|
||||
requireContext(),
|
||||
R.drawable.ic_map_available_20dp
|
||||
)
|
||||
binding.locationImageView.setImageDrawable(mapTick)
|
||||
binding.locationTextView.setText(R.string.edit_location)
|
||||
}
|
||||
|
||||
override fun onNegativeResponse() {
|
||||
Timber.d("negative response from similar image fragment")
|
||||
}
|
||||
}
|
||||
newFragment.arguments = bundleOf(
|
||||
"originalImagePath" to originalFilePath,
|
||||
"possibleImagePath" to possibleFilePath
|
||||
)
|
||||
newFragment.show(childFragmentManager, "dialog")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onImageProcessed(uploadItem: UploadItem) {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
binding.backgroundImage.setImageURI(uploadItem.mediaUri)
|
||||
}
|
||||
|
||||
override fun onNearbyPlaceFound(
|
||||
uploadItem: UploadItem, place: Place?
|
||||
) {
|
||||
nearbyPlace = place
|
||||
this.uploadItem = uploadItem
|
||||
showNearbyFound = true
|
||||
if (fragmentCallback == null) {
|
||||
return
|
||||
}
|
||||
if (indexOfFragment == 0) {
|
||||
if (UploadActivity.nearbyPopupAnswers!!.containsKey(nearbyPlace!!)) {
|
||||
val response = UploadActivity.nearbyPopupAnswers!![nearbyPlace!!]!!
|
||||
if (response) {
|
||||
if (fragmentCallback != null) {
|
||||
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
showNearbyPlaceFound(nearbyPlace!!)
|
||||
}
|
||||
showNearbyFound = false
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid") // To avoid the unwanted lint warning that string 'upload_nearby_place_found_description' is not of a valid format
|
||||
private fun showNearbyPlaceFound(place: Place) {
|
||||
val customLayout = layoutInflater.inflate(R.layout.custom_nearby_found, null)
|
||||
val nearbyFoundImage = customLayout.findViewById<ImageView>(R.id.nearbyItemImage)
|
||||
nearbyFoundImage.setImageURI(uploadItem!!.mediaUri)
|
||||
|
||||
val activity: Activity? = activity
|
||||
|
||||
if (activity is UploadActivity) {
|
||||
val isMultipleFilesSelected = activity.isMultipleFilesSelected
|
||||
|
||||
// Determine the message based on the selection status
|
||||
val message = if (isMultipleFilesSelected) {
|
||||
// Use plural message if multiple files are selected
|
||||
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
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
getString(R.string.upload_nearby_place_found_description_singular),
|
||||
place.getName()
|
||||
)
|
||||
}
|
||||
|
||||
// Show the AlertDialog with the determined message
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(R.string.upload_nearby_place_found_title),
|
||||
message,
|
||||
{
|
||||
// Execute when user confirms the upload is of the specified place
|
||||
UploadActivity.nearbyPopupAnswers!![place] = true
|
||||
presenter.onUserConfirmedUploadIsOfPlace(place, indexOfFragment)
|
||||
},
|
||||
{
|
||||
// Execute when user cancels the upload of the specified place
|
||||
UploadActivity.nearbyPopupAnswers!![place] = false
|
||||
},
|
||||
customLayout
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showProgress(shouldShow: Boolean) {
|
||||
if (fragmentCallback == null) {
|
||||
return
|
||||
}
|
||||
fragmentCallback!!.showProgress(shouldShow)
|
||||
}
|
||||
|
||||
override fun onImageValidationSuccess() {
|
||||
if (fragmentCallback == null) {
|
||||
return
|
||||
}
|
||||
fragmentCallback!!.onNextButtonClicked(indexOfFragment)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets called whenever the next/previous button is pressed
|
||||
*/
|
||||
override fun onBecameVisible() {
|
||||
super.onBecameVisible()
|
||||
if (fragmentCallback == null) {
|
||||
return
|
||||
}
|
||||
presenter.fetchTitleAndDescription(indexOfFragment)
|
||||
if (showNearbyFound) {
|
||||
if (UploadActivity.nearbyPopupAnswers!!.containsKey(nearbyPlace!!)) {
|
||||
val response = UploadActivity.nearbyPopupAnswers!![nearbyPlace!!]!!
|
||||
if (response) {
|
||||
presenter.onUserConfirmedUploadIsOfPlace(nearbyPlace, indexOfFragment)
|
||||
}
|
||||
} else {
|
||||
showNearbyPlaceFound(nearbyPlace!!)
|
||||
}
|
||||
showNearbyFound = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun showMessage(stringResourceId: Int, colorResourceId: Int) =
|
||||
showLongToast(requireContext(), stringResourceId)
|
||||
|
||||
override fun showMessage(message: String, colorResourceId: Int) =
|
||||
showLongToast(requireContext(), message)
|
||||
|
||||
override fun showDuplicatePicturePopup(uploadItem: UploadItem) {
|
||||
if (defaultKvStore.getBoolean("showDuplicatePicturePopup", true)) {
|
||||
val uploadTitleFormat = getString(R.string.upload_title_duplicate)
|
||||
val checkBoxView = View
|
||||
.inflate(activity, R.layout.nearby_permission_dialog, null)
|
||||
val checkBox = checkBoxView.findViewById<View>(R.id.never_ask_again) as CheckBox
|
||||
checkBox.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
defaultKvStore.putBoolean("showDuplicatePicturePopup", false)
|
||||
}
|
||||
}
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(R.string.duplicate_file_name),
|
||||
String.format(
|
||||
Locale.getDefault(),
|
||||
uploadTitleFormat,
|
||||
uploadItem.filename
|
||||
),
|
||||
getString(R.string.upload),
|
||||
getString(R.string.cancel),
|
||||
{
|
||||
uploadItem.imageQuality = ImageUtils.IMAGE_KEEP
|
||||
onImageValidationSuccess()
|
||||
}, null,
|
||||
checkBoxView
|
||||
)
|
||||
} else {
|
||||
uploadItem.imageQuality = ImageUtils.IMAGE_KEEP
|
||||
onImageValidationSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog alerting the user that internet connection is required for upload process
|
||||
* Does nothing if there is network connectivity and then the user presses okay
|
||||
*/
|
||||
override fun showConnectionErrorPopupForCaptionCheck() {
|
||||
showAlertDialog(requireActivity(),
|
||||
getString(R.string.upload_connection_error_alert_title),
|
||||
getString(R.string.upload_connection_error_alert_detail),
|
||||
getString(R.string.ok),
|
||||
getString(R.string.cancel_upload),
|
||||
{
|
||||
if (!isInternetConnectionEstablished(requireActivity())) {
|
||||
showConnectionErrorPopupForCaptionCheck()
|
||||
}
|
||||
},
|
||||
{
|
||||
requireActivity().finish()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog alerting the user that internet connection is required for upload process
|
||||
* Recalls UploadMediaPresenter.getImageQuality for all the next upload items,
|
||||
* if there is network connectivity and then the user presses okay
|
||||
*/
|
||||
override fun showConnectionErrorPopup() {
|
||||
try {
|
||||
val FLAG_ALERT_DIALOG_SHOWING = basicKvStore!!.getBoolean(
|
||||
keyForShowingAlertDialog, false
|
||||
)
|
||||
if (!FLAG_ALERT_DIALOG_SHOWING) {
|
||||
basicKvStore!!.putBoolean(keyForShowingAlertDialog, true)
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(R.string.upload_connection_error_alert_title),
|
||||
getString(R.string.upload_connection_error_alert_detail),
|
||||
getString(R.string.ok),
|
||||
getString(R.string.cancel_upload),
|
||||
{
|
||||
basicKvStore!!.putBoolean(keyForShowingAlertDialog, false)
|
||||
if (isInternetConnectionEstablished(requireActivity())) {
|
||||
val sizeOfUploads = basicKvStore!!.getInt(
|
||||
UploadActivity.keyForCurrentUploadImagesSize
|
||||
)
|
||||
for (i in indexOfFragment until sizeOfUploads) {
|
||||
presenter.getImageQuality(
|
||||
i,
|
||||
inAppPictureLocation,
|
||||
requireActivity()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
showConnectionErrorPopup()
|
||||
}
|
||||
},
|
||||
{
|
||||
basicKvStore!!.putBoolean(keyForShowingAlertDialog, false)
|
||||
requireActivity().finish()
|
||||
},
|
||||
null
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun showExternalMap(uploadItem: UploadItem) =
|
||||
goToLocationPickerActivity(uploadItem)
|
||||
|
||||
/**
|
||||
* Launches the image editing activity to edit the specified UploadItem.
|
||||
*
|
||||
* @param uploadItem The UploadItem to be edited.
|
||||
*
|
||||
* This method is called to start the image editing activity for a specific UploadItem.
|
||||
* It sets the UploadItem as the currently editable item, creates an intent to launch the
|
||||
* EditActivity, and passes the image file path as an extra in the intent. The activity
|
||||
* is started using resultLauncher that handles the result in respective callback.
|
||||
*/
|
||||
override fun showEditActivity(uploadItem: UploadItem) {
|
||||
editableUploadItem = uploadItem
|
||||
val intent = Intent(context, EditActivity::class.java)
|
||||
intent.putExtra("image", uploadableFile!!.getFilePath().toString())
|
||||
startForEditActivityResult.launch(intent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start Location picker activity. Show the location first then user can modify it by clicking
|
||||
* modify location button.
|
||||
* @param uploadItem current upload item
|
||||
*/
|
||||
private fun goToLocationPickerActivity(uploadItem: UploadItem) {
|
||||
editableUploadItem = uploadItem
|
||||
var defaultLatitude = 37.773972
|
||||
var defaultLongitude = -122.431297
|
||||
var defaultZoom = 16.0
|
||||
|
||||
val locationPickerIntent: Intent
|
||||
|
||||
/* Retrieve image location from EXIF if present or
|
||||
check if user has provided location while using the in-app camera.
|
||||
Use location of last UploadItem if none of them is available */
|
||||
if (uploadItem.gpsCoords != null && uploadItem.gpsCoords!!
|
||||
.decLatitude != 0.0 && uploadItem.gpsCoords!!.decLongitude != 0.0
|
||||
) {
|
||||
defaultLatitude = uploadItem.gpsCoords!!
|
||||
.decLatitude
|
||||
defaultLongitude = uploadItem.gpsCoords!!.decLongitude
|
||||
defaultZoom = uploadItem.gpsCoords!!.zoomLevel
|
||||
|
||||
locationPickerIntent = LocationPicker.IntentBuilder()
|
||||
.defaultLocation(CameraPosition(defaultLatitude, defaultLongitude, defaultZoom))
|
||||
.activityKey("UploadActivity")
|
||||
.build(requireActivity())
|
||||
} else {
|
||||
if (defaultKvStore.getString(LAST_LOCATION) != null) {
|
||||
val locationLatLng = defaultKvStore.getString(LAST_LOCATION)!!
|
||||
.split(",".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
defaultLatitude = locationLatLng[0].toDouble()
|
||||
defaultLongitude = locationLatLng[1].toDouble()
|
||||
}
|
||||
if (defaultKvStore.getString(LAST_ZOOM) != null) {
|
||||
defaultZoom = defaultKvStore.getString(LAST_ZOOM)!!
|
||||
.toDouble()
|
||||
}
|
||||
|
||||
locationPickerIntent = LocationPicker.IntentBuilder()
|
||||
.defaultLocation(CameraPosition(defaultLatitude, defaultLongitude, defaultZoom))
|
||||
.activityKey("NoLocationUploadActivity")
|
||||
.build(requireActivity())
|
||||
}
|
||||
startForResult.launch(locationPickerIntent)
|
||||
}
|
||||
|
||||
private fun onCameraPosition(result: ActivityResult) {
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
checkNotNull(result.data)
|
||||
val cameraPosition = getCameraPosition(
|
||||
result.data!!
|
||||
)
|
||||
|
||||
if (cameraPosition != null) {
|
||||
val latitude = cameraPosition.latitude.toString()
|
||||
val longitude = cameraPosition.longitude.toString()
|
||||
val zoom = cameraPosition.zoom
|
||||
|
||||
editLocation(latitude, longitude, zoom)
|
||||
// If isMissingLocationDialog is true, it means that the user has already tapped the
|
||||
// "Next" button, so go directly to the next step.
|
||||
if (isMissingLocationDialog) {
|
||||
isMissingLocationDialog = false
|
||||
presenter.displayLocDialog(
|
||||
indexOfFragment,
|
||||
inAppPictureLocation,
|
||||
hasUserRemovedLocation
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// If camera position is null means location is removed by the user
|
||||
removeLocation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onVoiceInput(result: ActivityResult) {
|
||||
if (result.resultCode == Activity.RESULT_OK && result.data != null) {
|
||||
val resultData = result.data!!.getStringArrayListExtra(
|
||||
RecognizerIntent.EXTRA_RESULTS
|
||||
)
|
||||
uploadMediaDetailAdapter.handleSpeechResult(resultData!![0])
|
||||
} else {
|
||||
Timber.e("Error %s", result.resultCode)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEditActivityResult(result: ActivityResult) {
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val path = result.data!!.getStringExtra("editedImageFilePath")
|
||||
|
||||
if (Objects.equals(result, "Error")) {
|
||||
Timber.e("Error in rotating image")
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (_binding != null) {
|
||||
binding.backgroundImage.setImageURI(Uri.fromFile(File(path!!)))
|
||||
}
|
||||
editableUploadItem!!.setContentAndMediaUri(Uri.fromFile(File(path!!)))
|
||||
fragmentCallback!!.changeThumbnail(
|
||||
indexOfFragment,
|
||||
path
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the location data from the image, by setting them to null
|
||||
*/
|
||||
private fun removeLocation() {
|
||||
editableUploadItem!!.gpsCoords!!.decimalCoords = null
|
||||
try {
|
||||
val sourceExif = ExifInterface(
|
||||
uploadableFile!!.getFilePath()
|
||||
)
|
||||
val exifTags = arrayOf(
|
||||
ExifInterface.TAG_GPS_LATITUDE,
|
||||
ExifInterface.TAG_GPS_LATITUDE_REF,
|
||||
ExifInterface.TAG_GPS_LONGITUDE,
|
||||
ExifInterface.TAG_GPS_LONGITUDE_REF,
|
||||
)
|
||||
|
||||
for (tag in exifTags) {
|
||||
sourceExif.setAttribute(tag, null)
|
||||
}
|
||||
sourceExif.saveAttributes()
|
||||
|
||||
val mapQuestion =
|
||||
ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_not_available_20dp)
|
||||
|
||||
if (_binding != null) {
|
||||
binding.locationImageView.setImageDrawable(mapQuestion)
|
||||
binding.locationTextView.setText(R.string.add_location)
|
||||
}
|
||||
|
||||
editableUploadItem!!.gpsCoords!!.decLatitude = 0.0
|
||||
editableUploadItem!!.gpsCoords!!.decLongitude = 0.0
|
||||
editableUploadItem!!.gpsCoords!!.imageCoordsExists = false
|
||||
hasUserRemovedLocation = true
|
||||
|
||||
Toast.makeText(context, getString(R.string.location_removed), Toast.LENGTH_LONG)
|
||||
.show()
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e)
|
||||
Toast.makeText(
|
||||
context, "Location could not be removed due to internal error",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the old coordinates with new one
|
||||
* @param latitude new latitude
|
||||
* @param longitude new longitude
|
||||
*/
|
||||
fun editLocation(latitude: String, longitude: String, zoom: Double) {
|
||||
editableUploadItem!!.gpsCoords!!.decLatitude = latitude.toDouble()
|
||||
editableUploadItem!!.gpsCoords!!.decLongitude = longitude.toDouble()
|
||||
editableUploadItem!!.gpsCoords!!.decimalCoords = "$latitude|$longitude"
|
||||
editableUploadItem!!.gpsCoords!!.imageCoordsExists = true
|
||||
editableUploadItem!!.gpsCoords!!.zoomLevel = zoom
|
||||
|
||||
// Replace the map icon using the one with a green tick
|
||||
val mapTick = ContextCompat.getDrawable(requireContext(), R.drawable.ic_map_available_20dp)
|
||||
|
||||
if (_binding != null) {
|
||||
binding.locationImageView.setImageDrawable(mapTick)
|
||||
binding.locationTextView.setText(R.string.edit_location)
|
||||
}
|
||||
|
||||
Toast.makeText(context, getString(R.string.location_updated), Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
override fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail>) {
|
||||
uploadMediaDetailAdapter.items = uploadMediaDetails
|
||||
showNearbyFound =
|
||||
showNearbyFound && (uploadMediaDetails.isEmpty() || listContainsEmptyDetails(
|
||||
uploadMediaDetails
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
* if the media details that come in here are empty
|
||||
* (empty caption AND empty description, with caption being the decider here)
|
||||
* this method allows usage of nearby place caption and description if any
|
||||
* else it takes the media details saved in prior for this picture
|
||||
* @param uploadMediaDetails saved media details,
|
||||
* ex: in case when "copy to subsequent media" button is clicked
|
||||
* for a previous image
|
||||
* @return boolean whether the details are empty or not
|
||||
*/
|
||||
private fun listContainsEmptyDetails(uploadMediaDetails: List<UploadMediaDetail>): Boolean {
|
||||
for ((_, descriptionText, captionText) in uploadMediaDetails) {
|
||||
if (!TextUtils.isEmpty(captionText) && !TextUtils.isEmpty(
|
||||
descriptionText
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing dialog for adding location
|
||||
*
|
||||
* @param runnable proceed for verifying image quality
|
||||
*/
|
||||
override fun displayAddLocationDialog(runnable: Runnable) {
|
||||
isMissingLocationDialog = true
|
||||
showAlertDialog(
|
||||
requireActivity(),
|
||||
getString(R.string.no_location_found_title),
|
||||
getString(R.string.no_location_found_message),
|
||||
getString(R.string.add_location),
|
||||
getString(R.string.skip_login),
|
||||
{
|
||||
presenter.onMapIconClicked(indexOfFragment)
|
||||
},
|
||||
runnable
|
||||
)
|
||||
}
|
||||
|
||||
override fun createBasicKvStore(storeName: String): BasicKvStore =
|
||||
BasicKvStore(requireActivity(), storeName)
|
||||
|
||||
override fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem) {
|
||||
//If the error message is null, we will probably not show anything
|
||||
val activity = requireActivity()
|
||||
val errorMessageForResult = getErrorMessageForResult(activity, errorCode)
|
||||
if (errorMessageForResult.isNotEmpty()) {
|
||||
showAlertDialog(
|
||||
activity,
|
||||
activity.getString(R.string.upload_problem_image),
|
||||
errorMessageForResult,
|
||||
activity.getString(R.string.upload),
|
||||
activity.getString(R.string.cancel),
|
||||
{
|
||||
showProgress(false)
|
||||
uploadItem.imageQuality = IMAGE_OK
|
||||
},
|
||||
{
|
||||
presenterCallback!!.deletePictureAtIndex(index)
|
||||
}
|
||||
)?.setCancelable(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
presenter.onDetachView()
|
||||
}
|
||||
|
||||
fun expandCollapseLlMediaDetail(shouldExpand: Boolean) {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
binding.llContainerMediaDetail.visibility =
|
||||
if (shouldExpand) View.VISIBLE else View.GONE
|
||||
isExpanded = !isExpanded
|
||||
binding.ibExpandCollapse.rotation = binding.ibExpandCollapse.rotation + 180
|
||||
}
|
||||
|
||||
override fun onPrimaryCaptionTextChange(isNotEmpty: Boolean) {
|
||||
if (_binding == null) {
|
||||
return
|
||||
}
|
||||
binding.btnCopySubsequentMedia.isEnabled = isNotEmpty
|
||||
binding.btnCopySubsequentMedia.isClickable = isNotEmpty
|
||||
binding.btnCopySubsequentMedia.alpha = if (isNotEmpty) 1.0f else 0.5f
|
||||
binding.btnNext.isEnabled = isNotEmpty
|
||||
binding.btnNext.isClickable = isNotEmpty
|
||||
binding.btnNext.alpha = if (isNotEmpty) 1.0f else 0.5f
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new language item to RecyclerView
|
||||
*/
|
||||
override fun addLanguage() {
|
||||
val uploadMediaDetail = UploadMediaDetail()
|
||||
uploadMediaDetail.isManuallyAdded = true //This was manually added by the user
|
||||
uploadMediaDetailAdapter.addDescription(uploadMediaDetail)
|
||||
binding.rvDescriptions.smoothScrollToPosition(uploadMediaDetailAdapter.itemCount - 1)
|
||||
}
|
||||
|
||||
fun onButtonCopyTitleDescToSubsequentMedia() {
|
||||
presenter.copyTitleAndDescriptionToSubsequentMedia(indexOfFragment)
|
||||
Toast.makeText(context, R.string.copied_successfully, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
if (uploadableFile != null) {
|
||||
outState.putParcelable(UPLOADABLE_FILE, uploadableFile)
|
||||
}
|
||||
outState.putParcelableArrayList(
|
||||
UPLOAD_MEDIA_DETAILS,
|
||||
ArrayList(uploadMediaDetailAdapter.items)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
interface UploadMediaDetailFragmentCallback : Callback {
|
||||
fun deletePictureAtIndex(index: Int)
|
||||
|
||||
fun changeThumbnail(index: Int, uri: String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* A key for applicationKvStore. By this key we can retrieve the location of last UploadItem ex.
|
||||
* 12.3433,54.78897 from applicationKvStore.
|
||||
*/
|
||||
const val LAST_LOCATION: String = "last_location_while_uploading"
|
||||
const val LAST_ZOOM: String = "last_zoom_level_while_uploading"
|
||||
const val UPLOADABLE_FILE: String = "uploadable_file"
|
||||
const val UPLOAD_MEDIA_DETAILS: String = "upload_media_detail_adapter"
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload.mediaDetails
|
|||
import android.app.Activity
|
||||
import fr.free.nrw.commons.BasePresenter
|
||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.ImageCoordinates
|
||||
|
|
@ -25,7 +26,7 @@ interface UploadMediaDetailsContract {
|
|||
|
||||
fun showMessage(stringResourceId: Int, colorResourceId: Int)
|
||||
|
||||
fun showMessage(message: String?, colorResourceId: Int)
|
||||
fun showMessage(message: String, colorResourceId: Int)
|
||||
|
||||
fun showDuplicatePicturePopup(uploadItem: UploadItem)
|
||||
|
||||
|
|
@ -49,6 +50,10 @@ interface UploadMediaDetailsContract {
|
|||
fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail>)
|
||||
|
||||
fun displayAddLocationDialog(runnable: Runnable)
|
||||
|
||||
fun createBasicKvStore(storeName: String): BasicKvStore
|
||||
|
||||
fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem)
|
||||
}
|
||||
|
||||
interface UserActionListener : BasePresenter<View?> {
|
||||
|
|
|
|||
|
|
@ -336,8 +336,7 @@ class UploadMediaPresenter @Inject constructor(
|
|||
*/
|
||||
override fun checkImageQuality(uploadItem: UploadItem, index: Int) {
|
||||
if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) {
|
||||
val store = BasicKvStore(
|
||||
UploadMediaDetailFragment.activity,
|
||||
val store = view.createBasicKvStore(
|
||||
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||
)
|
||||
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
||||
|
|
@ -363,8 +362,7 @@ class UploadMediaPresenter @Inject constructor(
|
|||
* @param index Index of the UploadItem which was deleted
|
||||
*/
|
||||
override fun updateImageQualitiesJSON(size: Int, index: Int) {
|
||||
val store = BasicKvStore(
|
||||
UploadMediaDetailFragment.activity,
|
||||
val store = view.createBasicKvStore(
|
||||
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||
)
|
||||
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
||||
|
|
@ -399,36 +397,7 @@ class UploadMediaPresenter @Inject constructor(
|
|||
|
||||
// If image has some other problems, show popup accordingly
|
||||
if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) {
|
||||
showBadImagePopup(errorCode, index, UploadMediaDetailFragment.activity, uploadItem)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a dialog describing the potential problems in the current image
|
||||
*
|
||||
* @param errorCode Has the potential problems in the current image
|
||||
* @param index Index of the UploadItem which has problems
|
||||
* @param activity Context reference
|
||||
* @param uploadItem UploadItem which has problems
|
||||
*/
|
||||
private fun showBadImagePopup(
|
||||
errorCode: Int, index: Int, activity: Activity, uploadItem: UploadItem
|
||||
) {
|
||||
//If the error message is null, we will probably not show anything
|
||||
val errorMessageForResult = getErrorMessageForResult(activity, errorCode)
|
||||
if (errorMessageForResult.isNotEmpty()) {
|
||||
showAlertDialog(activity,
|
||||
activity.getString(R.string.upload_problem_image),
|
||||
errorMessageForResult,
|
||||
activity.getString(R.string.upload),
|
||||
activity.getString(R.string.cancel),
|
||||
{
|
||||
view.showProgress(false)
|
||||
uploadItem.imageQuality = IMAGE_OK
|
||||
}, {
|
||||
presenterCallback!!.deletePictureAtIndex(index)
|
||||
}
|
||||
)?.setCancelable(false)
|
||||
view.showBadImagePopup(errorCode, index, uploadItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import com.nhaarman.mockitokotlin2.verify
|
|||
import fr.free.nrw.commons.CameraPosition
|
||||
import fr.free.nrw.commons.TestCommonsApplication
|
||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_LOCATION
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
|
||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.Assert
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import fr.free.nrw.commons.upload.ImageCoordinates
|
|||
import fr.free.nrw.commons.upload.UploadActivity
|
||||
import fr.free.nrw.commons.upload.UploadItem
|
||||
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM
|
||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
|
@ -153,12 +153,6 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
Assert.assertNotNull(fragment)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testSetCallback() {
|
||||
fragment.setCallback(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnCreate() {
|
||||
|
|
@ -229,22 +223,6 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
method.invoke(fragment, R.string.media_detail_step_title, R.string.media_details_tooltip)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnNextButtonClicked() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
Whitebox.setInternalState(fragment, "presenter", presenter)
|
||||
fragment.onNextButtonClicked()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnPreviousButtonClicked() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
Whitebox.setInternalState(fragment, "presenter", presenter)
|
||||
fragment.onPreviousButtonClicked()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testShowSimilarImageFragment() {
|
||||
|
|
@ -366,7 +344,10 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
||||
val activityResult = ActivityResult(Activity.RESULT_OK, intent)
|
||||
|
||||
val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod("onCameraPosition", ActivityResult::class.java)
|
||||
val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||
"onCameraPosition",
|
||||
ActivityResult::class.java
|
||||
)
|
||||
handleResultMethod.isAccessible = true
|
||||
|
||||
handleResultMethod.invoke(fragment, activityResult)
|
||||
|
|
@ -382,7 +363,7 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
val cameraPosition = Mockito.mock(CameraPosition::class.java)
|
||||
val latLng = Mockito.mock(LatLng::class.java)
|
||||
|
||||
Whitebox.setInternalState(fragment, "callback", callback)
|
||||
Whitebox.setInternalState(fragment, "fragmentCallback", callback)
|
||||
Whitebox.setInternalState(cameraPosition, "latitude", latLng.latitude)
|
||||
Whitebox.setInternalState(cameraPosition, "longitude", latLng.longitude)
|
||||
Whitebox.setInternalState(fragment, "editableUploadItem", uploadItem)
|
||||
|
|
@ -394,9 +375,12 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
`when`(latLng.longitude).thenReturn(0.0)
|
||||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
||||
|
||||
val activityResult = ActivityResult(Activity.RESULT_OK,intent)
|
||||
val activityResult = ActivityResult(Activity.RESULT_OK, intent)
|
||||
|
||||
val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod("onCameraPosition", ActivityResult::class.java)
|
||||
val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod(
|
||||
"onCameraPosition",
|
||||
ActivityResult::class.java
|
||||
)
|
||||
handleResultMethod.isAccessible = true
|
||||
|
||||
handleResultMethod.invoke(fragment, activityResult)
|
||||
|
|
@ -417,21 +401,6 @@ class UploadMediaDetailFragmentUnitTest {
|
|||
fragment.onDestroyView()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnLlContainerTitleClicked() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
fragment.onLlContainerTitleClicked()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnIbMapClicked() {
|
||||
Shadows.shadowOf(Looper.getMainLooper()).idle()
|
||||
Whitebox.setInternalState(fragment, "presenter", presenter)
|
||||
fragment.onIbMapClicked()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Throws(Exception::class)
|
||||
fun testOnPrimaryCaptionTextChange() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue