From dd7973cbbf0939e6c1c698688af8f0f1c7459f50 Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Fri, 21 Feb 2025 19:39:32 +0530 Subject: [PATCH 1/8] Rename .java to .kt --- ...ataItemDetailsActivity.java => WikidataItemDetailsActivity.kt} | 0 .../explore/map/{ExploreMapCalls.java => ExploreMapCalls.kt} | 0 .../map/{ExploreMapContract.java => ExploreMapContract.kt} | 0 .../map/{ExploreMapController.java => ExploreMapController.kt} | 0 .../map/{ExploreMapFragment.java => ExploreMapFragment.kt} | 0 .../map/{ExploreMapPresenter.java => ExploreMapPresenter.kt} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/fr/free/nrw/commons/explore/depictions/{WikidataItemDetailsActivity.java => WikidataItemDetailsActivity.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapCalls.java => ExploreMapCalls.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapContract.java => ExploreMapContract.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapController.java => ExploreMapController.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapFragment.java => ExploreMapFragment.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapPresenter.java => ExploreMapPresenter.kt} (100%) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java rename to app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt From a4109eb37c4766b697c9e334fd73e9530e876dd5 Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Fri, 21 Feb 2025 19:39:34 +0530 Subject: [PATCH 2/8] Refactor: Migrate WikidataItemDetailsActivity and explore/maps package to Kotlin The `WikidataItemDetailsActivity` has been migrated from Java to Kotlin. This includes updating the activity's structure, handling intents, setting up tabs and view pager, managing the media detail fragment, and implementing bookmark functionality. Additionally, the logic for displaying and interacting with depicted items' details, media, child classes, and parent classes has been adapted for the Kotlin implementation. --- .../contributions/ContributionsFragment.kt | 6 +- .../explore/ExploreMapRootFragment.java | 8 +- .../depictions/WikidataItemDetailsActivity.kt | 444 +++-- .../commons/explore/map/ExploreMapCalls.kt | 28 +- .../commons/explore/map/ExploreMapContract.kt | 70 +- .../explore/map/ExploreMapController.kt | 344 ++-- .../commons/explore/map/ExploreMapFragment.kt | 1443 ++++++++--------- .../explore/map/ExploreMapPresenter.kt | 284 ++-- .../location/LocationUpdateListener.kt | 6 +- .../NearbyParentFragmentPresenter.kt | 6 +- 10 files changed, 1269 insertions(+), 1370 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt index 0b7736bab7..58bc68a52b 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt @@ -697,12 +697,12 @@ class ContributionsFragment } } - override fun onLocationChangedSignificantly(latLng: LatLng) { + override fun onLocationChangedSignificantly(latLng: LatLng?) { // Will be called if location changed more than 1000 meter updateClosestNearbyCardViewInfo() } - override fun onLocationChangedSlightly(latLng: LatLng) { + override fun onLocationChangedSlightly(latLng: LatLng?) { /* Update closest nearby notification card onLocationChangedSlightly */ try { @@ -712,7 +712,7 @@ class ContributionsFragment } } - override fun onLocationChangedMedium(latLng: LatLng) { + override fun onLocationChangedMedium(latLng: LatLng?) { // Update closest nearby card view if location changed more than 500 meters updateClosestNearbyCardViewInfo() } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java index abf02758d5..bf37d019fe 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java @@ -144,8 +144,8 @@ public void onMediaClicked(int position) { */ @Override public Media getMediaAtPosition(int i) { - if (mapFragment != null && mapFragment.mediaList != null) { - return mapFragment.mediaList.get(i); + if (mapFragment != null && mapFragment.getMediaList() != null) { + return mapFragment.getMediaList().get(i); } else { return null; } @@ -159,8 +159,8 @@ public Media getMediaAtPosition(int i) { */ @Override public int getTotalMediaCount() { - if (mapFragment != null && mapFragment.mediaList != null) { - return mapFragment.mediaList.size(); + if (mapFragment != null && mapFragment.getMediaList() != null) { + return mapFragment.getMediaList().size(); } else { return 0; } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt index cf72691232..109f7e199a 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt @@ -1,161 +1,159 @@ -package fr.free.nrw.commons.explore.depictions; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.FrameLayout; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.viewpager.widget.ViewPager; -import com.google.android.material.snackbar.Snackbar; -import com.google.android.material.tabs.TabLayout; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.ViewPagerAdapter; -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao; -import fr.free.nrw.commons.category.CategoryImagesCallback; -import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding; -import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment; -import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment; -import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; -import fr.free.nrw.commons.theme.BaseActivity; -import fr.free.nrw.commons.upload.structure.depictions.DepictModel; -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import fr.free.nrw.commons.wikidata.WikidataConstants; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; +package fr.free.nrw.commons.explore.depictions + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import com.google.android.material.snackbar.Snackbar +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.ViewPagerAdapter +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao +import fr.free.nrw.commons.category.CategoryImagesCallback +import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding +import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment +import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment +import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment +import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.upload.structure.depictions.DepictModel +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import fr.free.nrw.commons.wikidata.WikidataConstants +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject /** * Activity to show depiction media, parent classes and child classes of depicted items in Explore */ -public class WikidataItemDetailsActivity extends BaseActivity implements MediaDetailPagerFragment.MediaDetailProvider, - CategoryImagesCallback { - private FragmentManager supportFragmentManager; - private DepictedImagesFragment depictionImagesListFragment; - private MediaDetailPagerFragment mediaDetailPagerFragment; +class WikidataItemDetailsActivity : BaseActivity(), + MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback { + + private lateinit var supportFragmentManager: FragmentManager + private lateinit var depictionImagesListFragment: DepictedImagesFragment + private var mediaDetailPagerFragment: MediaDetailPagerFragment? = null /** * Name of the depicted item * Ex: Rabbit */ + @Inject + lateinit var bookmarkItemsDao: BookmarkItemsDao - @Inject BookmarkItemsDao bookmarkItemsDao; - private CompositeDisposable compositeDisposable; @Inject - DepictModel depictModel; - private String wikidataItemName; - private ActivityWikidataItemDetailsBinding binding; - - ViewPagerAdapter viewPagerAdapter; - private DepictedItem wikidataItem; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityWikidataItemDetailsBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - compositeDisposable = new CompositeDisposable(); - supportFragmentManager = getSupportFragmentManager(); - viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); - binding.viewPager.setAdapter(viewPagerAdapter); - binding.viewPager.setOffscreenPageLimit(2); - binding.tabLayout.setupWithViewPager(binding.viewPager); - - final DepictedItem depictedItem = getIntent().getParcelableExtra( - WikidataConstants.BOOKMARKS_ITEMS); - wikidataItem = depictedItem; - setSupportActionBar(binding.toolbarBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setTabs(); - setPageTitle(); + lateinit var depictModel: DepictModel + private var wikidataItemName: String? = null + private lateinit var binding: ActivityWikidataItemDetailsBinding + + private lateinit var viewPagerAdapter: ViewPagerAdapter + private var wikidataItem: DepictedItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityWikidataItemDetailsBinding.inflate(layoutInflater) + setContentView(binding.root) + + supportFragmentManager = getSupportFragmentManager() + viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + binding.viewPager.adapter = viewPagerAdapter + binding.viewPager.offscreenPageLimit = 2 + binding.tabLayout.setupWithViewPager(binding.viewPager) + + wikidataItem = intent.getParcelableExtra(WikidataConstants.BOOKMARKS_ITEMS) + setSupportActionBar(binding.toolbarBinding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + setTabs() + setPageTitle() } /** - * Gets the passed wikidataItemName from the intents and displays it as the page title + * Gets the passed wikidataItemName from the intent and displays it as the page title */ - private void setPageTitle() { - if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) { - setTitle(getIntent().getStringExtra("wikidataItemName")); + private fun setPageTitle() { + intent.getStringExtra("wikidataItemName")?.let { + title = it } } /** - * This method is called on success of API call for featured Images. - * The viewpager will notified that number of items have changed. + * This method is called on success of API call for featured images. + * The ViewPager will be notified that the number of items has changed. */ - @Override - public void viewPagerNotifyDataSetChanged() { - if (mediaDetailPagerFragment !=null){ - mediaDetailPagerFragment.notifyDataSetChanged(); - } + override fun viewPagerNotifyDataSetChanged() { + mediaDetailPagerFragment?.notifyDataSetChanged() } /** - * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab, - * Set the fragments according to the tab selected in the viewPager. + * This activity contains 3 tabs and a ViewPager. + * This method is used to set the titles of tabs and the fragments according to the selected tab */ - private void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); - depictionImagesListFragment = new DepictedImagesFragment(); - ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment(); - ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment(); - wikidataItemName = getIntent().getStringExtra("wikidataItemName"); - String entityId = getIntent().getStringExtra("entityId"); - if (getIntent() != null && wikidataItemName != null) { - Bundle arguments = new Bundle(); - arguments.putString("wikidataItemName", wikidataItemName); - arguments.putString("entityId", entityId); - depictionImagesListFragment.setArguments(arguments); - parentDepictionsFragment.setArguments(arguments); - childDepictionsFragment.setArguments(arguments); + private fun setTabs() { + val fragmentList = mutableListOf() + val titleList = mutableListOf() + + depictionImagesListFragment = DepictedImagesFragment() + val childDepictionsFragment = ChildDepictionsFragment() + val parentDepictionsFragment = ParentDepictionsFragment() + + wikidataItemName = intent.getStringExtra("wikidataItemName") + val entityId = intent.getStringExtra("entityId") + + if (!wikidataItemName.isNullOrEmpty()) { + val arguments = Bundle().apply { + putString("wikidataItemName", wikidataItemName) + putString("entityId", entityId) + } + depictionImagesListFragment.arguments = arguments + parentDepictionsFragment.arguments = arguments + childDepictionsFragment.arguments = arguments } - fragmentList.add(depictionImagesListFragment); - titleList.add(getResources().getString(R.string.title_for_media)); - fragmentList.add(childDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_child_classes)); - fragmentList.add(parentDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_parent_classes)); - viewPagerAdapter.setTabData(fragmentList, titleList); - binding.viewPager.setOffscreenPageLimit(2); - viewPagerAdapter.notifyDataSetChanged(); - } + fragmentList.apply { + add(depictionImagesListFragment) + add(childDepictionsFragment) + add(parentDepictionsFragment) + } + + titleList.apply { + add(getString(R.string.title_for_media)) + add(getString(R.string.title_for_child_classes)) + add(getString(R.string.title_for_parent_classes)) + } + viewPagerAdapter.setTabData(fragmentList, titleList) + binding.viewPager.offscreenPageLimit = 2 + viewPagerAdapter.notifyDataSetChanged() + } /** * Shows media detail fragment when user clicks on any image in the list */ - @Override - public void onMediaClicked(int position) { - binding.tabLayout.setVisibility(View.GONE); - binding.viewPager.setVisibility(View.GONE); - binding.mediaContainer.setVisibility(View.VISIBLE); - if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) { - // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true); - FragmentManager supportFragmentManager = getSupportFragmentManager(); - supportFragmentManager - .beginTransaction() - .replace(R.id.mediaContainer, mediaDetailPagerFragment) - .addToBackStack(null) - .commit(); - supportFragmentManager.executePendingTransactions(); + override fun onMediaClicked(position: Int) { + binding.apply { + tabLayout.visibility = View.GONE + viewPager.visibility = View.GONE + mediaContainer.visibility = View.VISIBLE } - mediaDetailPagerFragment.showImage(position); + + if (mediaDetailPagerFragment == null || mediaDetailPagerFragment?.isVisible == false) { + mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true) + supportFragmentManager = getSupportFragmentManager() + supportFragmentManager.beginTransaction() + .replace(R.id.mediaContainer, mediaDetailPagerFragment!!) + .addToBackStack(null) + .commit() + supportFragmentManager.executePendingTransactions() + } + + mediaDetailPagerFragment?.showImage(position) } /** @@ -164,23 +162,23 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * current index of viewPager. * @return Media Object */ - @Override - public Media getMediaAtPosition(int i) { - return depictionImagesListFragment.getMediaAtPosition(i); + override fun getMediaAtPosition(i: Int): Media? { + return depictionImagesListFragment.getMediaAtPosition(i) } /** * This method is called on backPressed of anyFragment in the activity. * If condition is called when mediaDetailFragment is opened. */ - @Override - public void onBackPressed() { - if (supportFragmentManager.getBackStackEntryCount() == 1){ - binding.tabLayout.setVisibility(View.VISIBLE); - binding.viewPager.setVisibility(View.VISIBLE); - binding.mediaContainer.setVisibility(View.GONE); + override fun onBackPressed() { + if (supportFragmentManager.backStackEntryCount == 1) { + binding.apply { + tabLayout.visibility = View.VISIBLE + viewPager.visibility = View.VISIBLE + mediaContainer.visibility = View.GONE + } } - super.onBackPressed(); + super.onBackPressed() } /** @@ -188,14 +186,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * The viewpager will contain same number of media items as that of media elements in adapter. * @return Total Media count in the adapter */ - @Override - public int getTotalMediaCount() { - return depictionImagesListFragment.getTotalMediaCount(); + override fun getTotalMediaCount(): Int { + return depictionImagesListFragment.getTotalMediaCount() } - @Override - public Integer getContributionStateAt(int position) { - return null; + override fun getContributionStateAt(position: Int): Int? { + return null } /** @@ -203,107 +199,105 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * * @param index item position that has been nominated */ - @Override - public void refreshNominatedMedia(int index) { - if (getSupportFragmentManager().getBackStackEntryCount() == 1) { - onBackPressed(); - onMediaClicked(index); + override fun refreshNominatedMedia(index: Int) { + if (supportFragmentManager.backStackEntryCount == 1) { + onBackPressed() + onMediaClicked(index) } } - /** - * Consumers should be simply using this method to use this activity. - * - * @param context A Context of the application package implementing this class. - * @param depictedItem Name of the depicts for displaying its details - */ - public static void startYourself(Context context, DepictedItem depictedItem) { - Intent intent = new Intent(context, WikidataItemDetailsActivity.class); - intent.putExtra("wikidataItemName", depictedItem.getName()); - intent.putExtra("entityId", depictedItem.getId()); - intent.putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem); - context.startActivity(intent); + companion object { + /** + * Consumers should be simply using this method to use this activity. + * + * @param context a Context of the application package implementing this class. + * @param depictedItem Name of the depicts for displaying its details + */ + fun startYourself(context: Context, depictedItem: DepictedItem) { + val intent = Intent(context, WikidataItemDetailsActivity::class.java).apply { + putExtra("wikidataItemName", depictedItem.name) + putExtra("entityId", depictedItem.id) + putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem) + } + context.startActivity(intent) + } } /** - * This function inflates the menu + * Inflates the menu */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater menuInflater=getMenuInflater(); - menuInflater.inflate(R.menu.menu_wikidata_item,menu); - - updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item)); - - return super.onCreateOptionsMenu(menu); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_wikidata_item, menu) + updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item)) + return super.onCreateOptionsMenu(menu) } /** * This method handles the logic on item select in toolbar menu * Currently only 1 choice is available to open Wikidata item details page in browser */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()){ - case R.id.browser_actions_menu_items: - String entityId=getIntent().getStringExtra("entityId"); - Uri uri = Uri.parse("https://www.wikidata.org/wiki/" + entityId); - Utils.handleWebUrl(this, uri); - return true; - case R.id.menu_bookmark_current_item: - - if(getIntent().getStringExtra("fragment") != null) { - compositeDisposable.add(depictModel.getDepictions( - getIntent().getStringExtra("entityId") - ).subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(depictedItems -> { - final boolean bookmarkExists = bookmarkItemsDao.updateBookmarkItem( - depictedItems.get(0)); - final Snackbar snackbar - = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.add_bookmark, Snackbar.LENGTH_LONG) - : Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.remove_bookmark, - Snackbar.LENGTH_LONG); - - snackbar.show(); - updateBookmarkState(item); - })); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.browser_actions_menu_items -> { + val entityId = intent.getStringExtra("entityId") + val uri = Uri.parse("https://www.wikidata.org/wiki/$entityId") + Utils.handleWebUrl(this, uri) + return true + } + R.id.menu_bookmark_current_item -> { + val entityId = intent.getStringExtra("entityId") + if (intent.getStringExtra("fragment") != null) { + compositeDisposable.add( + depictModel.getDepictions(entityId!!) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { depictedItems -> + val bookmarkExists = bookmarkItemsDao + .updateBookmarkItem(depictedItems[0]) + val snackbarText = if (bookmarkExists) + R.string.add_bookmark + else + R.string.remove_bookmark + Snackbar.make( + findViewById(R.id.toolbar_layout), + snackbarText, + Snackbar.LENGTH_LONG + ).show() + updateBookmarkState(item) + } + ) } else { - final boolean bookmarkExists - = bookmarkItemsDao.updateBookmarkItem(wikidataItem); - final Snackbar snackbar - = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.add_bookmark, Snackbar.LENGTH_LONG) - : Snackbar.make(findViewById(R.id.toolbar_layout), R.string.remove_bookmark, - Snackbar.LENGTH_LONG); - - snackbar.show(); - updateBookmarkState(item); + val bookmarkExists = bookmarkItemsDao.updateBookmarkItem(wikidataItem!!) + val snackbarText = if (bookmarkExists) + R.string.add_bookmark + else + R.string.remove_bookmark + Snackbar.make( + findViewById(R.id.toolbar_layout), + snackbarText, + Snackbar.LENGTH_LONG + ).show() + updateBookmarkState(item) } - return true; - case android.R.id.home: - onBackPressed(); - return true; - default: - return super.onOptionsItemSelected(item); + return true + } + android.R.id.home -> { + onBackPressed() + return true + } + else -> return super.onOptionsItemSelected(item) } } - private void updateBookmarkState(final MenuItem item) { - final boolean isBookmarked; - if(getIntent().getStringExtra("fragment") != null) { - isBookmarked - = bookmarkItemsDao.findBookmarkItem(getIntent().getStringExtra("entityId")); - } else { - isBookmarked = bookmarkItemsDao.findBookmarkItem(wikidataItem.getId()); - } - final int icon - = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px - : R.drawable.menu_ic_round_star_border_24px; - item.setIcon(icon); + private fun updateBookmarkState(item: MenuItem) { + val isBookmarked = bookmarkItemsDao.findBookmarkItem( + intent.getStringExtra("entityId") ?: wikidataItem?.id + ) + val icon = if (isBookmarked) + R.drawable.menu_ic_round_star_filled_24px + else + R.drawable.menu_ic_round_star_border_24px + item.setIcon(icon) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt index ec426942c3..0738f1899f 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt @@ -1,21 +1,16 @@ -package fr.free.nrw.commons.explore.map; +package fr.free.nrw.commons.explore.map -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.media.MediaClient; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.media.MediaClient +import javax.inject.Inject +import javax.inject.Singleton @Singleton -public class ExploreMapCalls { +class ExploreMapCalls @Inject constructor() { @Inject - MediaClient mediaClient; - - @Inject - public ExploreMapCalls() { - } + lateinit var mediaClient: MediaClient /** * Calls method to query Commons for uploads around a location @@ -23,9 +18,8 @@ public class ExploreMapCalls { * @param currentLatLng coordinates of search location * @return list of places obtained */ - List callCommonsQuery(final LatLng currentLatLng) { - String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude(); - return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet(); + fun callCommonsQuery(currentLatLng: LatLng): List { + val coordinates = "${currentLatLng.latitude}|${currentLatLng.longitude}" + return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet() } - } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt index feb66bf55b..256e621e95 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt @@ -1,45 +1,43 @@ -package fr.free.nrw.commons.explore.map; +package fr.free.nrw.commons.explore.map -import android.content.Context; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.location.LocationServiceManager; -import java.util.List; +import android.content.Context +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.location.LocationServiceManager -public class ExploreMapContract { +interface ExploreMapContract { interface View { - boolean isNetworkConnectionEstablished(); - void populatePlaces(LatLng curlatLng); - void askForLocationPermission(); - void recenterMap(LatLng curLatLng); - void hideBottomDetailsSheet(); - LatLng getMapCenter(); - LatLng getMapFocus(); - LatLng getLastMapFocus(); - void addMarkersToMap(final List nearbyBaseMarkers); - void clearAllMarkers(); - void addSearchThisAreaButtonAction(); - void setSearchThisAreaButtonVisibility(boolean isVisible); - void setProgressBarVisibility(boolean isVisible); - boolean isDetailsBottomSheetVisible(); - boolean isSearchThisAreaButtonVisible(); - Context getContext(); - LatLng getLastLocation(); - void disableFABRecenter(); - void enableFABRecenter(); - void setFABRecenterAction(android.view.View.OnClickListener onClickListener); - boolean backButtonClicked(); + fun isNetworkConnectionEstablished(): Boolean + fun populatePlaces(curLatLng: LatLng?) + fun askForLocationPermission() + fun recenterMap(curLatLng: LatLng?) + fun hideBottomDetailsSheet() + fun getMapCenter(): LatLng + fun getMapFocus(): LatLng + fun getLastMapFocus(): LatLng + fun addMarkersToMap(nearbyBaseMarkers: List) + fun clearAllMarkers() + fun addSearchThisAreaButtonAction() + fun setSearchThisAreaButtonVisibility(isVisible: Boolean) + fun setProgressBarVisibility(isVisible: Boolean) + fun isDetailsBottomSheetVisible(): Boolean + fun isSearchThisAreaButtonVisible(): Boolean + fun getContext(): Context + fun getLastLocation(): LatLng + fun disableFABRecenter() + fun enableFABRecenter() + fun setFABRecenterAction(onClickListener: android.view.View.OnClickListener) + fun backButtonClicked(): Boolean } interface UserActions { - void updateMap(LocationServiceManager.LocationChangeType locationChangeType); - void lockUnlockNearby(boolean isNearbyLocked); - void attachView(View view); - void detachView(); - void setActionListeners(JsonKvStore applicationKvStore); - boolean backButtonClicked(); + fun updateMap(locationChangeType: LocationServiceManager.LocationChangeType) + fun lockUnlockNearby(isNearbyLocked: Boolean) + fun attachView(view: View) + fun detachView() + fun setActionListeners(applicationKvStore: JsonKvStore) + fun backButtonClicked(): Boolean } - } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt index c944f75a11..391d9d5c52 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt @@ -1,126 +1,120 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween; -import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.transition.Transition; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.utils.ImageUtils; -import fr.free.nrw.commons.utils.LocationUtils; -import fr.free.nrw.commons.utils.PlaceUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; -import timber.log.Timber; - -public class ExploreMapController extends MapController { - - private final ExploreMapCalls exploreMapCalls; - public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used - public LatLng currentLocation; // current location of user - public double latestSearchRadius = 0; // Any last search radius - public double currentLocationSearchRadius = 0; // Search radius of only searches around current location - - - @Inject - public ExploreMapController(ExploreMapCalls explorePlaces) { - this.exploreMapCalls = explorePlaces; - } +package fr.free.nrw.commons.explore.map + +import android.content.Context +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.utils.ImageUtils +import fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween +import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween +import fr.free.nrw.commons.utils.LocationUtils +import fr.free.nrw.commons.utils.PlaceUtils +import timber.log.Timber +import javax.inject.Inject + +class ExploreMapController @Inject constructor( + private val exploreMapCalls: ExploreMapCalls +) : MapController() { + + var latestSearchLocation: LatLng? = null // Can be current and camera target when search this + // area button is used + var currentLocation: LatLng? = null // Current location of user + var latestSearchRadius: Double = 0.0 // Any last search radius + var currentLocationSearchRadius: Double = 0.0 // Search radius of only searches around current + // location /** - * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList, - * explorePlaceList and boundaryCoordinates + * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, + * mediaList, explorePlaceList and boundaryCoordinates * - * @param currentLatLng is current geolocation - * @param searchLatLng is the location that we want to search around + * @param currentLatLng is current geolocation + * @param searchLatLng is the location that we want to search around * @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around * current location, false if another location * @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and * boundaryCoordinates */ - public ExplorePlacesInfo loadAttractionsFromLocation(LatLng currentLatLng, LatLng searchLatLng, - boolean checkingAroundCurrentLocation) { + fun loadAttractionsFromLocation( + currentLatLng: LatLng, + searchLatLng: LatLng?, + checkingAroundCurrentLocation: Boolean + ): ExplorePlacesInfo? { if (searchLatLng == null) { - Timber.d("Loading attractions explore map, but search is null"); - return null; + Timber.d("Loading attractions explore map, but search is null") + return null } - ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo(); + val explorePlacesInfo = ExplorePlacesInfo() try { - explorePlacesInfo.currentLatLng = currentLatLng; - latestSearchLocation = searchLatLng; - - List mediaList = exploreMapCalls.callCommonsQuery(searchLatLng); - LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south - mediaList.get(0).getCoordinates(), // north - mediaList.get(0).getCoordinates(), // west - mediaList.get(0).getCoordinates()};// east, init with a random location - - if (searchLatLng != null) { - Timber.d("Sorting places by distance..."); - final Map distances = new HashMap<>(); - for (Media media : mediaList) { - distances.put(media, - computeDistanceBetween(media.getCoordinates(), searchLatLng)); - // Find boundaries with basic find max approach - if (media.getCoordinates().getLatitude() - < boundaryCoordinates[0].getLatitude()) { - boundaryCoordinates[0] = media.getCoordinates(); - } - if (media.getCoordinates().getLatitude() - > boundaryCoordinates[1].getLatitude()) { - boundaryCoordinates[1] = media.getCoordinates(); - } - if (media.getCoordinates().getLongitude() - < boundaryCoordinates[2].getLongitude()) { - boundaryCoordinates[2] = media.getCoordinates(); - } - if (media.getCoordinates().getLongitude() - > boundaryCoordinates[3].getLongitude()) { - boundaryCoordinates[3] = media.getCoordinates(); - } + explorePlacesInfo.currentLatLng = currentLatLng + latestSearchLocation = searchLatLng + + val mediaList = exploreMapCalls.callCommonsQuery(searchLatLng) + val boundaryCoordinates = arrayOf( + mediaList[0].coordinates, // south + mediaList[0].coordinates, // north + mediaList[0].coordinates, // west + mediaList[0].coordinates // east, init with a random location + ) + + Timber.d("Sorting places by distance...") + val distances = mutableMapOf() + + for (media in mediaList) { + distances[media] = computeDistanceBetween(media.coordinates!!, searchLatLng) + + // Find boundaries with basic find max approach + if (media.coordinates!!.latitude < boundaryCoordinates[0]?.latitude!!) { + boundaryCoordinates[0] = media.coordinates + } + if (media.coordinates!!.latitude > boundaryCoordinates[1]?.latitude!!) { + boundaryCoordinates[1] = media.coordinates + } + if (media.coordinates!!.longitude < boundaryCoordinates[2]?.longitude!!) { + boundaryCoordinates[2] = media.coordinates + } + if (media.coordinates!!.longitude > boundaryCoordinates[3]?.longitude!!) { + boundaryCoordinates[3] = media.coordinates } } - explorePlacesInfo.mediaList = mediaList; - explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList); - explorePlacesInfo.boundaryCoordinates = boundaryCoordinates; + + explorePlacesInfo.mediaList = mediaList + explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList) + explorePlacesInfo.boundaryCoordinates = boundaryCoordinates // Sets latestSearchRadius to maximum distance among boundaries and search location - for (LatLng bound : boundaryCoordinates) { - double distance = LocationUtils.calculateDistance(bound.getLatitude(), - bound.getLongitude(), searchLatLng.getLatitude(), searchLatLng.getLongitude()); + for (bound in boundaryCoordinates) { + val distance = LocationUtils.calculateDistance( + bound?.latitude!!, bound.longitude, + searchLatLng.latitude, searchLatLng.longitude + ) if (distance > latestSearchRadius) { - latestSearchRadius = distance; + latestSearchRadius = distance } } - // Our radius searched around us, will be used to understand when user search their own location, we will follow them + // Our radius searched around us, will be used to understand when user search + // their own location, we will follow them if (checkingAroundCurrentLocation) { - currentLocationSearchRadius = latestSearchRadius; - currentLocation = currentLatLng; + currentLocationSearchRadius = latestSearchRadius + currentLocation = currentLatLng } - } catch (Exception e) { - e.printStackTrace(); + } catch (e: Exception) { + e.printStackTrace() } - return explorePlacesInfo; + return explorePlacesInfo } /** @@ -128,86 +122,96 @@ public class ExploreMapController extends MapController { * * @return baseMarkerOptions list that holds nearby places with their icons */ - public static List loadAttractionsFromLocationToBaseMarkerOptions( - LatLng currentLatLng, - final List placeList, - Context context, - NearbyBaseMarkerThumbCallback callback, - ExplorePlacesInfo explorePlacesInfo) { - List baseMarkerList = new ArrayList<>(); - - if (placeList == null) { - return baseMarkerList; - } + companion object { + fun loadAttractionsFromLocationToBaseMarkerOptions( + currentLatLng: LatLng, + placeList: List?, + context: Context, + callback: NearbyBaseMarkerThumbCallback, + explorePlacesInfo: ExplorePlacesInfo + ): List { + val baseMarkerList = mutableListOf() + + if (placeList == null) { + return baseMarkerList + } - VectorDrawableCompat vectorDrawable = null; - try { - vectorDrawable = VectorDrawableCompat.create( - context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme()); + var vectorDrawable: VectorDrawableCompat? = null + try { + vectorDrawable = VectorDrawableCompat.create( + context.resources, R.drawable.ic_custom_map_marker_dark, context.theme + ) + } catch (e: Resources.NotFoundException) { + // Ignore when running tests + } - } catch (Resources.NotFoundException e) { - // ignore when running tests. - } - if (vectorDrawable != null) { - for (Place explorePlace : placeList) { - final BaseMarker baseMarker = new BaseMarker(); - String distance = formatDistanceBetween(currentLatLng, explorePlace.location); - explorePlace.setDistance(distance); - - baseMarker.setTitle( - explorePlace.name.substring(5, explorePlace.name.lastIndexOf("."))); - baseMarker.setPosition( - new fr.free.nrw.commons.location.LatLng( - explorePlace.location.getLatitude(), - explorePlace.location.getLongitude(), 0)); - baseMarker.setPlace(explorePlace); - - Glide.with(context) - .asBitmap() - .load(explorePlace.getThumb()) - .placeholder(R.drawable.image_placeholder_96) - .apply(new RequestOptions().override(96, 96).centerCrop()) - .into(new CustomTarget() { - // We add icons to markers when bitmaps are ready - @Override - public void onResourceReady(@NonNull Bitmap resource, - @Nullable Transition transition) { - baseMarker.setIcon( - ImageUtils.addRedBorder(resource, 6, context)); - baseMarkerList.add(baseMarker); - if (baseMarkerList.size() - == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback - callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, - explorePlacesInfo); + vectorDrawable?.let { + for (explorePlace in placeList) { + val baseMarker = BaseMarker() + val distance = formatDistanceBetween(currentLatLng, explorePlace.location) + explorePlace.distance = distance + + baseMarker.title = explorePlace.name.substring( + 5, + explorePlace.name.lastIndexOf(".") + ) + baseMarker.position = LatLng( + explorePlace.location.latitude, + explorePlace.location.longitude, 0.0f + ) + baseMarker.place = explorePlace + + Glide.with(context) + .asBitmap() + .load(explorePlace.thumb) + .placeholder(R.drawable.image_placeholder_96) + .apply(RequestOptions().override(96, 96).centerCrop()) + .into(object : CustomTarget() { + // We add icons to markers when bitmaps are ready + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + baseMarker.icon = ImageUtils.addRedBorder(resource, 6, context) + baseMarkerList.add(baseMarker) + if (baseMarkerList.size == placeList.size) { + // If true, we added all markers to list and can trigger thumbs + // ready callback + callback.onNearbyBaseMarkerThumbsReady( + baseMarkerList, + explorePlacesInfo + ) + } } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - } - - // We add thumbnail icon for images that couldn't be loaded - @Override - public void onLoadFailed(@Nullable final Drawable errorDrawable) { - super.onLoadFailed(errorDrawable); - baseMarker.fromResource(context, R.drawable.image_placeholder_96); - baseMarkerList.add(baseMarker); - if (baseMarkerList.size() - == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback - callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, - explorePlacesInfo); + + override fun onLoadCleared(placeholder: Drawable?) {} + + // We add thumbnail icon for images that couldn't be loaded + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + baseMarker.fromResource(context, R.drawable.image_placeholder_96) + baseMarkerList.add(baseMarker) + if (baseMarkerList.size == placeList.size) { + // If true, we added all markers to list and can trigger thumbs + // ready callback + callback.onNearbyBaseMarkerThumbsReady( + baseMarkerList, + explorePlacesInfo + ) + } } - } - }); + }) + } } + return baseMarkerList } - return baseMarkerList; } interface NearbyBaseMarkerThumbCallback { - // Callback to notify thumbnails of explore markers are added as icons and ready - void onNearbyBaseMarkerThumbsReady(List baseMarkers, - ExplorePlacesInfo explorePlacesInfo); + fun onNearbyBaseMarkerThumbsReady( + baseMarkers: List, + explorePlacesInfo: ExplorePlacesInfo + ) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt index 1b16591828..87f768ceeb 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt @@ -1,494 +1,478 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED; -import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL; - -import android.Manifest.permission; -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.location.Location; -import android.location.LocationManager; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.snackbar.Snackbar; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.databinding.FragmentExploreMapBinding; -import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; -import fr.free.nrw.commons.explore.ExploreMapRootFragment; -import fr.free.nrw.commons.explore.paging.LiveDataConverter; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -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.media.MediaClient; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.utils.DialogUtil; -import fr.free.nrw.commons.utils.MapUtils; -import fr.free.nrw.commons.utils.NetworkUtils; -import fr.free.nrw.commons.utils.SystemThemeUtils; -import fr.free.nrw.commons.utils.ViewUtil; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Named; -import org.osmdroid.events.MapEventsReceiver; -import org.osmdroid.events.MapListener; -import org.osmdroid.events.ScrollEvent; -import org.osmdroid.events.ZoomEvent; -import org.osmdroid.tileprovider.tilesource.TileSourceFactory; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.util.constants.GeoConstants; -import org.osmdroid.views.CustomZoomButtonsController; -import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener; -import org.osmdroid.views.overlay.ItemizedOverlayWithFocus; -import org.osmdroid.views.overlay.MapEventsOverlay; -import org.osmdroid.views.overlay.Overlay; -import org.osmdroid.views.overlay.OverlayItem; -import org.osmdroid.views.overlay.ScaleBarOverlay; -import org.osmdroid.views.overlay.ScaleDiskOverlay; -import org.osmdroid.views.overlay.TilesOverlay; -import timber.log.Timber; - -public class ExploreMapFragment extends CommonsDaggerSupportFragment - implements ExploreMapContract.View, LocationUpdateListener, LocationPermissionCallback { - - private BottomSheetBehavior bottomSheetDetailsBehavior; - private BroadcastReceiver broadcastReceiver; - private boolean isNetworkErrorOccurred; - private Snackbar snackbar; - private boolean isDarkTheme; - private boolean isPermissionDenied; - private fr.free.nrw.commons.location.LatLng lastKnownLocation; // last location of user - private fr.free.nrw.commons.location.LatLng lastFocusLocation; // last location that map is focused - public List mediaList; - private boolean recenterToUserLocation; // true is recenter is needed (ie. when current location is in visible map boundaries) - private BaseMarker clickedMarker; - private GeoPoint mapCenter; - private GeoPoint lastMapFocus; - IntentFilter intentFilter = new IntentFilter(MapUtils.NETWORK_INTENT_ACTION); +package fr.free.nrw.commons.explore.map + +import android.Manifest +import android.Manifest.permission +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.BitmapDrawable +import android.location.Location +import android.location.LocationManager +import android.os.Build +import android.os.Bundle +import android.preference.PreferenceManager +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.snackbar.Snackbar +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.databinding.FragmentExploreMapBinding +import fr.free.nrw.commons.di.CommonsDaggerSupportFragment +import fr.free.nrw.commons.explore.ExploreMapRootFragment +import fr.free.nrw.commons.explore.paging.LiveDataConverter +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +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.media.MediaClient +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.utils.DialogUtil +import fr.free.nrw.commons.utils.MapUtils +import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL +import fr.free.nrw.commons.utils.NetworkUtils +import fr.free.nrw.commons.utils.SystemThemeUtils +import fr.free.nrw.commons.utils.ViewUtil +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import java.util.ArrayList + +import javax.inject.Inject +import javax.inject.Named +import org.osmdroid.events.MapEventsReceiver +import org.osmdroid.events.MapListener +import org.osmdroid.events.ScrollEvent +import org.osmdroid.events.ZoomEvent +import org.osmdroid.tileprovider.tilesource.TileSourceFactory +import org.osmdroid.util.GeoPoint +import org.osmdroid.util.constants.GeoConstants +import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener +import org.osmdroid.views.overlay.ItemizedOverlayWithFocus +import org.osmdroid.views.overlay.MapEventsOverlay +import org.osmdroid.views.overlay.Marker +import org.osmdroid.views.overlay.OverlayItem +import org.osmdroid.views.overlay.ScaleBarOverlay +import org.osmdroid.views.overlay.ScaleDiskOverlay +import org.osmdroid.views.overlay.TilesOverlay +import timber.log.Timber + +class ExploreMapFragment : CommonsDaggerSupportFragment(), + ExploreMapContract.View, LocationUpdateListener, LocationPermissionCallback { + + private lateinit var bottomSheetDetailsBehavior: BottomSheetBehavior<*> + private var broadcastReceiver: BroadcastReceiver? = null + private var isNetworkErrorOccurred = false + private var snackbar: Snackbar? = null + private var isDarkTheme = false + private var isPermissionDenied = false + private var lastKnownLocation: fr.free.nrw.commons.location.LatLng? = null // last location of user + private var lastFocusLocation: fr.free.nrw.commons.location.LatLng? = null // last focused location of the map + var mediaList: List? = null + private var recenterToUserLocation = false // true if recentering is needed + private var clickedMarker: BaseMarker? = null + private var mapCenter: GeoPoint? = null + private var lastMapFocus: GeoPoint? = null + private val intentFilter = IntentFilter(MapUtils.NETWORK_INTENT_ACTION) @Inject - LiveDataConverter liveDataConverter; + lateinit var liveDataConverter: LiveDataConverter + @Inject - MediaClient mediaClient; + lateinit var mediaClient: MediaClient + @Inject - LocationServiceManager locationManager; + lateinit var locationManager: LocationServiceManager + @Inject - ExploreMapController exploreMapController; + lateinit var exploreMapController: ExploreMapController + @Inject @Named("default_preferences") - JsonKvStore applicationKvStore; + lateinit var applicationKvStore: JsonKvStore + @Inject - BookmarkLocationsDao bookmarkLocationDao; // May be needed in future if we want to integrate bookmarking explore places + lateinit var bookmarkLocationDao: BookmarkLocationsDao // Future use for bookmarking explore places + @Inject - SystemThemeUtils systemThemeUtils; - LocationPermissionsHelper locationPermissionsHelper; + lateinit var systemThemeUtils: SystemThemeUtils + + private lateinit var locationPermissionsHelper: LocationPermissionsHelper // Nearby map state (if we came from Nearby) - private double prevZoom; - private double prevLatitude; - private double prevLongitude; + private var prevZoom = 0.0 + private var prevLatitude = 0.0 + private var prevLongitude = 0.0 - private ExploreMapPresenter presenter; + private var presenter: ExploreMapPresenter? = null - public FragmentExploreMapBinding binding; + private lateinit var binding: FragmentExploreMapBinding - private ActivityResultLauncher activityResultLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), isGranted -> { + private val activityResultLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { - locationPermissionGranted(); + locationPermissionGranted() } else { - if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - DialogUtil.showAlertDialog(getActivity(), - getActivity().getString(R.string.location_permission_title), - getActivity().getString(R.string.location_permission_rationale_explore), - getActivity().getString(android.R.string.ok), - getActivity().getString(android.R.string.cancel), - () -> { - askForLocationPermission(); - }, - null, - null - ); + if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) { + activity?.let { + DialogUtil.showAlertDialog( + it, + getString(R.string.location_permission_title), + getString(R.string.location_permission_rationale_explore), + getString(android.R.string.ok), + getString(android.R.string.cancel), + { + askForLocationPermission() + }, + null, + null + ) + } } else { if (isPermissionDenied) { - locationPermissionsHelper.showAppSettingsDialog(getActivity(), - R.string.explore_map_needs_location); + locationPermissionsHelper.showAppSettingsDialog( + requireActivity(), + R.string.explore_map_needs_location + ) } - Timber.d("The user checked 'Don't ask again' or denied the permission twice"); - isPermissionDenied = true; + Timber.d("The user checked 'Don't ask again' or denied the permission twice") + isPermissionDenied = true } } - }); + } - @NonNull - public static ExploreMapFragment newInstance() { - ExploreMapFragment fragment = new ExploreMapFragment(); - fragment.setRetainInstance(true); - return fragment; + companion object { + @JvmStatic + fun newInstance(): ExploreMapFragment { + return ExploreMapFragment().apply { retainInstance = true } + } } - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState - ) { - loadNearbyMapData(); - binding = FragmentExploreMapBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setSearchThisAreaButtonVisibility(false); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.tvAttribution.setText( - Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + loadNearbyMapData() + binding = FragmentExploreMapBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setSearchThisAreaButtonVisibility(false) + + binding.tvAttribution.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY) } else { - binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); + Html.fromHtml(getString(R.string.map_attribution)) } - initNetworkBroadCastReceiver(); - locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, - this); + + initNetworkBroadCastReceiver() + locationPermissionsHelper = LocationPermissionsHelper( + requireActivity(), + locationManager, + this + ) + if (presenter == null) { - presenter = new ExploreMapPresenter(bookmarkLocationDao); + presenter = ExploreMapPresenter(bookmarkLocationDao) } - setHasOptionsMenu(true); + setHasOptionsMenu(true) + + isDarkTheme = systemThemeUtils.isDeviceInNightMode() + isPermissionDenied = false + presenter?.attachView(this) + + initViews() + presenter?.setActionListeners(applicationKvStore) + + org.osmdroid.config.Configuration.getInstance().load( + requireContext(), + PreferenceManager.getDefaultSharedPreferences(requireContext()) + ) + + binding.mapView.apply { + setTileSource(TileSourceFactory.WIKIMEDIA) + setTilesScaledToDpi(true) + org.osmdroid.config.Configuration.getInstance() + .additionalHttpRequestProperties["Referer"] = "http://maps.wikimedia.org/" + + val scaleBarOverlay = ScaleBarOverlay(this).apply { + setScaleBarOffset(15, 25) + setBackgroundPaint( + Paint().apply { + setARGB(200, 255, 250, 250) + } + ) + enableScaleBar() + } + overlays.add(scaleBarOverlay) - isDarkTheme = systemThemeUtils.isDeviceInNightMode(); - isPermissionDenied = false; - presenter.attachView(this); + zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) + setMultiTouchControls(true) - initViews(); - presenter.setActionListeners(applicationKvStore); + if (!isCameFromNearbyMap()) { + controller.setZoom(ZOOM_LEVEL.toDouble()) + } + } - org.osmdroid.config.Configuration.getInstance().load(this.getContext(), - PreferenceManager.getDefaultSharedPreferences(this.getContext())); + performMapReadyActions() - binding.mapView.setTileSource(TileSourceFactory.WIKIMEDIA); - binding.mapView.setTilesScaledToDpi(true); + binding.mapView.overlays.add( + MapEventsOverlay(object : MapEventsReceiver { + override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean { + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + binding.mapView.invalidate() + } ?: Timber.e("CLICKED MARKER IS NULL") - org.osmdroid.config.Configuration.getInstance().getAdditionalHttpRequestProperties().put( - "Referer", "http://maps.wikimedia.org/" - ); + if (bottomSheetDetailsBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + } else if (isDetailsBottomSheetVisible()) { + hideBottomDetailsSheet() + } + return true + } - ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView); - scaleBarOverlay.setScaleBarOffset(15, 25); - Paint barPaint = new Paint(); - barPaint.setARGB(200, 255, 250, 250); - scaleBarOverlay.setBackgroundPaint(barPaint); - scaleBarOverlay.enableScaleBar(); - binding.mapView.getOverlays().add(scaleBarOverlay); - binding.mapView.getZoomController() - .setVisibility(CustomZoomButtonsController.Visibility.NEVER); - binding.mapView.setMultiTouchControls(true); + override fun longPressHelper(p: GeoPoint?): Boolean = false + }) + ) - if (!isCameFromNearbyMap()) { - binding.mapView.getController().setZoom(ZOOM_LEVEL); - } - performMapReadyActions(); + binding.mapView.addMapListener(object : MapListener { + override fun onScroll(event: ScrollEvent?): Boolean { + lastMapFocus?.let { + val myLocation = Location("").apply { + latitude = it.latitude + longitude = it.longitude + } - binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - binding.mapView.invalidate(); - } else { - Timber.e("CLICKED MARKER IS NULL"); - } - if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { - // Back should first hide the bottom sheet if it is expanded - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); - } - return true; - } + val destLocation = Location("").apply { + latitude = binding.mapView.mapCenter.latitude + longitude = binding.mapView.mapCenter.longitude + } - @Override - public boolean longPressHelper(GeoPoint p) { - return false; - } - })); - - binding.mapView.addMapListener(new MapListener() { - @Override - public boolean onScroll(ScrollEvent event) { - if (getLastMapFocus() != null) { - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude()); - dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude()); - mylocation.setLatitude(getLastMapFocus().getLatitude()); - mylocation.setLongitude(getLastMapFocus().getLongitude()); - Float distance = mylocation.distanceTo(dest_location);//in meters - if (getLastMapFocus() != null) { - if (isNetworkConnectionEstablished() && (event.getX() > 0 - || event.getY() > 0)) { - if (distance > 2000.0) { - setSearchThisAreaButtonVisibility(true); - } else { - setSearchThisAreaButtonVisibility(false); - } - } - } else { - setSearchThisAreaButtonVisibility(false); + val distance = myLocation.distanceTo(destLocation) + + if ( + isNetworkConnectionEstablished() + && + (event?.x!! > 0 || event.y > 0) + ) { + setSearchThisAreaButtonVisibility(distance > 2000.0) } - } + } ?: setSearchThisAreaButtonVisibility(false) - return true; + return true } - @Override - public boolean onZoom(ZoomEvent event) { - return false; - } + override fun onZoom(event: ZoomEvent?): Boolean = false + }) - }); - if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { - askForLocationPermission(); + if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) { + askForLocationPermission() } } - @Override - public void onResume() { - super.onResume(); - binding.mapView.onResume(); - presenter.attachView(this); - registerNetworkReceiver(); - if (isResumed()) { - if (locationPermissionsHelper.checkLocationPermission(getActivity())) { - performMapReadyActions(); + override fun onResume() { + super.onResume() + binding.mapView.onResume() + presenter?.attachView(this) + registerNetworkReceiver() + + if (isResumed) { + if (activity?.let { locationPermissionsHelper.checkLocationPermission(it) } == true) { + performMapReadyActions() } else { - startMapWithoutPermission(); + startMapWithoutPermission() } } } - @Override - public void onPause() { - super.onPause(); - // unregistering the broadcastReceiver, as it was causing an exception and a potential crash - unregisterNetworkReceiver(); + override fun onPause() { + super.onPause() + // Unregistering the broadcastReceiver to prevent crashes + unregisterNetworkReceiver() } - /** * Unregisters the networkReceiver */ - private void unregisterNetworkReceiver() { - if (getActivity() != null) { - getActivity().unregisterReceiver(broadcastReceiver); - } + private fun unregisterNetworkReceiver() { + activity?.unregisterReceiver(broadcastReceiver) } - private void startMapWithoutPermission() { - lastKnownLocation = MapUtils.getDefaultLatLng(); - moveCameraToPosition( - new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); - presenter.onMapReady(exploreMapController); + private fun startMapWithoutPermission() { + lastKnownLocation = MapUtils.defaultLatLng + moveCameraToPosition(GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude)) + presenter?.onMapReady(exploreMapController) } - private void registerNetworkReceiver() { - if (getActivity() != null) { - getActivity().registerReceiver(broadcastReceiver, intentFilter); - } + private fun registerNetworkReceiver() { + activity?.registerReceiver(broadcastReceiver, intentFilter) } - private void performMapReadyActions() { + private fun performMapReadyActions() { if (isDarkTheme) { - binding.mapView.getOverlayManager().getTilesOverlay() - .setColorFilter(TilesOverlay.INVERT_COLORS); + binding.mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS) } if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) && - !locationPermissionsHelper.checkLocationPermission(getActivity())) { - isPermissionDenied = true; + !locationPermissionsHelper.checkLocationPermission(requireActivity()) + ) { + isPermissionDenied = true } - lastKnownLocation = MapUtils.getDefaultLatLng(); - // if we came from 'Show in Explore' in Nearby, load Nearby map center and zoom + lastKnownLocation = MapUtils.defaultLatLng + + // If user came from 'Show in Explore' in Nearby, load saved map center and zoom if (isCameFromNearbyMap()) { - moveCameraToPosition( - new GeoPoint(prevLatitude, prevLongitude), - prevZoom, - 1L - ); + moveCameraToPosition(GeoPoint(prevLatitude, prevLongitude), prevZoom, 1L) } else { moveCameraToPosition( - new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); + GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude) + ) } - presenter.onMapReady(exploreMapController); + + presenter?.onMapReady(exploreMapController) } /** - * Fetch Nearby map camera data from fragment arguments if any. + * Fetch Nearby map camera data from fragment arguments if available. */ - public void loadNearbyMapData() { - // get fragment arguments - if (getArguments() != null) { - prevZoom = getArguments().getDouble("prev_zoom"); - prevLatitude = getArguments().getDouble("prev_latitude"); - prevLongitude = getArguments().getDouble("prev_longitude"); + fun loadNearbyMapData() { + arguments?.let { + prevZoom = it.getDouble("prev_zoom") + prevLatitude = it.getDouble("prev_latitude") + prevLongitude = it.getDouble("prev_longitude") } } /** - * Checks if fragment arguments contain data from Nearby map, indicating that the user navigated - * from Nearby using 'Show in Explore'. + * Checks if fragment arguments contain data from the Nearby map, + * indicating that the user navigated from Nearby using 'Show in Explore'. * * @return true if user navigated from Nearby map - **/ - public boolean isCameFromNearbyMap() { - return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0; + */ + fun isCameFromNearbyMap(): Boolean { + return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0 } - public void loadNearbyMapFromExplore() { - ((MainActivity) getContext()).loadNearbyMapFromExplore( - binding.mapView.getZoomLevelDouble(), - binding.mapView.getMapCenter().getLatitude(), - binding.mapView.getMapCenter().getLongitude() - ); + fun loadNearbyMapFromExplore() { + (requireContext() as MainActivity).loadNearbyMapFromExplore( + binding.mapView.zoomLevelDouble, + binding.mapView.mapCenter.latitude, + binding.mapView.mapCenter.longitude + ) } - private void initViews() { - Timber.d("init views called"); - initBottomSheets(); - setBottomSheetCallbacks(); + private fun initViews() { + Timber.d("init views called") + initBottomSheets() + setBottomSheetCallbacks() } /** - * a) Creates bottom sheet behaviours from bottom sheet, sets initial states and visibility + * a) Creates bottom sheet behaviors from bottom sheet, sets initial states and visibility. * b) Gets the touch event on the map to perform following actions: - * if bottom sheet details are expanded or collapsed hide the bottom sheet details. + * - If bottom sheet details are expanded or collapsed, hide the bottom sheet details. */ @SuppressLint("ClickableViewAccessibility") - private void initBottomSheets() { - bottomSheetDetailsBehavior = BottomSheetBehavior.from( - binding.bottomSheetDetailsBinding.getRoot()); - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE); + private fun initBottomSheets() { + bottomSheetDetailsBehavior = BottomSheetBehavior.from(binding.bottomSheetDetailsBinding.root) + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + binding.bottomSheetDetailsBinding.root.visibility = View.VISIBLE } /** * Defines how bottom sheets will act on click */ - private void setBottomSheetCallbacks() { - binding.bottomSheetDetailsBinding.getRoot().setOnClickListener(v -> { - if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); - } else if (bottomSheetDetailsBehavior.getState() - == BottomSheetBehavior.STATE_EXPANDED) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + private fun setBottomSheetCallbacks() { + binding.bottomSheetDetailsBinding.root.setOnClickListener { + bottomSheetDetailsBehavior.state = when (bottomSheetDetailsBehavior.state) { + BottomSheetBehavior.STATE_COLLAPSED -> BottomSheetBehavior.STATE_EXPANDED + BottomSheetBehavior.STATE_EXPANDED -> BottomSheetBehavior.STATE_COLLAPSED + else -> bottomSheetDetailsBehavior.state } - }); - } - - @Override - public void onLocationChangedSignificantly(LatLng latLng) { - Timber.d("Location significantly changed"); - if (latLng != null) { - handleLocationUpdate(latLng, LOCATION_SIGNIFICANTLY_CHANGED); } } - @Override - public void onLocationChangedSlightly(LatLng latLng) { - Timber.d("Location slightly changed"); - if (latLng != null) {//If the map has never ever shown the current location, lets do it know - handleLocationUpdate(latLng, LOCATION_SLIGHTLY_CHANGED); - } + override fun onLocationChangedSignificantly(latLng: LatLng?) { + Timber.d("Location significantly changed") + latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } } - private void handleLocationUpdate(final fr.free.nrw.commons.location.LatLng latLng, - final LocationServiceManager.LocationChangeType locationChangeType) { - lastKnownLocation = latLng; - exploreMapController.currentLocation = lastKnownLocation; - presenter.updateMap(locationChangeType); + override fun onLocationChangedSlightly(latLng: LatLng?) { + Timber.d("Location slightly changed") + latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED) } } - @Override - public void onLocationChangedMedium(LatLng latLng) { + private fun handleLocationUpdate( + latLng: fr.free.nrw.commons.location.LatLng, + locationChangeType: LocationServiceManager.LocationChangeType + ) { + lastKnownLocation = latLng + exploreMapController.currentLocation = lastKnownLocation + presenter?.updateMap(locationChangeType) + } + override fun onLocationChangedMedium(latLng: LatLng?) { + // No implementation required } - @Override - public boolean isNetworkConnectionEstablished() { - return NetworkUtils.isInternetConnectionEstablished(getActivity()); + override fun isNetworkConnectionEstablished(): Boolean { + return NetworkUtils.isInternetConnectionEstablished(activity) } - @Override - public void populatePlaces(LatLng currentLatLng) { - final Observable nearbyPlacesInfoObservable; - if (currentLatLng == null) { - return; - } - if (currentLatLng.equals( - getLastMapFocus())) { // Means we are checking around current location - nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng, - getLastMapFocus(), true); + override fun populatePlaces(curLatLng: LatLng?) { + if (curLatLng == null) return + + val nearbyPlacesInfoObservable: Observable = + if (curLatLng == getLastMapFocus()) { + // Checking around current location + presenter!!.loadAttractionsFromLocation(curLatLng, getLastMapFocus(), true) } else { - nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(getLastMapFocus(), - currentLatLng, false); + presenter!!.loadAttractionsFromLocation(getLastMapFocus(), curLatLng, false) } - getCompositeDisposable().add(nearbyPlacesInfoObservable - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(explorePlacesInfo -> { - mediaList = explorePlacesInfo.mediaList; - if (mediaList == null) { - showResponseMessage(getString(R.string.no_pictures_in_this_area)); + + compositeDisposable.add( + nearbyPlacesInfoObservable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ explorePlacesInfo -> + mediaList = explorePlacesInfo.mediaList + if (mediaList.isNullOrEmpty()) { + showResponseMessage(getString(R.string.no_pictures_in_this_area)) } - updateMapMarkers(explorePlacesInfo); - lastMapFocus = new GeoPoint(currentLatLng.getLatitude(), - currentLatLng.getLongitude()); - }, - throwable -> { - Timber.d(throwable); - // Not showing the user, throwable localizedErrorMessage - showErrorMessage(getString(R.string.error_fetching_nearby_places)); - - setProgressBarVisibility(false); - presenter.lockUnlockNearby(false); - })); + updateMapMarkers(explorePlacesInfo) + lastMapFocus = GeoPoint(curLatLng.latitude, curLatLng.longitude) + }, { throwable -> + Timber.d(throwable) + showErrorMessage(getString(R.string.error_fetching_nearby_places)) + setProgressBarVisibility(false) + presenter?.lockUnlockNearby(false) + }) + ) + if (recenterToUserLocation) { - recenterToUserLocation = false; + recenterToUserLocation = false } } @@ -497,103 +481,102 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param explorePlacesInfo holds several information as current location, marker list etc. */ - private void updateMapMarkers(final MapController.ExplorePlacesInfo explorePlacesInfo) { - presenter.updateMapMarkers(explorePlacesInfo); + private fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { + presenter?.updateMapMarkers(explorePlacesInfo) } - private void showErrorMessage(final String message) { - ViewUtil.showLongToast(getActivity(), message); + private fun showErrorMessage(message: String) { + ViewUtil.showLongToast(requireActivity(), message) } - private void showResponseMessage(final String message) { - ViewUtil.showLongSnackbar(getView(), message); + private fun showResponseMessage(message: String) { + ViewUtil.showLongSnackbar(requireView(), message) } - @Override - public void askForLocationPermission() { - Timber.d("Asking for location permission"); - activityResultLauncher.launch(permission.ACCESS_FINE_LOCATION); + override fun askForLocationPermission() { + Timber.d("Asking for location permission") + activityResultLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } - private void locationPermissionGranted() { - isPermissionDenied = false; - applicationKvStore.putBoolean("doNotAskForLocationPermission", false); - lastKnownLocation = locationManager.getLastLocation(); - fr.free.nrw.commons.location.LatLng target = lastKnownLocation; + private fun locationPermissionGranted() { + isPermissionDenied = false + applicationKvStore.putBoolean("doNotAskForLocationPermission", false) + lastKnownLocation = locationManager.getLastLocation() + val target = lastKnownLocation + if (lastKnownLocation != null) { - GeoPoint targetP = new GeoPoint(target.getLatitude(), target.getLongitude()); - mapCenter = targetP; - binding.mapView.getController().setCenter(targetP); - recenterMarkerToPosition(targetP); - moveCameraToPosition(targetP); - } else if (locationManager.isGPSProviderEnabled() - || locationManager.isNetworkProviderEnabled()) { - locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); - locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); - setProgressBarVisibility(true); + val targetP = GeoPoint(target!!.latitude, target.longitude) + mapCenter = targetP + binding.mapView.controller.setCenter(targetP) + recenterMarkerToPosition(targetP) + moveCameraToPosition(targetP) + } else if ( + locationManager.isGPSProviderEnabled() || locationManager.isNetworkProviderEnabled() + ) { + locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) + locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) + setProgressBarVisibility(true) } else { - locationPermissionsHelper.showLocationOffDialog(getActivity(), - R.string.ask_to_turn_location_on_text); + locationPermissionsHelper.showLocationOffDialog( + requireActivity(), + R.string.ask_to_turn_location_on_text + ) } - presenter.onMapReady(exploreMapController); - registerUnregisterLocationListener(false); + presenter?.onMapReady(exploreMapController) + registerUnregisterLocationListener(false) } - public void registerUnregisterLocationListener(final boolean removeLocationListener) { - MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this); + fun registerUnregisterLocationListener(removeLocationListener: Boolean) { + MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this) } - @Override - public void recenterMap(LatLng currentLatLng) { - // if user has denied permission twice, then show dialog + override fun recenterMap(currentLatLng: LatLng?) { if (isPermissionDenied) { - if (locationPermissionsHelper.checkLocationPermission(getActivity())) { - // this will run when user has given permission by opening app's settings - isPermissionDenied = false; - recenterMap(currentLatLng); + if (locationPermissionsHelper.checkLocationPermission(requireActivity())) { + isPermissionDenied = false + recenterMap(currentLatLng) } else { - askForLocationPermission(); + askForLocationPermission() } } else { - if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { - askForLocationPermission(); + if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) { + askForLocationPermission() } else { - locationPermissionGranted(); + locationPermissionGranted() } } + if (currentLatLng == null) { - recenterToUserLocation = true; - return; + recenterToUserLocation = true + return } - recenterMarkerToPosition( - new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); - binding.mapView.getController() - .animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); - if (lastMapFocus != null) { - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude()); - dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude()); - mylocation.setLatitude(lastMapFocus.getLatitude()); - mylocation.setLongitude(lastMapFocus.getLongitude()); - Float distance = mylocation.distanceTo(dest_location);//in meters - if (lastMapFocus != null) { - if (isNetworkConnectionEstablished()) { - if (distance > 2000.0) { - setSearchThisAreaButtonVisibility(true); - } else { - setSearchThisAreaButtonVisibility(false); - } - } + + recenterMarkerToPosition(GeoPoint(currentLatLng.latitude, currentLatLng.longitude)) + binding.mapView.controller.animateTo( + GeoPoint(currentLatLng.latitude, currentLatLng.longitude) + ) + + lastMapFocus?.let { + val myLocation = Location("").apply { + latitude = it.latitude + longitude = it.longitude + } + val destLocation = Location("").apply { + latitude = binding.mapView.mapCenter.latitude + longitude = binding.mapView.mapCenter.longitude + } + val distance = myLocation.distanceTo(destLocation) + + if (isNetworkConnectionEstablished()) { + setSearchThisAreaButtonVisibility(distance > 2000.0) } else { - setSearchThisAreaButtonVisibility(false); + setSearchThisAreaButtonVisibility(false) } - } + } ?: setSearchThisAreaButtonVisibility(false) } - @Override - public void hideBottomDetailsSheet() { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + override fun hideBottomDetailsSheet() { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN } /** @@ -602,97 +585,81 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param place Place of clicked nearby marker */ - private void passInfoToSheet(final Place place) { - binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener( - view -> Utils.handleGeoCoordinates(getActivity(), - place.getLocation(), binding.mapView.getZoomLevelDouble())); - - binding.bottomSheetDetailsBinding.commonsButton.setVisibility( - place.hasCommonsLink() ? View.VISIBLE : View.GONE); - binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener( - view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink())); - - int index = 0; - for (Media media : mediaList) { - if (media.getFilename().equals(place.name)) { - int finalIndex = index; - binding.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener(view -> { - ((ExploreMapRootFragment) getParentFragment()).onMediaClicked(finalIndex); - }); + private fun passInfoToSheet(place: Place) { + binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener { + Utils.handleGeoCoordinates(activity, place.location, binding.mapView.zoomLevelDouble) + } + + binding.bottomSheetDetailsBinding.commonsButton.visibility = + if (place.hasCommonsLink()) View.VISIBLE else View.GONE + + binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener { + Utils.handleWebUrl(context, place.siteLinks.commonsLink) + } + + mediaList?.indexOfFirst { it.filename == place.name }.takeIf { it!! >= 0 }?.let { index -> + binding.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener { + (parentFragment as? ExploreMapRootFragment)?.onMediaClicked(index) } - index++; } - binding.bottomSheetDetailsBinding.title.setText( - place.name.substring(5, place.name.lastIndexOf("."))); - binding.bottomSheetDetailsBinding.category.setText(place.distance); - // Remove label since it is double information - String descriptionText = place.getLongDescription() - .replace(place.getName() + " (", ""); - descriptionText = (descriptionText.equals(place.getLongDescription()) ? descriptionText - : descriptionText.replaceFirst(".$", "")); - // Set the short description after we remove place name from long description - binding.bottomSheetDetailsBinding.description.setText(descriptionText); + + binding.bottomSheetDetailsBinding.title.text = place.name.substring( + 5, + place.name.lastIndexOf(".") + ) + binding.bottomSheetDetailsBinding.category.text = place.distance + + var descriptionText = place.longDescription.replace("${place.name} (", "") + descriptionText = if (descriptionText == place.longDescription) descriptionText + else descriptionText.dropLast(1) + + binding.bottomSheetDetailsBinding.description.text = descriptionText } - @Override - public void addSearchThisAreaButtonAction() { - binding.searchThisAreaButton.setOnClickListener(presenter.onSearchThisAreaClicked()); + override fun addSearchThisAreaButtonAction() { + binding.searchThisAreaButton.setOnClickListener { presenter?.onSearchThisAreaClicked() } } - @Override - public void setSearchThisAreaButtonVisibility(boolean isVisible) { - binding.searchThisAreaButton.setVisibility(isVisible ? View.VISIBLE : View.GONE); + override fun setSearchThisAreaButtonVisibility(isVisible: Boolean) { + binding.searchThisAreaButton.visibility = if (isVisible) View.VISIBLE else View.GONE } - @Override - public void setProgressBarVisibility(boolean isVisible) { - binding.mapProgressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE); + override fun setProgressBarVisibility(isVisible: Boolean) { + binding.mapProgressBar.visibility = if (isVisible) View.VISIBLE else View.GONE } - @Override - public boolean isDetailsBottomSheetVisible() { - if (binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE) { - return true; - } else { - return false; - } + override fun isDetailsBottomSheetVisible(): Boolean { + return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE } - @Override - public boolean isSearchThisAreaButtonVisible() { - return binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE; + override fun isSearchThisAreaButtonVisible(): Boolean { + return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE } - @Override - public LatLng getLastLocation() { + override fun getLastLocation(): LatLng { if (lastKnownLocation == null) { - lastKnownLocation = locationManager.getLastLocation(); + lastKnownLocation = locationManager.getLastLocation() } - return lastKnownLocation; + return lastKnownLocation!! } - @Override - public void disableFABRecenter() { - binding.fabRecenter.setEnabled(false); + override fun disableFABRecenter() { + binding.fabRecenter.isEnabled = false } - @Override - public void enableFABRecenter() { - binding.fabRecenter.setEnabled(true); + override fun enableFABRecenter() { + binding.fabRecenter.isEnabled = true } /** - * Adds a markers to the map based on the list of NearbyBaseMarker. + * Adds markers to the map based on the list of NearbyBaseMarker. * * @param nearbyBaseMarkers The NearbyBaseMarker object representing the markers to be added. */ - @Override - public void addMarkersToMap(List nearbyBaseMarkers) { - clearAllMarkers(); - for (int i = 0; i < nearbyBaseMarkers.size(); i++) { - addMarkerToMap(nearbyBaseMarkers.get(i)); - } - binding.mapView.invalidate(); + override fun addMarkersToMap(nearbyBaseMarkers: List) { + clearAllMarkers() + nearbyBaseMarkers.forEach { addMarkerToMap(it) } + binding.mapView.invalidate() } /** @@ -700,312 +667,274 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added. */ - private void addMarkerToMap(BaseMarker nearbyBaseMarker) { + private fun addMarkerToMap(nearbyBaseMarker: BaseMarker) { if (isAttachedToActivity()) { - ArrayList items = new ArrayList<>(); - Bitmap icon = nearbyBaseMarker.getIcon(); - Drawable d = new BitmapDrawable(getResources(), icon); - GeoPoint point = new GeoPoint( - nearbyBaseMarker.getPlace().location.getLatitude(), - nearbyBaseMarker.getPlace().location.getLongitude()); - OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, - point); - item.setMarker(d); - items.add(item); - ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items, - new OnItemGestureListener() { - @Override - public boolean onItemSingleTapUp(int index, OverlayItem item) { - final Place place = nearbyBaseMarker.getPlace(); - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - bottomSheetDetailsBehavior.setState( - BottomSheetBehavior.STATE_COLLAPSED); - } - clickedMarker = nearbyBaseMarker; - passInfoToSheet(place); - return true; + val items = ArrayList() + val icon = nearbyBaseMarker.icon + val drawable = BitmapDrawable(resources, icon) + val point = GeoPoint( + nearbyBaseMarker.place.location.latitude, + nearbyBaseMarker.place.location.longitude + ) + val item = OverlayItem(nearbyBaseMarker.place.name, null, point).apply { + setMarker(drawable) + } + items.add(item) + + val overlay = ItemizedOverlayWithFocus(items, object : OnItemGestureListener { + override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean { + val place = nearbyBaseMarker.place + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_COLLAPSED } + clickedMarker = nearbyBaseMarker + passInfoToSheet(place) + return true + } - @Override - public boolean onItemLongPress(int index, OverlayItem item) { - return false; - } - }, getContext()); + override fun onItemLongPress(index: Int, item: OverlayItem): Boolean { + return false + } + }, context) - overlay.setFocusItemsOnTap(true); - binding.mapView.getOverlays().add(overlay); // Add the overlay to the map + overlay.setFocusItemsOnTap(true) + binding.mapView.overlays.add(overlay) // Add the overlay to the map } } - /** - * Removes a marker from the map based on the specified NearbyBaseMarker. - * - * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be removed. - */ - private void removeMarker(BaseMarker nearbyBaseMarker) { - Place place = nearbyBaseMarker.getPlace(); - List overlays = binding.mapView.getOverlays(); - ItemizedOverlayWithFocus item; - - for (int i = 0; i < overlays.size(); i++) { - if (overlays.get(i) instanceof ItemizedOverlayWithFocus) { - item = (ItemizedOverlayWithFocus) overlays.get(i); - OverlayItem overlayItem = item.getItem(0); - - if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() - && place.location.getLongitude() == overlayItem.getPoint().getLongitude()) { - binding.mapView.getOverlays().remove(i); - binding.mapView.invalidate(); - break; + private fun removeMarker(nearbyBaseMarker: BaseMarker) { + val place = nearbyBaseMarker.place + val overlays = binding.mapView.overlays + var item: ItemizedOverlayWithFocus + + for (i in overlays.indices) { + if (overlays[i] is ItemizedOverlayWithFocus<*>) { + item = overlays[i] as ItemizedOverlayWithFocus + val overlayItem = item.getItem(0) + + if (place.location.latitude == overlayItem.point.latitude && + place.location.longitude == overlayItem.point.longitude + ) { + binding.mapView.overlays.removeAt(i) + binding.mapView.invalidate() + break } } } } - /** - * Clears all markers from the map and resets certain map overlays and gestures. After clearing - * markers, it re-adds a scale bar overlay and rotation gesture overlay to the map. - */ - @Override - public void clearAllMarkers() { + override fun clearAllMarkers() { if (isAttachedToActivity()) { - binding.mapView.getOverlayManager().clear(); - GeoPoint geoPoint = mapCenter; - if (geoPoint != null) { - List overlays = binding.mapView.getOverlays(); - ScaleDiskOverlay diskOverlay = - new ScaleDiskOverlay(this.getContext(), - geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); - Paint circlePaint = new Paint(); - circlePaint.setColor(Color.rgb(128, 128, 128)); - circlePaint.setStyle(Paint.Style.STROKE); - circlePaint.setStrokeWidth(2f); - diskOverlay.setCirclePaint2(circlePaint); - Paint diskPaint = new Paint(); - diskPaint.setColor(Color.argb(40, 128, 128, 128)); - diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); - diskOverlay.setCirclePaint1(diskPaint); - diskOverlay.setDisplaySizeMin(900); - diskOverlay.setDisplaySizeMax(1700); - binding.mapView.getOverlays().add(diskOverlay); - org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( - binding.mapView); - startMarker.setPosition(geoPoint); - startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, - org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); - startMarker.setIcon( - ContextCompat.getDrawable(this.getContext(), - R.drawable.current_location_marker)); - startMarker.setTitle("Your Location"); - startMarker.setTextLabelFontSize(24); - binding.mapView.getOverlays().add(startMarker); + binding.mapView.overlayManager.clear() + mapCenter?.let { geoPoint -> + val overlays = binding.mapView.overlays + val diskOverlay = ScaleDiskOverlay( + context, geoPoint, 2000, GeoConstants.UnitOfMeasure.foot + ).apply { + val circlePaint = Paint().apply { + color = Color.rgb(128, 128, 128) + style = Paint.Style.STROKE + strokeWidth = 2f + } + setCirclePaint2(circlePaint) + + val diskPaint = Paint().apply { + color = Color.argb(40, 128, 128, 128) + style = Paint.Style.FILL_AND_STROKE + } + setCirclePaint1(diskPaint) + + setDisplaySizeMin(900) + setDisplaySizeMax(1700) + } + binding.mapView.overlays.add(diskOverlay) + + val startMarker = Marker(binding.mapView).apply { + position = geoPoint + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = ContextCompat.getDrawable( + requireContext(), + R.drawable.current_location_marker + ) + title = "Your Location" + textLabelFontSize = 24 + } + binding.mapView.overlays.add(startMarker) } - ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView); - scaleBarOverlay.setScaleBarOffset(15, 25); - Paint barPaint = new Paint(); - barPaint.setARGB(200, 255, 250, 250); - scaleBarOverlay.setBackgroundPaint(barPaint); - scaleBarOverlay.enableScaleBar(); - binding.mapView.getOverlays().add(scaleBarOverlay); - binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - binding.mapView.invalidate(); - } else { - Timber.e("CLICKED MARKER IS NULL"); + + val scaleBarOverlay = ScaleBarOverlay(binding.mapView).apply { + setScaleBarOffset(15, 25) + setBackgroundPaint( + Paint().apply { + setARGB(200, 255, 250, 250) } - if (bottomSheetDetailsBehavior.getState() - == BottomSheetBehavior.STATE_EXPANDED) { - // Back should first hide the bottom sheet if it is expanded - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + ) + enableScaleBar() + } + binding.mapView.overlays.add(scaleBarOverlay) + + binding.mapView.overlays.add(object : MapEventsOverlay(object : MapEventsReceiver { + override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean { + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + binding.mapView.invalidate() + } ?: Timber.e("CLICKED MARKER IS NULL") + + if (bottomSheetDetailsBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); + hideBottomDetailsSheet() } - return true; + return true } - @Override - public boolean longPressHelper(GeoPoint p) { - return false; + override fun longPressHelper(p: GeoPoint?): Boolean { + return false } - })); - binding.mapView.setMultiTouchControls(true); + }) {}) + + binding.mapView.setMultiTouchControls(true) } } - /** - * Recenters the map view to the specified GeoPoint and updates the marker to indicate the new - * position. - * - * @param geoPoint The GeoPoint representing the new center position for the map. - */ - private void recenterMarkerToPosition(GeoPoint geoPoint) { - if (geoPoint != null) { - binding.mapView.getController().setCenter(geoPoint); - List overlays = binding.mapView.getOverlays(); - for (int i = 0; i < overlays.size(); i++) { - if (overlays.get(i) instanceof org.osmdroid.views.overlay.Marker) { - binding.mapView.getOverlays().remove(i); - } else if (overlays.get(i) instanceof ScaleDiskOverlay) { - binding.mapView.getOverlays().remove(i); + private fun recenterMarkerToPosition(geoPoint: GeoPoint?) { + geoPoint?.let { + binding.mapView.controller.setCenter(it) + val overlays = binding.mapView.overlays + overlays.removeAll { overlay -> overlay is Marker || overlay is ScaleDiskOverlay } + + val diskOverlay = ScaleDiskOverlay( + context, it, 2000, GeoConstants.UnitOfMeasure.foot + ).apply { + val circlePaint = Paint().apply { + color = Color.rgb(128, 128, 128) + style = Paint.Style.STROKE + strokeWidth = 2f + } + setCirclePaint2(circlePaint) + + val diskPaint = Paint().apply { + color = Color.argb(40, 128, 128, 128) + style = Paint.Style.FILL_AND_STROKE } + setCirclePaint1(diskPaint) + + setDisplaySizeMin(900) + setDisplaySizeMax(1700) } - ScaleDiskOverlay diskOverlay = - new ScaleDiskOverlay(this.getContext(), - geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); - Paint circlePaint = new Paint(); - circlePaint.setColor(Color.rgb(128, 128, 128)); - circlePaint.setStyle(Paint.Style.STROKE); - circlePaint.setStrokeWidth(2f); - diskOverlay.setCirclePaint2(circlePaint); - Paint diskPaint = new Paint(); - diskPaint.setColor(Color.argb(40, 128, 128, 128)); - diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); - diskOverlay.setCirclePaint1(diskPaint); - diskOverlay.setDisplaySizeMin(900); - diskOverlay.setDisplaySizeMax(1700); - binding.mapView.getOverlays().add(diskOverlay); - org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( - binding.mapView); - startMarker.setPosition(geoPoint); - startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, - org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); - startMarker.setIcon( - ContextCompat.getDrawable(this.getContext(), R.drawable.current_location_marker)); - startMarker.setTitle("Your Location"); - startMarker.setTextLabelFontSize(24); - binding.mapView.getOverlays().add(startMarker); + binding.mapView.overlays.add(diskOverlay) + + val startMarker = Marker(binding.mapView).apply { + position = it + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = ContextCompat.getDrawable( + requireContext(), + R.drawable.current_location_marker + ) + title = "Your Location" + textLabelFontSize = 24 + } + binding.mapView.overlays.add(startMarker) } } - /** - * Moves the camera of the map view to the specified GeoPoint using an animation. - * - * @param geoPoint The GeoPoint representing the new camera position for the map. - */ - private void moveCameraToPosition(GeoPoint geoPoint) { - binding.mapView.getController().animateTo(geoPoint); + private fun moveCameraToPosition(geoPoint: GeoPoint) { + binding.mapView.controller.animateTo(geoPoint) } - /** - * Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed - * using an animation. - * - * @param geoPoint The GeoPoint representing the new camera position for the map. - * @param zoom Zoom level of the map camera - * @param speed Speed of animation - */ - private void moveCameraToPosition(GeoPoint geoPoint, double zoom, long speed) { - binding.mapView.getController().animateTo(geoPoint, zoom, speed); + private fun moveCameraToPosition(geoPoint: GeoPoint, zoom: Double, speed: Long) { + binding.mapView.controller.animateTo(geoPoint, zoom, speed) } - @Override - public fr.free.nrw.commons.location.LatLng getLastMapFocus() { - return lastMapFocus == null ? getMapCenter() : new fr.free.nrw.commons.location.LatLng( - lastMapFocus.getLatitude(), lastMapFocus.getLongitude(), 100); + override fun getLastMapFocus(): LatLng { + return lastMapFocus?.let { + LatLng(it.latitude, it.longitude, 100f) + } ?: getMapCenter() } - @Override - public fr.free.nrw.commons.location.LatLng getMapCenter() { - fr.free.nrw.commons.location.LatLng latLnge = null; + override fun getMapCenter(): LatLng { + var latLng: LatLng? = null + if (mapCenter != null) { - latLnge = new fr.free.nrw.commons.location.LatLng( - mapCenter.getLatitude(), mapCenter.getLongitude(), 100); + latLng = LatLng(mapCenter!!.latitude, mapCenter!!.longitude, 100f) } else { - if (applicationKvStore.getString("LastLocation") != null) { - final String[] locationLatLng - = applicationKvStore.getString("LastLocation").split(","); - lastKnownLocation - = new fr.free.nrw.commons.location.LatLng(Double.parseDouble(locationLatLng[0]), - Double.parseDouble(locationLatLng[1]), 1f); - latLnge = lastKnownLocation; - } else { - latLnge = new fr.free.nrw.commons.location.LatLng(51.506255446947776, - -0.07483536015053005, 1f); + applicationKvStore.getString("LastLocation")?.let { lastLocation -> + val locationLatLng = lastLocation.split(",").map { it.toDouble() } + lastKnownLocation = LatLng(locationLatLng[0], locationLatLng[1], 1f) + latLng = lastKnownLocation + } ?: run { + latLng = LatLng(51.506255446947776, -0.07483536015053005, 1f) } } + if (!isCameFromNearbyMap()) { - moveCameraToPosition(new GeoPoint(latLnge.getLatitude(), latLnge.getLongitude())); + moveCameraToPosition(GeoPoint(latLng?.latitude!!, latLng?.longitude!!)) } - return latLnge; + return latLng!! } - @Override - public fr.free.nrw.commons.location.LatLng getMapFocus() { - fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng( - binding.mapView.getMapCenter().getLatitude(), - binding.mapView.getMapCenter().getLongitude(), 100); - return mapFocusedLatLng; + override fun getMapFocus(): LatLng { + return LatLng( + binding.mapView.mapCenter.latitude, + binding.mapView.mapCenter.longitude, + 100f + ) } - @Override - public void setFABRecenterAction(OnClickListener onClickListener) { - binding.fabRecenter.setOnClickListener(onClickListener); + override fun setFABRecenterAction(onClickListener: View.OnClickListener) { + binding.fabRecenter.setOnClickListener(onClickListener) } - @Override - public boolean backButtonClicked() { - if (!(bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN)) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - return true; + override fun backButtonClicked(): Boolean { + return if (bottomSheetDetailsBehavior.state != BottomSheetBehavior.STATE_HIDDEN) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + true } else { - return false; + false } } - /** - * Adds network broadcast receiver to recognize connection established - */ - private void initNetworkBroadCastReceiver() { - broadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - if (getActivity() != null) { - if (NetworkUtils.isInternetConnectionEstablished(getActivity())) { + private fun initNetworkBroadCastReceiver() { + broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + activity?.let { + if (NetworkUtils.isInternetConnectionEstablished(it)) { if (isNetworkErrorOccurred) { - presenter.updateMap(LOCATION_SIGNIFICANTLY_CHANGED); - isNetworkErrorOccurred = false; - } - - if (snackbar != null) { - snackbar.dismiss(); - snackbar = null; + presenter?.updateMap( + LocationServiceManager + .LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED + ) + isNetworkErrorOccurred = false } + snackbar?.dismiss() + snackbar = null } else { if (snackbar == null) { - snackbar = Snackbar.make(getView(), R.string.no_internet, - Snackbar.LENGTH_INDEFINITE); - setSearchThisAreaButtonVisibility(false); - setProgressBarVisibility(false); + snackbar = Snackbar.make( + requireView(), + R.string.no_internet, + Snackbar.LENGTH_INDEFINITE + ) + setSearchThisAreaButtonVisibility(false) + setProgressBarVisibility(false) } - - isNetworkErrorOccurred = true; - snackbar.show(); + isNetworkErrorOccurred = true + snackbar?.show() } } } - }; + } } - /** - * helper function to confirm that this fragment has been attached. - **/ - public boolean isAttachedToActivity() { - boolean attached = isVisible() && getActivity() != null; - return attached; + fun isAttachedToActivity(): Boolean { + return isVisible && activity != null } - @Override - public void onLocationPermissionDenied(String toastMessage) { - } + override fun onLocationPermissionDenied(toastMessage: String) {} - @Override - public void onLocationPermissionGranted() { - } + override fun onLocationPermissionGranted() {} } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt index 94b9cf5ad3..bc92ff6eea 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt @@ -1,152 +1,139 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA; - - -import android.location.Location; -import android.view.View; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.MapController.ExplorePlacesInfo; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; -import io.reactivex.Observable; -import java.lang.reflect.Proxy; -import java.util.List; -import timber.log.Timber; - -public class ExploreMapPresenter - implements ExploreMapContract.UserActions, - NearbyBaseMarkerThumbCallback { - - BookmarkLocationsDao bookmarkLocationDao; - private boolean isNearbyLocked; - private LatLng currentLatLng; - private ExploreMapController exploreMapController; - - private static final ExploreMapContract.View DUMMY = (ExploreMapContract.View) Proxy - .newProxyInstance( - ExploreMapContract.View.class.getClassLoader(), - new Class[]{ExploreMapContract.View.class}, (proxy, method, args) -> { - if (method.getName().equals("onMyEvent")) { - return null; - } else if (String.class == method.getReturnType()) { - return ""; - } else if (Integer.class == method.getReturnType()) { - return Integer.valueOf(0); - } else if (int.class == method.getReturnType()) { - return 0; - } else if (Boolean.class == method.getReturnType()) { - return Boolean.FALSE; - } else if (boolean.class == method.getReturnType()) { - return false; - } else { - return null; - } +package fr.free.nrw.commons.explore.map + +import android.location.Location +import android.view.View +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.MapController.ExplorePlacesInfo +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao +import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType +import io.reactivex.Observable +import java.lang.reflect.Proxy +import timber.log.Timber + +class ExploreMapPresenter( + private val bookmarkLocationDao: BookmarkLocationsDao +) : ExploreMapContract.UserActions, ExploreMapController.NearbyBaseMarkerThumbCallback { + + private var isNearbyLocked: Boolean = false + private var currentLatLng: LatLng? = null + private var exploreMapController: ExploreMapController? = null + + companion object { + private val DUMMY: ExploreMapContract.View = Proxy.newProxyInstance( + ExploreMapContract.View::class.java.classLoader, + arrayOf(ExploreMapContract.View::class.java) + ) { _, method, _ -> + when (method.returnType) { + String::class.java -> "" + Integer::class.java -> 0 + Int::class.java -> 0 + Boolean::class.java -> false + Boolean::class.javaPrimitiveType -> false + else -> null } - ); - private ExploreMapContract.View exploreMapFragmentView = DUMMY; - - public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao) { - this.bookmarkLocationDao = bookmarkLocationDao; + } as ExploreMapContract.View } - @Override - public void updateMap(LocationChangeType locationChangeType) { - Timber.d("Presenter updates map and list" + locationChangeType.toString()); + private var exploreMapFragmentView: ExploreMapContract.View = DUMMY + + override fun updateMap(locationChangeType: LocationChangeType) { + Timber.d("Presenter updates map and list $locationChangeType") if (isNearbyLocked) { - Timber.d("Nearby is locked, so updateMapAndList returns"); - return; + Timber.d("Nearby is locked, so updateMapAndList returns") + return } if (!exploreMapFragmentView.isNetworkConnectionEstablished()) { - Timber.d("Network connection is not established"); - return; + Timber.d("Network connection is not established") + return } /** * Significant changed - Markers and current location will be updated together * Slightly changed - Only current position marker will be updated */ - if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)) { - Timber.d("LOCATION_SIGNIFICANTLY_CHANGED"); - lockUnlockNearby(true); - exploreMapFragmentView.setProgressBarVisibility(true); - exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapCenter()); - } else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) { - Timber.d("SEARCH_CUSTOM_AREA"); - lockUnlockNearby(true); - exploreMapFragmentView.setProgressBarVisibility(true); - exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus()); - } else { // Means location changed slightly, ie user is walking or driving. - Timber.d("Means location changed slightly"); + when (locationChangeType) { + LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED -> { + Timber.d("LOCATION_SIGNIFICANTLY_CHANGED") + lockUnlockNearby(true) + exploreMapFragmentView.setProgressBarVisibility(true) + exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapCenter()) + } + + LocationChangeType.SEARCH_CUSTOM_AREA -> { + Timber.d("SEARCH_CUSTOM_AREA") + lockUnlockNearby(true) + exploreMapFragmentView.setProgressBarVisibility(true) + exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus()) + } + + else -> { + Timber.d("Means location changed slightly") + } } } /** - * Nearby updates takes time, since they are network operations. During update time, we don't - * want to get any other calls from user. So locking nearby. + * Nearby updates take time since they are network operations. During update time, we don't + * want to get any other calls from the user. So locking nearby. * * @param isNearbyLocked true means lock, false means unlock */ - @Override - public void lockUnlockNearby(boolean isNearbyLocked) { - this.isNearbyLocked = isNearbyLocked; + override fun lockUnlockNearby(isNearbyLocked: Boolean) { + this.isNearbyLocked = isNearbyLocked if (isNearbyLocked) { - exploreMapFragmentView.disableFABRecenter(); + exploreMapFragmentView.disableFABRecenter() } else { - exploreMapFragmentView.enableFABRecenter(); + exploreMapFragmentView.enableFABRecenter() } } - @Override - public void attachView(ExploreMapContract.View view) { - exploreMapFragmentView = view; + override fun attachView(view: ExploreMapContract.View) { + exploreMapFragmentView = view } - @Override - public void detachView() { - exploreMapFragmentView = DUMMY; + override fun detachView() { + exploreMapFragmentView = DUMMY } /** * Sets click listener of FAB */ - @Override - public void setActionListeners(JsonKvStore applicationKvStore) { - exploreMapFragmentView.setFABRecenterAction(v -> { - exploreMapFragmentView.recenterMap(currentLatLng); - }); - + override fun setActionListeners(applicationKvStore: JsonKvStore) { + exploreMapFragmentView.setFABRecenterAction { + currentLatLng?.let { it1 -> exploreMapFragmentView.recenterMap(it1) } + } } - @Override - public boolean backButtonClicked() { - return exploreMapFragmentView.backButtonClicked(); + override fun backButtonClicked(): Boolean { + return exploreMapFragmentView.backButtonClicked() } - public void onMapReady(ExploreMapController exploreMapController) { - this.exploreMapController = exploreMapController; - if (null != exploreMapFragmentView) { - exploreMapFragmentView.addSearchThisAreaButtonAction(); - initializeMapOperations(); - } + fun onMapReady(exploreMapController: ExploreMapController) { + this.exploreMapController = exploreMapController + exploreMapFragmentView.addSearchThisAreaButtonAction() + initializeMapOperations() } - public void initializeMapOperations() { - lockUnlockNearby(false); - updateMap(LOCATION_SIGNIFICANTLY_CHANGED); + fun initializeMapOperations() { + lockUnlockNearby(false) + updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } - public Observable loadAttractionsFromLocation(LatLng currentLatLng, - LatLng searchLatLng, boolean checkingAroundCurrent) { - return Observable - .fromCallable(() -> exploreMapController - .loadAttractionsFromLocation(currentLatLng, searchLatLng, checkingAroundCurrent)); + fun loadAttractionsFromLocation( + currentLatLng: LatLng, + searchLatLng: LatLng?, + checkingAroundCurrent: Boolean + ): Observable { + return Observable.fromCallable { + exploreMapController?.loadAttractionsFromLocation( + currentLatLng, searchLatLng, checkingAroundCurrent + ) + } } /** @@ -155,47 +142,45 @@ public class ExploreMapPresenter * * @param explorePlacesInfo This variable has placeToCenter list information and distances. */ - public void updateMapMarkers( - MapController.ExplorePlacesInfo explorePlacesInfo) { + fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { if (explorePlacesInfo.mediaList != null) { - prepareNearbyBaseMarkers(explorePlacesInfo); + prepareNearbyBaseMarkers(explorePlacesInfo) } else { - lockUnlockNearby(false); // So that new location updates wont come - exploreMapFragmentView.setProgressBarVisibility(false); + lockUnlockNearby(false) // So that new location updates won't come + exploreMapFragmentView.setProgressBarVisibility(false) } } - void prepareNearbyBaseMarkers(MapController.ExplorePlacesInfo explorePlacesInfo) { - exploreMapController - .loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng, - // Curlatlang will be used to calculate distances - explorePlacesInfo.explorePlaceList, - exploreMapFragmentView.getContext(), - this, - explorePlacesInfo); + private fun prepareNearbyBaseMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { + ExploreMapController.loadAttractionsFromLocationToBaseMarkerOptions( + explorePlacesInfo.currentLatLng, + explorePlacesInfo.explorePlaceList, + exploreMapFragmentView.getContext(), + this, + explorePlacesInfo + ) } - @Override - public void onNearbyBaseMarkerThumbsReady(List baseMarkers, - ExplorePlacesInfo explorePlacesInfo) { - if (null != exploreMapFragmentView) { - exploreMapFragmentView.addMarkersToMap(baseMarkers); - lockUnlockNearby(false); // So that new location updates wont come - exploreMapFragmentView.setProgressBarVisibility(false); - } + override fun onNearbyBaseMarkerThumbsReady( + baseMarkers: List, + explorePlacesInfo: ExplorePlacesInfo + ) { + exploreMapFragmentView.addMarkersToMap(baseMarkers) + lockUnlockNearby(false) // So that new location updates won't come + exploreMapFragmentView.setProgressBarVisibility(false) } - public View.OnClickListener onSearchThisAreaClicked() { - return v -> { + fun onSearchThisAreaClicked(): View.OnClickListener { + return View.OnClickListener { // Lock map operations during search this area operation - exploreMapFragmentView.setSearchThisAreaButtonVisibility(false); + exploreMapFragmentView.setSearchThisAreaButtonVisibility(false) if (searchCloseToCurrentLocation()) { - updateMap(LOCATION_SIGNIFICANTLY_CHANGED); + updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } else { - updateMap(SEARCH_CUSTOM_AREA); + updateMap(LocationChangeType.SEARCH_CUSTOM_AREA) } - }; + } } /** @@ -204,24 +189,19 @@ public class ExploreMapPresenter * * @return Returns true if search this area button is used around our current location */ - public boolean searchCloseToCurrentLocation() { - if (null == exploreMapFragmentView.getLastMapFocus()) { - return true; - } + fun searchCloseToCurrentLocation(): Boolean { + val lastMapFocus = exploreMapFragmentView.getLastMapFocus() ?: return true - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(exploreMapFragmentView.getMapFocus().getLatitude()); - dest_location.setLongitude(exploreMapFragmentView.getMapFocus().getLongitude()); - mylocation.setLatitude(exploreMapFragmentView.getLastMapFocus().getLatitude()); - mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude()); - Float distance = mylocation.distanceTo(dest_location); + val myLocation = Location("").apply { + latitude = lastMapFocus.latitude + longitude = lastMapFocus.longitude + } - if (distance > 2000.0 * 3 / 4) { - return false; - } else { - return true; + val destLocation = Location("").apply { + latitude = exploreMapFragmentView.getMapFocus().latitude + longitude = exploreMapFragmentView.getMapFocus().longitude } - } + return myLocation.distanceTo(destLocation) <= 2000.0 * 3 / 4 + } } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt index e90cc12241..1dd3420f20 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt @@ -2,11 +2,11 @@ package fr.free.nrw.commons.location interface LocationUpdateListener { // Will be used to update all nearby markers on the map - fun onLocationChangedSignificantly(latLng: LatLng) + fun onLocationChangedSignificantly(latLng: LatLng?) // Will be used to track users motion - fun onLocationChangedSlightly(latLng: LatLng) + fun onLocationChangedSlightly(latLng: LatLng?) // Will be used updating nearby card view notification - fun onLocationChangedMedium(latLng: LatLng) + fun onLocationChangedMedium(latLng: LatLng?) } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt index ce268f7a30..9722600eed 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt @@ -465,17 +465,17 @@ class NearbyParentFragmentPresenter updateMapAndList(LocationChangeType.MAP_UPDATED) } - override fun onLocationChangedSignificantly(latLng: LatLng) { + override fun onLocationChangedSignificantly(latLng: LatLng?) { Timber.d("Location significantly changed") updateMapAndList(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } - override fun onLocationChangedSlightly(latLng: LatLng) { + override fun onLocationChangedSlightly(latLng: LatLng?) { Timber.d("Location significantly changed") updateMapAndList(LocationChangeType.LOCATION_SLIGHTLY_CHANGED) } - override fun onLocationChangedMedium(latLng: LatLng) { + override fun onLocationChangedMedium(latLng: LatLng?) { Timber.d("Location changed medium") } From 7175b7021f9b9ac28b7e246532a7e952b94937bd Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Sat, 22 Feb 2025 16:17:51 +0530 Subject: [PATCH 3/8] Refactor: Update `onLocationChanged` methods to accept nullable `LatLng` The `onLocationChangedSignificantly`, `onLocationChangedSlightly`, and `onLocationChangedMedium` methods in `NearbyParentFragment.java` have been updated to accept a nullable `LatLng` parameter. This change allows for cases where the location may not be available or valid. The methods previously required a non-null `LatLng` object. If the location is found to be valid then the location is handled by calling `handleLocationUpdate` method. --- .../nrw/commons/nearby/fragments/NearbyParentFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 2b64f0e373..0bef6c894c 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 @@ -1699,7 +1699,7 @@ private void handleLocationUpdate(final LatLng latLng, } @Override - public void onLocationChangedSignificantly(final LatLng latLng) { + public void onLocationChangedSignificantly(@Nullable final LatLng latLng) { Timber.d("Location significantly changed"); if (latLng != null) { handleLocationUpdate(latLng, LOCATION_SIGNIFICANTLY_CHANGED); @@ -1707,7 +1707,7 @@ public void onLocationChangedSignificantly(final LatLng latLng) { } @Override - public void onLocationChangedSlightly(final LatLng latLng) { + public void onLocationChangedSlightly(@Nullable final LatLng latLng) { Timber.d("Location slightly changed"); if (latLng != null) {//If the map has never ever shown the current location, lets do it know handleLocationUpdate(latLng, LOCATION_SLIGHTLY_CHANGED); @@ -1715,7 +1715,7 @@ public void onLocationChangedSlightly(final LatLng latLng) { } @Override - public void onLocationChangedMedium(final LatLng latLng) { + public void onLocationChangedMedium(@Nullable final LatLng latLng) { Timber.d("Location changed medium"); if (latLng != null) {//If the map has never ever shown the current location, lets do it know handleLocationUpdate(latLng, LOCATION_SIGNIFICANTLY_CHANGED); From 6de21aa3de10d1ffd3e533381583fe13cd620223 Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Fri, 21 Feb 2025 19:39:32 +0530 Subject: [PATCH 4/8] Rename .java to .kt --- ...ataItemDetailsActivity.java => WikidataItemDetailsActivity.kt} | 0 .../explore/map/{ExploreMapCalls.java => ExploreMapCalls.kt} | 0 .../map/{ExploreMapContract.java => ExploreMapContract.kt} | 0 .../map/{ExploreMapController.java => ExploreMapController.kt} | 0 .../map/{ExploreMapFragment.java => ExploreMapFragment.kt} | 0 .../map/{ExploreMapPresenter.java => ExploreMapPresenter.kt} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/fr/free/nrw/commons/explore/depictions/{WikidataItemDetailsActivity.java => WikidataItemDetailsActivity.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapCalls.java => ExploreMapCalls.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapContract.java => ExploreMapContract.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapController.java => ExploreMapController.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapFragment.java => ExploreMapFragment.kt} (100%) rename app/src/main/java/fr/free/nrw/commons/explore/map/{ExploreMapPresenter.java => ExploreMapPresenter.kt} (100%) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java rename to app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt similarity index 100% rename from app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java rename to app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt From 21404f6fbda8d270d8811b84a09354f19862974b Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Fri, 21 Feb 2025 19:39:34 +0530 Subject: [PATCH 5/8] Refactor: Migrate WikidataItemDetailsActivity and explore/maps package to Kotlin The `WikidataItemDetailsActivity` has been migrated from Java to Kotlin. This includes updating the activity's structure, handling intents, setting up tabs and view pager, managing the media detail fragment, and implementing bookmark functionality. Additionally, the logic for displaying and interacting with depicted items' details, media, child classes, and parent classes has been adapted for the Kotlin implementation. --- .../contributions/ContributionsFragment.kt | 6 +- .../explore/ExploreMapRootFragment.java | 8 +- .../depictions/WikidataItemDetailsActivity.kt | 444 +++-- .../commons/explore/map/ExploreMapCalls.kt | 28 +- .../commons/explore/map/ExploreMapContract.kt | 70 +- .../explore/map/ExploreMapController.kt | 344 ++-- .../commons/explore/map/ExploreMapFragment.kt | 1443 ++++++++--------- .../explore/map/ExploreMapPresenter.kt | 284 ++-- .../location/LocationUpdateListener.kt | 6 +- .../NearbyParentFragmentPresenter.kt | 6 +- 10 files changed, 1269 insertions(+), 1370 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt index 0b7736bab7..58bc68a52b 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.kt @@ -697,12 +697,12 @@ class ContributionsFragment } } - override fun onLocationChangedSignificantly(latLng: LatLng) { + override fun onLocationChangedSignificantly(latLng: LatLng?) { // Will be called if location changed more than 1000 meter updateClosestNearbyCardViewInfo() } - override fun onLocationChangedSlightly(latLng: LatLng) { + override fun onLocationChangedSlightly(latLng: LatLng?) { /* Update closest nearby notification card onLocationChangedSlightly */ try { @@ -712,7 +712,7 @@ class ContributionsFragment } } - override fun onLocationChangedMedium(latLng: LatLng) { + override fun onLocationChangedMedium(latLng: LatLng?) { // Update closest nearby card view if location changed more than 500 meters updateClosestNearbyCardViewInfo() } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java index abf02758d5..bf37d019fe 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreMapRootFragment.java @@ -144,8 +144,8 @@ public void onMediaClicked(int position) { */ @Override public Media getMediaAtPosition(int i) { - if (mapFragment != null && mapFragment.mediaList != null) { - return mapFragment.mediaList.get(i); + if (mapFragment != null && mapFragment.getMediaList() != null) { + return mapFragment.getMediaList().get(i); } else { return null; } @@ -159,8 +159,8 @@ public Media getMediaAtPosition(int i) { */ @Override public int getTotalMediaCount() { - if (mapFragment != null && mapFragment.mediaList != null) { - return mapFragment.mediaList.size(); + if (mapFragment != null && mapFragment.getMediaList() != null) { + return mapFragment.getMediaList().size(); } else { return 0; } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt index cf72691232..109f7e199a 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.kt @@ -1,161 +1,159 @@ -package fr.free.nrw.commons.explore.depictions; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.FrameLayout; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.viewpager.widget.ViewPager; -import com.google.android.material.snackbar.Snackbar; -import com.google.android.material.tabs.TabLayout; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.ViewPagerAdapter; -import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao; -import fr.free.nrw.commons.category.CategoryImagesCallback; -import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding; -import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment; -import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment; -import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; -import fr.free.nrw.commons.theme.BaseActivity; -import fr.free.nrw.commons.upload.structure.depictions.DepictModel; -import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; -import fr.free.nrw.commons.wikidata.WikidataConstants; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; +package fr.free.nrw.commons.explore.depictions + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import com.google.android.material.snackbar.Snackbar +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.ViewPagerAdapter +import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao +import fr.free.nrw.commons.category.CategoryImagesCallback +import fr.free.nrw.commons.databinding.ActivityWikidataItemDetailsBinding +import fr.free.nrw.commons.explore.depictions.child.ChildDepictionsFragment +import fr.free.nrw.commons.explore.depictions.media.DepictedImagesFragment +import fr.free.nrw.commons.explore.depictions.parent.ParentDepictionsFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment +import fr.free.nrw.commons.theme.BaseActivity +import fr.free.nrw.commons.upload.structure.depictions.DepictModel +import fr.free.nrw.commons.upload.structure.depictions.DepictedItem +import fr.free.nrw.commons.wikidata.WikidataConstants +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import javax.inject.Inject /** * Activity to show depiction media, parent classes and child classes of depicted items in Explore */ -public class WikidataItemDetailsActivity extends BaseActivity implements MediaDetailPagerFragment.MediaDetailProvider, - CategoryImagesCallback { - private FragmentManager supportFragmentManager; - private DepictedImagesFragment depictionImagesListFragment; - private MediaDetailPagerFragment mediaDetailPagerFragment; +class WikidataItemDetailsActivity : BaseActivity(), + MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback { + + private lateinit var supportFragmentManager: FragmentManager + private lateinit var depictionImagesListFragment: DepictedImagesFragment + private var mediaDetailPagerFragment: MediaDetailPagerFragment? = null /** * Name of the depicted item * Ex: Rabbit */ + @Inject + lateinit var bookmarkItemsDao: BookmarkItemsDao - @Inject BookmarkItemsDao bookmarkItemsDao; - private CompositeDisposable compositeDisposable; @Inject - DepictModel depictModel; - private String wikidataItemName; - private ActivityWikidataItemDetailsBinding binding; - - ViewPagerAdapter viewPagerAdapter; - private DepictedItem wikidataItem; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityWikidataItemDetailsBinding.inflate(getLayoutInflater()); - setContentView(binding.getRoot()); - compositeDisposable = new CompositeDisposable(); - supportFragmentManager = getSupportFragmentManager(); - viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); - binding.viewPager.setAdapter(viewPagerAdapter); - binding.viewPager.setOffscreenPageLimit(2); - binding.tabLayout.setupWithViewPager(binding.viewPager); - - final DepictedItem depictedItem = getIntent().getParcelableExtra( - WikidataConstants.BOOKMARKS_ITEMS); - wikidataItem = depictedItem; - setSupportActionBar(binding.toolbarBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setTabs(); - setPageTitle(); + lateinit var depictModel: DepictModel + private var wikidataItemName: String? = null + private lateinit var binding: ActivityWikidataItemDetailsBinding + + private lateinit var viewPagerAdapter: ViewPagerAdapter + private var wikidataItem: DepictedItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityWikidataItemDetailsBinding.inflate(layoutInflater) + setContentView(binding.root) + + supportFragmentManager = getSupportFragmentManager() + viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + binding.viewPager.adapter = viewPagerAdapter + binding.viewPager.offscreenPageLimit = 2 + binding.tabLayout.setupWithViewPager(binding.viewPager) + + wikidataItem = intent.getParcelableExtra(WikidataConstants.BOOKMARKS_ITEMS) + setSupportActionBar(binding.toolbarBinding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + setTabs() + setPageTitle() } /** - * Gets the passed wikidataItemName from the intents and displays it as the page title + * Gets the passed wikidataItemName from the intent and displays it as the page title */ - private void setPageTitle() { - if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) { - setTitle(getIntent().getStringExtra("wikidataItemName")); + private fun setPageTitle() { + intent.getStringExtra("wikidataItemName")?.let { + title = it } } /** - * This method is called on success of API call for featured Images. - * The viewpager will notified that number of items have changed. + * This method is called on success of API call for featured images. + * The ViewPager will be notified that the number of items has changed. */ - @Override - public void viewPagerNotifyDataSetChanged() { - if (mediaDetailPagerFragment !=null){ - mediaDetailPagerFragment.notifyDataSetChanged(); - } + override fun viewPagerNotifyDataSetChanged() { + mediaDetailPagerFragment?.notifyDataSetChanged() } /** - * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab, - * Set the fragments according to the tab selected in the viewPager. + * This activity contains 3 tabs and a ViewPager. + * This method is used to set the titles of tabs and the fragments according to the selected tab */ - private void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); - depictionImagesListFragment = new DepictedImagesFragment(); - ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment(); - ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment(); - wikidataItemName = getIntent().getStringExtra("wikidataItemName"); - String entityId = getIntent().getStringExtra("entityId"); - if (getIntent() != null && wikidataItemName != null) { - Bundle arguments = new Bundle(); - arguments.putString("wikidataItemName", wikidataItemName); - arguments.putString("entityId", entityId); - depictionImagesListFragment.setArguments(arguments); - parentDepictionsFragment.setArguments(arguments); - childDepictionsFragment.setArguments(arguments); + private fun setTabs() { + val fragmentList = mutableListOf() + val titleList = mutableListOf() + + depictionImagesListFragment = DepictedImagesFragment() + val childDepictionsFragment = ChildDepictionsFragment() + val parentDepictionsFragment = ParentDepictionsFragment() + + wikidataItemName = intent.getStringExtra("wikidataItemName") + val entityId = intent.getStringExtra("entityId") + + if (!wikidataItemName.isNullOrEmpty()) { + val arguments = Bundle().apply { + putString("wikidataItemName", wikidataItemName) + putString("entityId", entityId) + } + depictionImagesListFragment.arguments = arguments + parentDepictionsFragment.arguments = arguments + childDepictionsFragment.arguments = arguments } - fragmentList.add(depictionImagesListFragment); - titleList.add(getResources().getString(R.string.title_for_media)); - fragmentList.add(childDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_child_classes)); - fragmentList.add(parentDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_parent_classes)); - viewPagerAdapter.setTabData(fragmentList, titleList); - binding.viewPager.setOffscreenPageLimit(2); - viewPagerAdapter.notifyDataSetChanged(); - } + fragmentList.apply { + add(depictionImagesListFragment) + add(childDepictionsFragment) + add(parentDepictionsFragment) + } + + titleList.apply { + add(getString(R.string.title_for_media)) + add(getString(R.string.title_for_child_classes)) + add(getString(R.string.title_for_parent_classes)) + } + viewPagerAdapter.setTabData(fragmentList, titleList) + binding.viewPager.offscreenPageLimit = 2 + viewPagerAdapter.notifyDataSetChanged() + } /** * Shows media detail fragment when user clicks on any image in the list */ - @Override - public void onMediaClicked(int position) { - binding.tabLayout.setVisibility(View.GONE); - binding.viewPager.setVisibility(View.GONE); - binding.mediaContainer.setVisibility(View.VISIBLE); - if (mediaDetailPagerFragment == null || !mediaDetailPagerFragment.isVisible()) { - // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true); - FragmentManager supportFragmentManager = getSupportFragmentManager(); - supportFragmentManager - .beginTransaction() - .replace(R.id.mediaContainer, mediaDetailPagerFragment) - .addToBackStack(null) - .commit(); - supportFragmentManager.executePendingTransactions(); + override fun onMediaClicked(position: Int) { + binding.apply { + tabLayout.visibility = View.GONE + viewPager.visibility = View.GONE + mediaContainer.visibility = View.VISIBLE } - mediaDetailPagerFragment.showImage(position); + + if (mediaDetailPagerFragment == null || mediaDetailPagerFragment?.isVisible == false) { + mediaDetailPagerFragment = MediaDetailPagerFragment.newInstance(false, true) + supportFragmentManager = getSupportFragmentManager() + supportFragmentManager.beginTransaction() + .replace(R.id.mediaContainer, mediaDetailPagerFragment!!) + .addToBackStack(null) + .commit() + supportFragmentManager.executePendingTransactions() + } + + mediaDetailPagerFragment?.showImage(position) } /** @@ -164,23 +162,23 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * current index of viewPager. * @return Media Object */ - @Override - public Media getMediaAtPosition(int i) { - return depictionImagesListFragment.getMediaAtPosition(i); + override fun getMediaAtPosition(i: Int): Media? { + return depictionImagesListFragment.getMediaAtPosition(i) } /** * This method is called on backPressed of anyFragment in the activity. * If condition is called when mediaDetailFragment is opened. */ - @Override - public void onBackPressed() { - if (supportFragmentManager.getBackStackEntryCount() == 1){ - binding.tabLayout.setVisibility(View.VISIBLE); - binding.viewPager.setVisibility(View.VISIBLE); - binding.mediaContainer.setVisibility(View.GONE); + override fun onBackPressed() { + if (supportFragmentManager.backStackEntryCount == 1) { + binding.apply { + tabLayout.visibility = View.VISIBLE + viewPager.visibility = View.VISIBLE + mediaContainer.visibility = View.GONE + } } - super.onBackPressed(); + super.onBackPressed() } /** @@ -188,14 +186,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * The viewpager will contain same number of media items as that of media elements in adapter. * @return Total Media count in the adapter */ - @Override - public int getTotalMediaCount() { - return depictionImagesListFragment.getTotalMediaCount(); + override fun getTotalMediaCount(): Int { + return depictionImagesListFragment.getTotalMediaCount() } - @Override - public Integer getContributionStateAt(int position) { - return null; + override fun getContributionStateAt(position: Int): Int? { + return null } /** @@ -203,107 +199,105 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe * * @param index item position that has been nominated */ - @Override - public void refreshNominatedMedia(int index) { - if (getSupportFragmentManager().getBackStackEntryCount() == 1) { - onBackPressed(); - onMediaClicked(index); + override fun refreshNominatedMedia(index: Int) { + if (supportFragmentManager.backStackEntryCount == 1) { + onBackPressed() + onMediaClicked(index) } } - /** - * Consumers should be simply using this method to use this activity. - * - * @param context A Context of the application package implementing this class. - * @param depictedItem Name of the depicts for displaying its details - */ - public static void startYourself(Context context, DepictedItem depictedItem) { - Intent intent = new Intent(context, WikidataItemDetailsActivity.class); - intent.putExtra("wikidataItemName", depictedItem.getName()); - intent.putExtra("entityId", depictedItem.getId()); - intent.putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem); - context.startActivity(intent); + companion object { + /** + * Consumers should be simply using this method to use this activity. + * + * @param context a Context of the application package implementing this class. + * @param depictedItem Name of the depicts for displaying its details + */ + fun startYourself(context: Context, depictedItem: DepictedItem) { + val intent = Intent(context, WikidataItemDetailsActivity::class.java).apply { + putExtra("wikidataItemName", depictedItem.name) + putExtra("entityId", depictedItem.id) + putExtra(WikidataConstants.BOOKMARKS_ITEMS, depictedItem) + } + context.startActivity(intent) + } } /** - * This function inflates the menu + * Inflates the menu */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater menuInflater=getMenuInflater(); - menuInflater.inflate(R.menu.menu_wikidata_item,menu); - - updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item)); - - return super.onCreateOptionsMenu(menu); + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_wikidata_item, menu) + updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_item)) + return super.onCreateOptionsMenu(menu) } /** * This method handles the logic on item select in toolbar menu * Currently only 1 choice is available to open Wikidata item details page in browser */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()){ - case R.id.browser_actions_menu_items: - String entityId=getIntent().getStringExtra("entityId"); - Uri uri = Uri.parse("https://www.wikidata.org/wiki/" + entityId); - Utils.handleWebUrl(this, uri); - return true; - case R.id.menu_bookmark_current_item: - - if(getIntent().getStringExtra("fragment") != null) { - compositeDisposable.add(depictModel.getDepictions( - getIntent().getStringExtra("entityId") - ).subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(depictedItems -> { - final boolean bookmarkExists = bookmarkItemsDao.updateBookmarkItem( - depictedItems.get(0)); - final Snackbar snackbar - = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.add_bookmark, Snackbar.LENGTH_LONG) - : Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.remove_bookmark, - Snackbar.LENGTH_LONG); - - snackbar.show(); - updateBookmarkState(item); - })); + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.browser_actions_menu_items -> { + val entityId = intent.getStringExtra("entityId") + val uri = Uri.parse("https://www.wikidata.org/wiki/$entityId") + Utils.handleWebUrl(this, uri) + return true + } + R.id.menu_bookmark_current_item -> { + val entityId = intent.getStringExtra("entityId") + if (intent.getStringExtra("fragment") != null) { + compositeDisposable.add( + depictModel.getDepictions(entityId!!) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { depictedItems -> + val bookmarkExists = bookmarkItemsDao + .updateBookmarkItem(depictedItems[0]) + val snackbarText = if (bookmarkExists) + R.string.add_bookmark + else + R.string.remove_bookmark + Snackbar.make( + findViewById(R.id.toolbar_layout), + snackbarText, + Snackbar.LENGTH_LONG + ).show() + updateBookmarkState(item) + } + ) } else { - final boolean bookmarkExists - = bookmarkItemsDao.updateBookmarkItem(wikidataItem); - final Snackbar snackbar - = bookmarkExists ? Snackbar.make(findViewById(R.id.toolbar_layout), - R.string.add_bookmark, Snackbar.LENGTH_LONG) - : Snackbar.make(findViewById(R.id.toolbar_layout), R.string.remove_bookmark, - Snackbar.LENGTH_LONG); - - snackbar.show(); - updateBookmarkState(item); + val bookmarkExists = bookmarkItemsDao.updateBookmarkItem(wikidataItem!!) + val snackbarText = if (bookmarkExists) + R.string.add_bookmark + else + R.string.remove_bookmark + Snackbar.make( + findViewById(R.id.toolbar_layout), + snackbarText, + Snackbar.LENGTH_LONG + ).show() + updateBookmarkState(item) } - return true; - case android.R.id.home: - onBackPressed(); - return true; - default: - return super.onOptionsItemSelected(item); + return true + } + android.R.id.home -> { + onBackPressed() + return true + } + else -> return super.onOptionsItemSelected(item) } } - private void updateBookmarkState(final MenuItem item) { - final boolean isBookmarked; - if(getIntent().getStringExtra("fragment") != null) { - isBookmarked - = bookmarkItemsDao.findBookmarkItem(getIntent().getStringExtra("entityId")); - } else { - isBookmarked = bookmarkItemsDao.findBookmarkItem(wikidataItem.getId()); - } - final int icon - = isBookmarked ? R.drawable.menu_ic_round_star_filled_24px - : R.drawable.menu_ic_round_star_border_24px; - item.setIcon(icon); + private fun updateBookmarkState(item: MenuItem) { + val isBookmarked = bookmarkItemsDao.findBookmarkItem( + intent.getStringExtra("entityId") ?: wikidataItem?.id + ) + val icon = if (isBookmarked) + R.drawable.menu_ic_round_star_filled_24px + else + R.drawable.menu_ic_round_star_border_24px + item.setIcon(icon) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt index ec426942c3..0738f1899f 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.kt @@ -1,21 +1,16 @@ -package fr.free.nrw.commons.explore.map; +package fr.free.nrw.commons.explore.map -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.media.MediaClient; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Singleton; +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.media.MediaClient +import javax.inject.Inject +import javax.inject.Singleton @Singleton -public class ExploreMapCalls { +class ExploreMapCalls @Inject constructor() { @Inject - MediaClient mediaClient; - - @Inject - public ExploreMapCalls() { - } + lateinit var mediaClient: MediaClient /** * Calls method to query Commons for uploads around a location @@ -23,9 +18,8 @@ public class ExploreMapCalls { * @param currentLatLng coordinates of search location * @return list of places obtained */ - List callCommonsQuery(final LatLng currentLatLng) { - String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude(); - return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet(); + fun callCommonsQuery(currentLatLng: LatLng): List { + val coordinates = "${currentLatLng.latitude}|${currentLatLng.longitude}" + return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet() } - } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt index feb66bf55b..256e621e95 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapContract.kt @@ -1,45 +1,43 @@ -package fr.free.nrw.commons.explore.map; +package fr.free.nrw.commons.explore.map -import android.content.Context; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.location.LocationServiceManager; -import java.util.List; +import android.content.Context +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.location.LocationServiceManager -public class ExploreMapContract { +interface ExploreMapContract { interface View { - boolean isNetworkConnectionEstablished(); - void populatePlaces(LatLng curlatLng); - void askForLocationPermission(); - void recenterMap(LatLng curLatLng); - void hideBottomDetailsSheet(); - LatLng getMapCenter(); - LatLng getMapFocus(); - LatLng getLastMapFocus(); - void addMarkersToMap(final List nearbyBaseMarkers); - void clearAllMarkers(); - void addSearchThisAreaButtonAction(); - void setSearchThisAreaButtonVisibility(boolean isVisible); - void setProgressBarVisibility(boolean isVisible); - boolean isDetailsBottomSheetVisible(); - boolean isSearchThisAreaButtonVisible(); - Context getContext(); - LatLng getLastLocation(); - void disableFABRecenter(); - void enableFABRecenter(); - void setFABRecenterAction(android.view.View.OnClickListener onClickListener); - boolean backButtonClicked(); + fun isNetworkConnectionEstablished(): Boolean + fun populatePlaces(curLatLng: LatLng?) + fun askForLocationPermission() + fun recenterMap(curLatLng: LatLng?) + fun hideBottomDetailsSheet() + fun getMapCenter(): LatLng + fun getMapFocus(): LatLng + fun getLastMapFocus(): LatLng + fun addMarkersToMap(nearbyBaseMarkers: List) + fun clearAllMarkers() + fun addSearchThisAreaButtonAction() + fun setSearchThisAreaButtonVisibility(isVisible: Boolean) + fun setProgressBarVisibility(isVisible: Boolean) + fun isDetailsBottomSheetVisible(): Boolean + fun isSearchThisAreaButtonVisible(): Boolean + fun getContext(): Context + fun getLastLocation(): LatLng + fun disableFABRecenter() + fun enableFABRecenter() + fun setFABRecenterAction(onClickListener: android.view.View.OnClickListener) + fun backButtonClicked(): Boolean } interface UserActions { - void updateMap(LocationServiceManager.LocationChangeType locationChangeType); - void lockUnlockNearby(boolean isNearbyLocked); - void attachView(View view); - void detachView(); - void setActionListeners(JsonKvStore applicationKvStore); - boolean backButtonClicked(); + fun updateMap(locationChangeType: LocationServiceManager.LocationChangeType) + fun lockUnlockNearby(isNearbyLocked: Boolean) + fun attachView(view: View) + fun detachView() + fun setActionListeners(applicationKvStore: JsonKvStore) + fun backButtonClicked(): Boolean } - } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt index c944f75a11..391d9d5c52 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapController.kt @@ -1,126 +1,120 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween; -import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat; -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.transition.Transition; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.utils.ImageUtils; -import fr.free.nrw.commons.utils.LocationUtils; -import fr.free.nrw.commons.utils.PlaceUtils; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; -import timber.log.Timber; - -public class ExploreMapController extends MapController { - - private final ExploreMapCalls exploreMapCalls; - public LatLng latestSearchLocation; // Can be current and camera target on search this area button is used - public LatLng currentLocation; // current location of user - public double latestSearchRadius = 0; // Any last search radius - public double currentLocationSearchRadius = 0; // Search radius of only searches around current location - - - @Inject - public ExploreMapController(ExploreMapCalls explorePlaces) { - this.exploreMapCalls = explorePlaces; - } +package fr.free.nrw.commons.explore.map + +import android.content.Context +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.utils.ImageUtils +import fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween +import fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween +import fr.free.nrw.commons.utils.LocationUtils +import fr.free.nrw.commons.utils.PlaceUtils +import timber.log.Timber +import javax.inject.Inject + +class ExploreMapController @Inject constructor( + private val exploreMapCalls: ExploreMapCalls +) : MapController() { + + var latestSearchLocation: LatLng? = null // Can be current and camera target when search this + // area button is used + var currentLocation: LatLng? = null // Current location of user + var latestSearchRadius: Double = 0.0 // Any last search radius + var currentLocationSearchRadius: Double = 0.0 // Search radius of only searches around current + // location /** - * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, mediaList, - * explorePlaceList and boundaryCoordinates + * Takes location as parameter and returns ExplorePlaces info that holds currentLatLng, + * mediaList, explorePlaceList and boundaryCoordinates * - * @param currentLatLng is current geolocation - * @param searchLatLng is the location that we want to search around + * @param currentLatLng is current geolocation + * @param searchLatLng is the location that we want to search around * @param checkingAroundCurrentLocation is a boolean flag. True if we want to check around * current location, false if another location * @return explorePlacesInfo info that holds currentLatLng, mediaList, explorePlaceList and * boundaryCoordinates */ - public ExplorePlacesInfo loadAttractionsFromLocation(LatLng currentLatLng, LatLng searchLatLng, - boolean checkingAroundCurrentLocation) { + fun loadAttractionsFromLocation( + currentLatLng: LatLng, + searchLatLng: LatLng?, + checkingAroundCurrentLocation: Boolean + ): ExplorePlacesInfo? { if (searchLatLng == null) { - Timber.d("Loading attractions explore map, but search is null"); - return null; + Timber.d("Loading attractions explore map, but search is null") + return null } - ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo(); + val explorePlacesInfo = ExplorePlacesInfo() try { - explorePlacesInfo.currentLatLng = currentLatLng; - latestSearchLocation = searchLatLng; - - List mediaList = exploreMapCalls.callCommonsQuery(searchLatLng); - LatLng[] boundaryCoordinates = {mediaList.get(0).getCoordinates(), // south - mediaList.get(0).getCoordinates(), // north - mediaList.get(0).getCoordinates(), // west - mediaList.get(0).getCoordinates()};// east, init with a random location - - if (searchLatLng != null) { - Timber.d("Sorting places by distance..."); - final Map distances = new HashMap<>(); - for (Media media : mediaList) { - distances.put(media, - computeDistanceBetween(media.getCoordinates(), searchLatLng)); - // Find boundaries with basic find max approach - if (media.getCoordinates().getLatitude() - < boundaryCoordinates[0].getLatitude()) { - boundaryCoordinates[0] = media.getCoordinates(); - } - if (media.getCoordinates().getLatitude() - > boundaryCoordinates[1].getLatitude()) { - boundaryCoordinates[1] = media.getCoordinates(); - } - if (media.getCoordinates().getLongitude() - < boundaryCoordinates[2].getLongitude()) { - boundaryCoordinates[2] = media.getCoordinates(); - } - if (media.getCoordinates().getLongitude() - > boundaryCoordinates[3].getLongitude()) { - boundaryCoordinates[3] = media.getCoordinates(); - } + explorePlacesInfo.currentLatLng = currentLatLng + latestSearchLocation = searchLatLng + + val mediaList = exploreMapCalls.callCommonsQuery(searchLatLng) + val boundaryCoordinates = arrayOf( + mediaList[0].coordinates, // south + mediaList[0].coordinates, // north + mediaList[0].coordinates, // west + mediaList[0].coordinates // east, init with a random location + ) + + Timber.d("Sorting places by distance...") + val distances = mutableMapOf() + + for (media in mediaList) { + distances[media] = computeDistanceBetween(media.coordinates!!, searchLatLng) + + // Find boundaries with basic find max approach + if (media.coordinates!!.latitude < boundaryCoordinates[0]?.latitude!!) { + boundaryCoordinates[0] = media.coordinates + } + if (media.coordinates!!.latitude > boundaryCoordinates[1]?.latitude!!) { + boundaryCoordinates[1] = media.coordinates + } + if (media.coordinates!!.longitude < boundaryCoordinates[2]?.longitude!!) { + boundaryCoordinates[2] = media.coordinates + } + if (media.coordinates!!.longitude > boundaryCoordinates[3]?.longitude!!) { + boundaryCoordinates[3] = media.coordinates } } - explorePlacesInfo.mediaList = mediaList; - explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList); - explorePlacesInfo.boundaryCoordinates = boundaryCoordinates; + + explorePlacesInfo.mediaList = mediaList + explorePlacesInfo.explorePlaceList = PlaceUtils.mediaToExplorePlace(mediaList) + explorePlacesInfo.boundaryCoordinates = boundaryCoordinates // Sets latestSearchRadius to maximum distance among boundaries and search location - for (LatLng bound : boundaryCoordinates) { - double distance = LocationUtils.calculateDistance(bound.getLatitude(), - bound.getLongitude(), searchLatLng.getLatitude(), searchLatLng.getLongitude()); + for (bound in boundaryCoordinates) { + val distance = LocationUtils.calculateDistance( + bound?.latitude!!, bound.longitude, + searchLatLng.latitude, searchLatLng.longitude + ) if (distance > latestSearchRadius) { - latestSearchRadius = distance; + latestSearchRadius = distance } } - // Our radius searched around us, will be used to understand when user search their own location, we will follow them + // Our radius searched around us, will be used to understand when user search + // their own location, we will follow them if (checkingAroundCurrentLocation) { - currentLocationSearchRadius = latestSearchRadius; - currentLocation = currentLatLng; + currentLocationSearchRadius = latestSearchRadius + currentLocation = currentLatLng } - } catch (Exception e) { - e.printStackTrace(); + } catch (e: Exception) { + e.printStackTrace() } - return explorePlacesInfo; + return explorePlacesInfo } /** @@ -128,86 +122,96 @@ public class ExploreMapController extends MapController { * * @return baseMarkerOptions list that holds nearby places with their icons */ - public static List loadAttractionsFromLocationToBaseMarkerOptions( - LatLng currentLatLng, - final List placeList, - Context context, - NearbyBaseMarkerThumbCallback callback, - ExplorePlacesInfo explorePlacesInfo) { - List baseMarkerList = new ArrayList<>(); - - if (placeList == null) { - return baseMarkerList; - } + companion object { + fun loadAttractionsFromLocationToBaseMarkerOptions( + currentLatLng: LatLng, + placeList: List?, + context: Context, + callback: NearbyBaseMarkerThumbCallback, + explorePlacesInfo: ExplorePlacesInfo + ): List { + val baseMarkerList = mutableListOf() + + if (placeList == null) { + return baseMarkerList + } - VectorDrawableCompat vectorDrawable = null; - try { - vectorDrawable = VectorDrawableCompat.create( - context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme()); + var vectorDrawable: VectorDrawableCompat? = null + try { + vectorDrawable = VectorDrawableCompat.create( + context.resources, R.drawable.ic_custom_map_marker_dark, context.theme + ) + } catch (e: Resources.NotFoundException) { + // Ignore when running tests + } - } catch (Resources.NotFoundException e) { - // ignore when running tests. - } - if (vectorDrawable != null) { - for (Place explorePlace : placeList) { - final BaseMarker baseMarker = new BaseMarker(); - String distance = formatDistanceBetween(currentLatLng, explorePlace.location); - explorePlace.setDistance(distance); - - baseMarker.setTitle( - explorePlace.name.substring(5, explorePlace.name.lastIndexOf("."))); - baseMarker.setPosition( - new fr.free.nrw.commons.location.LatLng( - explorePlace.location.getLatitude(), - explorePlace.location.getLongitude(), 0)); - baseMarker.setPlace(explorePlace); - - Glide.with(context) - .asBitmap() - .load(explorePlace.getThumb()) - .placeholder(R.drawable.image_placeholder_96) - .apply(new RequestOptions().override(96, 96).centerCrop()) - .into(new CustomTarget() { - // We add icons to markers when bitmaps are ready - @Override - public void onResourceReady(@NonNull Bitmap resource, - @Nullable Transition transition) { - baseMarker.setIcon( - ImageUtils.addRedBorder(resource, 6, context)); - baseMarkerList.add(baseMarker); - if (baseMarkerList.size() - == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback - callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, - explorePlacesInfo); + vectorDrawable?.let { + for (explorePlace in placeList) { + val baseMarker = BaseMarker() + val distance = formatDistanceBetween(currentLatLng, explorePlace.location) + explorePlace.distance = distance + + baseMarker.title = explorePlace.name.substring( + 5, + explorePlace.name.lastIndexOf(".") + ) + baseMarker.position = LatLng( + explorePlace.location.latitude, + explorePlace.location.longitude, 0.0f + ) + baseMarker.place = explorePlace + + Glide.with(context) + .asBitmap() + .load(explorePlace.thumb) + .placeholder(R.drawable.image_placeholder_96) + .apply(RequestOptions().override(96, 96).centerCrop()) + .into(object : CustomTarget() { + // We add icons to markers when bitmaps are ready + override fun onResourceReady( + resource: Bitmap, + transition: Transition? + ) { + baseMarker.icon = ImageUtils.addRedBorder(resource, 6, context) + baseMarkerList.add(baseMarker) + if (baseMarkerList.size == placeList.size) { + // If true, we added all markers to list and can trigger thumbs + // ready callback + callback.onNearbyBaseMarkerThumbsReady( + baseMarkerList, + explorePlacesInfo + ) + } } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - } - - // We add thumbnail icon for images that couldn't be loaded - @Override - public void onLoadFailed(@Nullable final Drawable errorDrawable) { - super.onLoadFailed(errorDrawable); - baseMarker.fromResource(context, R.drawable.image_placeholder_96); - baseMarkerList.add(baseMarker); - if (baseMarkerList.size() - == placeList.size()) { // if true, we added all markers to list and can trigger thumbs ready callback - callback.onNearbyBaseMarkerThumbsReady(baseMarkerList, - explorePlacesInfo); + + override fun onLoadCleared(placeholder: Drawable?) {} + + // We add thumbnail icon for images that couldn't be loaded + override fun onLoadFailed(errorDrawable: Drawable?) { + super.onLoadFailed(errorDrawable) + baseMarker.fromResource(context, R.drawable.image_placeholder_96) + baseMarkerList.add(baseMarker) + if (baseMarkerList.size == placeList.size) { + // If true, we added all markers to list and can trigger thumbs + // ready callback + callback.onNearbyBaseMarkerThumbsReady( + baseMarkerList, + explorePlacesInfo + ) + } } - } - }); + }) + } } + return baseMarkerList } - return baseMarkerList; } interface NearbyBaseMarkerThumbCallback { - // Callback to notify thumbnails of explore markers are added as icons and ready - void onNearbyBaseMarkerThumbsReady(List baseMarkers, - ExplorePlacesInfo explorePlacesInfo); + fun onNearbyBaseMarkerThumbsReady( + baseMarkers: List, + explorePlacesInfo: ExplorePlacesInfo + ) } } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt index 1b16591828..87f768ceeb 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.kt @@ -1,494 +1,478 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED; -import static fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL; - -import android.Manifest.permission; -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.location.Location; -import android.location.LocationManager; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Html; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.snackbar.Snackbar; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.contributions.MainActivity; -import fr.free.nrw.commons.databinding.FragmentExploreMapBinding; -import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; -import fr.free.nrw.commons.explore.ExploreMapRootFragment; -import fr.free.nrw.commons.explore.paging.LiveDataConverter; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -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.media.MediaClient; -import fr.free.nrw.commons.nearby.Place; -import fr.free.nrw.commons.utils.DialogUtil; -import fr.free.nrw.commons.utils.MapUtils; -import fr.free.nrw.commons.utils.NetworkUtils; -import fr.free.nrw.commons.utils.SystemThemeUtils; -import fr.free.nrw.commons.utils.ViewUtil; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; -import javax.inject.Named; -import org.osmdroid.events.MapEventsReceiver; -import org.osmdroid.events.MapListener; -import org.osmdroid.events.ScrollEvent; -import org.osmdroid.events.ZoomEvent; -import org.osmdroid.tileprovider.tilesource.TileSourceFactory; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.util.constants.GeoConstants; -import org.osmdroid.views.CustomZoomButtonsController; -import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener; -import org.osmdroid.views.overlay.ItemizedOverlayWithFocus; -import org.osmdroid.views.overlay.MapEventsOverlay; -import org.osmdroid.views.overlay.Overlay; -import org.osmdroid.views.overlay.OverlayItem; -import org.osmdroid.views.overlay.ScaleBarOverlay; -import org.osmdroid.views.overlay.ScaleDiskOverlay; -import org.osmdroid.views.overlay.TilesOverlay; -import timber.log.Timber; - -public class ExploreMapFragment extends CommonsDaggerSupportFragment - implements ExploreMapContract.View, LocationUpdateListener, LocationPermissionCallback { - - private BottomSheetBehavior bottomSheetDetailsBehavior; - private BroadcastReceiver broadcastReceiver; - private boolean isNetworkErrorOccurred; - private Snackbar snackbar; - private boolean isDarkTheme; - private boolean isPermissionDenied; - private fr.free.nrw.commons.location.LatLng lastKnownLocation; // last location of user - private fr.free.nrw.commons.location.LatLng lastFocusLocation; // last location that map is focused - public List mediaList; - private boolean recenterToUserLocation; // true is recenter is needed (ie. when current location is in visible map boundaries) - private BaseMarker clickedMarker; - private GeoPoint mapCenter; - private GeoPoint lastMapFocus; - IntentFilter intentFilter = new IntentFilter(MapUtils.NETWORK_INTENT_ACTION); +package fr.free.nrw.commons.explore.map + +import android.Manifest +import android.Manifest.permission +import android.annotation.SuppressLint +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.graphics.Color +import android.graphics.Paint +import android.graphics.drawable.BitmapDrawable +import android.location.Location +import android.location.LocationManager +import android.os.Build +import android.os.Bundle +import android.preference.PreferenceManager +import android.text.Html +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts +import androidx.core.content.ContextCompat +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.snackbar.Snackbar +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao +import fr.free.nrw.commons.contributions.MainActivity +import fr.free.nrw.commons.databinding.FragmentExploreMapBinding +import fr.free.nrw.commons.di.CommonsDaggerSupportFragment +import fr.free.nrw.commons.explore.ExploreMapRootFragment +import fr.free.nrw.commons.explore.paging.LiveDataConverter +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +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.media.MediaClient +import fr.free.nrw.commons.nearby.Place +import fr.free.nrw.commons.utils.DialogUtil +import fr.free.nrw.commons.utils.MapUtils +import fr.free.nrw.commons.utils.MapUtils.ZOOM_LEVEL +import fr.free.nrw.commons.utils.NetworkUtils +import fr.free.nrw.commons.utils.SystemThemeUtils +import fr.free.nrw.commons.utils.ViewUtil +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import java.util.ArrayList + +import javax.inject.Inject +import javax.inject.Named +import org.osmdroid.events.MapEventsReceiver +import org.osmdroid.events.MapListener +import org.osmdroid.events.ScrollEvent +import org.osmdroid.events.ZoomEvent +import org.osmdroid.tileprovider.tilesource.TileSourceFactory +import org.osmdroid.util.GeoPoint +import org.osmdroid.util.constants.GeoConstants +import org.osmdroid.views.CustomZoomButtonsController +import org.osmdroid.views.overlay.ItemizedIconOverlay.OnItemGestureListener +import org.osmdroid.views.overlay.ItemizedOverlayWithFocus +import org.osmdroid.views.overlay.MapEventsOverlay +import org.osmdroid.views.overlay.Marker +import org.osmdroid.views.overlay.OverlayItem +import org.osmdroid.views.overlay.ScaleBarOverlay +import org.osmdroid.views.overlay.ScaleDiskOverlay +import org.osmdroid.views.overlay.TilesOverlay +import timber.log.Timber + +class ExploreMapFragment : CommonsDaggerSupportFragment(), + ExploreMapContract.View, LocationUpdateListener, LocationPermissionCallback { + + private lateinit var bottomSheetDetailsBehavior: BottomSheetBehavior<*> + private var broadcastReceiver: BroadcastReceiver? = null + private var isNetworkErrorOccurred = false + private var snackbar: Snackbar? = null + private var isDarkTheme = false + private var isPermissionDenied = false + private var lastKnownLocation: fr.free.nrw.commons.location.LatLng? = null // last location of user + private var lastFocusLocation: fr.free.nrw.commons.location.LatLng? = null // last focused location of the map + var mediaList: List? = null + private var recenterToUserLocation = false // true if recentering is needed + private var clickedMarker: BaseMarker? = null + private var mapCenter: GeoPoint? = null + private var lastMapFocus: GeoPoint? = null + private val intentFilter = IntentFilter(MapUtils.NETWORK_INTENT_ACTION) @Inject - LiveDataConverter liveDataConverter; + lateinit var liveDataConverter: LiveDataConverter + @Inject - MediaClient mediaClient; + lateinit var mediaClient: MediaClient + @Inject - LocationServiceManager locationManager; + lateinit var locationManager: LocationServiceManager + @Inject - ExploreMapController exploreMapController; + lateinit var exploreMapController: ExploreMapController + @Inject @Named("default_preferences") - JsonKvStore applicationKvStore; + lateinit var applicationKvStore: JsonKvStore + @Inject - BookmarkLocationsDao bookmarkLocationDao; // May be needed in future if we want to integrate bookmarking explore places + lateinit var bookmarkLocationDao: BookmarkLocationsDao // Future use for bookmarking explore places + @Inject - SystemThemeUtils systemThemeUtils; - LocationPermissionsHelper locationPermissionsHelper; + lateinit var systemThemeUtils: SystemThemeUtils + + private lateinit var locationPermissionsHelper: LocationPermissionsHelper // Nearby map state (if we came from Nearby) - private double prevZoom; - private double prevLatitude; - private double prevLongitude; + private var prevZoom = 0.0 + private var prevLatitude = 0.0 + private var prevLongitude = 0.0 - private ExploreMapPresenter presenter; + private var presenter: ExploreMapPresenter? = null - public FragmentExploreMapBinding binding; + private lateinit var binding: FragmentExploreMapBinding - private ActivityResultLauncher activityResultLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), isGranted -> { + private val activityResultLauncher = + registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { - locationPermissionGranted(); + locationPermissionGranted() } else { - if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { - DialogUtil.showAlertDialog(getActivity(), - getActivity().getString(R.string.location_permission_title), - getActivity().getString(R.string.location_permission_rationale_explore), - getActivity().getString(android.R.string.ok), - getActivity().getString(android.R.string.cancel), - () -> { - askForLocationPermission(); - }, - null, - null - ); + if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) { + activity?.let { + DialogUtil.showAlertDialog( + it, + getString(R.string.location_permission_title), + getString(R.string.location_permission_rationale_explore), + getString(android.R.string.ok), + getString(android.R.string.cancel), + { + askForLocationPermission() + }, + null, + null + ) + } } else { if (isPermissionDenied) { - locationPermissionsHelper.showAppSettingsDialog(getActivity(), - R.string.explore_map_needs_location); + locationPermissionsHelper.showAppSettingsDialog( + requireActivity(), + R.string.explore_map_needs_location + ) } - Timber.d("The user checked 'Don't ask again' or denied the permission twice"); - isPermissionDenied = true; + Timber.d("The user checked 'Don't ask again' or denied the permission twice") + isPermissionDenied = true } } - }); + } - @NonNull - public static ExploreMapFragment newInstance() { - ExploreMapFragment fragment = new ExploreMapFragment(); - fragment.setRetainInstance(true); - return fragment; + companion object { + @JvmStatic + fun newInstance(): ExploreMapFragment { + return ExploreMapFragment().apply { retainInstance = true } + } } - @Override - public View onCreateView( - @NonNull LayoutInflater inflater, - ViewGroup container, - Bundle savedInstanceState - ) { - loadNearbyMapData(); - binding = FragmentExploreMapBinding.inflate(getLayoutInflater()); - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setSearchThisAreaButtonVisibility(false); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - binding.tvAttribution.setText( - Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + loadNearbyMapData() + binding = FragmentExploreMapBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setSearchThisAreaButtonVisibility(false) + + binding.tvAttribution.text = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY) } else { - binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution))); + Html.fromHtml(getString(R.string.map_attribution)) } - initNetworkBroadCastReceiver(); - locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, - this); + + initNetworkBroadCastReceiver() + locationPermissionsHelper = LocationPermissionsHelper( + requireActivity(), + locationManager, + this + ) + if (presenter == null) { - presenter = new ExploreMapPresenter(bookmarkLocationDao); + presenter = ExploreMapPresenter(bookmarkLocationDao) } - setHasOptionsMenu(true); + setHasOptionsMenu(true) + + isDarkTheme = systemThemeUtils.isDeviceInNightMode() + isPermissionDenied = false + presenter?.attachView(this) + + initViews() + presenter?.setActionListeners(applicationKvStore) + + org.osmdroid.config.Configuration.getInstance().load( + requireContext(), + PreferenceManager.getDefaultSharedPreferences(requireContext()) + ) + + binding.mapView.apply { + setTileSource(TileSourceFactory.WIKIMEDIA) + setTilesScaledToDpi(true) + org.osmdroid.config.Configuration.getInstance() + .additionalHttpRequestProperties["Referer"] = "http://maps.wikimedia.org/" + + val scaleBarOverlay = ScaleBarOverlay(this).apply { + setScaleBarOffset(15, 25) + setBackgroundPaint( + Paint().apply { + setARGB(200, 255, 250, 250) + } + ) + enableScaleBar() + } + overlays.add(scaleBarOverlay) - isDarkTheme = systemThemeUtils.isDeviceInNightMode(); - isPermissionDenied = false; - presenter.attachView(this); + zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER) + setMultiTouchControls(true) - initViews(); - presenter.setActionListeners(applicationKvStore); + if (!isCameFromNearbyMap()) { + controller.setZoom(ZOOM_LEVEL.toDouble()) + } + } - org.osmdroid.config.Configuration.getInstance().load(this.getContext(), - PreferenceManager.getDefaultSharedPreferences(this.getContext())); + performMapReadyActions() - binding.mapView.setTileSource(TileSourceFactory.WIKIMEDIA); - binding.mapView.setTilesScaledToDpi(true); + binding.mapView.overlays.add( + MapEventsOverlay(object : MapEventsReceiver { + override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean { + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + binding.mapView.invalidate() + } ?: Timber.e("CLICKED MARKER IS NULL") - org.osmdroid.config.Configuration.getInstance().getAdditionalHttpRequestProperties().put( - "Referer", "http://maps.wikimedia.org/" - ); + if (bottomSheetDetailsBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + } else if (isDetailsBottomSheetVisible()) { + hideBottomDetailsSheet() + } + return true + } - ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView); - scaleBarOverlay.setScaleBarOffset(15, 25); - Paint barPaint = new Paint(); - barPaint.setARGB(200, 255, 250, 250); - scaleBarOverlay.setBackgroundPaint(barPaint); - scaleBarOverlay.enableScaleBar(); - binding.mapView.getOverlays().add(scaleBarOverlay); - binding.mapView.getZoomController() - .setVisibility(CustomZoomButtonsController.Visibility.NEVER); - binding.mapView.setMultiTouchControls(true); + override fun longPressHelper(p: GeoPoint?): Boolean = false + }) + ) - if (!isCameFromNearbyMap()) { - binding.mapView.getController().setZoom(ZOOM_LEVEL); - } - performMapReadyActions(); + binding.mapView.addMapListener(object : MapListener { + override fun onScroll(event: ScrollEvent?): Boolean { + lastMapFocus?.let { + val myLocation = Location("").apply { + latitude = it.latitude + longitude = it.longitude + } - binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - binding.mapView.invalidate(); - } else { - Timber.e("CLICKED MARKER IS NULL"); - } - if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) { - // Back should first hide the bottom sheet if it is expanded - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); - } - return true; - } + val destLocation = Location("").apply { + latitude = binding.mapView.mapCenter.latitude + longitude = binding.mapView.mapCenter.longitude + } - @Override - public boolean longPressHelper(GeoPoint p) { - return false; - } - })); - - binding.mapView.addMapListener(new MapListener() { - @Override - public boolean onScroll(ScrollEvent event) { - if (getLastMapFocus() != null) { - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude()); - dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude()); - mylocation.setLatitude(getLastMapFocus().getLatitude()); - mylocation.setLongitude(getLastMapFocus().getLongitude()); - Float distance = mylocation.distanceTo(dest_location);//in meters - if (getLastMapFocus() != null) { - if (isNetworkConnectionEstablished() && (event.getX() > 0 - || event.getY() > 0)) { - if (distance > 2000.0) { - setSearchThisAreaButtonVisibility(true); - } else { - setSearchThisAreaButtonVisibility(false); - } - } - } else { - setSearchThisAreaButtonVisibility(false); + val distance = myLocation.distanceTo(destLocation) + + if ( + isNetworkConnectionEstablished() + && + (event?.x!! > 0 || event.y > 0) + ) { + setSearchThisAreaButtonVisibility(distance > 2000.0) } - } + } ?: setSearchThisAreaButtonVisibility(false) - return true; + return true } - @Override - public boolean onZoom(ZoomEvent event) { - return false; - } + override fun onZoom(event: ZoomEvent?): Boolean = false + }) - }); - if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { - askForLocationPermission(); + if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) { + askForLocationPermission() } } - @Override - public void onResume() { - super.onResume(); - binding.mapView.onResume(); - presenter.attachView(this); - registerNetworkReceiver(); - if (isResumed()) { - if (locationPermissionsHelper.checkLocationPermission(getActivity())) { - performMapReadyActions(); + override fun onResume() { + super.onResume() + binding.mapView.onResume() + presenter?.attachView(this) + registerNetworkReceiver() + + if (isResumed) { + if (activity?.let { locationPermissionsHelper.checkLocationPermission(it) } == true) { + performMapReadyActions() } else { - startMapWithoutPermission(); + startMapWithoutPermission() } } } - @Override - public void onPause() { - super.onPause(); - // unregistering the broadcastReceiver, as it was causing an exception and a potential crash - unregisterNetworkReceiver(); + override fun onPause() { + super.onPause() + // Unregistering the broadcastReceiver to prevent crashes + unregisterNetworkReceiver() } - /** * Unregisters the networkReceiver */ - private void unregisterNetworkReceiver() { - if (getActivity() != null) { - getActivity().unregisterReceiver(broadcastReceiver); - } + private fun unregisterNetworkReceiver() { + activity?.unregisterReceiver(broadcastReceiver) } - private void startMapWithoutPermission() { - lastKnownLocation = MapUtils.getDefaultLatLng(); - moveCameraToPosition( - new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); - presenter.onMapReady(exploreMapController); + private fun startMapWithoutPermission() { + lastKnownLocation = MapUtils.defaultLatLng + moveCameraToPosition(GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude)) + presenter?.onMapReady(exploreMapController) } - private void registerNetworkReceiver() { - if (getActivity() != null) { - getActivity().registerReceiver(broadcastReceiver, intentFilter); - } + private fun registerNetworkReceiver() { + activity?.registerReceiver(broadcastReceiver, intentFilter) } - private void performMapReadyActions() { + private fun performMapReadyActions() { if (isDarkTheme) { - binding.mapView.getOverlayManager().getTilesOverlay() - .setColorFilter(TilesOverlay.INVERT_COLORS); + binding.mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS) } if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) && - !locationPermissionsHelper.checkLocationPermission(getActivity())) { - isPermissionDenied = true; + !locationPermissionsHelper.checkLocationPermission(requireActivity()) + ) { + isPermissionDenied = true } - lastKnownLocation = MapUtils.getDefaultLatLng(); - // if we came from 'Show in Explore' in Nearby, load Nearby map center and zoom + lastKnownLocation = MapUtils.defaultLatLng + + // If user came from 'Show in Explore' in Nearby, load saved map center and zoom if (isCameFromNearbyMap()) { - moveCameraToPosition( - new GeoPoint(prevLatitude, prevLongitude), - prevZoom, - 1L - ); + moveCameraToPosition(GeoPoint(prevLatitude, prevLongitude), prevZoom, 1L) } else { moveCameraToPosition( - new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); + GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude) + ) } - presenter.onMapReady(exploreMapController); + + presenter?.onMapReady(exploreMapController) } /** - * Fetch Nearby map camera data from fragment arguments if any. + * Fetch Nearby map camera data from fragment arguments if available. */ - public void loadNearbyMapData() { - // get fragment arguments - if (getArguments() != null) { - prevZoom = getArguments().getDouble("prev_zoom"); - prevLatitude = getArguments().getDouble("prev_latitude"); - prevLongitude = getArguments().getDouble("prev_longitude"); + fun loadNearbyMapData() { + arguments?.let { + prevZoom = it.getDouble("prev_zoom") + prevLatitude = it.getDouble("prev_latitude") + prevLongitude = it.getDouble("prev_longitude") } } /** - * Checks if fragment arguments contain data from Nearby map, indicating that the user navigated - * from Nearby using 'Show in Explore'. + * Checks if fragment arguments contain data from the Nearby map, + * indicating that the user navigated from Nearby using 'Show in Explore'. * * @return true if user navigated from Nearby map - **/ - public boolean isCameFromNearbyMap() { - return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0; + */ + fun isCameFromNearbyMap(): Boolean { + return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0 } - public void loadNearbyMapFromExplore() { - ((MainActivity) getContext()).loadNearbyMapFromExplore( - binding.mapView.getZoomLevelDouble(), - binding.mapView.getMapCenter().getLatitude(), - binding.mapView.getMapCenter().getLongitude() - ); + fun loadNearbyMapFromExplore() { + (requireContext() as MainActivity).loadNearbyMapFromExplore( + binding.mapView.zoomLevelDouble, + binding.mapView.mapCenter.latitude, + binding.mapView.mapCenter.longitude + ) } - private void initViews() { - Timber.d("init views called"); - initBottomSheets(); - setBottomSheetCallbacks(); + private fun initViews() { + Timber.d("init views called") + initBottomSheets() + setBottomSheetCallbacks() } /** - * a) Creates bottom sheet behaviours from bottom sheet, sets initial states and visibility + * a) Creates bottom sheet behaviors from bottom sheet, sets initial states and visibility. * b) Gets the touch event on the map to perform following actions: - * if bottom sheet details are expanded or collapsed hide the bottom sheet details. + * - If bottom sheet details are expanded or collapsed, hide the bottom sheet details. */ @SuppressLint("ClickableViewAccessibility") - private void initBottomSheets() { - bottomSheetDetailsBehavior = BottomSheetBehavior.from( - binding.bottomSheetDetailsBinding.getRoot()); - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - binding.bottomSheetDetailsBinding.getRoot().setVisibility(View.VISIBLE); + private fun initBottomSheets() { + bottomSheetDetailsBehavior = BottomSheetBehavior.from(binding.bottomSheetDetailsBinding.root) + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + binding.bottomSheetDetailsBinding.root.visibility = View.VISIBLE } /** * Defines how bottom sheets will act on click */ - private void setBottomSheetCallbacks() { - binding.bottomSheetDetailsBinding.getRoot().setOnClickListener(v -> { - if (bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_COLLAPSED) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); - } else if (bottomSheetDetailsBehavior.getState() - == BottomSheetBehavior.STATE_EXPANDED) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + private fun setBottomSheetCallbacks() { + binding.bottomSheetDetailsBinding.root.setOnClickListener { + bottomSheetDetailsBehavior.state = when (bottomSheetDetailsBehavior.state) { + BottomSheetBehavior.STATE_COLLAPSED -> BottomSheetBehavior.STATE_EXPANDED + BottomSheetBehavior.STATE_EXPANDED -> BottomSheetBehavior.STATE_COLLAPSED + else -> bottomSheetDetailsBehavior.state } - }); - } - - @Override - public void onLocationChangedSignificantly(LatLng latLng) { - Timber.d("Location significantly changed"); - if (latLng != null) { - handleLocationUpdate(latLng, LOCATION_SIGNIFICANTLY_CHANGED); } } - @Override - public void onLocationChangedSlightly(LatLng latLng) { - Timber.d("Location slightly changed"); - if (latLng != null) {//If the map has never ever shown the current location, lets do it know - handleLocationUpdate(latLng, LOCATION_SLIGHTLY_CHANGED); - } + override fun onLocationChangedSignificantly(latLng: LatLng?) { + Timber.d("Location significantly changed") + latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } } - private void handleLocationUpdate(final fr.free.nrw.commons.location.LatLng latLng, - final LocationServiceManager.LocationChangeType locationChangeType) { - lastKnownLocation = latLng; - exploreMapController.currentLocation = lastKnownLocation; - presenter.updateMap(locationChangeType); + override fun onLocationChangedSlightly(latLng: LatLng?) { + Timber.d("Location slightly changed") + latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED) } } - @Override - public void onLocationChangedMedium(LatLng latLng) { + private fun handleLocationUpdate( + latLng: fr.free.nrw.commons.location.LatLng, + locationChangeType: LocationServiceManager.LocationChangeType + ) { + lastKnownLocation = latLng + exploreMapController.currentLocation = lastKnownLocation + presenter?.updateMap(locationChangeType) + } + override fun onLocationChangedMedium(latLng: LatLng?) { + // No implementation required } - @Override - public boolean isNetworkConnectionEstablished() { - return NetworkUtils.isInternetConnectionEstablished(getActivity()); + override fun isNetworkConnectionEstablished(): Boolean { + return NetworkUtils.isInternetConnectionEstablished(activity) } - @Override - public void populatePlaces(LatLng currentLatLng) { - final Observable nearbyPlacesInfoObservable; - if (currentLatLng == null) { - return; - } - if (currentLatLng.equals( - getLastMapFocus())) { // Means we are checking around current location - nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(currentLatLng, - getLastMapFocus(), true); + override fun populatePlaces(curLatLng: LatLng?) { + if (curLatLng == null) return + + val nearbyPlacesInfoObservable: Observable = + if (curLatLng == getLastMapFocus()) { + // Checking around current location + presenter!!.loadAttractionsFromLocation(curLatLng, getLastMapFocus(), true) } else { - nearbyPlacesInfoObservable = presenter.loadAttractionsFromLocation(getLastMapFocus(), - currentLatLng, false); + presenter!!.loadAttractionsFromLocation(getLastMapFocus(), curLatLng, false) } - getCompositeDisposable().add(nearbyPlacesInfoObservable - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(explorePlacesInfo -> { - mediaList = explorePlacesInfo.mediaList; - if (mediaList == null) { - showResponseMessage(getString(R.string.no_pictures_in_this_area)); + + compositeDisposable.add( + nearbyPlacesInfoObservable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ explorePlacesInfo -> + mediaList = explorePlacesInfo.mediaList + if (mediaList.isNullOrEmpty()) { + showResponseMessage(getString(R.string.no_pictures_in_this_area)) } - updateMapMarkers(explorePlacesInfo); - lastMapFocus = new GeoPoint(currentLatLng.getLatitude(), - currentLatLng.getLongitude()); - }, - throwable -> { - Timber.d(throwable); - // Not showing the user, throwable localizedErrorMessage - showErrorMessage(getString(R.string.error_fetching_nearby_places)); - - setProgressBarVisibility(false); - presenter.lockUnlockNearby(false); - })); + updateMapMarkers(explorePlacesInfo) + lastMapFocus = GeoPoint(curLatLng.latitude, curLatLng.longitude) + }, { throwable -> + Timber.d(throwable) + showErrorMessage(getString(R.string.error_fetching_nearby_places)) + setProgressBarVisibility(false) + presenter?.lockUnlockNearby(false) + }) + ) + if (recenterToUserLocation) { - recenterToUserLocation = false; + recenterToUserLocation = false } } @@ -497,103 +481,102 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param explorePlacesInfo holds several information as current location, marker list etc. */ - private void updateMapMarkers(final MapController.ExplorePlacesInfo explorePlacesInfo) { - presenter.updateMapMarkers(explorePlacesInfo); + private fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { + presenter?.updateMapMarkers(explorePlacesInfo) } - private void showErrorMessage(final String message) { - ViewUtil.showLongToast(getActivity(), message); + private fun showErrorMessage(message: String) { + ViewUtil.showLongToast(requireActivity(), message) } - private void showResponseMessage(final String message) { - ViewUtil.showLongSnackbar(getView(), message); + private fun showResponseMessage(message: String) { + ViewUtil.showLongSnackbar(requireView(), message) } - @Override - public void askForLocationPermission() { - Timber.d("Asking for location permission"); - activityResultLauncher.launch(permission.ACCESS_FINE_LOCATION); + override fun askForLocationPermission() { + Timber.d("Asking for location permission") + activityResultLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION) } - private void locationPermissionGranted() { - isPermissionDenied = false; - applicationKvStore.putBoolean("doNotAskForLocationPermission", false); - lastKnownLocation = locationManager.getLastLocation(); - fr.free.nrw.commons.location.LatLng target = lastKnownLocation; + private fun locationPermissionGranted() { + isPermissionDenied = false + applicationKvStore.putBoolean("doNotAskForLocationPermission", false) + lastKnownLocation = locationManager.getLastLocation() + val target = lastKnownLocation + if (lastKnownLocation != null) { - GeoPoint targetP = new GeoPoint(target.getLatitude(), target.getLongitude()); - mapCenter = targetP; - binding.mapView.getController().setCenter(targetP); - recenterMarkerToPosition(targetP); - moveCameraToPosition(targetP); - } else if (locationManager.isGPSProviderEnabled() - || locationManager.isNetworkProviderEnabled()) { - locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); - locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); - setProgressBarVisibility(true); + val targetP = GeoPoint(target!!.latitude, target.longitude) + mapCenter = targetP + binding.mapView.controller.setCenter(targetP) + recenterMarkerToPosition(targetP) + moveCameraToPosition(targetP) + } else if ( + locationManager.isGPSProviderEnabled() || locationManager.isNetworkProviderEnabled() + ) { + locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) + locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER) + setProgressBarVisibility(true) } else { - locationPermissionsHelper.showLocationOffDialog(getActivity(), - R.string.ask_to_turn_location_on_text); + locationPermissionsHelper.showLocationOffDialog( + requireActivity(), + R.string.ask_to_turn_location_on_text + ) } - presenter.onMapReady(exploreMapController); - registerUnregisterLocationListener(false); + presenter?.onMapReady(exploreMapController) + registerUnregisterLocationListener(false) } - public void registerUnregisterLocationListener(final boolean removeLocationListener) { - MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this); + fun registerUnregisterLocationListener(removeLocationListener: Boolean) { + MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this) } - @Override - public void recenterMap(LatLng currentLatLng) { - // if user has denied permission twice, then show dialog + override fun recenterMap(currentLatLng: LatLng?) { if (isPermissionDenied) { - if (locationPermissionsHelper.checkLocationPermission(getActivity())) { - // this will run when user has given permission by opening app's settings - isPermissionDenied = false; - recenterMap(currentLatLng); + if (locationPermissionsHelper.checkLocationPermission(requireActivity())) { + isPermissionDenied = false + recenterMap(currentLatLng) } else { - askForLocationPermission(); + askForLocationPermission() } } else { - if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { - askForLocationPermission(); + if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) { + askForLocationPermission() } else { - locationPermissionGranted(); + locationPermissionGranted() } } + if (currentLatLng == null) { - recenterToUserLocation = true; - return; + recenterToUserLocation = true + return } - recenterMarkerToPosition( - new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); - binding.mapView.getController() - .animateTo(new GeoPoint(currentLatLng.getLatitude(), currentLatLng.getLongitude())); - if (lastMapFocus != null) { - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(binding.mapView.getMapCenter().getLatitude()); - dest_location.setLongitude(binding.mapView.getMapCenter().getLongitude()); - mylocation.setLatitude(lastMapFocus.getLatitude()); - mylocation.setLongitude(lastMapFocus.getLongitude()); - Float distance = mylocation.distanceTo(dest_location);//in meters - if (lastMapFocus != null) { - if (isNetworkConnectionEstablished()) { - if (distance > 2000.0) { - setSearchThisAreaButtonVisibility(true); - } else { - setSearchThisAreaButtonVisibility(false); - } - } + + recenterMarkerToPosition(GeoPoint(currentLatLng.latitude, currentLatLng.longitude)) + binding.mapView.controller.animateTo( + GeoPoint(currentLatLng.latitude, currentLatLng.longitude) + ) + + lastMapFocus?.let { + val myLocation = Location("").apply { + latitude = it.latitude + longitude = it.longitude + } + val destLocation = Location("").apply { + latitude = binding.mapView.mapCenter.latitude + longitude = binding.mapView.mapCenter.longitude + } + val distance = myLocation.distanceTo(destLocation) + + if (isNetworkConnectionEstablished()) { + setSearchThisAreaButtonVisibility(distance > 2000.0) } else { - setSearchThisAreaButtonVisibility(false); + setSearchThisAreaButtonVisibility(false) } - } + } ?: setSearchThisAreaButtonVisibility(false) } - @Override - public void hideBottomDetailsSheet() { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + override fun hideBottomDetailsSheet() { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN } /** @@ -602,97 +585,81 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param place Place of clicked nearby marker */ - private void passInfoToSheet(final Place place) { - binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener( - view -> Utils.handleGeoCoordinates(getActivity(), - place.getLocation(), binding.mapView.getZoomLevelDouble())); - - binding.bottomSheetDetailsBinding.commonsButton.setVisibility( - place.hasCommonsLink() ? View.VISIBLE : View.GONE); - binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener( - view -> Utils.handleWebUrl(getContext(), place.siteLinks.getCommonsLink())); - - int index = 0; - for (Media media : mediaList) { - if (media.getFilename().equals(place.name)) { - int finalIndex = index; - binding.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener(view -> { - ((ExploreMapRootFragment) getParentFragment()).onMediaClicked(finalIndex); - }); + private fun passInfoToSheet(place: Place) { + binding.bottomSheetDetailsBinding.directionsButton.setOnClickListener { + Utils.handleGeoCoordinates(activity, place.location, binding.mapView.zoomLevelDouble) + } + + binding.bottomSheetDetailsBinding.commonsButton.visibility = + if (place.hasCommonsLink()) View.VISIBLE else View.GONE + + binding.bottomSheetDetailsBinding.commonsButton.setOnClickListener { + Utils.handleWebUrl(context, place.siteLinks.commonsLink) + } + + mediaList?.indexOfFirst { it.filename == place.name }.takeIf { it!! >= 0 }?.let { index -> + binding.bottomSheetDetailsBinding.mediaDetailsButton.setOnClickListener { + (parentFragment as? ExploreMapRootFragment)?.onMediaClicked(index) } - index++; } - binding.bottomSheetDetailsBinding.title.setText( - place.name.substring(5, place.name.lastIndexOf("."))); - binding.bottomSheetDetailsBinding.category.setText(place.distance); - // Remove label since it is double information - String descriptionText = place.getLongDescription() - .replace(place.getName() + " (", ""); - descriptionText = (descriptionText.equals(place.getLongDescription()) ? descriptionText - : descriptionText.replaceFirst(".$", "")); - // Set the short description after we remove place name from long description - binding.bottomSheetDetailsBinding.description.setText(descriptionText); + + binding.bottomSheetDetailsBinding.title.text = place.name.substring( + 5, + place.name.lastIndexOf(".") + ) + binding.bottomSheetDetailsBinding.category.text = place.distance + + var descriptionText = place.longDescription.replace("${place.name} (", "") + descriptionText = if (descriptionText == place.longDescription) descriptionText + else descriptionText.dropLast(1) + + binding.bottomSheetDetailsBinding.description.text = descriptionText } - @Override - public void addSearchThisAreaButtonAction() { - binding.searchThisAreaButton.setOnClickListener(presenter.onSearchThisAreaClicked()); + override fun addSearchThisAreaButtonAction() { + binding.searchThisAreaButton.setOnClickListener { presenter?.onSearchThisAreaClicked() } } - @Override - public void setSearchThisAreaButtonVisibility(boolean isVisible) { - binding.searchThisAreaButton.setVisibility(isVisible ? View.VISIBLE : View.GONE); + override fun setSearchThisAreaButtonVisibility(isVisible: Boolean) { + binding.searchThisAreaButton.visibility = if (isVisible) View.VISIBLE else View.GONE } - @Override - public void setProgressBarVisibility(boolean isVisible) { - binding.mapProgressBar.setVisibility(isVisible ? View.VISIBLE : View.GONE); + override fun setProgressBarVisibility(isVisible: Boolean) { + binding.mapProgressBar.visibility = if (isVisible) View.VISIBLE else View.GONE } - @Override - public boolean isDetailsBottomSheetVisible() { - if (binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE) { - return true; - } else { - return false; - } + override fun isDetailsBottomSheetVisible(): Boolean { + return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE } - @Override - public boolean isSearchThisAreaButtonVisible() { - return binding.bottomSheetDetailsBinding.getRoot().getVisibility() == View.VISIBLE; + override fun isSearchThisAreaButtonVisible(): Boolean { + return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE } - @Override - public LatLng getLastLocation() { + override fun getLastLocation(): LatLng { if (lastKnownLocation == null) { - lastKnownLocation = locationManager.getLastLocation(); + lastKnownLocation = locationManager.getLastLocation() } - return lastKnownLocation; + return lastKnownLocation!! } - @Override - public void disableFABRecenter() { - binding.fabRecenter.setEnabled(false); + override fun disableFABRecenter() { + binding.fabRecenter.isEnabled = false } - @Override - public void enableFABRecenter() { - binding.fabRecenter.setEnabled(true); + override fun enableFABRecenter() { + binding.fabRecenter.isEnabled = true } /** - * Adds a markers to the map based on the list of NearbyBaseMarker. + * Adds markers to the map based on the list of NearbyBaseMarker. * * @param nearbyBaseMarkers The NearbyBaseMarker object representing the markers to be added. */ - @Override - public void addMarkersToMap(List nearbyBaseMarkers) { - clearAllMarkers(); - for (int i = 0; i < nearbyBaseMarkers.size(); i++) { - addMarkerToMap(nearbyBaseMarkers.get(i)); - } - binding.mapView.invalidate(); + override fun addMarkersToMap(nearbyBaseMarkers: List) { + clearAllMarkers() + nearbyBaseMarkers.forEach { addMarkerToMap(it) } + binding.mapView.invalidate() } /** @@ -700,312 +667,274 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment * * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added. */ - private void addMarkerToMap(BaseMarker nearbyBaseMarker) { + private fun addMarkerToMap(nearbyBaseMarker: BaseMarker) { if (isAttachedToActivity()) { - ArrayList items = new ArrayList<>(); - Bitmap icon = nearbyBaseMarker.getIcon(); - Drawable d = new BitmapDrawable(getResources(), icon); - GeoPoint point = new GeoPoint( - nearbyBaseMarker.getPlace().location.getLatitude(), - nearbyBaseMarker.getPlace().location.getLongitude()); - OverlayItem item = new OverlayItem(nearbyBaseMarker.getPlace().name, null, - point); - item.setMarker(d); - items.add(item); - ItemizedOverlayWithFocus overlay = new ItemizedOverlayWithFocus(items, - new OnItemGestureListener() { - @Override - public boolean onItemSingleTapUp(int index, OverlayItem item) { - final Place place = nearbyBaseMarker.getPlace(); - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - bottomSheetDetailsBehavior.setState( - BottomSheetBehavior.STATE_COLLAPSED); - } - clickedMarker = nearbyBaseMarker; - passInfoToSheet(place); - return true; + val items = ArrayList() + val icon = nearbyBaseMarker.icon + val drawable = BitmapDrawable(resources, icon) + val point = GeoPoint( + nearbyBaseMarker.place.location.latitude, + nearbyBaseMarker.place.location.longitude + ) + val item = OverlayItem(nearbyBaseMarker.place.name, null, point).apply { + setMarker(drawable) + } + items.add(item) + + val overlay = ItemizedOverlayWithFocus(items, object : OnItemGestureListener { + override fun onItemSingleTapUp(index: Int, item: OverlayItem): Boolean { + val place = nearbyBaseMarker.place + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_COLLAPSED } + clickedMarker = nearbyBaseMarker + passInfoToSheet(place) + return true + } - @Override - public boolean onItemLongPress(int index, OverlayItem item) { - return false; - } - }, getContext()); + override fun onItemLongPress(index: Int, item: OverlayItem): Boolean { + return false + } + }, context) - overlay.setFocusItemsOnTap(true); - binding.mapView.getOverlays().add(overlay); // Add the overlay to the map + overlay.setFocusItemsOnTap(true) + binding.mapView.overlays.add(overlay) // Add the overlay to the map } } - /** - * Removes a marker from the map based on the specified NearbyBaseMarker. - * - * @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be removed. - */ - private void removeMarker(BaseMarker nearbyBaseMarker) { - Place place = nearbyBaseMarker.getPlace(); - List overlays = binding.mapView.getOverlays(); - ItemizedOverlayWithFocus item; - - for (int i = 0; i < overlays.size(); i++) { - if (overlays.get(i) instanceof ItemizedOverlayWithFocus) { - item = (ItemizedOverlayWithFocus) overlays.get(i); - OverlayItem overlayItem = item.getItem(0); - - if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() - && place.location.getLongitude() == overlayItem.getPoint().getLongitude()) { - binding.mapView.getOverlays().remove(i); - binding.mapView.invalidate(); - break; + private fun removeMarker(nearbyBaseMarker: BaseMarker) { + val place = nearbyBaseMarker.place + val overlays = binding.mapView.overlays + var item: ItemizedOverlayWithFocus + + for (i in overlays.indices) { + if (overlays[i] is ItemizedOverlayWithFocus<*>) { + item = overlays[i] as ItemizedOverlayWithFocus + val overlayItem = item.getItem(0) + + if (place.location.latitude == overlayItem.point.latitude && + place.location.longitude == overlayItem.point.longitude + ) { + binding.mapView.overlays.removeAt(i) + binding.mapView.invalidate() + break } } } } - /** - * Clears all markers from the map and resets certain map overlays and gestures. After clearing - * markers, it re-adds a scale bar overlay and rotation gesture overlay to the map. - */ - @Override - public void clearAllMarkers() { + override fun clearAllMarkers() { if (isAttachedToActivity()) { - binding.mapView.getOverlayManager().clear(); - GeoPoint geoPoint = mapCenter; - if (geoPoint != null) { - List overlays = binding.mapView.getOverlays(); - ScaleDiskOverlay diskOverlay = - new ScaleDiskOverlay(this.getContext(), - geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); - Paint circlePaint = new Paint(); - circlePaint.setColor(Color.rgb(128, 128, 128)); - circlePaint.setStyle(Paint.Style.STROKE); - circlePaint.setStrokeWidth(2f); - diskOverlay.setCirclePaint2(circlePaint); - Paint diskPaint = new Paint(); - diskPaint.setColor(Color.argb(40, 128, 128, 128)); - diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); - diskOverlay.setCirclePaint1(diskPaint); - diskOverlay.setDisplaySizeMin(900); - diskOverlay.setDisplaySizeMax(1700); - binding.mapView.getOverlays().add(diskOverlay); - org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( - binding.mapView); - startMarker.setPosition(geoPoint); - startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, - org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); - startMarker.setIcon( - ContextCompat.getDrawable(this.getContext(), - R.drawable.current_location_marker)); - startMarker.setTitle("Your Location"); - startMarker.setTextLabelFontSize(24); - binding.mapView.getOverlays().add(startMarker); + binding.mapView.overlayManager.clear() + mapCenter?.let { geoPoint -> + val overlays = binding.mapView.overlays + val diskOverlay = ScaleDiskOverlay( + context, geoPoint, 2000, GeoConstants.UnitOfMeasure.foot + ).apply { + val circlePaint = Paint().apply { + color = Color.rgb(128, 128, 128) + style = Paint.Style.STROKE + strokeWidth = 2f + } + setCirclePaint2(circlePaint) + + val diskPaint = Paint().apply { + color = Color.argb(40, 128, 128, 128) + style = Paint.Style.FILL_AND_STROKE + } + setCirclePaint1(diskPaint) + + setDisplaySizeMin(900) + setDisplaySizeMax(1700) + } + binding.mapView.overlays.add(diskOverlay) + + val startMarker = Marker(binding.mapView).apply { + position = geoPoint + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = ContextCompat.getDrawable( + requireContext(), + R.drawable.current_location_marker + ) + title = "Your Location" + textLabelFontSize = 24 + } + binding.mapView.overlays.add(startMarker) } - ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.mapView); - scaleBarOverlay.setScaleBarOffset(15, 25); - Paint barPaint = new Paint(); - barPaint.setARGB(200, 255, 250, 250); - scaleBarOverlay.setBackgroundPaint(barPaint); - scaleBarOverlay.enableScaleBar(); - binding.mapView.getOverlays().add(scaleBarOverlay); - binding.mapView.getOverlays().add(new MapEventsOverlay(new MapEventsReceiver() { - @Override - public boolean singleTapConfirmedHelper(GeoPoint p) { - if (clickedMarker != null) { - removeMarker(clickedMarker); - addMarkerToMap(clickedMarker); - binding.mapView.invalidate(); - } else { - Timber.e("CLICKED MARKER IS NULL"); + + val scaleBarOverlay = ScaleBarOverlay(binding.mapView).apply { + setScaleBarOffset(15, 25) + setBackgroundPaint( + Paint().apply { + setARGB(200, 255, 250, 250) } - if (bottomSheetDetailsBehavior.getState() - == BottomSheetBehavior.STATE_EXPANDED) { - // Back should first hide the bottom sheet if it is expanded - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + ) + enableScaleBar() + } + binding.mapView.overlays.add(scaleBarOverlay) + + binding.mapView.overlays.add(object : MapEventsOverlay(object : MapEventsReceiver { + override fun singleTapConfirmedHelper(p: GeoPoint?): Boolean { + clickedMarker?.let { + removeMarker(it) + addMarkerToMap(it) + binding.mapView.invalidate() + } ?: Timber.e("CLICKED MARKER IS NULL") + + if (bottomSheetDetailsBehavior.state == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN } else if (isDetailsBottomSheetVisible()) { - hideBottomDetailsSheet(); + hideBottomDetailsSheet() } - return true; + return true } - @Override - public boolean longPressHelper(GeoPoint p) { - return false; + override fun longPressHelper(p: GeoPoint?): Boolean { + return false } - })); - binding.mapView.setMultiTouchControls(true); + }) {}) + + binding.mapView.setMultiTouchControls(true) } } - /** - * Recenters the map view to the specified GeoPoint and updates the marker to indicate the new - * position. - * - * @param geoPoint The GeoPoint representing the new center position for the map. - */ - private void recenterMarkerToPosition(GeoPoint geoPoint) { - if (geoPoint != null) { - binding.mapView.getController().setCenter(geoPoint); - List overlays = binding.mapView.getOverlays(); - for (int i = 0; i < overlays.size(); i++) { - if (overlays.get(i) instanceof org.osmdroid.views.overlay.Marker) { - binding.mapView.getOverlays().remove(i); - } else if (overlays.get(i) instanceof ScaleDiskOverlay) { - binding.mapView.getOverlays().remove(i); + private fun recenterMarkerToPosition(geoPoint: GeoPoint?) { + geoPoint?.let { + binding.mapView.controller.setCenter(it) + val overlays = binding.mapView.overlays + overlays.removeAll { overlay -> overlay is Marker || overlay is ScaleDiskOverlay } + + val diskOverlay = ScaleDiskOverlay( + context, it, 2000, GeoConstants.UnitOfMeasure.foot + ).apply { + val circlePaint = Paint().apply { + color = Color.rgb(128, 128, 128) + style = Paint.Style.STROKE + strokeWidth = 2f + } + setCirclePaint2(circlePaint) + + val diskPaint = Paint().apply { + color = Color.argb(40, 128, 128, 128) + style = Paint.Style.FILL_AND_STROKE } + setCirclePaint1(diskPaint) + + setDisplaySizeMin(900) + setDisplaySizeMax(1700) } - ScaleDiskOverlay diskOverlay = - new ScaleDiskOverlay(this.getContext(), - geoPoint, 2000, GeoConstants.UnitOfMeasure.foot); - Paint circlePaint = new Paint(); - circlePaint.setColor(Color.rgb(128, 128, 128)); - circlePaint.setStyle(Paint.Style.STROKE); - circlePaint.setStrokeWidth(2f); - diskOverlay.setCirclePaint2(circlePaint); - Paint diskPaint = new Paint(); - diskPaint.setColor(Color.argb(40, 128, 128, 128)); - diskPaint.setStyle(Paint.Style.FILL_AND_STROKE); - diskOverlay.setCirclePaint1(diskPaint); - diskOverlay.setDisplaySizeMin(900); - diskOverlay.setDisplaySizeMax(1700); - binding.mapView.getOverlays().add(diskOverlay); - org.osmdroid.views.overlay.Marker startMarker = new org.osmdroid.views.overlay.Marker( - binding.mapView); - startMarker.setPosition(geoPoint); - startMarker.setAnchor(org.osmdroid.views.overlay.Marker.ANCHOR_CENTER, - org.osmdroid.views.overlay.Marker.ANCHOR_BOTTOM); - startMarker.setIcon( - ContextCompat.getDrawable(this.getContext(), R.drawable.current_location_marker)); - startMarker.setTitle("Your Location"); - startMarker.setTextLabelFontSize(24); - binding.mapView.getOverlays().add(startMarker); + binding.mapView.overlays.add(diskOverlay) + + val startMarker = Marker(binding.mapView).apply { + position = it + setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM) + icon = ContextCompat.getDrawable( + requireContext(), + R.drawable.current_location_marker + ) + title = "Your Location" + textLabelFontSize = 24 + } + binding.mapView.overlays.add(startMarker) } } - /** - * Moves the camera of the map view to the specified GeoPoint using an animation. - * - * @param geoPoint The GeoPoint representing the new camera position for the map. - */ - private void moveCameraToPosition(GeoPoint geoPoint) { - binding.mapView.getController().animateTo(geoPoint); + private fun moveCameraToPosition(geoPoint: GeoPoint) { + binding.mapView.controller.animateTo(geoPoint) } - /** - * Moves the camera of the map view to the specified GeoPoint at specified zoom level and speed - * using an animation. - * - * @param geoPoint The GeoPoint representing the new camera position for the map. - * @param zoom Zoom level of the map camera - * @param speed Speed of animation - */ - private void moveCameraToPosition(GeoPoint geoPoint, double zoom, long speed) { - binding.mapView.getController().animateTo(geoPoint, zoom, speed); + private fun moveCameraToPosition(geoPoint: GeoPoint, zoom: Double, speed: Long) { + binding.mapView.controller.animateTo(geoPoint, zoom, speed) } - @Override - public fr.free.nrw.commons.location.LatLng getLastMapFocus() { - return lastMapFocus == null ? getMapCenter() : new fr.free.nrw.commons.location.LatLng( - lastMapFocus.getLatitude(), lastMapFocus.getLongitude(), 100); + override fun getLastMapFocus(): LatLng { + return lastMapFocus?.let { + LatLng(it.latitude, it.longitude, 100f) + } ?: getMapCenter() } - @Override - public fr.free.nrw.commons.location.LatLng getMapCenter() { - fr.free.nrw.commons.location.LatLng latLnge = null; + override fun getMapCenter(): LatLng { + var latLng: LatLng? = null + if (mapCenter != null) { - latLnge = new fr.free.nrw.commons.location.LatLng( - mapCenter.getLatitude(), mapCenter.getLongitude(), 100); + latLng = LatLng(mapCenter!!.latitude, mapCenter!!.longitude, 100f) } else { - if (applicationKvStore.getString("LastLocation") != null) { - final String[] locationLatLng - = applicationKvStore.getString("LastLocation").split(","); - lastKnownLocation - = new fr.free.nrw.commons.location.LatLng(Double.parseDouble(locationLatLng[0]), - Double.parseDouble(locationLatLng[1]), 1f); - latLnge = lastKnownLocation; - } else { - latLnge = new fr.free.nrw.commons.location.LatLng(51.506255446947776, - -0.07483536015053005, 1f); + applicationKvStore.getString("LastLocation")?.let { lastLocation -> + val locationLatLng = lastLocation.split(",").map { it.toDouble() } + lastKnownLocation = LatLng(locationLatLng[0], locationLatLng[1], 1f) + latLng = lastKnownLocation + } ?: run { + latLng = LatLng(51.506255446947776, -0.07483536015053005, 1f) } } + if (!isCameFromNearbyMap()) { - moveCameraToPosition(new GeoPoint(latLnge.getLatitude(), latLnge.getLongitude())); + moveCameraToPosition(GeoPoint(latLng?.latitude!!, latLng?.longitude!!)) } - return latLnge; + return latLng!! } - @Override - public fr.free.nrw.commons.location.LatLng getMapFocus() { - fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng( - binding.mapView.getMapCenter().getLatitude(), - binding.mapView.getMapCenter().getLongitude(), 100); - return mapFocusedLatLng; + override fun getMapFocus(): LatLng { + return LatLng( + binding.mapView.mapCenter.latitude, + binding.mapView.mapCenter.longitude, + 100f + ) } - @Override - public void setFABRecenterAction(OnClickListener onClickListener) { - binding.fabRecenter.setOnClickListener(onClickListener); + override fun setFABRecenterAction(onClickListener: View.OnClickListener) { + binding.fabRecenter.setOnClickListener(onClickListener) } - @Override - public boolean backButtonClicked() { - if (!(bottomSheetDetailsBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN)) { - bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - return true; + override fun backButtonClicked(): Boolean { + return if (bottomSheetDetailsBehavior.state != BottomSheetBehavior.STATE_HIDDEN) { + bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN + true } else { - return false; + false } } - /** - * Adds network broadcast receiver to recognize connection established - */ - private void initNetworkBroadCastReceiver() { - broadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(final Context context, final Intent intent) { - if (getActivity() != null) { - if (NetworkUtils.isInternetConnectionEstablished(getActivity())) { + private fun initNetworkBroadCastReceiver() { + broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + activity?.let { + if (NetworkUtils.isInternetConnectionEstablished(it)) { if (isNetworkErrorOccurred) { - presenter.updateMap(LOCATION_SIGNIFICANTLY_CHANGED); - isNetworkErrorOccurred = false; - } - - if (snackbar != null) { - snackbar.dismiss(); - snackbar = null; + presenter?.updateMap( + LocationServiceManager + .LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED + ) + isNetworkErrorOccurred = false } + snackbar?.dismiss() + snackbar = null } else { if (snackbar == null) { - snackbar = Snackbar.make(getView(), R.string.no_internet, - Snackbar.LENGTH_INDEFINITE); - setSearchThisAreaButtonVisibility(false); - setProgressBarVisibility(false); + snackbar = Snackbar.make( + requireView(), + R.string.no_internet, + Snackbar.LENGTH_INDEFINITE + ) + setSearchThisAreaButtonVisibility(false) + setProgressBarVisibility(false) } - - isNetworkErrorOccurred = true; - snackbar.show(); + isNetworkErrorOccurred = true + snackbar?.show() } } } - }; + } } - /** - * helper function to confirm that this fragment has been attached. - **/ - public boolean isAttachedToActivity() { - boolean attached = isVisible() && getActivity() != null; - return attached; + fun isAttachedToActivity(): Boolean { + return isVisible && activity != null } - @Override - public void onLocationPermissionDenied(String toastMessage) { - } + override fun onLocationPermissionDenied(toastMessage: String) {} - @Override - public void onLocationPermissionGranted() { - } + override fun onLocationPermissionGranted() {} } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt index 94b9cf5ad3..bc92ff6eea 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.kt @@ -1,152 +1,139 @@ -package fr.free.nrw.commons.explore.map; - -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; -import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.SEARCH_CUSTOM_AREA; - - -import android.location.Location; -import android.view.View; -import fr.free.nrw.commons.BaseMarker; -import fr.free.nrw.commons.MapController; -import fr.free.nrw.commons.MapController.ExplorePlacesInfo; -import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; -import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback; -import fr.free.nrw.commons.kvstore.JsonKvStore; -import fr.free.nrw.commons.location.LatLng; -import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; -import io.reactivex.Observable; -import java.lang.reflect.Proxy; -import java.util.List; -import timber.log.Timber; - -public class ExploreMapPresenter - implements ExploreMapContract.UserActions, - NearbyBaseMarkerThumbCallback { - - BookmarkLocationsDao bookmarkLocationDao; - private boolean isNearbyLocked; - private LatLng currentLatLng; - private ExploreMapController exploreMapController; - - private static final ExploreMapContract.View DUMMY = (ExploreMapContract.View) Proxy - .newProxyInstance( - ExploreMapContract.View.class.getClassLoader(), - new Class[]{ExploreMapContract.View.class}, (proxy, method, args) -> { - if (method.getName().equals("onMyEvent")) { - return null; - } else if (String.class == method.getReturnType()) { - return ""; - } else if (Integer.class == method.getReturnType()) { - return Integer.valueOf(0); - } else if (int.class == method.getReturnType()) { - return 0; - } else if (Boolean.class == method.getReturnType()) { - return Boolean.FALSE; - } else if (boolean.class == method.getReturnType()) { - return false; - } else { - return null; - } +package fr.free.nrw.commons.explore.map + +import android.location.Location +import android.view.View +import fr.free.nrw.commons.BaseMarker +import fr.free.nrw.commons.MapController +import fr.free.nrw.commons.MapController.ExplorePlacesInfo +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao +import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThumbCallback +import fr.free.nrw.commons.kvstore.JsonKvStore +import fr.free.nrw.commons.location.LatLng +import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType +import io.reactivex.Observable +import java.lang.reflect.Proxy +import timber.log.Timber + +class ExploreMapPresenter( + private val bookmarkLocationDao: BookmarkLocationsDao +) : ExploreMapContract.UserActions, ExploreMapController.NearbyBaseMarkerThumbCallback { + + private var isNearbyLocked: Boolean = false + private var currentLatLng: LatLng? = null + private var exploreMapController: ExploreMapController? = null + + companion object { + private val DUMMY: ExploreMapContract.View = Proxy.newProxyInstance( + ExploreMapContract.View::class.java.classLoader, + arrayOf(ExploreMapContract.View::class.java) + ) { _, method, _ -> + when (method.returnType) { + String::class.java -> "" + Integer::class.java -> 0 + Int::class.java -> 0 + Boolean::class.java -> false + Boolean::class.javaPrimitiveType -> false + else -> null } - ); - private ExploreMapContract.View exploreMapFragmentView = DUMMY; - - public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao) { - this.bookmarkLocationDao = bookmarkLocationDao; + } as ExploreMapContract.View } - @Override - public void updateMap(LocationChangeType locationChangeType) { - Timber.d("Presenter updates map and list" + locationChangeType.toString()); + private var exploreMapFragmentView: ExploreMapContract.View = DUMMY + + override fun updateMap(locationChangeType: LocationChangeType) { + Timber.d("Presenter updates map and list $locationChangeType") if (isNearbyLocked) { - Timber.d("Nearby is locked, so updateMapAndList returns"); - return; + Timber.d("Nearby is locked, so updateMapAndList returns") + return } if (!exploreMapFragmentView.isNetworkConnectionEstablished()) { - Timber.d("Network connection is not established"); - return; + Timber.d("Network connection is not established") + return } /** * Significant changed - Markers and current location will be updated together * Slightly changed - Only current position marker will be updated */ - if (locationChangeType.equals(LOCATION_SIGNIFICANTLY_CHANGED)) { - Timber.d("LOCATION_SIGNIFICANTLY_CHANGED"); - lockUnlockNearby(true); - exploreMapFragmentView.setProgressBarVisibility(true); - exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapCenter()); - } else if (locationChangeType.equals(SEARCH_CUSTOM_AREA)) { - Timber.d("SEARCH_CUSTOM_AREA"); - lockUnlockNearby(true); - exploreMapFragmentView.setProgressBarVisibility(true); - exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus()); - } else { // Means location changed slightly, ie user is walking or driving. - Timber.d("Means location changed slightly"); + when (locationChangeType) { + LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED -> { + Timber.d("LOCATION_SIGNIFICANTLY_CHANGED") + lockUnlockNearby(true) + exploreMapFragmentView.setProgressBarVisibility(true) + exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapCenter()) + } + + LocationChangeType.SEARCH_CUSTOM_AREA -> { + Timber.d("SEARCH_CUSTOM_AREA") + lockUnlockNearby(true) + exploreMapFragmentView.setProgressBarVisibility(true) + exploreMapFragmentView.populatePlaces(exploreMapFragmentView.getMapFocus()) + } + + else -> { + Timber.d("Means location changed slightly") + } } } /** - * Nearby updates takes time, since they are network operations. During update time, we don't - * want to get any other calls from user. So locking nearby. + * Nearby updates take time since they are network operations. During update time, we don't + * want to get any other calls from the user. So locking nearby. * * @param isNearbyLocked true means lock, false means unlock */ - @Override - public void lockUnlockNearby(boolean isNearbyLocked) { - this.isNearbyLocked = isNearbyLocked; + override fun lockUnlockNearby(isNearbyLocked: Boolean) { + this.isNearbyLocked = isNearbyLocked if (isNearbyLocked) { - exploreMapFragmentView.disableFABRecenter(); + exploreMapFragmentView.disableFABRecenter() } else { - exploreMapFragmentView.enableFABRecenter(); + exploreMapFragmentView.enableFABRecenter() } } - @Override - public void attachView(ExploreMapContract.View view) { - exploreMapFragmentView = view; + override fun attachView(view: ExploreMapContract.View) { + exploreMapFragmentView = view } - @Override - public void detachView() { - exploreMapFragmentView = DUMMY; + override fun detachView() { + exploreMapFragmentView = DUMMY } /** * Sets click listener of FAB */ - @Override - public void setActionListeners(JsonKvStore applicationKvStore) { - exploreMapFragmentView.setFABRecenterAction(v -> { - exploreMapFragmentView.recenterMap(currentLatLng); - }); - + override fun setActionListeners(applicationKvStore: JsonKvStore) { + exploreMapFragmentView.setFABRecenterAction { + currentLatLng?.let { it1 -> exploreMapFragmentView.recenterMap(it1) } + } } - @Override - public boolean backButtonClicked() { - return exploreMapFragmentView.backButtonClicked(); + override fun backButtonClicked(): Boolean { + return exploreMapFragmentView.backButtonClicked() } - public void onMapReady(ExploreMapController exploreMapController) { - this.exploreMapController = exploreMapController; - if (null != exploreMapFragmentView) { - exploreMapFragmentView.addSearchThisAreaButtonAction(); - initializeMapOperations(); - } + fun onMapReady(exploreMapController: ExploreMapController) { + this.exploreMapController = exploreMapController + exploreMapFragmentView.addSearchThisAreaButtonAction() + initializeMapOperations() } - public void initializeMapOperations() { - lockUnlockNearby(false); - updateMap(LOCATION_SIGNIFICANTLY_CHANGED); + fun initializeMapOperations() { + lockUnlockNearby(false) + updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } - public Observable loadAttractionsFromLocation(LatLng currentLatLng, - LatLng searchLatLng, boolean checkingAroundCurrent) { - return Observable - .fromCallable(() -> exploreMapController - .loadAttractionsFromLocation(currentLatLng, searchLatLng, checkingAroundCurrent)); + fun loadAttractionsFromLocation( + currentLatLng: LatLng, + searchLatLng: LatLng?, + checkingAroundCurrent: Boolean + ): Observable { + return Observable.fromCallable { + exploreMapController?.loadAttractionsFromLocation( + currentLatLng, searchLatLng, checkingAroundCurrent + ) + } } /** @@ -155,47 +142,45 @@ public class ExploreMapPresenter * * @param explorePlacesInfo This variable has placeToCenter list information and distances. */ - public void updateMapMarkers( - MapController.ExplorePlacesInfo explorePlacesInfo) { + fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { if (explorePlacesInfo.mediaList != null) { - prepareNearbyBaseMarkers(explorePlacesInfo); + prepareNearbyBaseMarkers(explorePlacesInfo) } else { - lockUnlockNearby(false); // So that new location updates wont come - exploreMapFragmentView.setProgressBarVisibility(false); + lockUnlockNearby(false) // So that new location updates won't come + exploreMapFragmentView.setProgressBarVisibility(false) } } - void prepareNearbyBaseMarkers(MapController.ExplorePlacesInfo explorePlacesInfo) { - exploreMapController - .loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng, - // Curlatlang will be used to calculate distances - explorePlacesInfo.explorePlaceList, - exploreMapFragmentView.getContext(), - this, - explorePlacesInfo); + private fun prepareNearbyBaseMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) { + ExploreMapController.loadAttractionsFromLocationToBaseMarkerOptions( + explorePlacesInfo.currentLatLng, + explorePlacesInfo.explorePlaceList, + exploreMapFragmentView.getContext(), + this, + explorePlacesInfo + ) } - @Override - public void onNearbyBaseMarkerThumbsReady(List baseMarkers, - ExplorePlacesInfo explorePlacesInfo) { - if (null != exploreMapFragmentView) { - exploreMapFragmentView.addMarkersToMap(baseMarkers); - lockUnlockNearby(false); // So that new location updates wont come - exploreMapFragmentView.setProgressBarVisibility(false); - } + override fun onNearbyBaseMarkerThumbsReady( + baseMarkers: List, + explorePlacesInfo: ExplorePlacesInfo + ) { + exploreMapFragmentView.addMarkersToMap(baseMarkers) + lockUnlockNearby(false) // So that new location updates won't come + exploreMapFragmentView.setProgressBarVisibility(false) } - public View.OnClickListener onSearchThisAreaClicked() { - return v -> { + fun onSearchThisAreaClicked(): View.OnClickListener { + return View.OnClickListener { // Lock map operations during search this area operation - exploreMapFragmentView.setSearchThisAreaButtonVisibility(false); + exploreMapFragmentView.setSearchThisAreaButtonVisibility(false) if (searchCloseToCurrentLocation()) { - updateMap(LOCATION_SIGNIFICANTLY_CHANGED); + updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } else { - updateMap(SEARCH_CUSTOM_AREA); + updateMap(LocationChangeType.SEARCH_CUSTOM_AREA) } - }; + } } /** @@ -204,24 +189,19 @@ public class ExploreMapPresenter * * @return Returns true if search this area button is used around our current location */ - public boolean searchCloseToCurrentLocation() { - if (null == exploreMapFragmentView.getLastMapFocus()) { - return true; - } + fun searchCloseToCurrentLocation(): Boolean { + val lastMapFocus = exploreMapFragmentView.getLastMapFocus() ?: return true - Location mylocation = new Location(""); - Location dest_location = new Location(""); - dest_location.setLatitude(exploreMapFragmentView.getMapFocus().getLatitude()); - dest_location.setLongitude(exploreMapFragmentView.getMapFocus().getLongitude()); - mylocation.setLatitude(exploreMapFragmentView.getLastMapFocus().getLatitude()); - mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude()); - Float distance = mylocation.distanceTo(dest_location); + val myLocation = Location("").apply { + latitude = lastMapFocus.latitude + longitude = lastMapFocus.longitude + } - if (distance > 2000.0 * 3 / 4) { - return false; - } else { - return true; + val destLocation = Location("").apply { + latitude = exploreMapFragmentView.getMapFocus().latitude + longitude = exploreMapFragmentView.getMapFocus().longitude } - } + return myLocation.distanceTo(destLocation) <= 2000.0 * 3 / 4 + } } diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt index e90cc12241..1dd3420f20 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationUpdateListener.kt @@ -2,11 +2,11 @@ package fr.free.nrw.commons.location interface LocationUpdateListener { // Will be used to update all nearby markers on the map - fun onLocationChangedSignificantly(latLng: LatLng) + fun onLocationChangedSignificantly(latLng: LatLng?) // Will be used to track users motion - fun onLocationChangedSlightly(latLng: LatLng) + fun onLocationChangedSlightly(latLng: LatLng?) // Will be used updating nearby card view notification - fun onLocationChangedMedium(latLng: LatLng) + fun onLocationChangedMedium(latLng: LatLng?) } \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt index ed84751b0e..2fd7bc49bd 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt +++ b/app/src/main/java/fr/free/nrw/commons/nearby/presenter/NearbyParentFragmentPresenter.kt @@ -485,17 +485,17 @@ class NearbyParentFragmentPresenter updateMapAndList(LocationChangeType.MAP_UPDATED) } - override fun onLocationChangedSignificantly(latLng: LatLng) { + override fun onLocationChangedSignificantly(latLng: LatLng?) { Timber.d("Location significantly changed") updateMapAndList(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) } - override fun onLocationChangedSlightly(latLng: LatLng) { + override fun onLocationChangedSlightly(latLng: LatLng?) { Timber.d("Location significantly changed") updateMapAndList(LocationChangeType.LOCATION_SLIGHTLY_CHANGED) } - override fun onLocationChangedMedium(latLng: LatLng) { + override fun onLocationChangedMedium(latLng: LatLng?) { Timber.d("Location changed medium") } From 2655a3d5eca16596077a684b61d7df234330d8dd Mon Sep 17 00:00:00 2001 From: Saifuddin Date: Sat, 22 Feb 2025 16:17:51 +0530 Subject: [PATCH 6/8] Rebase onto main --- .../fragments/NearbyParentFragment.java | 2468 +++++++++++++++++ 1 file changed, 2468 insertions(+) create mode 100644 app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java 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 new file mode 100644 index 0000000000..0bef6c894c --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -0,0 +1,2468 @@ +package fr.free.nrw.commons.nearby.fragments; + +import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.CUSTOM_QUERY; +import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED; +import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED; +import static fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType.MAP_UPDATED; +import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT; + +import android.Manifest.permission; +import android.annotation.SuppressLint; +import android.app.ProgressDialog; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.drawable.Drawable; +import android.location.Location; +import android.location.LocationManager; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.Toast; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; +import androidx.activity.result.contract.ActivityResultContracts.RequestPermission; +import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog.Builder; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.lifecycle.LifecycleCoroutineScope; +import androidx.lifecycle.LifecycleOwnerKt; +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.bottomsheet.BottomSheetBehavior.BottomSheetCallback; +import com.google.android.material.snackbar.Snackbar; +import com.jakewharton.rxbinding2.view.RxView; +import com.jakewharton.rxbinding3.appcompat.RxSearchView; +import fr.free.nrw.commons.CommonsApplication; +import fr.free.nrw.commons.CommonsApplication.BaseLogoutListener; +import fr.free.nrw.commons.MapController.NearbyPlacesInfo; +import fr.free.nrw.commons.R; +import fr.free.nrw.commons.Utils; +import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao; +import fr.free.nrw.commons.contributions.ContributionController; +import fr.free.nrw.commons.contributions.MainActivity; +import fr.free.nrw.commons.contributions.MainActivity.ActiveFragment; +import fr.free.nrw.commons.databinding.FragmentNearbyParentBinding; +import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; +import fr.free.nrw.commons.kvstore.JsonKvStore; +import fr.free.nrw.commons.location.LatLng; +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.LocationServiceManager.LocationChangeType; +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; +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.PlacesRepository; +import fr.free.nrw.commons.nearby.Sitelinks; +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; +import fr.free.nrw.commons.utils.ExecutorUtils; +import fr.free.nrw.commons.utils.LayoutUtils; +import fr.free.nrw.commons.utils.MapUtils; +import fr.free.nrw.commons.utils.NearbyFABUtils; +import fr.free.nrw.commons.utils.NetworkUtils; +import fr.free.nrw.commons.utils.SystemThemeUtils; +import fr.free.nrw.commons.utils.ViewUtil; +import fr.free.nrw.commons.wikidata.WikidataEditListener; +import io.reactivex.Completable; +import io.reactivex.Observable; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.inject.Named; +import kotlin.Unit; +import org.jetbrains.annotations.NotNull; +import org.osmdroid.api.IGeoPoint; +import org.osmdroid.events.MapEventsReceiver; +import org.osmdroid.events.MapListener; +import org.osmdroid.events.ScrollEvent; +import org.osmdroid.events.ZoomEvent; +import org.osmdroid.tileprovider.tilesource.TileSourceFactory; +import org.osmdroid.util.GeoPoint; +import org.osmdroid.util.constants.GeoConstants.UnitOfMeasure; +import org.osmdroid.views.CustomZoomButtonsController.Visibility; +import org.osmdroid.views.overlay.MapEventsOverlay; +import org.osmdroid.views.overlay.Marker; +import org.osmdroid.views.overlay.Overlay; +import org.osmdroid.views.overlay.ScaleBarOverlay; +import org.osmdroid.views.overlay.ScaleDiskOverlay; +import org.osmdroid.views.overlay.TilesOverlay; +import timber.log.Timber; + + +public class NearbyParentFragment extends CommonsDaggerSupportFragment + implements NearbyParentFragmentContract.View, + WikidataEditListener.WikidataP18EditListener, LocationUpdateListener, + LocationPermissionCallback, BottomSheetAdapter.ItemClickListener { + + FragmentNearbyParentBinding binding; + + public final MapEventsOverlay mapEventsOverlay = new MapEventsOverlay(new MapEventsReceiver() { + @Override + public boolean singleTapConfirmedHelper(GeoPoint p) { + if (clickedMarker != null) { + clickedMarker.closeInfoWindow(); + } else { + Timber.e("CLICKED MARKER IS NULL"); + } + if (isListBottomSheetExpanded()) { + // Back should first hide the bottom sheet if it is expanded + hideBottomSheet(); + } else if (isDetailsBottomSheetVisible()) { + hideBottomDetailsSheet(); + } + return true; + } + + @Override + public boolean longPressHelper(GeoPoint p) { + return false; + } + }); + + @Inject + LocationServiceManager locationManager; + @Inject + NearbyController nearbyController; + @Inject + @Named("default_preferences") + JsonKvStore applicationKvStore; + @Inject + BookmarkLocationsDao bookmarkLocationDao; + @Inject + PlacesRepository placesRepository; + @Inject + ContributionController controller; + @Inject + WikidataEditListener wikidataEditListener; + @Inject + SystemThemeUtils systemThemeUtils; + @Inject + CommonPlaceClickActions commonPlaceClickActions; + + private LocationPermissionsHelper locationPermissionsHelper; + private NearbyFilterSearchRecyclerViewAdapter nearbyFilterSearchRecyclerViewAdapter; + private BottomSheetBehavior bottomSheetListBehavior; + private BottomSheetBehavior bottomSheetDetailsBehavior; + private Animation rotate_backward; + private Animation fab_close; + private Animation fab_open; + private Animation rotate_forward; + private static final float ZOOM_LEVEL = 15f; + private final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"; + private BroadcastReceiver broadcastReceiver; + private boolean isNetworkErrorOccurred; + private Snackbar snackbar; + private View view; + private LifecycleCoroutineScope scope; + private NearbyParentFragmentPresenter presenter; + private boolean isDarkTheme; + private boolean isFABsExpanded; + private Place selectedPlace; + private Marker clickedMarker; + private ProgressDialog progressDialog; + private final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005; + private final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004; + private boolean isPermissionDenied; + private boolean recenterToUserLocation; + private GeoPoint mapCenter; + IntentFilter intentFilter = new IntentFilter(NETWORK_INTENT_ACTION); + private Place lastPlaceToCenter; + private LatLng lastKnownLocation; + private boolean isVisibleToUser; + private LatLng lastFocusLocation; + private PlaceAdapter adapter; + private GeoPoint lastMapFocus; + private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback; + private boolean isAdvancedQueryFragmentVisible = false; + private Place nearestPlace; + private volatile boolean stopQuery; + + // Explore map data (for if we came from Explore) + private double prevZoom; + private double prevLatitude; + private double prevLongitude; + + private final Handler searchHandler = new Handler(); + private Runnable searchRunnable; + + private LatLng updatedLatLng; + private boolean searchable; + + private ConstraintLayout nearbyLegend; + + private GridLayoutManager gridLayoutManager; + private List dataList; + private BottomSheetAdapter bottomSheetAdapter; + + private final ActivityResultLauncher galleryPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks); + }); + }); + + private final ActivityResultLauncher customSelectorLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCustomSelector(result, requireActivity(), + callbacks); + }); + }); + + private final ActivityResultLauncher cameraPickLauncherForResult = + registerForActivityResult(new StartActivityForResult(), + result -> { + controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { + controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); + }); + }); + + private ActivityResultLauncher inAppCameraLocationPermissionLauncher = registerForActivityResult( + new RequestMultiplePermissions(), + new ActivityResultCallback>() { + @Override + public void onActivityResult(Map result) { + boolean areAllGranted = true; + for (final boolean b : result.values()) { + areAllGranted = areAllGranted && b; + } + + if (areAllGranted) { + controller.locationPermissionCallback.onLocationPermissionGranted(); + } else { + if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { + controller.handleShowRationaleFlowCameraLocation(getActivity(), + inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); + } else { + controller.locationPermissionCallback.onLocationPermissionDenied( + getActivity().getString( + R.string.in_app_camera_location_permission_denied)); + } + } + } + }); + + private ActivityResultLauncher locationPermissionLauncher = registerForActivityResult( + new RequestPermission(), isGranted -> { + if (isGranted) { + locationPermissionGranted(); + } else { + if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { + DialogUtil.showAlertDialog(getActivity(), + getActivity().getString(R.string.location_permission_title), + getActivity().getString(R.string.location_permission_rationale_nearby), + getActivity().getString(android.R.string.ok), + getActivity().getString(android.R.string.cancel), + () -> { + askForLocationPermission(); + }, + null, + null + ); + } else { + if (isPermissionDenied) { + locationPermissionsHelper.showAppSettingsDialog(getActivity(), + R.string.nearby_needs_location); + } + Timber.d("The user checked 'Don't ask again' or denied the permission twice"); + isPermissionDenied = true; + } + } + }); + + /** + * WLM URL + */ + public static final String WLM_URL = "https://commons.wikimedia.org/wiki/Commons:Mobile_app/Contributing_to_WLM_using_the_app"; + + @NonNull + public static NearbyParentFragment newInstance() { + NearbyParentFragment fragment = new NearbyParentFragment(); + fragment.setRetainInstance(true); + return fragment; + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + loadExploreMapData(); + + binding = FragmentNearbyParentBinding.inflate(inflater, container, false); + view = binding.getRoot(); + + initNetworkBroadCastReceiver(); + scope = LifecycleOwnerKt.getLifecycleScope(getViewLifecycleOwner()); + presenter = new NearbyParentFragmentPresenter(bookmarkLocationDao, placesRepository, + nearbyController); + progressDialog = new ProgressDialog(getActivity()); + progressDialog.setCancelable(false); + progressDialog.setMessage("Saving in progress..."); + setHasOptionsMenu(true); + + // Inflate the layout for this fragment + return view; + + } + + @Override + public void onCreateOptionsMenu(@NonNull final Menu menu, + @NonNull final MenuInflater inflater) { + inflater.inflate(R.menu.nearby_fragment_menu, menu); + MenuItem refreshButton = menu.findItem(R.id.item_refresh); + MenuItem listMenu = menu.findItem(R.id.list_sheet); + MenuItem showInExploreButton = menu.findItem(R.id.list_item_show_in_explore); + MenuItem saveAsGPXButton = menu.findItem(R.id.list_item_gpx); + MenuItem saveAsKMLButton = menu.findItem(R.id.list_item_kml); + refreshButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + try { + emptyCache(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + }); + listMenu.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + listOptionMenuItemClicked(); + return false; + } + }); + showInExploreButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(@NonNull MenuItem item) { + ((MainActivity) getContext()).loadExploreMapFromNearby( + binding.map.getZoomLevelDouble(), + binding.map.getMapCenter().getLatitude(), + binding.map.getMapCenter().getLongitude() + ); + return false; + } + }); + saveAsGPXButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(@NonNull MenuItem item) { + try { + progressDialog.setTitle(getString(R.string.saving_gpx_file)); + progressDialog.show(); + savePlacesAsGPX(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + }); + saveAsKMLButton.setOnMenuItemClickListener(new OnMenuItemClickListener() { + + @Override + public boolean onMenuItemClick(@NonNull MenuItem item) { + try { + progressDialog.setTitle(getString(R.string.saving_kml_file)); + progressDialog.show(); + savePlacesAsKML(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return false; + } + }); + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + isDarkTheme = systemThemeUtils.isDeviceInNightMode(); + if (Utils.isMonumentsEnabled(new Date())) { + binding.rlContainerWlmMonthMessage.setVisibility(View.VISIBLE); + } else { + binding.rlContainerWlmMonthMessage.setVisibility(View.GONE); + } + locationPermissionsHelper = new LocationPermissionsHelper(getActivity(), locationManager, + this); + + // Set up the floating activity button to toggle the visibility of the legend + binding.fabLegend.setOnClickListener(v -> { + if (binding.nearbyLegendLayout.getRoot().getVisibility() == View.VISIBLE) { + binding.nearbyLegendLayout.getRoot().setVisibility(View.GONE); + } else { + binding.nearbyLegendLayout.getRoot().setVisibility(View.VISIBLE); + } + }); + + presenter.attachView(this); + isPermissionDenied = false; + recenterToUserLocation = false; + initThemePreferences(); + initViews(); + presenter.setActionListeners(applicationKvStore); + org.osmdroid.config.Configuration.getInstance().load(this.getContext(), + PreferenceManager.getDefaultSharedPreferences(this.getContext())); + + // Use the Wikimedia tile server, rather than OpenStreetMap (Mapnik) which has various + // restrictions that we do not satisfy. + binding.map.setTileSource(TileSourceFactory.WIKIMEDIA); + binding.map.setTilesScaledToDpi(true); + + // Add referer HTTP header because the Wikimedia tile server requires it. + // This was suggested by Dmitry Brant within an email thread between us and WMF. + org.osmdroid.config.Configuration.getInstance().getAdditionalHttpRequestProperties().put( + "Referer", "http://maps.wikimedia.org/" + ); + + if (applicationKvStore.getString("LastLocation") + != null) { // Checking for last searched location + String[] locationLatLng = applicationKvStore.getString("LastLocation").split(","); + lastMapFocus = new GeoPoint(Double.valueOf(locationLatLng[0]), + Double.valueOf(locationLatLng[1])); + } else { + lastMapFocus = new GeoPoint(51.50550, -0.07520); + } + ScaleBarOverlay scaleBarOverlay = new ScaleBarOverlay(binding.map); + scaleBarOverlay.setScaleBarOffset(15, 25); + Paint barPaint = new Paint(); + barPaint.setARGB(200, 255, 250, 250); + scaleBarOverlay.setBackgroundPaint(barPaint); + scaleBarOverlay.enableScaleBar(); + binding.map.getOverlays().add(scaleBarOverlay); + binding.map.getZoomController().setVisibility(Visibility.NEVER); + binding.map.getController().setZoom(ZOOM_LEVEL); + // if we came from Explore map using 'Show in Nearby', load Explore map camera position + if (isCameFromExploreMap()) { + moveCameraToPosition( + new GeoPoint(prevLatitude, prevLongitude), + prevZoom, + 1L + ); + } + binding.map.getOverlays().add(mapEventsOverlay); + + binding.map.addMapListener(new MapListener() { + @Override + public boolean onScroll(ScrollEvent event) { + presenter.handleMapScrolled(scope, !isNetworkErrorOccurred); + return true; + } + + @Override + public boolean onZoom(ZoomEvent event) { + return false; + } + + }); + + binding.map.setMultiTouchControls(true); + if (nearbyParentFragmentInstanceReadyCallback != null) { + nearbyParentFragmentInstanceReadyCallback.onReady(); + } + initNearbyFilter(); + addCheckBoxCallback(); + if (!isCameFromExploreMap()) { + moveCameraToPosition(lastMapFocus); + } + initRvNearbyList(); + onResume(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + binding.tvAttribution.setText( + Html.fromHtml(getString(R.string.map_attribution), Html.FROM_HTML_MODE_LEGACY)); + } else { + //noinspection deprecation + 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(); + showHideAdvancedQueryFragment(true); + final AdvanceQueryFragment fragment = new AdvanceQueryFragment(); + final Bundle bundle = new Bundle(); + try { + bundle.putString("query", + FileUtils.INSTANCE.readFromResource( + "/queries/radius_query_for_upload_wizard.rq") + ); + } catch (IOException e) { + Timber.e(e); + } + fragment.setArguments(bundle); + fragment.callback = new Callback() { + @Override + public void close() { + showHideAdvancedQueryFragment(false); + } + + @Override + public void reset() { + presenter.setAdvancedQuery(null); + presenter.updateMapAndList(LOCATION_SIGNIFICANTLY_CHANGED); + showHideAdvancedQueryFragment(false); + } + + @Override + public void apply(@NotNull final String query) { + presenter.setAdvancedQuery(query); + presenter.updateMapAndList(CUSTOM_QUERY); + showHideAdvancedQueryFragment(false); + } + }; + getChildFragmentManager().beginTransaction() + .replace(R.id.fl_container_nearby_children, fragment) + .commit(); + }); + + binding.tvLearnMore.setOnClickListener(v -> onLearnMoreClicked()); + + if (!locationPermissionsHelper.checkLocationPermission(getActivity())) { + askForLocationPermission(); + } + } + + /** + * Fetch Explore map camera data from fragment arguments if any. + */ + public void loadExploreMapData() { + // get fragment arguments + if (getArguments() != null) { + prevZoom = getArguments().getDouble("prev_zoom"); + prevLatitude = getArguments().getDouble("prev_latitude"); + prevLongitude = getArguments().getDouble("prev_longitude"); + } + } + + /** + * Checks if fragment arguments contain data from Explore map. if present, then the user + * navigated from Explore using 'Show in Nearby'. + * + * @return true if user navigated from Explore map + **/ + public boolean isCameFromExploreMap() { + return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0; + } + + /** + * Initialise background based on theme, this should be doe ideally via styles, that would need + * another refactor + */ + private void initThemePreferences() { + if (isDarkTheme) { + binding.bottomSheetNearby.rvNearbyList.setBackgroundColor( + getContext().getResources().getColor(R.color.contributionListDarkBackground)); + binding.nearbyFilterList.checkboxTriStates.setTextColor( + getContext().getResources().getColor(android.R.color.white)); + binding.nearbyFilterList.checkboxTriStates.setTextColor( + getContext().getResources().getColor(android.R.color.white)); + binding.nearbyFilterList.getRoot().setBackgroundColor( + getContext().getResources().getColor(R.color.contributionListDarkBackground)); + binding.map.getOverlayManager().getTilesOverlay() + .setColorFilter(TilesOverlay.INVERT_COLORS); + } else { + binding.bottomSheetNearby.rvNearbyList.setBackgroundColor( + getContext().getResources().getColor(android.R.color.white)); + binding.nearbyFilterList.checkboxTriStates.setTextColor( + getContext().getResources().getColor(R.color.contributionListDarkBackground)); + binding.nearbyFilterList.getRoot().setBackgroundColor( + getContext().getResources().getColor(android.R.color.white)); + binding.nearbyFilterList.getRoot().setBackgroundColor( + getContext().getResources().getColor(android.R.color.white)); + } + } + + private void initRvNearbyList() { + binding.bottomSheetNearby.rvNearbyList.setLayoutManager( + new LinearLayoutManager(getContext())); + adapter = new PlaceAdapter(bookmarkLocationDao, + place -> { + moveCameraToPosition( + new GeoPoint(place.location.getLatitude(), place.location.getLongitude())); + return Unit.INSTANCE; + }, + (place, isBookmarked) -> { + presenter.toggleBookmarkedStatus(place); + return Unit.INSTANCE; + }, + commonPlaceClickActions, + inAppCameraLocationPermissionLauncher, + galleryPickLauncherForResult, + cameraPickLauncherForResult + ); + binding.bottomSheetNearby.rvNearbyList.setAdapter(adapter); + } + + private void addCheckBoxCallback() { + binding.nearbyFilterList.checkboxTriStates.setCallback( + (o, state, b, b1) -> presenter.filterByMarkerType(o, state, b, b1)); + } + + private void performMapReadyActions() { + if (((MainActivity) getActivity()).activeFragment == ActiveFragment.NEARBY) { + if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) && + !locationPermissionsHelper.checkLocationPermission(getActivity())) { + isPermissionDenied = true; + } + } + presenter.onMapReady(); + } + + @Override + public void askForLocationPermission() { + Timber.d("Asking for location permission"); + locationPermissionLauncher.launch(permission.ACCESS_FINE_LOCATION); + } + + private void locationPermissionGranted() { + isPermissionDenied = false; + applicationKvStore.putBoolean("doNotAskForLocationPermission", false); + lastKnownLocation = locationManager.getLastLocation(); + LatLng target = lastKnownLocation; + if (lastKnownLocation != null) { + GeoPoint targetP = new GeoPoint(target.getLatitude(), target.getLongitude()); + mapCenter = targetP; + binding.map.getController().setCenter(targetP); + recenterMarkerToPosition(targetP); + if (!isCameFromExploreMap()) { + moveCameraToPosition(targetP); + } + } else if (locationManager.isGPSProviderEnabled() + || locationManager.isNetworkProviderEnabled()) { + locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER); + locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER); + setProgressBarVisibility(true); + } else { + locationPermissionsHelper.showLocationOffDialog(getActivity(), + R.string.ask_to_turn_location_on_text); + } + presenter.onMapReady(); + registerUnregisterLocationListener(false); + } + + @Override + public void onResume() { + super.onResume(); + binding.map.onResume(); + presenter.attachView(this); + registerNetworkReceiver(); + if (isResumed() && ((MainActivity) getActivity()).activeFragment == ActiveFragment.NEARBY) { + if (locationPermissionsHelper.checkLocationPermission(getActivity())) { + locationPermissionGranted(); + } else { + startMapWithoutPermission(); + } + } + } + + /** + * 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) { + final String[] locationLatLng + = applicationKvStore.getString("LastLocation").split(","); + lastKnownLocation + = new LatLng(Double.parseDouble(locationLatLng[0]), + Double.parseDouble(locationLatLng[1]), 1f); + } else { + lastKnownLocation = MapUtils.getDefaultLatLng(); + } + if (binding.map != null && !isCameFromExploreMap()) { + moveCameraToPosition( + new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude())); + } + presenter.onMapReady(); + } + + private void registerNetworkReceiver() { + if (getActivity() != null) { + getActivity().registerReceiver(broadcastReceiver, intentFilter); + } + } + + @Override + public void onPause() { + super.onPause(); + binding.map.onPause(); + getCompositeDisposable().clear(); + presenter.detachView(); + registerUnregisterLocationListener(true); + try { + if (broadcastReceiver != null && getActivity() != null) { + getContext().unregisterReceiver(broadcastReceiver); + } + + if (locationManager != null && presenter != null) { + locationManager.removeLocationListener(presenter); + locationManager.unregisterLocationManager(); + } + } catch (final Exception e) { + Timber.e(e); + //Broadcast receivers should always be unregistered inside catch, you never know if they were already registered or not + } + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + searchHandler.removeCallbacks(searchRunnable); + presenter.removeNearbyPreferences(applicationKvStore); + } + + private void initViews() { + Timber.d("init views called"); + initBottomSheets(); + loadAnimations(); + setBottomSheetCallbacks(); + addActionToTitle(); + if (!Utils.isMonumentsEnabled(new Date())) { + NearbyFilterState.setWlmSelected(false); + } + } + + /** + * a) Creates bottom sheet behaviours from bottom sheets, sets initial states and visibility b) + * Gets the touch event on the map to perform following actions: if fab is open then close fab. + * if bottom sheet details are expanded then collapse bottom sheet details. if bottom sheet + * details are collapsed then hide the bottom sheet details. if listBottomSheet is open then + * hide the list bottom sheet. + */ + @SuppressLint("ClickableViewAccessibility") + private void initBottomSheets() { + bottomSheetListBehavior = BottomSheetBehavior.from(binding.bottomSheetNearby.bottomSheet); + bottomSheetDetailsBehavior = BottomSheetBehavior.from(binding.bottomSheetDetails.getRoot()); + bottomSheetDetailsBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + binding.bottomSheetDetails.getRoot().setVisibility(View.VISIBLE); + 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.setLayoutHeightAlignedToWidth(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); + nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter( + getContext(), new ArrayList<>(Label.valuesAsList()), + binding.nearbyFilterList.searchListView); + nearbyFilterSearchRecyclerViewAdapter.setCallback( + new NearbyFilterSearchRecyclerViewAdapter.Callback() { + @Override + public void setCheckboxUnknown() { + presenter.setCheckboxUnknown(); + } + + @Override + public void filterByMarkerType(final ArrayList