diff --git a/app/build.gradle b/app/build.gradle index 277bc92b4..ef8929b6a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -114,6 +114,7 @@ android { exclude 'META-INF/LICENSE.txt' exclude 'META-INF/LICENSE' exclude 'META-INF/NOTICE' + exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/rxjava.properties' } @@ -267,7 +268,10 @@ dependencies { implementation 'com.github.Kennyc1012:BottomSheet:2.4.1' implementation 'com.github.nextcloud:PopupBubble:master-SNAPSHOT' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' - implementation 'eu.medsea.mimeutil:mime-util:2.1.3' + + implementation('eu.medsea.mimeutil:mime-util:2.1.3', { + exclude group: 'org.slf4j' + }) implementation "com.afollestad.material-dialogs:core:${materialDialogsVersion}" implementation "com.afollestad.material-dialogs:datetime:${materialDialogsVersion}" @@ -287,6 +291,7 @@ dependencies { implementation 'com.elyeproj.libraries:loaderviewlibrary:2.0.0' implementation 'org.osmdroid:osmdroid-android:6.1.10' + implementation 'fr.dudie:nominatim-api:3.4' testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:3.11.0' diff --git a/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt new file mode 100644 index 000000000..0cd2e8a6a --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt @@ -0,0 +1,40 @@ +package com.nextcloud.talk.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.nextcloud.talk.R +import fr.dudie.nominatim.model.Address + +class GeocodingAdapter(context: Context, val dataSource: List
) : BaseAdapter() { + + private val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + + override fun getCount(): Int { + return dataSource.size + } + + override fun getItem(position: Int): Any { + return dataSource[position] + } + + override fun getItemId(position: Int): Long { + return position.toLong() + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val rowView = inflater.inflate(R.layout.geocoding_item, parent, false) + + val nameView = rowView.findViewById(R.id.name) as TextView + + val address = getItem(position) as Address + nameView.text = address.displayName + + return rowView + } +} + + diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt index da7a76610..cd2450f3f 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/ChatController.kt @@ -824,7 +824,7 @@ class ChatController(args: Bundle) : val bundle = Bundle() bundle.putString(BundleKeys.KEY_ROOM_TOKEN,roomToken) router.pushController( - RouterTransaction.with(LocationController(bundle)) + RouterTransaction.with(LocationPickerController(bundle)) .pushChangeHandler(HorizontalChangeHandler()) .popChangeHandler(HorizontalChangeHandler()) ) diff --git a/app/src/main/java/com/nextcloud/talk/controllers/GeocodingController.kt b/app/src/main/java/com/nextcloud/talk/controllers/GeocodingController.kt new file mode 100644 index 000000000..76624849c --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/controllers/GeocodingController.kt @@ -0,0 +1,199 @@ +package com.nextcloud.talk.controllers + +import android.app.SearchManager +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.os.Build +import android.os.Bundle +import android.text.InputType +import android.util.Log +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.EditorInfo +import android.widget.ListView +import androidx.appcompat.widget.SearchView +import androidx.core.view.MenuItemCompat +import androidx.preference.PreferenceManager +import autodagger.AutoInjector +import butterknife.BindView +import com.nextcloud.talk.R +import com.nextcloud.talk.adapters.GeocodingAdapter +import com.nextcloud.talk.application.NextcloudTalkApplication +import com.nextcloud.talk.controllers.base.BaseController +import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.utils.preferences.AppPreferences +import fr.dudie.nominatim.client.JsonNominatimClient +import fr.dudie.nominatim.model.Address +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.apache.http.client.HttpClient +import org.apache.http.conn.ClientConnectionManager +import org.apache.http.conn.scheme.Scheme +import org.apache.http.conn.scheme.SchemeRegistry +import org.apache.http.conn.ssl.SSLSocketFactory +import org.apache.http.impl.client.DefaultHttpClient +import org.apache.http.impl.conn.SingleClientConnManager +import org.osmdroid.config.Configuration +import javax.inject.Inject + +@AutoInjector(NextcloudTalkApplication::class) +class GeocodingController(args: Bundle) : BaseController(args), SearchView.OnQueryTextListener { + + @Inject + @JvmField + var context: Context? = null + + @Inject + @JvmField + var appPreferences: AppPreferences? = null + + @BindView(R.id.geocoding_results) + @JvmField + var geocodingResultListView: ListView? = null + + var nominatimClient: JsonNominatimClient? = null + + var searchItem: MenuItem? = null + var searchView: SearchView? = null + var query: String? = null + + lateinit var adapter: GeocodingAdapter + private var geocodingResults: List
= ArrayList() + + init { + setHasOptionsMenu(true) + NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this) + Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context)) + query = args.getString(BundleKeys.KEY_GEOCODING_QUERY) + + initAdapter(geocodingResults) + } + + private fun initAdapter(addresses : List
) { + adapter = GeocodingAdapter(context!!, addresses) + geocodingResultListView?.adapter = adapter + } + + override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { + return inflater.inflate(R.layout.controller_geocoding, container, false) + } + + override fun onAttach(view: View) { + super.onAttach(view) + initGeocoder() + if (!query.isNullOrEmpty()) { + searchLocation() + } else { + Log.e(TAG, "search string that was passed to GeocodingController was null or empty") + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + inflater.inflate(R.menu.menu_geocoding, menu) + searchItem = menu.findItem(R.id.geocoding_action_search) + initSearchView() + + searchItem?.expandActionView() + searchView?.setQuery(query, false) + searchView?.clearFocus() + } + + override fun onPrepareOptionsMenu(menu: Menu) { + super.onPrepareOptionsMenu(menu) + hideSearchBar() + actionBar.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent))) + actionBar.title = "Share location" + } + + override fun onQueryTextSubmit(query: String?): Boolean { + this.query = query + searchLocation() + searchView?.clearFocus() + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + return true + } + + private fun initSearchView() { + if (activity != null) { + val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager + if (searchItem != null) { + searchView = MenuItemCompat.getActionView(searchItem) as SearchView + searchView?.setMaxWidth(Int.MAX_VALUE) + searchView?.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER) + var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) { + imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } + searchView?.setImeOptions(imeOptions) + searchView?.setQueryHint(resources!!.getString(R.string.nc_search)) + searchView?.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName)) + searchView?.setOnQueryTextListener(this) + + + searchItem?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { + override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean { + return true + } + + override fun onMenuItemActionCollapse(menuItem: MenuItem): Boolean { + router.popCurrentController() + return true + } + }) + } + } + } + + private fun initGeocoder() { + val registry = SchemeRegistry() + registry.register(Scheme("https", SSLSocketFactory.getSocketFactory(), 443)) + val connexionManager: ClientConnectionManager = SingleClientConnManager(null, registry) + val httpClient: HttpClient = DefaultHttpClient(connexionManager, null) + val baseUrl = "https://nominatim.openstreetmap.org/" + val email = "android@nextcloud.com" + nominatimClient = JsonNominatimClient(baseUrl, httpClient, email) + } + + private fun searchLocation(): Boolean { + CoroutineScope(IO).launch { + executeGeocodingRequest() + } + return true + } + + private suspend fun executeGeocodingRequest() { + var results: ArrayList
= ArrayList() + try { + results = nominatimClient!!.search(query) as ArrayList
+ for (address in results) { + Log.d(TAG, address.displayName) + Log.d(TAG, address.latitude.toString()) + Log.d(TAG, address.longitude.toString()) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to get geocoded addresses", e) + } + updateResultsOnMainThread(results) + } + + private suspend fun updateResultsOnMainThread(results: ArrayList
) { + withContext(Main) { + initAdapter(results) + } + } + + companion object { + private val TAG = "GeocodingController" + } +} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/LocationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt similarity index 78% rename from app/src/main/java/com/nextcloud/talk/controllers/LocationController.kt rename to app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt index bce09f74c..b559f9078 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/LocationController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/LocationPickerController.kt @@ -1,31 +1,40 @@ package com.nextcloud.talk.controllers import android.Manifest +import android.app.SearchManager import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.ColorDrawable import android.os.Build import android.os.Bundle +import android.text.InputType import android.util.Log import android.view.LayoutInflater import android.view.Menu import android.view.MenuInflater +import android.view.MenuItem import android.view.View import android.view.ViewGroup +import android.view.inputmethod.EditorInfo import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast +import androidx.appcompat.widget.SearchView import androidx.cardview.widget.CardView import androidx.core.content.PermissionChecker +import androidx.core.view.MenuItemCompat import androidx.preference.PreferenceManager import autodagger.AutoInjector import butterknife.BindView +import com.bluelinelabs.conductor.RouterTransaction +import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.controllers.base.BaseController import com.nextcloud.talk.models.json.generic.GenericOverall import com.nextcloud.talk.utils.ApiUtils +import com.nextcloud.talk.utils.bundle.BundleKeys import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN import com.nextcloud.talk.utils.database.user.UserUtils import com.nextcloud.talk.utils.preferences.AppPreferences @@ -47,7 +56,7 @@ import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) -class LocationController(args: Bundle) : BaseController(args) { +class LocationPickerController(args: Bundle) : BaseController(args), SearchView.OnQueryTextListener { @Inject lateinit var ncApi: NcApi @@ -87,6 +96,8 @@ class LocationController(args: Bundle) : BaseController(args) { var moveToCurrentLocationWasClicked: Boolean = true var readyToShareLocation: Boolean = false + var searchItem: MenuItem? = null + var searchView: SearchView? = null init { setHasOptionsMenu(true) @@ -107,31 +118,24 @@ class LocationController(args: Bundle) : BaseController(args) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) - inflater.inflate(R.menu.menu_conversation_plus_filter, menu) - // searchItem = menu.findItem(R.id.action_search) - // initSearchView() + inflater.inflate(R.menu.menu_locationpicker, menu) + searchItem = menu.findItem(R.id.location_action_search) + initSearchView() } override fun onPrepareOptionsMenu(menu: Menu) { super.onPrepareOptionsMenu(menu) Log.d(TAG, "onPrepareOptionsMenu") hideSearchBar() - actionBar.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent))); + actionBar.setIcon(ColorDrawable(resources!!.getColor(android.R.color.transparent))) actionBar.title = "Share location" - - // searchView = MenuItemCompat.getActionView(searchItem) as SearchView - // showShareToScreen = !shareToScreenWasShown && hasActivityActionSendIntent() - // if (showShareToScreen) { - // hideSearchBar() - // actionBar.setTitle(R.string.send_to_three_dots) - // } } override fun onViewBound(view: View) { setCurrentLocationDescription() shareLocation?.isClickable = false shareLocation?.setOnClickListener { - if(readyToShareLocation){ + if (readyToShareLocation) { shareLocation() } else { Log.d(TAG, "readyToShareLocation was false while user tried to share location.") @@ -139,6 +143,44 @@ class LocationController(args: Bundle) : BaseController(args) { } } + private fun initSearchView() { + if (activity != null) { + val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager + if (searchItem != null) { + searchView = MenuItemCompat.getActionView(searchItem) as SearchView + searchView?.setMaxWidth(Int.MAX_VALUE) + searchView?.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER) + var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) { + imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING + } + searchView?.setImeOptions(imeOptions) + searchView?.setQueryHint(resources!!.getString(R.string.nc_search)) + if (searchManager != null) { + searchView?.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName)) + } + searchView?.setOnQueryTextListener(this) + } + } + } + + override fun onQueryTextSubmit(query: String?): Boolean { + if (!query.isNullOrEmpty()) { + val bundle = Bundle() + bundle.putString(BundleKeys.KEY_GEOCODING_QUERY, query) + router.pushController( + RouterTransaction.with(GeocodingController(bundle)) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler()) + ) + } + return true + } + + override fun onQueryTextChange(newText: String?): Boolean { + return true + } + private fun initMap() { if (!isFineLocationPermissionGranted()) { requestFineLocationPermission() @@ -279,7 +321,7 @@ class LocationController(args: Bundle) : BaseController(args) { } companion object { - private val TAG = "LocationController" + private val TAG = "LocationPickerController" private val REQUEST_PERMISSIONS_REQUEST_CODE = 1; } } diff --git a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt index 39ea5d998..bc66d2691 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/bundle/BundleKeys.kt @@ -66,4 +66,5 @@ object BundleKeys { val KEY_FILE_ID = "KEY_FILE_ID" val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID" val KEY_SHARED_TEXT = "KEY_SHARED_TEXT" + val KEY_GEOCODING_QUERY = "KEY_GEOCODING_QUERY" } diff --git a/app/src/main/res/layout/controller_geocoding.xml b/app/src/main/res/layout/controller_geocoding.xml new file mode 100644 index 000000000..f5f71c5a9 --- /dev/null +++ b/app/src/main/res/layout/controller_geocoding.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/geocoding_item.xml b/app/src/main/res/layout/geocoding_item.xml new file mode 100644 index 000000000..f04ef3cff --- /dev/null +++ b/app/src/main/res/layout/geocoding_item.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_geocoding.xml b/app/src/main/res/menu/menu_geocoding.xml new file mode 100644 index 000000000..02eb6e210 --- /dev/null +++ b/app/src/main/res/menu/menu_geocoding.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/app/src/main/res/menu/menu_locationpicker.xml b/app/src/main/res/menu/menu_locationpicker.xml new file mode 100644 index 000000000..b8753d983 --- /dev/null +++ b/app/src/main/res/menu/menu_locationpicker.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 5cda48c58..592da6077 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -38,6 +38,8 @@ 6dp 8dp + 18sp + 192dp 80dp