Author: Adam <git@apiote.xyz>
choose Bimba server and feeds
%!v(PANIC=String method: strings: negative Repeat count)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8d4aaff42578cccac40b793bd26d41d6b93a4b93..d49ebffd9f8b3c10f8e253c32395f43bf2f20289 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,6 +18,18 @@ android:supportsRtl="true" android:theme="@style/Theme.Bimba.Style" tools:targetApi="31"> <activity + android:name=".feeds.FeedChooserActivity" + android:exported="false" /> + <activity + android:name=".FirstRunActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity android:name=".departures.DeparturesActivity" android:exported="false" /> <activity @@ -27,13 +39,8 @@ android:label="@string/title_activity_results" android:theme="@style/Theme.Bimba.Style" /> <activity android:name=".dashboard.MainActivity" - android:exported="true" + android:exported="false" android:windowSoftInputMode="adjustPan"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> </activity> </application> diff --git a/app/src/main/java/ml/adamsprogs/bimba/FirstRunActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/FirstRunActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..7cd423c91d4152df677f57e69b27d07bb473d234 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/FirstRunActivity.kt @@ -0,0 +1,24 @@ +package ml.adamsprogs.bimba + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import ml.adamsprogs.bimba.dashboard.MainActivity +import ml.adamsprogs.bimba.feeds.FeedChooserActivity + +class FirstRunActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + super.onCreate(savedInstanceState) + + val preferences = getSharedPreferences("shp", MODE_PRIVATE) + val intent = if (preferences.getBoolean("firstRun", true)) { + Intent(this, FeedChooserActivity::class.java) + } else { + Intent(this, MainActivity::class.java) + } + startActivity(intent) + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt index 76f2a4096a43a1b61b786cf62b0330a38245b88c..8141e3022c5c7737454c10e19ec6c0765c430d8a 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Api.kt @@ -8,28 +8,53 @@ import java.net.HttpURLConnection import java.net.URL import java.net.URLEncoder -suspend fun queryItems(query: String): InputStream? { - return request("https://bimba.apiote.xyz", "poznan_ztm", "items", mapOf("q" to query)) +data class Server(val host: String, val token: String, val feeds: String) + +@Suppress("BlockingMethodInNonBlockingContext") +suspend fun getFeeds(server: Server): InputStream? { // todo if 401 then needs token + return rawRequest(URL("${hostWithScheme(server.host)}/api/"), server) } -suspend fun locateItems(plusCode: String): InputStream? { - return request("https://bimba.apiote.xyz", "poznan_ztm", "items", mapOf("near" to plusCode)) +suspend fun queryItems(server: Server, query: String): InputStream? { + return request(server, "items", mapOf("q" to query)) } -suspend fun getDepartures(stop: String, line: String?): InputStream? { - return request("https://bimba.apiote.xyz", "poznan_ztm", "departures", mapOf("code" to stop)) +suspend fun locateItems(server: Server, plusCode: String): InputStream? { + return request(server, "items", mapOf("near" to plusCode)) +} + +suspend fun getDepartures(server: Server, stop: String, line: String?): InputStream? { + val params = mutableMapOf("code" to stop) + if (line != null) { + params["line"] = line + } + return request(server, "departures", params) +} + +@Suppress("BlockingMethodInNonBlockingContext") +suspend fun rawRequest(url: URL, server: Server): InputStream? { + return withContext(Dispatchers.IO) { + val c = (url.openConnection() as HttpURLConnection).apply { + setRequestProperty("X-Bimba-Token", server.token) + } + try { + c.inputStream + } catch (e: Exception) { + Log.e("request", e.stackTraceToString()) + null + } + } } @Suppress("BlockingMethodInNonBlockingContext") suspend fun request( - host: String, - feed: String, + server: Server, resource: String, params: Map<String, String> ): InputStream? { return withContext(Dispatchers.IO) { val url = URL( - "$host/api/$feed/$resource${ + "${hostWithScheme(server.host)}/api/${server.feeds}/$resource${ params.map { "${it.key}=${ URLEncoder.encode( @@ -40,14 +65,13 @@ }" }.joinToString("&", "?") }" ) - val c = (url.openConnection() as HttpURLConnection).apply { - setRequestProperty("X-Bimba-Token", "ef0179272e7270e1a2da1710a8ba24e1") - } - try { - c.inputStream - } catch (e: Exception) { - Log.e("request", e.stackTraceToString()) - null - } + rawRequest(url, server) } -} \ No newline at end of file +} + +fun hostWithScheme(host: String): String = + if (host.startsWith("http://") or host.startsWith("https://")) { + host + } else { + "https://$host" + } \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt index 30173fc3e7147a28c49168583853b62f3d1aab17..c909c287b157221e2c2104426ca740a7aca8657f 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Responses.kt @@ -101,6 +101,47 @@ } } } +interface FeedsResponse { + companion object { + fun unmarshal(stream: InputStream): FeedsResponse { + val reader = Reader(stream) + when (reader.readUInt()) { + 0UL -> { + TODO("error response") + } + 1UL -> { + return FeedsSuccess.unmarshal(stream) + } + else -> { + TODO("throw unknown tag") + } + } + } + } +} + +data class FeedsSuccess( + val adminContact: String, + val rateLimited: Boolean, + val private: Boolean, + val feeds: List<FeedInfo> +) : FeedsResponse { + companion object { + fun unmarshal(stream: InputStream): FeedsSuccess { + val feeds = mutableListOf<FeedInfo>() + val reader = Reader(stream) + val adminContact = reader.readString() + val rateLimited = reader.readBoolean() + val private = reader.readBoolean() + val itemsNum = reader.readUInt() + for (i in 0UL until itemsNum) { + feeds.add(FeedInfo.unmarshal(stream)) + } + return FeedsSuccess(adminContact, rateLimited, private, feeds) + } + } +} + data class Error(val message: String) : ItemsResponse, DeparturesResponse { } \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt b/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt index dc1bae961eb42ae229d5e6d66a7935b31f1db9bd..f3f8729221329c304cfa34577e5f0c402b4351de 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/api/Structs.kt @@ -12,6 +12,27 @@ import kotlin.math.abs import kotlin.math.pow import kotlin.math.roundToInt +data class FeedInfo( + val name: String, + val id: String, + val attribution: String, + val description: String, + val lastUpdate: String // todo date from RFC +) { + companion object { + fun unmarshal(stream: InputStream): FeedInfo { + val reader = Reader(stream) + return FeedInfo( + reader.readString(), + reader.readString(), + reader.readString(), + reader.readString(), + reader.readString() + ) + } + } +} + data class Alert( val header: String, val Description: String, diff --git a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt index bb2fdec0e9a13aa35dc468fc7bbfd1c8be215d82..21d9a6aed2b9609ca89d5ba99509f1f4eee9a4ba 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeFragment.kt @@ -1,5 +1,6 @@ package ml.adamsprogs.bimba.dashboard.ui.home +import android.content.SharedPreferences import android.os.Bundle import android.os.Handler import android.os.Looper @@ -8,6 +9,7 @@ import android.text.TextWatcher import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import com.mancj.materialsearchbar.MaterialSearchBar @@ -15,6 +17,7 @@ import com.mancj.materialsearchbar.MaterialSearchBar.BUTTON_NAVIGATION import ml.adamsprogs.bimba.search.BimbaSuggestionsAdapter import ml.adamsprogs.bimba.dashboard.MainActivity import ml.adamsprogs.bimba.api.Item +import ml.adamsprogs.bimba.api.Server import ml.adamsprogs.bimba.databinding.FragmentHomeBinding class HomeFragment : Fragment() { @@ -34,11 +37,13 @@ _binding = FragmentHomeBinding.inflate(inflater, container, false) val root: View = binding.root + val shp = requireContext().getSharedPreferences("shp", AppCompatActivity.MODE_PRIVATE) + binding.searchBar.lastSuggestions = listOf<Item>() homeViewModel.items.observe(viewLifecycleOwner) { binding.searchBar.updateLastSuggestions(it.take(6)) // xxx workaround for suggestions behind navbar; should be paginated server-side } - binding.searchBar.addTextChangeListener(SearchBarWatcher(homeViewModel)) + binding.searchBar.addTextChangeListener(SearchBarWatcher(homeViewModel, shp)) // todo pass shp and host binding.searchBar.setOnSearchActionListener(object : MaterialSearchBar.OnSearchActionListener { override fun onButtonClicked(buttonCode: Int) { when (buttonCode) { @@ -77,7 +82,7 @@ _binding = null } } -class SearchBarWatcher(private val homeViewModel: HomeViewModel) : +class SearchBarWatcher(private val homeViewModel: HomeViewModel, private val shp: SharedPreferences) : TextWatcher { private val handler = Handler(Looper.getMainLooper()) private var workRunnable = Runnable {} @@ -91,8 +96,13 @@ override fun afterTextChanged(s: Editable?) { handler.removeCallbacks(workRunnable) workRunnable = Runnable { + val host = shp.getString("host", "bimba.apiote.xyz")!! val text = s.toString() - homeViewModel.getItems(text) + homeViewModel.getItems( + Server( + host, shp.getString("token", "")!!, + shp.getString("${host}_feeds", "")!! + ), text) } handler.postDelayed(workRunnable, 1000) // todo make good time (probably between 500, 1000ms) } diff --git a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt index 38855e9f151ec3819b86e03416efd65de74c9048..c6d5594594dd7abd59a9cb183108acb16517c41d 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/dashboard/ui/home/HomeViewModel.kt @@ -15,9 +15,9 @@ private val mutableItems = MutableLiveData<List<Item>>() val items: LiveData<List<Item>> = mutableItems - fun getItems(query: String) { + fun getItems(server: Server, query: String) { viewModelScope.launch { - val itemsStream = queryItems(query) + val itemsStream = queryItems(server, query) if (itemsStream == null) { // todo Toast.makeText(context, "Couldn't get response", Toast.LENGTH_SHORT).show() } else { diff --git a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt index 001f1d8342f06e3828826971892a4b4b6db61ede..ca814dc1cebfd445554539279afac988f8e8458d 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/departures/DeparturesActivity.kt @@ -39,14 +39,26 @@ } binding.departuresRecycler.adapter = adapter WindowCompat.setDecorFitsSystemWindows(window, false) + val shp = getSharedPreferences("shp", MODE_PRIVATE) + val host = shp.getString("host", "bimba.apiote.xyz")!! + // todo check every 30s MainScope().launch { intent?.extras?.getString("code")?.let { - val departuresStream = getDepartures(it, null) + val departuresStream = getDepartures( + Server( + host, shp.getString("token", "")!!, + shp.getString("${host}_feeds", "")!! + ), it, null + ) if (departuresStream == null) { // todo show empty state - Toast.makeText(this@DeparturesActivity as Context, "Couldn't get response", Toast.LENGTH_SHORT).show() + Toast.makeText( + this@DeparturesActivity as Context, + "Couldn't get response", + Toast.LENGTH_SHORT + ).show() } else { updateItems(unmarshallDepartureResponse(departuresStream)) } diff --git a/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedChooserActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedChooserActivity.kt new file mode 100644 index 0000000000000000000000000000000000000000..be80cba347e523e88e871490b9b4f1ca371bc0e1 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedChooserActivity.kt @@ -0,0 +1,152 @@ +package ml.adamsprogs.bimba.feeds +// git:fixup feeds +import android.content.Context +import android.content.Intent +import android.content.SharedPreferences +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.core.content.edit +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.api.FeedsResponse +import ml.adamsprogs.bimba.api.FeedsSuccess +import ml.adamsprogs.bimba.api.Server +import ml.adamsprogs.bimba.api.getFeeds +import ml.adamsprogs.bimba.dashboard.MainActivity +import ml.adamsprogs.bimba.databinding.ActivityFeedChooserBinding +import java.io.InputStream + + +class FeedChooserActivity : AppCompatActivity() { + private var _binding: ActivityFeedChooserBinding? = null + private val binding get() = _binding!! + + private lateinit var adapter: BimbaFeedInfoAdapter + private lateinit var preferences: SharedPreferences + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + _binding = ActivityFeedChooserBinding.inflate(layoutInflater) + setContentView(binding.root) + + preferences = getSharedPreferences("shp", MODE_PRIVATE) + + setUpRecycler() + + binding.button.setOnClickListener { + getServer( + binding.serverField.editText!!.text.toString(), + binding.tokenField.editText!!.text.toString() + ) + } + + binding.serverField.editText!!.apply { + setText(preferences.getString("host", "bimba.apiote.xyz")) + addTextChangedListener { editable -> + binding.button.apply { + text = context.getString(R.string.cont) + isEnabled = !editable.isNullOrBlank() + setOnClickListener { + getServer( + editable!!.toString(), + binding.tokenField.editText!!.text.toString() + ) + } + } + } + } + } + + private fun setUpRecycler() { + binding.resultsRecycler.layoutManager = LinearLayoutManager(this) + adapter = BimbaFeedInfoAdapter(layoutInflater, listOf(), this) { + Log.v("FeedInfo", "clicked: $it") + // todo show bottom sheet + } + binding.resultsRecycler.adapter = adapter + } + + private fun getServer(host: String, token: String) { + assert(binding.serverField.editText!!.text.isNotEmpty()) + + preferences.edit(true) { + putString("server", host) + putString("token", token) + } + + binding.circularProgressIndicator.visibility = View.VISIBLE + binding.resultsRecycler.visibility = View.GONE + binding.feedInfo.visibility = View.GONE + + MainScope().launch { + val feedsStream = getFeeds(Server(host, token, "")) + if (feedsStream == null) { + // todo(error-handling) show empty state + Toast.makeText( + this@FeedChooserActivity as Context, + "Couldn't get response", + Toast.LENGTH_SHORT + ).show() + } else { + updateItems(unmarshallFeedsResponse(feedsStream)) + binding.button.apply { + text = context.getString(R.string.save) + setOnClickListener { + moveOn() + } + } + } + } + } + + private fun moveOn() { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + val wasFirstRun = preferences.getBoolean("firstRun", true) + preferences.edit(true) { + putBoolean("firstRun", false) + } + if (wasFirstRun) { + finish() + } + } + + private suspend fun unmarshallFeedsResponse(stream: InputStream): FeedsSuccess { + return withContext(Dispatchers.IO) { + when (val response = FeedsResponse.unmarshal(stream)) { + is FeedsSuccess -> { + return@withContext response + } + else -> { + TODO("Error response") + } + } + } + } + + private fun updateItems(response: FeedsSuccess) { + Log.v("items", "${response.adminContact} ${response.rateLimited}") + response.feeds.forEach { + Log.v("items", "$it") + } + binding.circularProgressIndicator.visibility = View.GONE + binding.resultsRecycler.visibility = View.VISIBLE + binding.feedInfo.visibility = View.VISIBLE + binding.feedInfo.text = + if(response.rateLimited) { + getString(R.string.server_info_rate_limited, response.adminContact) + } else { + getString(R.string.server_info_not_rate_limited, response.adminContact) + } + + adapter.update(response.feeds) + } +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedInfos.kt b/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedInfos.kt new file mode 100644 index 0000000000000000000000000000000000000000..09707bfb08879bbb89bbc881b3e715bd43f2b275 --- /dev/null +++ b/app/src/main/java/ml/adamsprogs/bimba/feeds/FeedInfos.kt @@ -0,0 +1,77 @@ +package ml.adamsprogs.bimba.feeds +// git:fixup feeds +import android.content.Context +import android.content.Context.MODE_PRIVATE +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.edit +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.materialswitch.MaterialSwitch +import ml.adamsprogs.bimba.R +import ml.adamsprogs.bimba.api.FeedInfo + + +class BimbaFeedInfoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val root: View = itemView.findViewById(R.id.feedinfo) + val switch: MaterialSwitch = itemView.findViewById(R.id.feed_switch) + val name: TextView = itemView.findViewById(R.id.feed_name) + + companion object { + fun bind( + feed: FeedInfo, + context: Context, + holder: BimbaFeedInfoViewHolder?, + onClickListener: (FeedInfo) -> Unit + ) { + val shp = context.getSharedPreferences("shp", MODE_PRIVATE) + val host = shp.getString("host", "bimba.apiote.xyz")!! + val enabledFeeds = + shp.getString("${host}_feeds", "")!!.split(",").associateWith { }.toMutableMap() + + holder?.root?.setOnClickListener { + onClickListener(feed) + } + holder?.name?.text = feed.name + holder?.switch?.apply { + isChecked = feed.id in enabledFeeds + setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + enabledFeeds[feed.id] = Unit + } else { + enabledFeeds.remove(feed.id) + } + shp.edit(true) { + putString("${host}_feeds", enabledFeeds.map { it.key }.filter { it != "" }.joinToString(separator = ",")) + } + } + } + } + } +} + +class BimbaFeedInfoAdapter( + private val inflater: LayoutInflater, + private var feeds: List<FeedInfo>, + private val context: Context, + private val onClickListener: ((FeedInfo) -> Unit) +) : + RecyclerView.Adapter<BimbaFeedInfoViewHolder>() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BimbaFeedInfoViewHolder { + val rowView = inflater.inflate(R.layout.feedinfo, parent, false) + return BimbaFeedInfoViewHolder(rowView) + } + + override fun onBindViewHolder(holder: BimbaFeedInfoViewHolder, position: Int) { + BimbaFeedInfoViewHolder.bind(feeds[position], context, holder, onClickListener) + } + + override fun getItemCount(): Int = feeds.size + + fun update(items: List<FeedInfo>) { + feeds = items + notifyDataSetChanged() + } + +} \ No newline at end of file diff --git a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt index 09164011bdd3a00a8bb7a32d8a8428619fd93deb..eeba8562591e33fb4434dde98e994810f5d18176 100644 --- a/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt +++ b/app/src/main/java/ml/adamsprogs/bimba/search/ResultsActivity.kt @@ -3,6 +3,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.location.Location import android.location.LocationListener import android.location.LocationManager @@ -31,10 +32,15 @@ private val binding get() = _binding!! private lateinit var adapter: BimbaResultsAdapter + private lateinit var shp: SharedPreferences + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) _binding = ActivityResultsBinding.inflate(layoutInflater) setContentView(binding.root) + + shp = getSharedPreferences("shp", MODE_PRIVATE) + binding.resultsRecycler.layoutManager = LinearLayoutManager(this) adapter = BimbaResultsAdapter(layoutInflater, this, listOf()) { when (it) { @@ -64,7 +70,13 @@ } Mode.MODE_SEARCH -> { val query = intent.extras?.getString("query")!! supportActionBar?.title = "Results for ‘$query’" - getItemsByQuery(query) + val host = shp.getString("host", "bimba.apiote.xyz")!! + getItemsByQuery( + Server( + host, shp.getString("token", "")!!, + shp.getString("${host}_feeds", "")!! + ), query + ) } } } @@ -83,7 +95,13 @@ Log.v("Location", "found $location") val code = OpenLocationCode.encode(location.latitude, location.longitude) val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager.removeUpdates(this) - getItemsByLocation(code) + val host = shp.getString("host", "bimba.apiote.xyz")!! + getItemsByLocation( + Server( + host, shp.getString("token", "")!!, + shp.getString("${host}_feeds", "")!! + ), code + ) } override fun onDestroy() { // todo also on hide this activity (onPause?) @@ -92,9 +110,9 @@ val locationManager = getSystemService(Context.LOCATION_SERVICE) as LocationManager locationManager.removeUpdates(this) } - private fun getItemsByQuery(query: String) { + private fun getItemsByQuery(server: Server, query: String) { MainScope().launch { - val itemsStream = queryItems(query) + val itemsStream = queryItems(server, query) if (itemsStream == null) { // todo show empty state } else { @@ -104,10 +122,10 @@ Log.v("RESPONSE", "getItemsByQuery") } } - private fun getItemsByLocation(plusCode: String) { + private fun getItemsByLocation(server: Server, plusCode: String) { Log.v("RESPONSE", "getting ItemsByLocation") MainScope().launch { - val itemsStream = locateItems(plusCode) + val itemsStream = locateItems(server, plusCode) if (itemsStream == null) { // todo show empty state } else { diff --git a/app/src/main/res/layout/activity_feed_chooser.xml b/app/src/main/res/layout/activity_feed_chooser.xml new file mode 100644 index 0000000000000000000000000000000000000000..95a30db5071a769fd056dba5d8a314522377c934 --- /dev/null +++ b/app/src/main/res/layout/activity_feed_chooser.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- git:fixup feeds --> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".feeds.FeedChooserActivity"> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/server_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:hint="Bimba server" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <com.google.android.material.textfield.TextInputEditText + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/token_field" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:hint="Token" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/server_field"> + + <com.google.android.material.textfield.TextInputEditText + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </com.google.android.material.textfield.TextInputLayout> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:text="Continue" + app:layout_constraintEnd_toEndOf="@+id/token_field" + app:layout_constraintStart_toStartOf="@+id/token_field" + app:layout_constraintTop_toBottomOf="@+id/token_field" /> + + <com.google.android.material.divider.MaterialDivider + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + app:layout_constraintTop_toBottomOf="@+id/button" /> + + <com.google.android.material.progressindicator.CircularProgressIndicator + android:id="@+id/circularProgressIndicator" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:indeterminate="true" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <TextView + android:id="@+id/feed_info" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="16dp" + android:layout_marginEnd="8dp" + android:autoLink="web|email" + android:text="" + android:textAppearance="@style/TextAppearance.Material3.BodyLarge" + android:visibility="gone" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/divider" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/results_recycler" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:visibility="gone" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/feed_info" /> +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/feedinfo.xml b/app/src/main/res/layout/feedinfo.xml new file mode 100644 index 0000000000000000000000000000000000000000..4129ff7c6ffa1c1a9308dfc0872327e96d23ccee --- /dev/null +++ b/app/src/main/res/layout/feedinfo.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- git:fixup feeds --> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/feedinfo" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/feed_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:layout_marginBottom="16dp" + android:text="" + android:textAppearance="@style/TextAppearance.Material3.HeadlineMedium" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.materialswitch.MaterialSwitch + android:id="@+id/feed_switch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" + android:layout_marginBottom="16dp" + android:text="" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file