diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ba49201a..02f31185a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,262 +1,266 @@ - - - - - - - - - - - - - - - - - - + xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - + - + - + - + - + - - - + - - + + + - + + - - + - - - + + - + + + - - - - - + - + + + + + - - - - - - - + - + + + + + + + - + - + - + - + - + - + - + - + - + - + - - - - - - + - + + + + + + - - - + - + + + - + - + - + - + - + - - - - + - - + + + + - + + - + - + + + \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt index cb153b7cb..93e833c15 100644 --- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt +++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt @@ -4,6 +4,7 @@ import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException import io.reactivex.Observable import io.reactivex.Single import fr.free.nrw.commons.auth.csrf.CsrfTokenClient +import timber.log.Timber /** * This class acts as a Client to facilitate wiki page editing @@ -27,7 +28,41 @@ class PageEditClient( fun edit(pageTitle: String, text: String, summary: String): Observable { return try { pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking()) - .map { editResponse -> editResponse.edit()!!.editSucceeded() } + .map { editResponse -> + editResponse.edit()!!.editSucceeded() + } + } catch (throwable: Throwable) { + if (throwable is InvalidLoginTokenException) { + throw throwable + } else { + Observable.just(false) + } + } + } + + /** + * Creates a new page with the given title, text, and summary. + * + * @param pageTitle The title of the page to be created. + * @param text The content of the page in wikitext format. + * @param summary The edit summary for the page creation. + * @return An observable that emits true if the page creation succeeded, false otherwise. + * @throws InvalidLoginTokenException If an invalid login token is encountered during the process. + */ + fun postCreate(pageTitle: String, text: String, summary: String): Observable { + return try { + pageEditInterface.postCreate( + pageTitle, + summary, + text, + "text/x-wiki", + "wikitext", + true, + true, + csrfTokenClient.getTokenBlocking() + ).map { editResponse -> + editResponse.edit()!!.editSucceeded() + } } catch (throwable: Throwable) { if (throwable is InvalidLoginTokenException) { throw throwable diff --git a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt index 070aaaa64..1aa0bbfce 100644 --- a/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt +++ b/app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt @@ -36,6 +36,33 @@ interface PageEditInterface { @Field("token") token: String ): Observable + /** + * This method creates or edits a page for nearby items. + * + * @param title Title of the page to edit. Cannot be used together with pageid. + * @param summary Edit summary. Also used as the section title when section=new and sectiontitle is not set. + * @param text Text of the page. + * @param contentformat Format of the content (e.g., "text/x-wiki"). + * @param contentmodel Model of the content (e.g., "wikitext"). + * @param minor Whether the edit is a minor edit. + * @param recreate Whether to recreate the page if it does not exist. + * @param token A "csrf" token. This should always be sent as the last field of form data. + */ + @FormUrlEncoded + @Headers("Cache-Control: no-cache") + @POST(MW_API_PREFIX + "action=edit") + fun postCreate( + @Field("title") title: String, + @Field("summary") summary: String, + @Field("text") text: String, + @Field("contentformat") contentformat: String, + @Field("contentmodel") contentmodel: String, + @Field("minor") minor: Boolean, + @Field("recreate") recreate: Boolean, + // NOTE: This csrf shold always be sent as the last field of form data + @Field("token") token: String + ): Observable + /** * This method posts such that the Content which the page * has will be appended with the value being passed to the diff --git a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java index 6a9277906..0df9685c1 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java @@ -14,6 +14,7 @@ import fr.free.nrw.commons.description.DescriptionEditActivity; import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity; import fr.free.nrw.commons.explore.SearchActivity; import fr.free.nrw.commons.media.ZoomableActivity; +import fr.free.nrw.commons.nearby.WikidataFeedback; import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.profile.ProfileActivity; import fr.free.nrw.commons.review.ReviewActivity; @@ -79,4 +80,7 @@ public abstract class ActivityBuilderModule { @ContributesAndroidInjector abstract ZoomableActivity bindZoomableActivity(); + + @ContributesAndroidInjector + abstract WikidataFeedback bindWikiFeedback(); } diff --git a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java index cbbf1f0c5..1390bd8ef 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java +++ b/app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java @@ -2,9 +2,11 @@ package fr.free.nrw.commons.di; import com.google.gson.Gson; +import fr.free.nrw.commons.actions.PageEditClient; import fr.free.nrw.commons.explore.categories.CategoriesModule; import fr.free.nrw.commons.navtab.MoreBottomSheetFragment; import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment; +import fr.free.nrw.commons.nearby.NearbyController; import fr.free.nrw.commons.upload.worker.UploadWorker; import javax.inject.Singleton; @@ -68,6 +70,9 @@ public interface CommonsApplicationComponent extends AndroidInjector" + "\n"; - List placeBindings = runQuery(leftLatLng,rightLatLng); + List placeBindings = runQuery(leftLatLng, rightLatLng); if (placeBindings != null) { for (PlaceBindings item : placeBindings) { if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt new file mode 100644 index 000000000..ca21f25ad --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/BottomSheetAdapter.kt @@ -0,0 +1,108 @@ +package fr.free.nrw.commons.nearby + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnLongClickListener +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.NonNull +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import fr.free.nrw.commons.R +import fr.free.nrw.commons.nearby.model.BottomSheetItem + +/** + * RecyclerView Adapter for displaying items in a bottom sheet. + * + * @property context The context used for inflating layout resources. + * @property itemList The list of BottomSheetItem objects to display. + * @constructor Creates an instance of BottomSheetAdapter. + */ +class BottomSheetAdapter(context: Context?, private val itemList: List) : + RecyclerView.Adapter() { + private val layoutInflater: LayoutInflater = LayoutInflater.from(context) + private var itemClickListener: ItemClickListener? = null + + @NonNull + override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder { + val view: View = layoutInflater.inflate(R.layout.bottom_sheet_item_layout, parent, false) + return ViewHolder(view) + } + + override fun onBindViewHolder(@NonNull holder: ViewHolder, position: Int) { + val item = itemList[position] + holder.imageView.setImageDrawable( + ContextCompat.getDrawable( + getContext(), + item.imageResourceId + ) + ) + holder.title.setText(item.title) + } + + /** + * Returns the total number of items in the data set held by the adapter. + * + * @return The total number of items in this adapter. + */ + override fun getItemCount(): Int { + return itemList.size + } + + /** + * Updates the icon for bookmark item. + * + * @param icon The resource ID of the new icon to set. + */ + fun updateBookmarkIcon(icon: Int) { + itemList.forEachIndexed { index, item -> + if (item.imageResourceId == R.drawable.ic_round_star_filled_24px || item.imageResourceId == R.drawable.ic_round_star_border_24px) { + item.imageResourceId = icon + this.notifyItemChanged(index) + return + } + } + } + + inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView), + View.OnClickListener, OnLongClickListener { + var imageView: ImageView = itemView.findViewById(R.id.buttonImage) + var title: TextView = itemView.findViewById(R.id.buttonText) + + init { + itemView.setOnClickListener(this) + itemView.setOnLongClickListener(this) + } + + override fun onClick(view: View) { + if (itemClickListener != null) itemClickListener!!.onBottomSheetItemClick( + view, + adapterPosition + ) + } + + override fun onLongClick(view: View): Boolean { + if (itemClickListener != null) itemClickListener!!.onBottomSheetItemLongClick( + view, + adapterPosition + ) + return true + } + } + + fun setClickListener(itemClickListener: ItemClickListener?) { + this.itemClickListener = itemClickListener + } + + fun getContext(): Context { + return layoutInflater.context + } + + interface ItemClickListener { + fun onBottomSheetItemClick(view: View?, position: Int) + fun onBottomSheetItemLongClick(view: View?, position: Int) + } +} + diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/PageEditHelper.java b/app/src/main/java/fr/free/nrw/commons/nearby/PageEditHelper.java new file mode 100644 index 000000000..9d4b1069c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/PageEditHelper.java @@ -0,0 +1,164 @@ +package fr.free.nrw.commons.nearby; + + +import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import androidx.appcompat.app.AlertDialog; +import fr.free.nrw.commons.BuildConfig; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.actions.PageEditClient; +import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException; +import fr.free.nrw.commons.notification.NotificationHelper; +import fr.free.nrw.commons.utils.ViewUtilWrapper; +import io.reactivex.Observable; +import io.reactivex.Single; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import timber.log.Timber; + +/** + * Singleton class for making edits to wiki pages asynchronously using RxJava. + * + * @property notificationHelper A helper class for displaying notifications. + * @property pageEditClient A client for making page edit requests. + * @property viewUtil A utility class for common view operations. + * @property username The username used for page edits. + * @constructor Initializes the PageEditHelper with required dependencies. + */ +@Singleton +public class PageEditHelper { + + private final NotificationHelper notificationHelper; + private final PageEditClient pageEditClient; + private final ViewUtilWrapper viewUtil; + private final String username; + private AlertDialog d; + private DialogInterface.OnMultiChoiceClickListener listener; + + @Inject + public PageEditHelper(NotificationHelper notificationHelper, + @Named("wikidata-page-edit") PageEditClient pageEditClient, + ViewUtilWrapper viewUtil, + @Named("username") String username) { + this.notificationHelper = notificationHelper; + this.pageEditClient = pageEditClient; + this.viewUtil = viewUtil; + this.username = username; + } + + /** + * Public interface to make a page edit request asynchronously. + * + * @param context The context for displaying messages. + * @param title The title of the page to edit. + * @param preText The existing content of the page. + * @param description The description of the issue to be fixed. + * @param details Additional details about the issue. + * @param lat The latitude of the location related to the page. + * @param lng The longitude of the location related to the page. + * @return A Single emitting true if the edit was successful, false otherwise. + */ + public Single makePageEdit(Context context, String title, String preText, + String description, + String details, Double lat, Double lng) { + viewUtil.showShortToast(context, "Trying to edit " + title); + + return editPage(title, preText, description, details, lat, lng) + .flatMapSingle(result -> Single.just(showNotification(context, title, result))) + .firstOrError() + .onErrorResumeNext(throwable -> { + if (throwable instanceof InvalidLoginTokenException) { + return Single.error(throwable); + } + return Single.error(throwable); + }); + } + + /** + * Creates the text content for the page edit based on provided parameters. + * + * @param title The title of the page to edit. + * @param preText The existing content of the page. + * @param description The description of the issue to be fixed. + * @param details Additional details about the issue. + * @param lat The latitude of the location related to the page. + * @param lng The longitude of the location related to the page. + * @return An Observable emitting true if the edit was successful, false otherwise. + */ + private Observable editPage(String title, String preText, String description, + String details, Double lat, Double lng) { + Timber.d("thread is edit %s", Thread.currentThread().getName()); + String summary = "Please fix this item"; + String text = ""; + String marker = "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!"; + int markerIndex = preText.indexOf(marker); + if (preText == "" || markerIndex == -1) { + text = "==Please fix this item==\n" + + "Someone using the [[Commons:Mobile_app|Commons Android app]] went to this item's geographical location (" + + lat + "," + lng + + ") and noted the following problem(s):\n" + + "* " + description + "\n" + + "\n" + + "Details: " + details + "\n" + + "\n" + + "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!\n" + + "\n" + + "~~~~"; + } else { + text = preText.substring(0, markerIndex); + text = text + "* " + description + "\n" + + "\n" + + "Details: " + details + "\n" + + "\n" + + "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!\n" + + "\n" + + "~~~~"; + } + + return pageEditClient.postCreate(title, text, summary); + } + + /** + * Displays a notification based on the result of the page edit. + * + * @param context The context for displaying the notification. + * @param title The title of the page edited. + * @param result The result of the edit operation. + * @return true if the edit was successful, false otherwise. + */ + private boolean showNotification(Context context, String title, boolean result) { + String message; + + if (result) { + message = title + " Edited Successfully"; + } else { + message = context.getString(R.string.delete_helper_show_deletion_message_else); + } + + String url = BuildConfig.WIKIDATA_URL + "/wiki/" + title; + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + notificationHelper.showNotification(context, title, message, NOTIFICATION_DELETE, + browserIntent); + return result; + } + + /** + * returns the instance of shown AlertDialog, used for taking reference during unit test + */ + public AlertDialog getDialog() { + return d; + } + + /** + * returns the instance of shown DialogInterface.OnMultiChoiceClickListener, used for taking + * reference during unit test + */ + public DialogInterface.OnMultiChoiceClickListener getListener() { + return listener; + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt new file mode 100644 index 000000000..7c62b3646 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/WikidataFeedback.kt @@ -0,0 +1,96 @@ +package fr.free.nrw.commons.nearby + +import android.annotation.SuppressLint +import android.os.Bundle +import android.widget.RadioButton +import android.widget.Toast +import fr.free.nrw.commons.R +import fr.free.nrw.commons.databinding.ActivityWikidataFeedbackBinding +import fr.free.nrw.commons.theme.BaseActivity +import io.reactivex.Single +import io.reactivex.SingleSource +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import timber.log.Timber +import java.util.concurrent.Callable +import javax.inject.Inject + +/** + * Activity for providing feedback about Wikidata items. + */ +class WikidataFeedback : BaseActivity() { + private lateinit var binding: ActivityWikidataFeedbackBinding + var place: String = "" + var wikidataQId: String = "" + var pageTitle: String = "" + var preText: String = "" + var lat: Double = 0.0 + var lng: Double = 0.0 + + @Inject + lateinit var pageEditHelper: PageEditHelper + + @Inject + lateinit var nearbyController: NearbyController + + @SuppressLint("CheckResult") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityWikidataFeedbackBinding.inflate(layoutInflater) + setContentView(binding.root) + lat = intent.getDoubleExtra("lat", 0.0) + lng = intent.getDoubleExtra("lng", 0.0) + place = intent.getStringExtra("place") ?: "" + wikidataQId = intent.getStringExtra("qid") ?: "" + pageTitle = getString(R.string.talk) + ":" + wikidataQId + binding.toolbarBinding.toolbar.title = pageTitle + binding.textHeader.text = + getString(R.string.write_something_about_the) + "'$place'" + getString(R.string.item_it_will_be_publicly_visible) + binding.radioButton1.setText( + getString( + R.string.does_not_exist_anymore_no_picture_can_ever_be_taken_of_it, + place + )) + binding.radioButton2.setText( + getString( + R.string.is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude, + place + )) + binding.radioButton3.setText(getString(R.string.other_problem_or_information_please_explain_below)) + setSupportActionBar(binding.toolbarBinding.toolbar) + supportActionBar!!.setDisplayHomeAsUpEnabled(true) + + binding.appCompatButton.setOnClickListener { + var desc = findViewById(binding.radioGroup.checkedRadioButtonId).text + var det = binding.detailsEditText.text.toString() + if (binding.radioGroup.checkedRadioButtonId == R.id.radioButton3 && binding.detailsEditText.text.isNullOrEmpty()) { + Toast.makeText( + this, + getString(R.string.please_enter_some_comments), Toast.LENGTH_SHORT + ).show() + } else { + binding.radioGroup.clearCheck() + binding.detailsEditText.setText("") + Single.defer(Callable> { + pageEditHelper.makePageEdit( + this, pageTitle, preText, + desc.toString(), + det, lat, lng + ) + } as Callable>) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ aBoolean: Boolean? -> + }, { throwable: Throwable? -> + Timber.e(throwable!!) + }) + } + } + } + + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 877f22f5a..d1e28eb7e 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -48,6 +48,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.core.content.FileProvider; import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.snackbar.Snackbar; @@ -70,6 +71,7 @@ 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.location.LocationUpdateListener; +import fr.free.nrw.commons.nearby.BottomSheetAdapter; import fr.free.nrw.commons.nearby.CheckBoxTriStates; import fr.free.nrw.commons.nearby.Label; import fr.free.nrw.commons.nearby.MarkerPlaceGroup; @@ -77,8 +79,10 @@ import fr.free.nrw.commons.nearby.NearbyController; import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter; import fr.free.nrw.commons.nearby.NearbyFilterState; import fr.free.nrw.commons.nearby.Place; +import fr.free.nrw.commons.nearby.WikidataFeedback; import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract; import fr.free.nrw.commons.nearby.fragments.AdvanceQueryFragment.Callback; +import fr.free.nrw.commons.nearby.model.BottomSheetItem; import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter; import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.utils.DialogUtil; @@ -131,7 +135,7 @@ import timber.log.Timber; public class NearbyParentFragment extends CommonsDaggerSupportFragment implements NearbyParentFragmentContract.View, WikidataEditListener.WikidataP18EditListener, LocationUpdateListener, - LocationPermissionCallback { + LocationPermissionCallback, BottomSheetAdapter.ItemClickListener { FragmentNearbyParentBinding binding; @@ -189,6 +193,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback; private boolean isAdvancedQueryFragmentVisible = false; private Place nearestPlace; + private GridLayoutManager gridLayoutManager; + private List dataList; + private BottomSheetAdapter bottomSheetAdapter; private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult( new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { @@ -203,7 +210,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment controller.locationPermissionCallback.onLocationPermissionGranted(); } else { if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - controller.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher); + controller.handleShowRationaleFlowCameraLocation(getActivity(), + inAppCameraLocationPermissionLauncher); } else { controller.locationPermissionCallback.onLocationPermissionDenied( getActivity().getString( @@ -434,10 +442,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment moveCameraToPosition(lastMapFocus); initRvNearbyList(); onResume(); - binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); - binding.tvAttribution.setMovementMethod(LinkMovementMethod.getInstance()); + binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); + binding.tvAttribution.setMovementMethod(LinkMovementMethod.getInstance()); binding.nearbyFilterList.btnAdvancedOptions.setOnClickListener(v -> { - binding.nearbyFilter.searchViewLayout.searchView.clearFocus(); + binding.nearbyFilter.searchViewLayout.searchView.clearFocus(); showHideAdvancedQueryFragment(true); final AdvanceQueryFragment fragment = new AdvanceQueryFragment(); final Bundle bundle = new Bundle(); @@ -473,7 +481,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment .commit(); }); - binding.tvLearnMore.setOnClickListener(v ->onLearnMoreClicked()); + binding.tvLearnMore.setOnClickListener(v -> onLearnMoreClicked()); binding.nearbyFilter.ivToggleChips.setOnClickListener(v -> onToggleChipsClicked()); if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { @@ -510,7 +518,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } private void initRvNearbyList() { - binding.bottomSheetNearby.rvNearbyList.setLayoutManager(new LinearLayoutManager(getContext())); + binding.bottomSheetNearby.rvNearbyList.setLayoutManager( + new LinearLayoutManager(getContext())); adapter = new PlaceAdapter(bookmarkLocationDao, place -> { moveCameraToPosition( @@ -566,7 +575,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); setProgressBarVisibility(true); } else { - locationPermissionsHelper.showLocationOffDialog(getActivity(), R.string.ask_to_turn_location_on_text); + locationPermissionsHelper.showLocationOffDialog(getActivity(), + R.string.ask_to_turn_location_on_text); } presenter.onMapReady(); registerUnregisterLocationListener(false); @@ -591,7 +601,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment * Starts the map without GPS and without permission By default it points to 51.50550,-0.07520 * coordinates, other than that it points to the last known location which can be get by the key * "LastLocation" from applicationKvStore - * */ private void startMapWithoutPermission() { if (applicationKvStore.getString("LastLocation") != null) { @@ -649,7 +658,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment initBottomSheets(); loadAnimations(); setBottomSheetCallbacks(); - decideButtonVisibilities(); addActionToTitle(); if (!Utils.isMonumentsEnabled(new Date())) { NearbyFilterState.setWlmSelected(false); @@ -673,26 +681,46 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); } + /** + * Determines the number of spans (columns) in the RecyclerView based on device orientation + * and adapter item count. + * + * @return The number of spans to be used in the RecyclerView. + */ + private int getSpanCount() { + int orientation = getResources().getConfiguration().orientation; + if (bottomSheetAdapter != null) { + return (orientation == Configuration.ORIENTATION_PORTRAIT) ? 3 + : bottomSheetAdapter.getItemCount(); + } else { + return (orientation == Configuration.ORIENTATION_PORTRAIT) ? 3 : 6; + } + } + public void initNearbyFilter() { binding.nearbyFilterList.getRoot().setVisibility(View.GONE); hideBottomSheet(); - binding.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> { - LayoutUtils.setLayoutHeightAllignedToWidth(1.25, binding.nearbyFilterList.getRoot()); - if (hasFocus) { - binding.nearbyFilterList.getRoot().setVisibility(View.VISIBLE); - presenter.searchViewGainedFocus(); - } else { - binding.nearbyFilterList.getRoot().setVisibility(View.GONE); - } - }); - binding.nearbyFilterList.searchListView.setHasFixedSize(true); - binding.nearbyFilterList.searchListView.addItemDecoration(new DividerItemDecoration(getContext(), - DividerItemDecoration.VERTICAL)); + binding.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener( + (v, hasFocus) -> { + LayoutUtils.setLayoutHeightAllignedToWidth(1.25, + binding.nearbyFilterList.getRoot()); + if (hasFocus) { + binding.nearbyFilterList.getRoot().setVisibility(View.VISIBLE); + presenter.searchViewGainedFocus(); + } else { + binding.nearbyFilterList.getRoot().setVisibility(View.GONE); + } + }); + binding.nearbyFilterList.searchListView.setHasFixedSize(true); + binding.nearbyFilterList.searchListView.addItemDecoration( + new DividerItemDecoration(getContext(), + DividerItemDecoration.VERTICAL)); final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); - binding.nearbyFilterList.searchListView.setLayoutManager(linearLayoutManager); + binding.nearbyFilterList.searchListView.setLayoutManager(linearLayoutManager); nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter( - getContext(), new ArrayList<>(Label.valuesAsList()), binding.nearbyFilterList.searchListView); + getContext(), new ArrayList<>(Label.valuesAsList()), + binding.nearbyFilterList.searchListView); nearbyFilterSearchRecyclerViewAdapter.setCallback( new NearbyFilterSearchRecyclerViewAdapter.Callback() { @Override @@ -711,18 +739,20 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return isDarkTheme; } }); - binding.nearbyFilterList.getRoot().getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(), + binding.nearbyFilterList.getRoot() + .getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(), 0.75); - binding.nearbyFilterList.searchListView.setAdapter(nearbyFilterSearchRecyclerViewAdapter); + binding.nearbyFilterList.searchListView.setAdapter(nearbyFilterSearchRecyclerViewAdapter); LayoutUtils.setLayoutHeightAllignedToWidth(1.25, binding.nearbyFilterList.getRoot()); - compositeDisposable.add(RxSearchView.queryTextChanges( binding.nearbyFilter.searchViewLayout.searchView) - .takeUntil(RxView.detaches(binding.nearbyFilter.searchViewLayout.searchView)) - .debounce(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(query -> { - ((NearbyFilterSearchRecyclerViewAdapter) binding.nearbyFilterList.searchListView.getAdapter()).getFilter() - .filter(query.toString()); - })); + compositeDisposable.add( + RxSearchView.queryTextChanges(binding.nearbyFilter.searchViewLayout.searchView) + .takeUntil(RxView.detaches(binding.nearbyFilter.searchViewLayout.searchView)) + .debounce(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(query -> { + ((NearbyFilterSearchRecyclerViewAdapter) binding.nearbyFilterList.searchListView.getAdapter()).getFilter() + .filter(query.toString()); + })); initFilterChips(); } @@ -739,9 +769,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void setFilterState() { - binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(NearbyFilterState.getInstance().isNeedPhotoSelected()); - binding.nearbyFilter.chipView.choiceChipExists.setChecked(NearbyFilterState.getInstance().isExistsSelected()); - binding.nearbyFilter.chipView.choiceChipWlm.setChecked(NearbyFilterState.getInstance().isWlmSelected()); + binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked( + NearbyFilterState.getInstance().isNeedPhotoSelected()); + binding.nearbyFilter.chipView.choiceChipExists.setChecked( + NearbyFilterState.getInstance().isExistsSelected()); + binding.nearbyFilter.chipView.choiceChipWlm.setChecked( + NearbyFilterState.getInstance().isWlmSelected()); if (NearbyController.currentLocation != null) { presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels, binding.nearbyFilterList.checkboxTriStates.getState(), true, false); @@ -749,44 +782,53 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } private void initFilterChips() { - binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (NearbyController.currentLocation != null) { - binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); - NearbyFilterState.setNeedPhotoSelected(isChecked); - presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels, - binding.nearbyFilterList.checkboxTriStates.getState(), true, true); - updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), - binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); - } else { - binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(!isChecked); - } - }); + binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (NearbyController.currentLocation != null) { + binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); + NearbyFilterState.setNeedPhotoSelected(isChecked); + presenter.filterByMarkerType( + nearbyFilterSearchRecyclerViewAdapter.selectedLabels, + binding.nearbyFilterList.checkboxTriStates.getState(), true, true); + updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), + binding.nearbyFilter.chipView.choiceChipExists.isChecked(), + binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); + } else { + binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(!isChecked); + } + }); - binding.nearbyFilter.chipView.choiceChipExists.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (NearbyController.currentLocation != null) { - binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); - NearbyFilterState.setExistsSelected(isChecked); - presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels, - binding.nearbyFilterList.checkboxTriStates.getState(), true, true); - updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), - binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); - } else { - binding.nearbyFilter.chipView.choiceChipExists.setChecked(!isChecked); - } - }); + binding.nearbyFilter.chipView.choiceChipExists.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (NearbyController.currentLocation != null) { + binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); + NearbyFilterState.setExistsSelected(isChecked); + presenter.filterByMarkerType( + nearbyFilterSearchRecyclerViewAdapter.selectedLabels, + binding.nearbyFilterList.checkboxTriStates.getState(), true, true); + updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), + binding.nearbyFilter.chipView.choiceChipExists.isChecked(), + binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); + } else { + binding.nearbyFilter.chipView.choiceChipExists.setChecked(!isChecked); + } + }); - binding.nearbyFilter.chipView.choiceChipWlm.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (NearbyController.currentLocation != null) { - binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); - NearbyFilterState.setWlmSelected(isChecked); - presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels, - binding.nearbyFilterList.checkboxTriStates.getState(), true, true); - updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), - binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); - } else { - binding.nearbyFilter.chipView.choiceChipWlm.setChecked(!isChecked); - } - }); + binding.nearbyFilter.chipView.choiceChipWlm.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (NearbyController.currentLocation != null) { + binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED); + NearbyFilterState.setWlmSelected(isChecked); + presenter.filterByMarkerType( + nearbyFilterSearchRecyclerViewAdapter.selectedLabels, + binding.nearbyFilterList.checkboxTriStates.getState(), true, true); + updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(), + binding.nearbyFilter.chipView.choiceChipExists.isChecked(), + binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); + } else { + binding.nearbyFilter.chipView.choiceChipWlm.setChecked(!isChecked); + } + }); } /** @@ -838,7 +880,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } adapter.setItems(updatedPlaces); - binding.bottomSheetNearby.noResultsMessage.setVisibility(updatedPlaces.isEmpty() ? View.VISIBLE : View.GONE); + binding.bottomSheetNearby.noResultsMessage.setVisibility( + updatedPlaces.isEmpty() ? View.VISIBLE : View.GONE); } /** @@ -867,8 +910,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } }); - binding.bottomSheetNearby.bottomSheet.getLayoutParams().height = getActivity().getWindowManager() - .getDefaultDisplay().getHeight() / 16 * 9; + binding.bottomSheetNearby.bottomSheet.getLayoutParams().height = + getActivity().getWindowManager() + .getDefaultDisplay().getHeight() / 16 * 9; bottomSheetListBehavior = BottomSheetBehavior.from(binding.bottomSheetNearby.bottomSheet); bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); bottomSheetListBehavior.setBottomSheetCallback(new BottomSheetBehavior @@ -897,26 +941,13 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward); } - /** - * Fits buttons according to our layout - */ - private void decideButtonVisibilities() { - // Remove button text if they exceed 1 line or if internal layout has not been built - // Only need to check for directions button because it is the longest - if ( binding.bottomSheetDetails.directionsButtonText.getLineCount() > 1 || binding.bottomSheetDetails.directionsButtonText.getLineCount() == 0) { - binding.bottomSheetDetails.wikipediaButtonText.setVisibility(View.GONE); - binding.bottomSheetDetails.wikidataButtonText.setVisibility(View.GONE); - binding.bottomSheetDetails.commonsButtonText.setVisibility(View.GONE); - binding.bottomSheetDetails.directionsButtonText.setVisibility(View.GONE); - } - } - /** * */ private void addActionToTitle() { binding.bottomSheetDetails.title.setOnLongClickListener(view -> { - Utils.copy("place", binding.bottomSheetDetails.title.getText().toString(), getContext()); + Utils.copy("place", binding.bottomSheetDetails.title.getText().toString(), + getContext()); Toast.makeText(getContext(), R.string.text_copy, Toast.LENGTH_SHORT).show(); return true; }); @@ -963,7 +994,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment public void updateListFragment(final List placeList) { places = placeList; adapter.setItems(placeList); - binding.bottomSheetNearby.noResultsMessage.setVisibility(placeList.isEmpty() ? View.VISIBLE : View.GONE); + binding.bottomSheetNearby.noResultsMessage.setVisibility( + placeList.isEmpty() ? View.VISIBLE : View.GONE); } @Override @@ -1006,7 +1038,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public fr.free.nrw.commons.location.LatLng getMapFocus() { fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng( - binding.map.getMapCenter().getLatitude(), binding.map.getMapCenter().getLongitude(), 100); + binding.map.getMapCenter().getLatitude(), binding.map.getMapCenter().getLongitude(), + 100); return mapFocusedLatLng; } @@ -1077,8 +1110,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void populatePlaces(final fr.free.nrw.commons.location.LatLng currentLatLng) { - IGeoPoint screenTopRight = binding.map.getProjection().fromPixels(binding.map.getWidth(), 0); - IGeoPoint screenBottomLeft = binding.map.getProjection().fromPixels(0, binding.map.getHeight()); + IGeoPoint screenTopRight = binding.map.getProjection() + .fromPixels(binding.map.getWidth(), 0); + IGeoPoint screenBottomLeft = binding.map.getProjection() + .fromPixels(0, binding.map.getHeight()); fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng( screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0); fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng( @@ -1135,8 +1170,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment populatePlaces(currentLatLng); return; } - IGeoPoint screenTopRight = binding.map.getProjection().fromPixels(binding.map.getWidth(), 0); - IGeoPoint screenBottomLeft = binding.map.getProjection().fromPixels(0, binding.map.getHeight()); + IGeoPoint screenTopRight = binding.map.getProjection() + .fromPixels(binding.map.getWidth(), 0); + IGeoPoint screenBottomLeft = binding.map.getProjection() + .fromPixels(0, binding.map.getHeight()); fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng( screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0); fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng( @@ -1250,7 +1287,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment (isGPX) ? getString(R.string.do_you_want_to_open_gpx_file) : getString(R.string.do_you_want_to_open_kml_file); Runnable runnable = () -> openFile(context, fileName, isGPX); - DialogUtil.showAlertDialog(getActivity(), title, message, runnable,() -> {}); + DialogUtil.showAlertDialog(getActivity(), title, message, runnable, () -> { + }); } private void openFile(Context context, String fileName, Boolean isGPX) { @@ -1418,9 +1456,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void setProgressBarVisibility(final boolean isVisible) { if (isVisible) { - binding.mapProgressBar.setVisibility(View.VISIBLE); + binding.mapProgressBar.setVisibility(View.VISIBLE); } else { - binding.mapProgressBar.setVisibility(View.GONE); + binding.mapProgressBar.setVisibility(View.GONE); } } @@ -1444,7 +1482,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } private void showFABs() { - NearbyFABUtils.addAnchorToBigFABs(binding.fabPlus, binding.bottomSheetDetails.getRoot().getId()); + NearbyFABUtils.addAnchorToBigFABs(binding.fabPlus, + binding.bottomSheetDetails.getRoot().getId()); binding.fabPlus.show(); NearbyFABUtils.addAnchorToSmallFABs(binding.fabGallery, getView().findViewById(R.id.empty_view).getId()); @@ -1576,17 +1615,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment @Override public void setFABRecenterAction(final View.OnClickListener onClickListener) { - binding.fabRecenter.setOnClickListener(onClickListener); + binding.fabRecenter.setOnClickListener(onClickListener); } @Override public void disableFABRecenter() { - binding.fabRecenter.setEnabled(false); + binding.fabRecenter.setEnabled(false); } @Override public void enableFABRecenter() { - binding.fabRecenter.setEnabled(true); + binding.fabRecenter.setEnabled(true); } /** @@ -1865,7 +1904,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } /** - * Extracts text between the first occurrence of '(' and its corresponding ')' in the input string. + * Extracts text between the first occurrence of '(' and its corresponding ')' in the input + * string. * * @param input The input string from which to extract text between parentheses. * @return The text between parentheses if found, or {@code null} if no parentheses are found. @@ -1890,14 +1930,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment return input.contains("(") || input.contains(")"); } - private void removeMarker(Place place){ + private void removeMarker(Place place) { List overlays = binding.map.getOverlays(); - for (int i = 0; i < overlays.size();i++){ - if (overlays.get(i) instanceof ItemizedOverlayWithFocus){ - ItemizedOverlayWithFocus item = (ItemizedOverlayWithFocus)overlays.get(i); + for (int i = 0; i < overlays.size(); i++) { + if (overlays.get(i) instanceof ItemizedOverlayWithFocus) { + ItemizedOverlayWithFocus item = (ItemizedOverlayWithFocus) overlays.get(i); OverlayItem overlayItem = item.getItem(0); - fr.free.nrw.commons.location.LatLng diffLatLang = new fr.free.nrw.commons.location.LatLng(overlayItem.getPoint().getLatitude(),overlayItem.getPoint().getLongitude(),100); - if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() && place.location.getLongitude() == overlayItem.getPoint().getLongitude()){ + fr.free.nrw.commons.location.LatLng diffLatLang = new fr.free.nrw.commons.location.LatLng( + overlayItem.getPoint().getLatitude(), overlayItem.getPoint().getLongitude(), + 100); + if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() + && place.location.getLongitude() == overlayItem.getPoint().getLongitude()) { binding.map.getOverlays().remove(i); binding.map.invalidate(); break; @@ -2020,50 +2063,33 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment */ private void passInfoToSheet(final Place place) { selectedPlace = place; + dataList = new ArrayList<>(); + // TODO: Decide button text for fitting in the screen + dataList.add(new BottomSheetItem(R.drawable.ic_round_star_border_24px, "")); + dataList.add(new BottomSheetItem(R.drawable.ic_directions_black_24dp, + getResources().getString(R.string.nearby_directions))); + if (place.hasWikidataLink()) { + dataList.add(new BottomSheetItem(R.drawable.ic_wikidata_logo_24dp, + getResources().getString(R.string.nearby_wikidata))); + } + dataList.add(new BottomSheetItem(R.drawable.ic_feedback_black_24dp, + getResources().getString(R.string.nearby_wikitalk))); + if (place.hasWikipediaLink()) { + dataList.add(new BottomSheetItem(R.drawable.ic_wikipedia_logo_24dp, + getResources().getString(R.string.nearby_wikipedia))); + } + if (selectedPlace.hasCommonsLink()) { + dataList.add(new BottomSheetItem(R.drawable.ic_commons_icon_vector, + getResources().getString(R.string.nearby_commons))); + } + int spanCount = getSpanCount(); + gridLayoutManager = new GridLayoutManager(this.getContext(), spanCount); + binding.bottomSheetDetails.bottomSheetRecyclerView.setLayoutManager(gridLayoutManager); + bottomSheetAdapter = new BottomSheetAdapter(this.getContext(), dataList); + bottomSheetAdapter.setClickListener(this); + binding.bottomSheetDetails.bottomSheetRecyclerView.setAdapter(bottomSheetAdapter); updateBookmarkButtonImage(selectedPlace); - binding.bottomSheetDetails.bookmarkButton.setOnClickListener(view -> { - final boolean isBookmarked = bookmarkLocationDao.updateBookmarkLocation(selectedPlace); - updateBookmarkButtonImage(selectedPlace); - updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation()); - binding.map.invalidate(); - }); - binding.bottomSheetDetails.bookmarkButton.setOnLongClickListener(view -> { - Toast.makeText(getContext(), R.string.menu_bookmark, Toast.LENGTH_SHORT).show(); - return true; - }); - - binding.bottomSheetDetails.wikipediaButton.setVisibility(place.hasWikipediaLink() ? View.VISIBLE : View.GONE); - binding.bottomSheetDetails.wikipediaButton.setOnClickListener( - view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getWikipediaLink())); - binding.bottomSheetDetails.wikipediaButton.setOnLongClickListener(view -> { - Toast.makeText(getContext(), R.string.nearby_wikipedia, Toast.LENGTH_SHORT).show(); - return true; - }); - - binding.bottomSheetDetails.wikidataButton.setVisibility(place.hasWikidataLink() ? View.VISIBLE : View.GONE); - binding.bottomSheetDetails.wikidataButton.setOnClickListener( - view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getWikidataLink())); - binding.bottomSheetDetails.wikidataButton.setOnLongClickListener(view -> { - Toast.makeText(getContext(), R.string.nearby_wikidata, Toast.LENGTH_SHORT).show(); - return true; - }); - - binding.bottomSheetDetails.directionsButton.setOnClickListener(view -> Utils.handleGeoCoordinates(getActivity(), - selectedPlace.getLocation())); - binding.bottomSheetDetails.directionsButton.setOnLongClickListener(view -> { - Toast.makeText(getContext(), R.string.nearby_directions, Toast.LENGTH_SHORT).show(); - return true; - }); - - binding.bottomSheetDetails.commonsButton.setVisibility(selectedPlace.hasCommonsLink() ? View.VISIBLE : View.GONE); - binding.bottomSheetDetails.commonsButton.setOnClickListener( - view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getCommonsLink())); - binding.bottomSheetDetails.commonsButton.setOnLongClickListener(view -> { - Toast.makeText(getContext(), R.string.nearby_commons, Toast.LENGTH_SHORT).show(); - return true; - }); - binding.bottomSheetDetails.icon.setImageResource(selectedPlace.getLabel().getIcon()); binding.bottomSheetDetails.title.setText(selectedPlace.name); @@ -2074,7 +2100,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment descriptionText = (descriptionText.equals(selectedPlace.getLongDescription()) ? descriptionText : descriptionText.replaceFirst(".$", "")); // Set the short description after we remove place name from long description - binding.bottomSheetDetails.description.setText(descriptionText); + binding.bottomSheetDetails.description.setText(descriptionText); binding.fabCamera.setOnClickListener(view -> { if (binding.fabCamera.isShown()) { @@ -2088,7 +2114,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment if (binding.fabGallery.isShown()) { Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString()); storeSharedPrefs(selectedPlace); - controller.initiateGalleryPick(getActivity(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); + controller.initiateGalleryPick(getActivity(), + binding.nearbyFilter.chipView.choiceChipWlm.isChecked()); } }); @@ -2115,9 +2142,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } else { bookmarkIcon = R.drawable.ic_round_star_border_24px; } - if ( binding.bottomSheetDetails.bookmarkButtonImage != null) { - binding.bottomSheetDetails.bookmarkButtonImage.setImageResource(bookmarkIcon); - } + bottomSheetAdapter.updateBookmarkIcon(bookmarkIcon); } @Override @@ -2295,6 +2320,83 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment binding.map.getController().animateTo(geoPoint); } + @Override + public void onBottomSheetItemClick(@Nullable View view, int position) { + BottomSheetItem item = dataList.get(position); + boolean isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); + switch (item.getImageResourceId()) { + case R.drawable.ic_round_star_border_24px: + bookmarkLocationDao.updateBookmarkLocation(selectedPlace); + updateBookmarkButtonImage(selectedPlace); + isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); + updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation()); + binding.map.invalidate(); + break; + case R.drawable.ic_round_star_filled_24px: + bookmarkLocationDao.updateBookmarkLocation(selectedPlace); + updateBookmarkButtonImage(selectedPlace); + isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace); + updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation()); + binding.map.invalidate(); + break; + case R.drawable.ic_directions_black_24dp: + Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation()); + break; + case R.drawable.ic_wikidata_logo_24dp: + Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getWikidataLink()); + break; + case R.drawable.ic_feedback_black_24dp: + Intent intent = new Intent(this.getContext(), WikidataFeedback.class); + intent.putExtra("lat", selectedPlace.location.getLatitude()); + intent.putExtra("lng", selectedPlace.location.getLongitude()); + intent.putExtra("place", selectedPlace.name); + intent.putExtra("qid", selectedPlace.getWikiDataEntityId()); + startActivity(intent); + break; + case R.drawable.ic_wikipedia_logo_24dp: + Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getWikipediaLink()); + break; + case R.drawable.ic_commons_icon_vector: + Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getCommonsLink()); + break; + default: + break; + } + } + + @Override + public void onBottomSheetItemLongClick(@Nullable View view, int position) { + BottomSheetItem item = dataList.get(position); + String message; + switch (item.getImageResourceId()) { + case R.drawable.ic_round_star_border_24px: + message = getString(R.string.menu_bookmark); + break; + case R.drawable.ic_round_star_filled_24px: + message = getString(R.string.menu_bookmark); + break; + case R.drawable.ic_directions_black_24dp: + message = getString(R.string.nearby_directions); + break; + case R.drawable.ic_wikidata_logo_24dp: + message = getString(R.string.nearby_wikidata); + break; + case R.drawable.ic_feedback_black_24dp: + message = getString(R.string.nearby_wikitalk); + break; + case R.drawable.ic_wikipedia_logo_24dp: + message = getString(R.string.nearby_wikipedia); + break; + case R.drawable.ic_commons_icon_vector: + message = getString(R.string.nearby_commons); + break; + default: + message = "Long click"; + break; + } + Toast.makeText(this.getContext(), message, Toast.LENGTH_SHORT).show(); + } + public interface NearbyParentFragmentInstanceReadyCallback { void onReady(); @@ -2312,9 +2414,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment rlBottomSheetLayoutParams.height = getActivity().getWindowManager().getDefaultDisplay().getHeight() / 16 * 9; binding.bottomSheetNearby.bottomSheet.setLayoutParams(rlBottomSheetLayoutParams); + int spanCount = getSpanCount(); + if (gridLayoutManager != null) { + gridLayoutManager.setSpanCount(spanCount); + } } - public void onLearnMoreClicked() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse(WLM_URL)); @@ -2322,11 +2427,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment } public void onToggleChipsClicked() { - if ( binding.nearbyFilter.chipView.getRoot().getVisibility() == View.VISIBLE) { - binding.nearbyFilter.chipView.getRoot().setVisibility(View.GONE); + if (binding.nearbyFilter.chipView.getRoot().getVisibility() == View.VISIBLE) { + binding.nearbyFilter.chipView.getRoot().setVisibility(View.GONE); } else { - binding.nearbyFilter.chipView.getRoot().setVisibility(View.VISIBLE); + binding.nearbyFilter.chipView.getRoot().setVisibility(View.VISIBLE); } - binding.nearbyFilter.ivToggleChips.setRotation(binding.nearbyFilter.ivToggleChips.getRotation() + 180); + binding.nearbyFilter.ivToggleChips.setRotation( + binding.nearbyFilter.ivToggleChips.getRotation() + 180); } } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/model/BottomSheetItem.kt b/app/src/main/java/fr/free/nrw/commons/nearby/model/BottomSheetItem.kt new file mode 100644 index 000000000..bf30be6ec --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/model/BottomSheetItem.kt @@ -0,0 +1,3 @@ +package fr.free.nrw.commons.nearby.model + +class BottomSheetItem(var imageResourceId: Int, val title: String) diff --git a/app/src/main/res/layout/activity_wikidata_feedback.xml b/app/src/main/res/layout/activity_wikidata_feedback.xml new file mode 100644 index 000000000..5f6647efe --- /dev/null +++ b/app/src/main/res/layout/activity_wikidata_feedback.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_details.xml b/app/src/main/res/layout/bottom_sheet_details.xml index a8889aafa..8c354daae 100644 --- a/app/src/main/res/layout/bottom_sheet_details.xml +++ b/app/src/main/res/layout/bottom_sheet_details.xml @@ -53,169 +53,21 @@ android:layout_width="match_parent" android:layout_height="@dimen/tiny_height" android:layout_marginTop="@dimen/small_height" - android:layout_marginBottom="@dimen/activity_margin_horizontal" - android:background="@android:color/darker_gray"/> - + android:layout_marginBottom="@dimen/activity_margin_horizontal" + android:background="@android:color/darker_gray" /> - - - - + - - - - - - - - - - - - - - - - - - - + android:layout_width="match_parent" + android:layout_height="@dimen/tiny_height" + android:layout_marginTop="@dimen/small_height" + android:layout_marginBottom="@dimen/activity_margin_horizontal" + android:background="@android:color/darker_gray" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fb73dd694..21741f6dc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -818,5 +818,13 @@ Upload your first media by tapping on the add button. Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads. Note about multi-uploads + Report a problem about this item to Wikidata + Please enter some comments + Talk + "Write something about the " + " item. It will be publicly visible." + \'%1$s\' does not exist anymore, no picture can ever be taken of it. + \'%1$s\' is at a different place (please specify the correct place below, if possible tell us the correct latitude/longitude). + Other problem or information (please explain below). Your feedback gets posted to the following wiki page: Commons:Mobile app/Feedback ]]>