mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-29 22:03:55 +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.LocationPermissionsHelper.LocationPermissionCallback
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager
|
import fr.free.nrw.commons.location.LocationServiceManager
|
||||||
import fr.free.nrw.commons.theme.BaseActivity
|
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.Companion.LAST_LOCATION
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
|
||||||
import fr.free.nrw.commons.utils.DialogUtil
|
import fr.free.nrw.commons.utils.DialogUtil
|
||||||
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
|
import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
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.
|
* ensuring that the thumbnail change is reflected in the UI.
|
||||||
*
|
*
|
||||||
* @param index The index of the UploadableFile to be updated.
|
* @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.removeAt(index)
|
||||||
uploadableFiles.add(index, UploadableFile(File(filepath)))
|
uploadableFiles.add(index, UploadableFile(File(uri)))
|
||||||
binding.rvThumbnails.adapter!!.notifyDataSetChanged()
|
binding.rvThumbnails.adapter!!.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -544,9 +544,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C
|
||||||
|
|
||||||
if (isFragmentsSaved) {
|
if (isFragmentsSaved) {
|
||||||
val fragment = fragments!![0] as UploadMediaDetailFragment?
|
val fragment = fragments!![0] as UploadMediaDetailFragment?
|
||||||
fragment!!.setCallback(uploadMediaDetailFragmentCallback)
|
fragment!!.fragmentCallback = uploadMediaDetailFragmentCallback
|
||||||
} else {
|
} else {
|
||||||
uploadMediaDetailFragment.setCallback(uploadMediaDetailFragmentCallback)
|
uploadMediaDetailFragment.fragmentCallback = uploadMediaDetailFragmentCallback
|
||||||
fragments!!.add(uploadMediaDetailFragment)
|
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 android.app.Activity
|
||||||
import fr.free.nrw.commons.BasePresenter
|
import fr.free.nrw.commons.BasePresenter
|
||||||
import fr.free.nrw.commons.filepicker.UploadableFile
|
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.location.LatLng
|
||||||
import fr.free.nrw.commons.nearby.Place
|
import fr.free.nrw.commons.nearby.Place
|
||||||
import fr.free.nrw.commons.upload.ImageCoordinates
|
import fr.free.nrw.commons.upload.ImageCoordinates
|
||||||
|
|
@ -25,7 +26,7 @@ interface UploadMediaDetailsContract {
|
||||||
|
|
||||||
fun showMessage(stringResourceId: Int, colorResourceId: Int)
|
fun showMessage(stringResourceId: Int, colorResourceId: Int)
|
||||||
|
|
||||||
fun showMessage(message: String?, colorResourceId: Int)
|
fun showMessage(message: String, colorResourceId: Int)
|
||||||
|
|
||||||
fun showDuplicatePicturePopup(uploadItem: UploadItem)
|
fun showDuplicatePicturePopup(uploadItem: UploadItem)
|
||||||
|
|
||||||
|
|
@ -49,6 +50,10 @@ interface UploadMediaDetailsContract {
|
||||||
fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail>)
|
fun updateMediaDetails(uploadMediaDetails: List<UploadMediaDetail>)
|
||||||
|
|
||||||
fun displayAddLocationDialog(runnable: Runnable)
|
fun displayAddLocationDialog(runnable: Runnable)
|
||||||
|
|
||||||
|
fun createBasicKvStore(storeName: String): BasicKvStore
|
||||||
|
|
||||||
|
fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserActionListener : BasePresenter<View?> {
|
interface UserActionListener : BasePresenter<View?> {
|
||||||
|
|
|
||||||
|
|
@ -336,8 +336,7 @@ class UploadMediaPresenter @Inject constructor(
|
||||||
*/
|
*/
|
||||||
override fun checkImageQuality(uploadItem: UploadItem, index: Int) {
|
override fun checkImageQuality(uploadItem: UploadItem, index: Int) {
|
||||||
if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) {
|
if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) {
|
||||||
val store = BasicKvStore(
|
val store = view.createBasicKvStore(
|
||||||
UploadMediaDetailFragment.activity,
|
|
||||||
UploadActivity.storeNameForCurrentUploadImagesSize
|
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||||
)
|
)
|
||||||
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
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
|
* @param index Index of the UploadItem which was deleted
|
||||||
*/
|
*/
|
||||||
override fun updateImageQualitiesJSON(size: Int, index: Int) {
|
override fun updateImageQualitiesJSON(size: Int, index: Int) {
|
||||||
val store = BasicKvStore(
|
val store = view.createBasicKvStore(
|
||||||
UploadMediaDetailFragment.activity,
|
|
||||||
UploadActivity.storeNameForCurrentUploadImagesSize
|
UploadActivity.storeNameForCurrentUploadImagesSize
|
||||||
)
|
)
|
||||||
val value = store.getString(UPLOAD_QUALITIES_KEY, null)
|
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 image has some other problems, show popup accordingly
|
||||||
if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) {
|
if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) {
|
||||||
showBadImagePopup(errorCode, index, UploadMediaDetailFragment.activity, uploadItem)
|
view.showBadImagePopup(errorCode, index, 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,8 @@ import com.nhaarman.mockitokotlin2.verify
|
||||||
import fr.free.nrw.commons.CameraPosition
|
import fr.free.nrw.commons.CameraPosition
|
||||||
import fr.free.nrw.commons.TestCommonsApplication
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore
|
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.Companion.LAST_LOCATION
|
||||||
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM
|
import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM
|
||||||
import io.reactivex.android.plugins.RxAndroidPlugins
|
import io.reactivex.android.plugins.RxAndroidPlugins
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import org.junit.Assert
|
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.UploadActivity
|
||||||
import fr.free.nrw.commons.upload.UploadItem
|
import fr.free.nrw.commons.upload.UploadItem
|
||||||
import fr.free.nrw.commons.upload.UploadMediaDetailAdapter
|
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.Assert
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -153,12 +153,6 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
Assert.assertNotNull(fragment)
|
Assert.assertNotNull(fragment)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Throws(Exception::class)
|
|
||||||
fun testSetCallback() {
|
|
||||||
fragment.setCallback(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnCreate() {
|
fun testOnCreate() {
|
||||||
|
|
@ -229,22 +223,6 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
method.invoke(fragment, R.string.media_detail_step_title, R.string.media_details_tooltip)
|
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
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testShowSimilarImageFragment() {
|
fun testShowSimilarImageFragment() {
|
||||||
|
|
@ -366,7 +344,10 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
`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.isAccessible = true
|
||||||
|
|
||||||
handleResultMethod.invoke(fragment, activityResult)
|
handleResultMethod.invoke(fragment, activityResult)
|
||||||
|
|
@ -382,7 +363,7 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
val cameraPosition = Mockito.mock(CameraPosition::class.java)
|
val cameraPosition = Mockito.mock(CameraPosition::class.java)
|
||||||
val latLng = Mockito.mock(LatLng::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, "latitude", latLng.latitude)
|
||||||
Whitebox.setInternalState(cameraPosition, "longitude", latLng.longitude)
|
Whitebox.setInternalState(cameraPosition, "longitude", latLng.longitude)
|
||||||
Whitebox.setInternalState(fragment, "editableUploadItem", uploadItem)
|
Whitebox.setInternalState(fragment, "editableUploadItem", uploadItem)
|
||||||
|
|
@ -394,9 +375,12 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
`when`(latLng.longitude).thenReturn(0.0)
|
`when`(latLng.longitude).thenReturn(0.0)
|
||||||
`when`(uploadItem.gpsCoords).thenReturn(imageCoordinates)
|
`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.isAccessible = true
|
||||||
|
|
||||||
handleResultMethod.invoke(fragment, activityResult)
|
handleResultMethod.invoke(fragment, activityResult)
|
||||||
|
|
@ -417,21 +401,6 @@ class UploadMediaDetailFragmentUnitTest {
|
||||||
fragment.onDestroyView()
|
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
|
@Test
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnPrimaryCaptionTextChange() {
|
fun testOnPrimaryCaptionTextChange() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue