diff --git a/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt index 8350da0b0f..603c73537e 100644 --- a/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt +++ b/app/src/main/java/com/nextcloud/talk/adapters/GeocodingAdapter.kt @@ -20,6 +20,7 @@ package com.nextcloud.talk.adapters +import android.annotation.SuppressLint import android.content.Context import android.view.LayoutInflater import android.view.View @@ -29,13 +30,19 @@ import androidx.recyclerview.widget.RecyclerView import com.nextcloud.talk.R import fr.dudie.nominatim.model.Address -class GeocodingAdapter(private val context: Context, private val dataSource: List
) : +class GeocodingAdapter(private val context: Context, private var dataSource: List
) : RecyclerView.Adapter() { interface OnItemClickListener { fun onItemClick(position: Int) } + @SuppressLint("NotifyDataSetChanged") + fun updateData(data: List
) { + this.dataSource = data + notifyDataSetChanged() + } + private var listener: OnItemClickListener? = null fun setOnItemClickListener(listener: OnItemClickListener) { this.listener = listener diff --git a/app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt b/app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt index ff08ef6e8e..123b8ee10b 100644 --- a/app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt +++ b/app/src/main/java/com/nextcloud/talk/location/GeocodingActivity.kt @@ -32,12 +32,11 @@ import android.view.Menu import android.view.MenuItem import android.view.inputmethod.EditorInfo import androidx.appcompat.widget.SearchView -import androidx.core.view.MenuItemCompat +import androidx.lifecycle.ViewModelProvider import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import autodagger.AutoInjector -import com.google.android.material.snackbar.Snackbar import com.nextcloud.talk.R import com.nextcloud.talk.activities.BaseActivity import com.nextcloud.talk.adapters.GeocodingAdapter @@ -45,21 +44,16 @@ import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.databinding.ActivityGeocodingBinding import com.nextcloud.talk.utils.bundle.BundleKeys +import com.nextcloud.talk.viewmodels.GeoCodingViewModel import fr.dudie.nominatim.client.TalkJsonNominatimClient 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 okhttp3.OkHttpClient import org.osmdroid.config.Configuration import javax.inject.Inject @AutoInjector(NextcloudTalkApplication::class) class GeocodingActivity : - BaseActivity(), - SearchView.OnQueryTextListener { + BaseActivity() { private lateinit var binding: ActivityGeocodingBinding @@ -70,15 +64,15 @@ class GeocodingActivity : lateinit var okHttpClient: OkHttpClient lateinit var roomToken: String - var nominatimClient: TalkJsonNominatimClient? = null + private var nominatimClient: TalkJsonNominatimClient? = null - var searchItem: MenuItem? = null + private var searchItem: MenuItem? = null var searchView: SearchView? = null - var query: String? = null lateinit var adapter: GeocodingAdapter private var geocodingResults: List
= ArrayList() private lateinit var recyclerView: RecyclerView + private lateinit var viewModel: GeoCodingViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -92,11 +86,27 @@ class GeocodingActivity : Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context)) roomToken = intent.getStringExtra(BundleKeys.KEY_ROOM_TOKEN)!! - query = intent.getStringExtra(BundleKeys.KEY_GEOCODING_QUERY) + recyclerView = findViewById(R.id.geocoding_results) recyclerView.layoutManager = LinearLayoutManager(this) adapter = GeocodingAdapter(this, geocodingResults) recyclerView.adapter = adapter + viewModel = ViewModelProvider(this)[GeoCodingViewModel::class.java] + + var query = viewModel.getQuery() + if (query.isEmpty() && intent.hasExtra(BundleKeys.KEY_GEOCODING_QUERY)) { + query = intent.getStringExtra(BundleKeys.KEY_GEOCODING_QUERY).orEmpty() + viewModel.setQuery(query) + } + val savedResults = viewModel.getGeocodingResults() + initAdapter(savedResults) + viewModel.getGeocodingResultsLiveData().observe(this) { results -> + geocodingResults = results + adapter.updateData(results) + } + val baseUrl = getString(R.string.osm_geocoder_url) + val email = context.getString(R.string.osm_geocoder_contact) + nominatimClient = TalkJsonNominatimClient(baseUrl, okHttpClient, email) } override fun onStart() { @@ -108,12 +118,11 @@ class GeocodingActivity : override fun onResume() { super.onResume() - if (!query.isNullOrEmpty()) { - searchLocation() + if (viewModel.getQuery().isNotEmpty() && adapter.itemCount == 0) { + viewModel.searchLocation() } else { Log.e(TAG, "search string that was passed to GeocodingController was null or empty") } - adapter.setOnItemClickListener(object : GeocodingAdapter.OnItemClickListener { override fun onItemClick(position: Int) { val address: Address = adapter.getItem(position) as Address @@ -125,6 +134,7 @@ class GeocodingActivity : startActivity(intent) } }) + searchView?.setQuery(viewModel.getQuery(), false) } private fun setupActionBar() { @@ -160,38 +170,43 @@ class GeocodingActivity : menuInflater.inflate(R.menu.menu_geocoding, menu) searchItem = menu.findItem(R.id.geocoding_action_search) initSearchView() - searchItem?.expandActionView() - searchView?.setQuery(query, false) + searchView?.setQuery(viewModel.getQuery(), false) searchView?.clearFocus() return true } - 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() { val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager if (searchItem != null) { - searchView = MenuItemCompat.getActionView(searchItem) as SearchView + searchView = searchItem!!.actionView as SearchView? + searchView?.maxWidth = Int.MAX_VALUE searchView?.inputType = 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) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) { imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING } searchView?.imeOptions = imeOptions searchView?.queryHint = resources!!.getString(R.string.nc_search) searchView?.setSearchableInfo(searchManager.getSearchableInfo(componentName)) - searchView?.setOnQueryTextListener(this) + searchView?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + viewModel.setQuery(query) + viewModel.searchLocation() + searchView?.clearFocus() + return true + } + + override fun onQueryTextChange(query: String): Boolean { + // This is a workaround to not set viewModel data when onQueryTextChange is triggered on startup + // Otherwise it would be set to an empty string. + if (searchView?.width!! > 0) { + viewModel.setQuery(query) + } + return true + } + }) searchItem?.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { override fun onMenuItemActionExpand(menuItem: MenuItem): Boolean { @@ -215,37 +230,7 @@ class GeocodingActivity : nominatimClient = TalkJsonNominatimClient(baseUrl, okHttpClient, email) } - private fun searchLocation(): Boolean { - CoroutineScope(IO).launch { - executeGeocodingRequest() - } - return true - } - - @Suppress("Detekt.TooGenericExceptionCaught") - 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) - Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show() - } - updateResultsOnMainThread(results) - } - - private suspend fun updateResultsOnMainThread(results: ArrayList
) { - withContext(Main) { - initAdapter(results) - } - } - companion object { - private val TAG = GeocodingActivity::class.java.simpleName + val TAG = GeocodingActivity::class.java.simpleName } } diff --git a/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt b/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt new file mode 100644 index 0000000000..d66d9059c8 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/viewmodels/GeoCodingViewModel.kt @@ -0,0 +1,85 @@ +/* + * Nextcloud Talk application + * + * @author Samanwith KSN + * Copyright (C) 2023 Samanwith KSN + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.nextcloud.talk.viewmodels + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.nextcloud.talk.activities.CallActivity.Companion.TAG +import com.nextcloud.talk.location.GeocodingActivity +import fr.dudie.nominatim.client.TalkJsonNominatimClient +import fr.dudie.nominatim.model.Address +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import java.io.IOException + +class GeoCodingViewModel : ViewModel() { + private val geocodingResultsLiveData = MutableLiveData>() + private val nominatimClient: TalkJsonNominatimClient + private val okHttpClient: OkHttpClient = OkHttpClient.Builder().build() + private var geocodingResults: List
= ArrayList() + private var query: String = "" + fun getGeocodingResultsLiveData(): LiveData> { + return geocodingResultsLiveData + } + + fun getQuery(): String { + return query + } + + fun setQuery(query: String) { + this.query = query + } + + fun getGeocodingResults(): List
{ + return geocodingResults + } + + init { + nominatimClient = TalkJsonNominatimClient( + "https://nominatim.openstreetmap.org/", + okHttpClient, + " android@nextcloud.com" + ) + } + + fun searchLocation() { + if (query.isNotEmpty()) { + CoroutineScope(Dispatchers.IO).launch { + try { + val results = nominatimClient.search(query) as ArrayList
+ for (address in results) { + Log.d(GeocodingActivity.TAG, address.displayName) + Log.d(GeocodingActivity.TAG, address.latitude.toString()) + Log.d(GeocodingActivity.TAG, address.longitude.toString()) + } + geocodingResults = results + geocodingResultsLiveData.postValue(results) + } catch (e: IOException) { + Log.e(TAG, "Failed to get geocoded addresses", e) + } + } + } + } +}