From b9c2d79fe7f82140a9844b028890e063add8de1e Mon Sep 17 00:00:00 2001 From: Paul Hawke Date: Tue, 31 Dec 2024 17:10:45 -0600 Subject: [PATCH] Convert UploadMediaDetailFragment to kotlin --- .../locationpicker/LocationPickerActivity.kt | 4 +- .../free/nrw/commons/upload/UploadActivity.kt | 10 +- .../UploadMediaDetailFragment.java | 924 ------------------ .../mediaDetails/UploadMediaDetailFragment.kt | 904 +++++++++++++++++ .../UploadMediaDetailsContract.kt | 7 +- .../mediaDetails/UploadMediaPresenter.kt | 37 +- .../LocationPickerActivityUnitTests.kt | 4 +- .../UploadMediaDetailFragmentUnitTest.kt | 53 +- 8 files changed, 933 insertions(+), 1010 deletions(-) delete mode 100644 app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java create mode 100644 app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt diff --git a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt index 69e4eee64..080bc058d 100644 --- a/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/locationpicker/LocationPickerActivity.kt @@ -41,8 +41,8 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback import fr.free.nrw.commons.location.LocationServiceManager import fr.free.nrw.commons.theme.BaseActivity -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_LOCATION +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM import fr.free.nrw.commons.utils.DialogUtil import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL import io.reactivex.android.schedulers.AndroidSchedulers diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt index bc4704147..5fdcc4301 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.kt @@ -511,11 +511,11 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C * ensuring that the thumbnail change is reflected in the UI. * * @param index The index of the UploadableFile to be updated. - * @param filepath The file path of the new thumbnail image. + * @param uri The file path of the new thumbnail image. */ - override fun changeThumbnail(index: Int, filepath: String) { + override fun changeThumbnail(index: Int, uri: String) { uploadableFiles.removeAt(index) - uploadableFiles.add(index, UploadableFile(File(filepath))) + uploadableFiles.add(index, UploadableFile(File(uri))) binding.rvThumbnails.adapter!!.notifyDataSetChanged() } @@ -544,9 +544,9 @@ class UploadActivity : BaseActivity(), UploadContract.View, UploadBaseFragment.C if (isFragmentsSaved) { val fragment = fragments!![0] as UploadMediaDetailFragment? - fragment!!.setCallback(uploadMediaDetailFragmentCallback) + fragment!!.fragmentCallback = uploadMediaDetailFragmentCallback } else { - uploadMediaDetailFragment.setCallback(uploadMediaDetailFragmentCallback) + uploadMediaDetailFragment.fragmentCallback = uploadMediaDetailFragmentCallback fragments!!.add(uploadMediaDetailFragment) } } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java deleted file mode 100644 index 90e721302..000000000 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ /dev/null @@ -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 startForResult = registerForActivityResult( - new StartActivityForResult(), result -> { - onCameraPosition(result); - }); - - private final ActivityResultLauncher startForEditActivityResult = registerForActivityResult( - new StartActivityForResult(), result -> { - onEditActivityResult(result); - } - ); - - private final ActivityResultLauncher 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 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 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 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) uploadMediaDetailAdapter.getItems()); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - binding = null; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt new file mode 100644 index 000000000..173805f33 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.kt @@ -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( + ActivityResultContracts.StartActivityForResult(), ::onCameraPosition) + + private val startForEditActivityResult = registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), ::onEditActivityResult) + + private val voiceInputResultLauncher = registerForActivityResult( + 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(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(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) { + 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): 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" + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt index 7bdd6b9df..33262d4f1 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailsContract.kt @@ -3,6 +3,7 @@ package fr.free.nrw.commons.upload.mediaDetails import android.app.Activity import fr.free.nrw.commons.BasePresenter import fr.free.nrw.commons.filepicker.UploadableFile +import fr.free.nrw.commons.kvstore.BasicKvStore import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.upload.ImageCoordinates @@ -25,7 +26,7 @@ interface UploadMediaDetailsContract { fun showMessage(stringResourceId: Int, colorResourceId: Int) - fun showMessage(message: String?, colorResourceId: Int) + fun showMessage(message: String, colorResourceId: Int) fun showDuplicatePicturePopup(uploadItem: UploadItem) @@ -49,6 +50,10 @@ interface UploadMediaDetailsContract { fun updateMediaDetails(uploadMediaDetails: List) fun displayAddLocationDialog(runnable: Runnable) + + fun createBasicKvStore(storeName: String): BasicKvStore + + fun showBadImagePopup(errorCode: Int, index: Int, uploadItem: UploadItem) } interface UserActionListener : BasePresenter { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt index 47e2544e8..dd101e3cf 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaPresenter.kt @@ -336,8 +336,7 @@ class UploadMediaPresenter @Inject constructor( */ override fun checkImageQuality(uploadItem: UploadItem, index: Int) { if ((uploadItem.imageQuality != IMAGE_OK) && (uploadItem.imageQuality != IMAGE_KEEP)) { - val store = BasicKvStore( - UploadMediaDetailFragment.activity, + val store = view.createBasicKvStore( UploadActivity.storeNameForCurrentUploadImagesSize ) val value = store.getString(UPLOAD_QUALITIES_KEY, null) @@ -363,8 +362,7 @@ class UploadMediaPresenter @Inject constructor( * @param index Index of the UploadItem which was deleted */ override fun updateImageQualitiesJSON(size: Int, index: Int) { - val store = BasicKvStore( - UploadMediaDetailFragment.activity, + val store = view.createBasicKvStore( UploadActivity.storeNameForCurrentUploadImagesSize ) val value = store.getString(UPLOAD_QUALITIES_KEY, null) @@ -399,36 +397,7 @@ class UploadMediaPresenter @Inject constructor( // If image has some other problems, show popup accordingly if (errorCode != EMPTY_CAPTION && errorCode != FILE_NAME_EXISTS) { - showBadImagePopup(errorCode, index, UploadMediaDetailFragment.activity, uploadItem) - } - } - - /** - * Shows a dialog describing the potential problems in the current image - * - * @param errorCode Has the potential problems in the current image - * @param index Index of the UploadItem which has problems - * @param activity Context reference - * @param uploadItem UploadItem which has problems - */ - private fun showBadImagePopup( - errorCode: Int, index: Int, activity: Activity, uploadItem: UploadItem - ) { - //If the error message is null, we will probably not show anything - val errorMessageForResult = getErrorMessageForResult(activity, errorCode) - if (errorMessageForResult.isNotEmpty()) { - showAlertDialog(activity, - activity.getString(R.string.upload_problem_image), - errorMessageForResult, - activity.getString(R.string.upload), - activity.getString(R.string.cancel), - { - view.showProgress(false) - uploadItem.imageQuality = IMAGE_OK - }, { - presenterCallback!!.deletePictureAtIndex(index) - } - )?.setCancelable(false) + view.showBadImagePopup(errorCode, index, uploadItem) } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/locationpicker/LocationPickerActivityUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/locationpicker/LocationPickerActivityUnitTests.kt index b407dc4a3..bbf416e2c 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/locationpicker/LocationPickerActivityUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/locationpicker/LocationPickerActivityUnitTests.kt @@ -13,8 +13,8 @@ import com.nhaarman.mockitokotlin2.verify import fr.free.nrw.commons.CameraPosition import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.kvstore.JsonKvStore -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_LOCATION -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_LOCATION +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM import io.reactivex.android.plugins.RxAndroidPlugins import io.reactivex.schedulers.Schedulers import org.junit.Assert diff --git a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt index a969b3448..a37bcc927 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragmentUnitTest.kt @@ -34,7 +34,7 @@ import fr.free.nrw.commons.upload.ImageCoordinates import fr.free.nrw.commons.upload.UploadActivity import fr.free.nrw.commons.upload.UploadItem import fr.free.nrw.commons.upload.UploadMediaDetailAdapter -import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.LAST_ZOOM +import fr.free.nrw.commons.upload.mediaDetails.UploadMediaDetailFragment.Companion.LAST_ZOOM import org.junit.Assert import org.junit.Before import org.junit.Test @@ -153,12 +153,6 @@ class UploadMediaDetailFragmentUnitTest { Assert.assertNotNull(fragment) } - @Test - @Throws(Exception::class) - fun testSetCallback() { - fragment.setCallback(null) - } - @Test @Throws(Exception::class) fun testOnCreate() { @@ -229,22 +223,6 @@ class UploadMediaDetailFragmentUnitTest { method.invoke(fragment, R.string.media_detail_step_title, R.string.media_details_tooltip) } - @Test - @Throws(Exception::class) - fun testOnNextButtonClicked() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - Whitebox.setInternalState(fragment, "presenter", presenter) - fragment.onNextButtonClicked() - } - - @Test - @Throws(Exception::class) - fun testOnPreviousButtonClicked() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - Whitebox.setInternalState(fragment, "presenter", presenter) - fragment.onPreviousButtonClicked() - } - @Test @Throws(Exception::class) fun testShowSimilarImageFragment() { @@ -366,7 +344,10 @@ class UploadMediaDetailFragmentUnitTest { `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) val activityResult = ActivityResult(Activity.RESULT_OK, intent) - val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod("onCameraPosition", ActivityResult::class.java) + val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod( + "onCameraPosition", + ActivityResult::class.java + ) handleResultMethod.isAccessible = true handleResultMethod.invoke(fragment, activityResult) @@ -382,7 +363,7 @@ class UploadMediaDetailFragmentUnitTest { val cameraPosition = Mockito.mock(CameraPosition::class.java) val latLng = Mockito.mock(LatLng::class.java) - Whitebox.setInternalState(fragment, "callback", callback) + Whitebox.setInternalState(fragment, "fragmentCallback", callback) Whitebox.setInternalState(cameraPosition, "latitude", latLng.latitude) Whitebox.setInternalState(cameraPosition, "longitude", latLng.longitude) Whitebox.setInternalState(fragment, "editableUploadItem", uploadItem) @@ -394,9 +375,12 @@ class UploadMediaDetailFragmentUnitTest { `when`(latLng.longitude).thenReturn(0.0) `when`(uploadItem.gpsCoords).thenReturn(imageCoordinates) - val activityResult = ActivityResult(Activity.RESULT_OK,intent) + val activityResult = ActivityResult(Activity.RESULT_OK, intent) - val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod("onCameraPosition", ActivityResult::class.java) + val handleResultMethod = UploadMediaDetailFragment::class.java.getDeclaredMethod( + "onCameraPosition", + ActivityResult::class.java + ) handleResultMethod.isAccessible = true handleResultMethod.invoke(fragment, activityResult) @@ -417,21 +401,6 @@ class UploadMediaDetailFragmentUnitTest { fragment.onDestroyView() } - @Test - @Throws(Exception::class) - fun testOnLlContainerTitleClicked() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - fragment.onLlContainerTitleClicked() - } - - @Test - @Throws(Exception::class) - fun testOnIbMapClicked() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - Whitebox.setInternalState(fragment, "presenter", presenter) - fragment.onIbMapClicked() - } - @Test @Throws(Exception::class) fun testOnPrimaryCaptionTextChange() {