mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge e5f42562b3 into 44966645ca
This commit is contained in:
commit
047c4872f5
17 changed files with 1747 additions and 1852 deletions
|
|
@ -696,12 +696,12 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -711,7 +711,7 @@ class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.On
|
|||
}
|
||||
}
|
||||
|
||||
override fun onLocationChangedMedium(latLng: LatLng) {
|
||||
override fun onLocationChangedMedium(latLng: LatLng?) {
|
||||
// Update closest nearby card view if location changed more than 500 meters
|
||||
updateClosestNearbyCardViewInfo()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ public class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme
|
|||
*/
|
||||
@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 class ExploreMapRootFragment extends CommonsDaggerSupportFragment impleme
|
|||
*/
|
||||
@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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,309 +0,0 @@
|
|||
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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Name of the depicted item
|
||||
* Ex: Rabbit
|
||||
*/
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the passed wikidataItemName from the intents and displays it as the page title
|
||||
*/
|
||||
private void setPageTitle() {
|
||||
if (getIntent() != null && getIntent().getStringExtra("wikidataItemName") != null) {
|
||||
setTitle(getIntent().getStringExtra("wikidataItemName"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on success of API call for featured Images.
|
||||
* The viewpager will notified that number of items have changed.
|
||||
*/
|
||||
@Override
|
||||
public void viewPagerNotifyDataSetChanged() {
|
||||
if (mediaDetailPagerFragment !=null){
|
||||
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.
|
||||
*/
|
||||
private void setTabs() {
|
||||
List<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> 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);
|
||||
}
|
||||
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();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
mediaDetailPagerFragment.showImage(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||
* @param i It is the index of which media object is to be returned which is same as
|
||||
* current index of viewPager.
|
||||
* @return Media Object
|
||||
*/
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
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);
|
||||
}
|
||||
super.onBackPressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on from getCount of MediaDetailPagerFragment
|
||||
* 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
|
||||
public Integer getContributionStateAt(int position) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
@Override
|
||||
public void refreshNominatedMedia(int index) {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}));
|
||||
|
||||
} 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);
|
||||
}
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,303 @@
|
|||
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
|
||||
*/
|
||||
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
|
||||
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 intent and displays it as the page title
|
||||
*/
|
||||
private fun setPageTitle() {
|
||||
intent.getStringExtra("wikidataItemName")?.let {
|
||||
title = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 fun viewPagerNotifyDataSetChanged() {
|
||||
mediaDetailPagerFragment?.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 fun setTabs() {
|
||||
val fragmentList = mutableListOf<Fragment>()
|
||||
val titleList = mutableListOf<String>()
|
||||
|
||||
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.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 fun onMediaClicked(position: Int) {
|
||||
binding.apply {
|
||||
tabLayout.visibility = View.GONE
|
||||
viewPager.visibility = View.GONE
|
||||
mediaContainer.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called mediaDetailPagerFragment. It returns the Media Object at that Index
|
||||
* @param i It is the index of which media object is to be returned which is same as
|
||||
* current index of viewPager.
|
||||
* @return Media Object
|
||||
*/
|
||||
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 fun onBackPressed() {
|
||||
if (supportFragmentManager.backStackEntryCount == 1) {
|
||||
binding.apply {
|
||||
tabLayout.visibility = View.VISIBLE
|
||||
viewPager.visibility = View.VISIBLE
|
||||
mediaContainer.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
super.onBackPressed()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called on from getCount of MediaDetailPagerFragment
|
||||
* The viewpager will contain same number of media items as that of media elements in adapter.
|
||||
* @return Total Media count in the adapter
|
||||
*/
|
||||
override fun getTotalMediaCount(): Int {
|
||||
return depictionImagesListFragment.getTotalMediaCount()
|
||||
}
|
||||
|
||||
override fun getContributionStateAt(position: Int): Int? {
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload media detail fragment once media is nominated
|
||||
*
|
||||
* @param index item position that has been nominated
|
||||
*/
|
||||
override fun refreshNominatedMedia(index: Int) {
|
||||
if (supportFragmentManager.backStackEntryCount == 1) {
|
||||
onBackPressed()
|
||||
onMediaClicked(index)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the 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 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 {
|
||||
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
|
||||
}
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
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;
|
||||
|
||||
@Singleton
|
||||
public class ExploreMapCalls {
|
||||
|
||||
@Inject
|
||||
MediaClient mediaClient;
|
||||
|
||||
@Inject
|
||||
public ExploreMapCalls() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls method to query Commons for uploads around a location
|
||||
*
|
||||
* @param currentLatLng coordinates of search location
|
||||
* @return list of places obtained
|
||||
*/
|
||||
List<Media> callCommonsQuery(final LatLng currentLatLng) {
|
||||
String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude();
|
||||
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
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 javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class ExploreMapCalls @Inject constructor() {
|
||||
|
||||
@Inject
|
||||
lateinit var mediaClient: MediaClient
|
||||
|
||||
/**
|
||||
* Calls method to query Commons for uploads around a location
|
||||
*
|
||||
* @param currentLatLng coordinates of search location
|
||||
* @return list of places obtained
|
||||
*/
|
||||
fun callCommonsQuery(currentLatLng: LatLng): List<Media> {
|
||||
val coordinates = "${currentLatLng.latitude}|${currentLatLng.longitude}"
|
||||
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
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;
|
||||
|
||||
public class 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<BaseMarker> 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();
|
||||
}
|
||||
|
||||
interface UserActions {
|
||||
void updateMap(LocationServiceManager.LocationChangeType locationChangeType);
|
||||
void lockUnlockNearby(boolean isNearbyLocked);
|
||||
void attachView(View view);
|
||||
void detachView();
|
||||
void setActionListeners(JsonKvStore applicationKvStore);
|
||||
boolean backButtonClicked();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
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
|
||||
|
||||
interface ExploreMapContract {
|
||||
|
||||
interface View {
|
||||
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<BaseMarker>)
|
||||
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 {
|
||||
fun updateMap(locationChangeType: LocationServiceManager.LocationChangeType)
|
||||
fun lockUnlockNearby(isNearbyLocked: Boolean)
|
||||
fun attachView(view: View)
|
||||
fun detachView()
|
||||
fun setActionListeners(applicationKvStore: JsonKvStore)
|
||||
fun backButtonClicked(): Boolean
|
||||
}
|
||||
}
|
||||
|
|
@ -1,213 +0,0 @@
|
|||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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) {
|
||||
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions explore map, but search is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
ExplorePlacesInfo explorePlacesInfo = new ExplorePlacesInfo();
|
||||
try {
|
||||
explorePlacesInfo.currentLatLng = currentLatLng;
|
||||
latestSearchLocation = searchLatLng;
|
||||
|
||||
List<Media> 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<Media, Double> 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.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());
|
||||
if (distance > latestSearchRadius) {
|
||||
latestSearchRadius = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return explorePlacesInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads attractions from location for map view, we need to return places in Place data type
|
||||
*
|
||||
* @return baseMarkerOptions list that holds nearby places with their icons
|
||||
*/
|
||||
public static List<BaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
LatLng currentLatLng,
|
||||
final List<Place> placeList,
|
||||
Context context,
|
||||
NearbyBaseMarkerThumbCallback callback,
|
||||
ExplorePlacesInfo explorePlacesInfo) {
|
||||
List<BaseMarker> baseMarkerList = new ArrayList<>();
|
||||
|
||||
if (placeList == null) {
|
||||
return baseMarkerList;
|
||||
}
|
||||
|
||||
VectorDrawableCompat vectorDrawable = null;
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
context.getResources(), R.drawable.ic_custom_map_marker_dark, context.getTheme());
|
||||
|
||||
} 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<Bitmap>() {
|
||||
// We add icons to markers when bitmaps are ready
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Bitmap resource,
|
||||
@Nullable Transition<? super Bitmap> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return baseMarkerList;
|
||||
}
|
||||
|
||||
interface NearbyBaseMarkerThumbCallback {
|
||||
|
||||
// Callback to notify thumbnails of explore markers are added as icons and ready
|
||||
void onNearbyBaseMarkerThumbsReady(List<BaseMarker> baseMarkers,
|
||||
ExplorePlacesInfo explorePlacesInfo);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
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
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
fun loadAttractionsFromLocation(
|
||||
currentLatLng: LatLng,
|
||||
searchLatLng: LatLng?,
|
||||
checkingAroundCurrentLocation: Boolean
|
||||
): ExplorePlacesInfo? {
|
||||
|
||||
if (searchLatLng == null) {
|
||||
Timber.d("Loading attractions explore map, but search is null")
|
||||
return null
|
||||
}
|
||||
|
||||
val explorePlacesInfo = ExplorePlacesInfo()
|
||||
try {
|
||||
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<Media, Double>()
|
||||
|
||||
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
|
||||
|
||||
// Sets latestSearchRadius to maximum distance among boundaries and search location
|
||||
for (bound in boundaryCoordinates) {
|
||||
val distance = LocationUtils.calculateDistance(
|
||||
bound?.latitude!!, bound.longitude,
|
||||
searchLatLng.latitude, searchLatLng.longitude
|
||||
)
|
||||
if (distance > latestSearchRadius) {
|
||||
latestSearchRadius = distance
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return explorePlacesInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads attractions from location for map view, we need to return places in Place data type
|
||||
*
|
||||
* @return baseMarkerOptions list that holds nearby places with their icons
|
||||
*/
|
||||
companion object {
|
||||
fun loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
currentLatLng: LatLng,
|
||||
placeList: List<Place>?,
|
||||
context: Context,
|
||||
callback: NearbyBaseMarkerThumbCallback,
|
||||
explorePlacesInfo: ExplorePlacesInfo
|
||||
): List<BaseMarker> {
|
||||
val baseMarkerList = mutableListOf<BaseMarker>()
|
||||
|
||||
if (placeList == null) {
|
||||
return baseMarkerList
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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<Bitmap>() {
|
||||
// We add icons to markers when bitmaps are ready
|
||||
override fun onResourceReady(
|
||||
resource: Bitmap,
|
||||
transition: Transition<in Bitmap>?
|
||||
) {
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
interface NearbyBaseMarkerThumbCallback {
|
||||
// Callback to notify thumbnails of explore markers are added as icons and ready
|
||||
fun onNearbyBaseMarkerThumbsReady(
|
||||
baseMarkers: List<BaseMarker>,
|
||||
explorePlacesInfo: ExplorePlacesInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,937 @@
|
|||
package fr.free.nrw.commons.explore.map
|
||||
|
||||
import android.Manifest
|
||||
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 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
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
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: LatLng? = null // last location of user
|
||||
private var lastFocusLocation: LatLng? = null // last focused location of the map
|
||||
var mediaList: List<Media>? = 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
|
||||
lateinit var liveDataConverter: LiveDataConverter
|
||||
|
||||
@Inject
|
||||
lateinit var mediaClient: MediaClient
|
||||
|
||||
@Inject
|
||||
lateinit var locationManager: LocationServiceManager
|
||||
|
||||
@Inject
|
||||
lateinit var exploreMapController: ExploreMapController
|
||||
|
||||
@Inject
|
||||
@Named("default_preferences")
|
||||
lateinit var applicationKvStore: JsonKvStore
|
||||
|
||||
@Inject
|
||||
lateinit var bookmarkLocationDao: BookmarkLocationsDao // Future use for bookmarking explore places
|
||||
|
||||
@Inject
|
||||
lateinit var systemThemeUtils: SystemThemeUtils
|
||||
|
||||
private lateinit var locationPermissionsHelper: LocationPermissionsHelper
|
||||
|
||||
// Nearby map state (if we came from Nearby)
|
||||
private var prevZoom = 0.0
|
||||
private var prevLatitude = 0.0
|
||||
private var prevLongitude = 0.0
|
||||
|
||||
private var presenter: ExploreMapPresenter? = null
|
||||
|
||||
private lateinit var binding: FragmentExploreMapBinding
|
||||
|
||||
private val activityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
if (isGranted) {
|
||||
locationPermissionGranted()
|
||||
} else {
|
||||
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(
|
||||
requireActivity(),
|
||||
R.string.explore_map_needs_location
|
||||
)
|
||||
}
|
||||
Timber.d("The user checked 'Don't ask again' or denied the permission twice")
|
||||
isPermissionDenied = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(): ExploreMapFragment {
|
||||
return ExploreMapFragment().apply { retainInstance = true }
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Html.fromHtml(getString(R.string.map_attribution))
|
||||
}
|
||||
|
||||
initNetworkBroadCastReceiver()
|
||||
locationPermissionsHelper = LocationPermissionsHelper(
|
||||
requireActivity(),
|
||||
locationManager,
|
||||
this
|
||||
)
|
||||
|
||||
if (presenter == null) {
|
||||
presenter = ExploreMapPresenter(bookmarkLocationDao)
|
||||
}
|
||||
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)
|
||||
|
||||
zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
|
||||
setMultiTouchControls(true)
|
||||
|
||||
if (!isCameFromNearbyMap()) {
|
||||
controller.setZoom(ZOOM_LEVEL.toDouble())
|
||||
}
|
||||
}
|
||||
|
||||
performMapReadyActions()
|
||||
|
||||
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")
|
||||
|
||||
if (bottomSheetDetailsBehavior.state == BottomSheetBehavior.STATE_EXPANDED) {
|
||||
bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
} else if (isDetailsBottomSheetVisible()) {
|
||||
hideBottomDetailsSheet()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun longPressHelper(p: GeoPoint?): Boolean = false
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
binding.mapView.addMapListener(object : MapListener {
|
||||
override fun onScroll(event: ScrollEvent?): Boolean {
|
||||
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()
|
||||
&&
|
||||
(event?.x!! > 0 || event.y > 0)
|
||||
) {
|
||||
setSearchThisAreaButtonVisibility(distance > 2000.0)
|
||||
}
|
||||
} ?: setSearchThisAreaButtonVisibility(false)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onZoom(event: ZoomEvent?): Boolean = false
|
||||
})
|
||||
|
||||
if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) {
|
||||
askForLocationPermission()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.mapView.onResume()
|
||||
presenter?.attachView(this)
|
||||
registerNetworkReceiver()
|
||||
|
||||
if (isResumed) {
|
||||
if (activity?.let { locationPermissionsHelper.checkLocationPermission(it) } == true) {
|
||||
performMapReadyActions()
|
||||
} else {
|
||||
startMapWithoutPermission()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Unregistering the broadcastReceiver to prevent crashes
|
||||
unregisterNetworkReceiver()
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the networkReceiver
|
||||
*/
|
||||
private fun unregisterNetworkReceiver() {
|
||||
activity?.unregisterReceiver(broadcastReceiver)
|
||||
}
|
||||
|
||||
private fun startMapWithoutPermission() {
|
||||
lastKnownLocation = MapUtils.defaultLatLng
|
||||
moveCameraToPosition(GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude))
|
||||
presenter?.onMapReady(exploreMapController)
|
||||
}
|
||||
|
||||
private fun registerNetworkReceiver() {
|
||||
activity?.registerReceiver(broadcastReceiver, intentFilter)
|
||||
}
|
||||
|
||||
private fun performMapReadyActions() {
|
||||
if (isDarkTheme) {
|
||||
binding.mapView.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
|
||||
}
|
||||
if (applicationKvStore.getBoolean("doNotAskForLocationPermission", false) &&
|
||||
!locationPermissionsHelper.checkLocationPermission(requireActivity())
|
||||
) {
|
||||
isPermissionDenied = true
|
||||
}
|
||||
|
||||
lastKnownLocation = MapUtils.defaultLatLng
|
||||
|
||||
// If user came from 'Show in Explore' in Nearby, load saved map center and zoom
|
||||
if (isCameFromNearbyMap()) {
|
||||
moveCameraToPosition(GeoPoint(prevLatitude, prevLongitude), prevZoom, 1L)
|
||||
} else {
|
||||
moveCameraToPosition(
|
||||
GeoPoint(lastKnownLocation!!.latitude, lastKnownLocation!!.longitude)
|
||||
)
|
||||
}
|
||||
|
||||
presenter?.onMapReady(exploreMapController)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch Nearby map camera data from fragment arguments if available.
|
||||
*/
|
||||
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 the Nearby map,
|
||||
* indicating that the user navigated from Nearby using 'Show in Explore'.
|
||||
*
|
||||
* @return true if user navigated from Nearby map
|
||||
*/
|
||||
fun isCameFromNearbyMap(): Boolean {
|
||||
return prevZoom != 0.0 || prevLatitude != 0.0 || prevLongitude != 0.0
|
||||
}
|
||||
|
||||
fun loadNearbyMapFromExplore() {
|
||||
(requireContext() as MainActivity).loadNearbyMapFromExplore(
|
||||
binding.mapView.zoomLevelDouble,
|
||||
binding.mapView.mapCenter.latitude,
|
||||
binding.mapView.mapCenter.longitude
|
||||
)
|
||||
}
|
||||
|
||||
private fun initViews() {
|
||||
Timber.d("init views called")
|
||||
initBottomSheets()
|
||||
setBottomSheetCallbacks()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
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 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 fun onLocationChangedSignificantly(latLng: LatLng?) {
|
||||
Timber.d("Location significantly changed")
|
||||
latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) }
|
||||
}
|
||||
|
||||
override fun onLocationChangedSlightly(latLng: LatLng?) {
|
||||
Timber.d("Location slightly changed")
|
||||
latLng?.let { handleLocationUpdate(it, LocationServiceManager.LocationChangeType.LOCATION_SLIGHTLY_CHANGED) }
|
||||
}
|
||||
|
||||
private fun handleLocationUpdate(
|
||||
latLng: LatLng,
|
||||
locationChangeType: LocationServiceManager.LocationChangeType
|
||||
) {
|
||||
lastKnownLocation = latLng
|
||||
exploreMapController.currentLocation = lastKnownLocation
|
||||
presenter?.updateMap(locationChangeType)
|
||||
}
|
||||
|
||||
override fun onLocationChangedMedium(latLng: LatLng?) {
|
||||
// No implementation required
|
||||
}
|
||||
|
||||
override fun isNetworkConnectionEstablished(): Boolean {
|
||||
return NetworkUtils.isInternetConnectionEstablished(activity)
|
||||
}
|
||||
|
||||
override fun populatePlaces(curLatLng: LatLng?) {
|
||||
if (curLatLng == null) return
|
||||
|
||||
val nearbyPlacesInfoObservable: Observable<MapController.ExplorePlacesInfo> =
|
||||
if (curLatLng == getLastMapFocus()) {
|
||||
// Checking around current location
|
||||
presenter!!.loadAttractionsFromLocation(curLatLng, getLastMapFocus(), true)
|
||||
} else {
|
||||
presenter!!.loadAttractionsFromLocation(getLastMapFocus(), curLatLng, false)
|
||||
}
|
||||
|
||||
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 = 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates map markers according to latest situation
|
||||
*
|
||||
* @param explorePlacesInfo holds several information as current location, marker list etc.
|
||||
*/
|
||||
private fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) {
|
||||
presenter?.updateMapMarkers(explorePlacesInfo)
|
||||
}
|
||||
|
||||
private fun showErrorMessage(message: String) {
|
||||
ViewUtil.showLongToast(requireActivity(), message)
|
||||
}
|
||||
|
||||
private fun showResponseMessage(message: String) {
|
||||
ViewUtil.showLongSnackbar(requireView(), message)
|
||||
}
|
||||
|
||||
override fun askForLocationPermission() {
|
||||
Timber.d("Asking for location permission")
|
||||
activityResultLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
}
|
||||
|
||||
private fun locationPermissionGranted() {
|
||||
isPermissionDenied = false
|
||||
applicationKvStore.putBoolean("doNotAskForLocationPermission", false)
|
||||
lastKnownLocation = locationManager.getLastLocation()
|
||||
val target = lastKnownLocation
|
||||
|
||||
if (lastKnownLocation != null) {
|
||||
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(
|
||||
requireActivity(),
|
||||
R.string.ask_to_turn_location_on_text
|
||||
)
|
||||
}
|
||||
presenter?.onMapReady(exploreMapController)
|
||||
registerUnregisterLocationListener(false)
|
||||
}
|
||||
|
||||
fun registerUnregisterLocationListener(removeLocationListener: Boolean) {
|
||||
MapUtils.registerUnregisterLocationListener(removeLocationListener, locationManager, this)
|
||||
}
|
||||
|
||||
override fun recenterMap(currentLatLng: LatLng?) {
|
||||
if (isPermissionDenied) {
|
||||
if (locationPermissionsHelper.checkLocationPermission(requireActivity())) {
|
||||
isPermissionDenied = false
|
||||
recenterMap(currentLatLng)
|
||||
} else {
|
||||
askForLocationPermission()
|
||||
}
|
||||
} else {
|
||||
if (!locationPermissionsHelper.checkLocationPermission(requireActivity())) {
|
||||
askForLocationPermission()
|
||||
} else {
|
||||
locationPermissionGranted()
|
||||
}
|
||||
}
|
||||
|
||||
if (currentLatLng == null) {
|
||||
recenterToUserLocation = true
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override fun hideBottomDetailsSheet() {
|
||||
bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
}
|
||||
|
||||
/**
|
||||
* Same bottom sheet carries information for all nearby places, so we need to pass information
|
||||
* (title, description, distance and links) to view on nearby marker click
|
||||
*
|
||||
* @param place Place of clicked nearby marker
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 fun addSearchThisAreaButtonAction() {
|
||||
binding.searchThisAreaButton.setOnClickListener(presenter?.onSearchThisAreaClicked())
|
||||
}
|
||||
|
||||
override fun setSearchThisAreaButtonVisibility(isVisible: Boolean) {
|
||||
binding.searchThisAreaButton.visibility = if (isVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun setProgressBarVisibility(isVisible: Boolean) {
|
||||
binding.mapProgressBar.visibility = if (isVisible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
override fun isDetailsBottomSheetVisible(): Boolean {
|
||||
return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE
|
||||
}
|
||||
|
||||
override fun isSearchThisAreaButtonVisible(): Boolean {
|
||||
return binding.bottomSheetDetailsBinding.root.visibility == View.VISIBLE
|
||||
}
|
||||
|
||||
override fun getLastLocation(): LatLng {
|
||||
if (lastKnownLocation == null) {
|
||||
lastKnownLocation = locationManager.getLastLocation()
|
||||
}
|
||||
return lastKnownLocation!!
|
||||
}
|
||||
|
||||
override fun disableFABRecenter() {
|
||||
binding.fabRecenter.isEnabled = false
|
||||
}
|
||||
|
||||
override fun enableFABRecenter() {
|
||||
binding.fabRecenter.isEnabled = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds markers to the map based on the list of NearbyBaseMarker.
|
||||
*
|
||||
* @param nearbyBaseMarkers The NearbyBaseMarker object representing the markers to be added.
|
||||
*/
|
||||
override fun addMarkersToMap(nearbyBaseMarkers: List<BaseMarker>) {
|
||||
clearAllMarkers()
|
||||
nearbyBaseMarkers.forEach { addMarkerToMap(it) }
|
||||
binding.mapView.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a marker to the map based on the specified NearbyBaseMarker.
|
||||
*
|
||||
* @param nearbyBaseMarker The NearbyBaseMarker object representing the marker to be added.
|
||||
*/
|
||||
private fun addMarkerToMap(nearbyBaseMarker: BaseMarker) {
|
||||
if (isAttachedToActivity()) {
|
||||
val items = ArrayList<OverlayItem>()
|
||||
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<OverlayItem> {
|
||||
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 fun onItemLongPress(index: Int, item: OverlayItem): Boolean {
|
||||
return false
|
||||
}
|
||||
}, context)
|
||||
|
||||
overlay.setFocusItemsOnTap(true)
|
||||
binding.mapView.overlays.add(overlay) // Add the overlay to the map
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeMarker(nearbyBaseMarker: BaseMarker) {
|
||||
val place = nearbyBaseMarker.place
|
||||
val overlays = binding.mapView.overlays
|
||||
var item: ItemizedOverlayWithFocus<OverlayItem>
|
||||
|
||||
for (i in overlays.indices) {
|
||||
if (overlays[i] is ItemizedOverlayWithFocus<*>) {
|
||||
item = overlays[i] as ItemizedOverlayWithFocus<OverlayItem>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun clearAllMarkers() {
|
||||
if (isAttachedToActivity()) {
|
||||
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)
|
||||
}
|
||||
|
||||
val scaleBarOverlay = ScaleBarOverlay(binding.mapView).apply {
|
||||
setScaleBarOffset(15, 25)
|
||||
setBackgroundPaint(
|
||||
Paint().apply {
|
||||
setARGB(200, 255, 250, 250)
|
||||
}
|
||||
)
|
||||
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()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun longPressHelper(p: GeoPoint?): Boolean {
|
||||
return false
|
||||
}
|
||||
}) {})
|
||||
|
||||
binding.mapView.setMultiTouchControls(true)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveCameraToPosition(geoPoint: GeoPoint) {
|
||||
binding.mapView.controller.animateTo(geoPoint)
|
||||
}
|
||||
|
||||
private fun moveCameraToPosition(geoPoint: GeoPoint, zoom: Double, speed: Long) {
|
||||
binding.mapView.controller.animateTo(geoPoint, zoom, speed)
|
||||
}
|
||||
|
||||
override fun getLastMapFocus(): LatLng {
|
||||
return lastMapFocus?.let {
|
||||
LatLng(it.latitude, it.longitude, 100f)
|
||||
} ?: getMapCenter()
|
||||
}
|
||||
|
||||
override fun getMapCenter(): LatLng {
|
||||
var latLng: LatLng? = null
|
||||
|
||||
if (mapCenter != null) {
|
||||
latLng = LatLng(mapCenter!!.latitude, mapCenter!!.longitude, 100f)
|
||||
} else {
|
||||
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(GeoPoint(latLng?.latitude!!, latLng?.longitude!!))
|
||||
}
|
||||
return latLng!!
|
||||
}
|
||||
|
||||
override fun getMapFocus(): LatLng {
|
||||
return LatLng(
|
||||
binding.mapView.mapCenter.latitude,
|
||||
binding.mapView.mapCenter.longitude,
|
||||
100f
|
||||
)
|
||||
}
|
||||
|
||||
override fun setFABRecenterAction(onClickListener: View.OnClickListener) {
|
||||
binding.fabRecenter.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
override fun backButtonClicked(): Boolean {
|
||||
return if (bottomSheetDetailsBehavior.state != BottomSheetBehavior.STATE_HIDDEN) {
|
||||
bottomSheetDetailsBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun initNetworkBroadCastReceiver() {
|
||||
broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
activity?.let {
|
||||
if (NetworkUtils.isInternetConnectionEstablished(it)) {
|
||||
if (isNetworkErrorOccurred) {
|
||||
presenter?.updateMap(
|
||||
LocationServiceManager
|
||||
.LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
|
||||
)
|
||||
isNetworkErrorOccurred = false
|
||||
}
|
||||
snackbar?.dismiss()
|
||||
snackbar = null
|
||||
} else {
|
||||
if (snackbar == null) {
|
||||
snackbar = Snackbar.make(
|
||||
requireView(),
|
||||
R.string.no_internet,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
setSearchThisAreaButtonVisibility(false)
|
||||
setProgressBarVisibility(false)
|
||||
}
|
||||
isNetworkErrorOccurred = true
|
||||
snackbar?.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isAttachedToActivity(): Boolean {
|
||||
return isVisible && activity != null
|
||||
}
|
||||
|
||||
override fun onLocationPermissionDenied(toastMessage: String) {}
|
||||
|
||||
override fun onLocationPermissionGranted() {}
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
);
|
||||
private ExploreMapContract.View exploreMapFragmentView = DUMMY;
|
||||
|
||||
public ExploreMapPresenter(BookmarkLocationsDao bookmarkLocationDao) {
|
||||
this.bookmarkLocationDao = bookmarkLocationDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateMap(LocationChangeType locationChangeType) {
|
||||
Timber.d("Presenter updates map and list" + locationChangeType.toString());
|
||||
if (isNearbyLocked) {
|
||||
Timber.d("Nearby is locked, so updateMapAndList returns");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!exploreMapFragmentView.isNetworkConnectionEstablished()) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param isNearbyLocked true means lock, false means unlock
|
||||
*/
|
||||
@Override
|
||||
public void lockUnlockNearby(boolean isNearbyLocked) {
|
||||
this.isNearbyLocked = isNearbyLocked;
|
||||
if (isNearbyLocked) {
|
||||
exploreMapFragmentView.disableFABRecenter();
|
||||
} else {
|
||||
exploreMapFragmentView.enableFABRecenter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachView(ExploreMapContract.View view) {
|
||||
exploreMapFragmentView = view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void detachView() {
|
||||
exploreMapFragmentView = DUMMY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener of FAB
|
||||
*/
|
||||
@Override
|
||||
public void setActionListeners(JsonKvStore applicationKvStore) {
|
||||
exploreMapFragmentView.setFABRecenterAction(v -> {
|
||||
exploreMapFragmentView.recenterMap(currentLatLng);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean backButtonClicked() {
|
||||
return exploreMapFragmentView.backButtonClicked();
|
||||
}
|
||||
|
||||
public void onMapReady(ExploreMapController exploreMapController) {
|
||||
this.exploreMapController = exploreMapController;
|
||||
if (null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction();
|
||||
initializeMapOperations();
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeMapOperations() {
|
||||
lockUnlockNearby(false);
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
}
|
||||
|
||||
public Observable<ExplorePlacesInfo> loadAttractionsFromLocation(LatLng currentLatLng,
|
||||
LatLng searchLatLng, boolean checkingAroundCurrent) {
|
||||
return Observable
|
||||
.fromCallable(() -> exploreMapController
|
||||
.loadAttractionsFromLocation(currentLatLng, searchLatLng, checkingAroundCurrent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
*
|
||||
* @param explorePlacesInfo This variable has placeToCenter list information and distances.
|
||||
*/
|
||||
public void updateMapMarkers(
|
||||
MapController.ExplorePlacesInfo explorePlacesInfo) {
|
||||
if (explorePlacesInfo.mediaList != null) {
|
||||
prepareNearbyBaseMarkers(explorePlacesInfo);
|
||||
} else {
|
||||
lockUnlockNearby(false); // So that new location updates wont 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNearbyBaseMarkerThumbsReady(List<BaseMarker> baseMarkers,
|
||||
ExplorePlacesInfo explorePlacesInfo) {
|
||||
if (null != exploreMapFragmentView) {
|
||||
exploreMapFragmentView.addMarkersToMap(baseMarkers);
|
||||
lockUnlockNearby(false); // So that new location updates wont come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
public View.OnClickListener onSearchThisAreaClicked() {
|
||||
return v -> {
|
||||
// Lock map operations during search this area operation
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false);
|
||||
|
||||
if (searchCloseToCurrentLocation()) {
|
||||
updateMap(LOCATION_SIGNIFICANTLY_CHANGED);
|
||||
} else {
|
||||
updateMap(SEARCH_CUSTOM_AREA);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that we can
|
||||
* continue following our current location again
|
||||
*
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
public boolean searchCloseToCurrentLocation() {
|
||||
if (null == 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);
|
||||
|
||||
if (distance > 2000.0 * 3 / 4) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
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.kvstore.JsonKvStore
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
|
||||
import io.reactivex.Observable
|
||||
import timber.log.Timber
|
||||
import java.lang.reflect.Proxy
|
||||
|
||||
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
|
||||
}
|
||||
} as ExploreMapContract.View
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
if (!exploreMapFragmentView.isNetworkConnectionEstablished()) {
|
||||
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
|
||||
*/
|
||||
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 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 fun lockUnlockNearby(isNearbyLocked: Boolean) {
|
||||
this.isNearbyLocked = isNearbyLocked
|
||||
if (isNearbyLocked) {
|
||||
exploreMapFragmentView.disableFABRecenter()
|
||||
} else {
|
||||
exploreMapFragmentView.enableFABRecenter()
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachView(view: ExploreMapContract.View) {
|
||||
exploreMapFragmentView = view
|
||||
}
|
||||
|
||||
override fun detachView() {
|
||||
exploreMapFragmentView = DUMMY
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets click listener of FAB
|
||||
*/
|
||||
override fun setActionListeners(applicationKvStore: JsonKvStore) {
|
||||
exploreMapFragmentView.setFABRecenterAction {
|
||||
currentLatLng?.let { it1 -> exploreMapFragmentView.recenterMap(it1) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun backButtonClicked(): Boolean {
|
||||
return exploreMapFragmentView.backButtonClicked()
|
||||
}
|
||||
|
||||
fun onMapReady(exploreMapController: ExploreMapController) {
|
||||
this.exploreMapController = exploreMapController
|
||||
exploreMapFragmentView.addSearchThisAreaButtonAction()
|
||||
initializeMapOperations()
|
||||
}
|
||||
|
||||
fun initializeMapOperations() {
|
||||
lockUnlockNearby(false)
|
||||
updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
}
|
||||
|
||||
fun loadAttractionsFromLocation(
|
||||
currentLatLng: LatLng,
|
||||
searchLatLng: LatLng?,
|
||||
checkingAroundCurrent: Boolean
|
||||
): Observable<ExplorePlacesInfo> {
|
||||
return Observable.fromCallable {
|
||||
exploreMapController?.loadAttractionsFromLocation(
|
||||
currentLatLng, searchLatLng, checkingAroundCurrent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates places for custom location, should be used for finding nearby places around a
|
||||
* location where you are not at.
|
||||
*
|
||||
* @param explorePlacesInfo This variable has placeToCenter list information and distances.
|
||||
*/
|
||||
fun updateMapMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) {
|
||||
if (explorePlacesInfo.mediaList != null) {
|
||||
prepareNearbyBaseMarkers(explorePlacesInfo)
|
||||
} else {
|
||||
lockUnlockNearby(false) // So that new location updates won't come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareNearbyBaseMarkers(explorePlacesInfo: MapController.ExplorePlacesInfo) {
|
||||
ExploreMapController.loadAttractionsFromLocationToBaseMarkerOptions(
|
||||
explorePlacesInfo.currentLatLng,
|
||||
explorePlacesInfo.explorePlaceList,
|
||||
exploreMapFragmentView.getContext(),
|
||||
this,
|
||||
explorePlacesInfo
|
||||
)
|
||||
}
|
||||
|
||||
override fun onNearbyBaseMarkerThumbsReady(
|
||||
baseMarkers: List<BaseMarker>,
|
||||
explorePlacesInfo: ExplorePlacesInfo
|
||||
) {
|
||||
exploreMapFragmentView.addMarkersToMap(baseMarkers)
|
||||
lockUnlockNearby(false) // So that new location updates won't come
|
||||
exploreMapFragmentView.setProgressBarVisibility(false)
|
||||
}
|
||||
|
||||
fun onSearchThisAreaClicked(): View.OnClickListener {
|
||||
return View.OnClickListener {
|
||||
// Lock map operations during search this area operation
|
||||
exploreMapFragmentView.setSearchThisAreaButtonVisibility(false)
|
||||
|
||||
if (searchCloseToCurrentLocation()) {
|
||||
updateMap(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
} else {
|
||||
updateMap(LocationChangeType.SEARCH_CUSTOM_AREA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if search this area button is used around our current location, so that we can
|
||||
* continue following our current location again
|
||||
*
|
||||
* @return Returns true if search this area button is used around our current location
|
||||
*/
|
||||
fun searchCloseToCurrentLocation(): Boolean {
|
||||
val lastMapFocus = exploreMapFragmentView.getLastMapFocus() ?: return true
|
||||
|
||||
val myLocation = Location("").apply {
|
||||
latitude = lastMapFocus.latitude
|
||||
longitude = lastMapFocus.longitude
|
||||
}
|
||||
|
||||
val destLocation = Location("").apply {
|
||||
latitude = exploreMapFragmentView.getMapFocus().latitude
|
||||
longitude = exploreMapFragmentView.getMapFocus().longitude
|
||||
}
|
||||
|
||||
return myLocation.distanceTo(destLocation) <= 2000.0 * 3 / 4
|
||||
}
|
||||
}
|
||||
|
|
@ -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?)
|
||||
}
|
||||
|
|
@ -1864,21 +1864,21 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
|
|||
presenter!!.updateMapAndList(locationChangeType)
|
||||
}
|
||||
|
||||
override fun onLocationChangedSignificantly(latLng: LatLng) {
|
||||
override fun onLocationChangedSignificantly(latLng: LatLng?) {
|
||||
Timber.d("Location significantly changed")
|
||||
if (latLng != null) {
|
||||
handleLocationUpdate(latLng, LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationChangedSlightly(latLng: LatLng) {
|
||||
override fun 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, LocationChangeType.LOCATION_SLIGHTLY_CHANGED)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLocationChangedMedium(latLng: LatLng) {
|
||||
override fun onLocationChangedMedium(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, LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)
|
||||
|
|
|
|||
|
|
@ -495,17 +495,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")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue