Compare commits

...

3 Commits

Author SHA1 Message Date
Amine Louveau
89992967be Merge pull request 'Sources Upsert' (#119) from sources-edit into master
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/119
2022-12-28 21:31:49 +00:00
aminecmi
3c68bde62b Source update screen.
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-28 22:19:11 +01:00
aminecmi
c38251f5b3 Sources menu. 2022-12-28 21:45:00 +01:00
33 changed files with 258 additions and 102 deletions

View File

@ -52,7 +52,7 @@
android:value=".HomeActivity" /> android:value=".HomeActivity" />
</activity> </activity>
<activity <activity
android:name=".AddSourceActivity" android:name=".UpsertSourceActivity"
android:parentActivityName=".SourcesActivity" android:parentActivityName=".SourcesActivity"
android:exported="true"> android:exported="true">
<meta-data <meta-data

View File

@ -603,6 +603,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
settingsLauncher.launch(Intent(this, SettingsActivity::class.java)) settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
return true return true
} }
R.id.action_sources -> {
startActivity(Intent(this, SourcesActivity::class.java))
return true
}
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
} }

View File

@ -73,7 +73,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
} }
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java)) startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java))
} }
} }
} }

View File

@ -3,12 +3,15 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.* import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -19,38 +22,59 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class AddSourceActivity : AppCompatActivity(), DIAware { class UpsertSourceActivity : AppCompatActivity(), DIAware {
private var existingSource: SelfossModel.Source? = null
private var mSpoutsValue: String? = null private var mSpoutsValue: String? = null
private lateinit var binding: ActivityAddSourceBinding private lateinit var binding: ActivityUpsertSourceBinding
override val di by closestDI() override val di by closestDI()
private val repository : Repository by instance() private val repository: Repository by instance()
private val appSettingsService : AppSettingsService by instance() private val appSettingsService: AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater) binding = ActivityUpsertSourceBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
existingSource = repository.getSelectedSource()
if (existingSource != null) {
binding.formContainer.visibility = View.GONE
binding.progress.visibility = View.VISIBLE
}
val title = if (existingSource == null) R.string.add_source else R.string.update_source
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(title)
}
}
setContentView(view) setContentView(view)
setSupportActionBar(binding.toolbar) setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.title = resources.getString(title)
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
maybeGetDetailsFromIntentSharing(intent)
binding.saveBtn.setOnClickListener { binding.saveBtn.setOnClickListener {
handleSaveSource( handleSaveSource()
binding.tags,
binding.nameInput.text.toString(),
binding.sourceUri.text.toString()
)
} }
} }
private fun initFields(items: Map<String, SelfossModel.Spout>) {
binding.nameInput.setText(existingSource!!.title)
binding.tags.setText(existingSource!!.tags.joinToString(", "))
binding.sourceUri.setText(existingSource!!.params?.url)
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -58,17 +82,13 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) { if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource() mustLoginToAddSource()
} else { } else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer) handleSpoutsSpinner()
} }
} }
private fun handleSpoutsSpinner( private fun handleSpoutsSpinner() {
spoutsSpinner: Spinner,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
val spoutsKV = HashMap<String, String>() val spoutsKV = HashMap<String, String>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { binding.spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) { override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
if (view != null) { if (view != null) {
val spoutName = (view as TextView).text.toString() val spoutName = (view as TextView).text.toString()
@ -84,11 +104,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
fun handleSpoutFailure(networkIssue: Boolean = false) { fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText( Toast.makeText(
this@AddSourceActivity, this@UpsertSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts, if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
mProgress.visibility = View.GONE binding.progress.visibility = View.GONE
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -100,17 +120,21 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
spoutsKV[value.name] = key spoutsKV[value.name] = key
} }
mProgress.visibility = View.GONE binding.progress.visibility = View.GONE
formContainer.visibility = View.VISIBLE binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter = val spinnerArrayAdapter =
ArrayAdapter( ArrayAdapter(
this@AddSourceActivity, this@UpsertSourceActivity,
android.R.layout.simple_spinner_item, android.R.layout.simple_spinner_item,
itemsStrings itemsStrings
) )
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else { } else {
handleSpoutFailure() handleSpoutFailure()
} }
@ -121,13 +145,11 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
} }
private fun maybeGetDetailsFromIntentSharing( private fun maybeGetDetailsFromIntentSharing(
intent: Intent, intent: Intent
sourceUri: EditText,
nameInput: EditText
) { ) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
} }
} }
@ -138,7 +160,8 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
finish() finish()
} }
private fun handleSaveSource(tags: EditText, title: String, url: String) { private fun handleSaveSource() {
val url = binding.sourceUri.text.toString()
val sourceDetailsUnavailable = val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty() title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
@ -149,18 +172,27 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
} }
else -> { else -> {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = repository.createSource( val successfullyAddedSource = if (existingSource != null) {
title, repository.updateSource(
existingSource!!.id,
binding.nameInput.text.toString(),
url, url,
mSpoutsValue!!, mSpoutsValue!!,
tags.text.toString(), binding.tags.text.toString()
"",
) )
} else {
repository.createSource(
binding.nameInput.text.toString(),
url,
mSpoutsValue!!,
binding.tags.text.toString(),
)
}
if (successfullyAddedSource) { if (successfullyAddedSource) {
finish() finish()
} else { } else {
Toast.makeText( Toast.makeText(
this@AddSourceActivity, this@UpsertSourceActivity,
R.string.cant_create_source, R.string.cant_create_source,
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
).show() ).show()
@ -169,4 +201,9 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
} }
} }
} }
override fun onDestroy() {
super.onDestroy()
repository.unsetSelectedSource()
}
} }

View File

@ -2,12 +2,14 @@ package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
@ -94,6 +96,14 @@ class SourcesListAdapter(
} }
} }
} }
mView.setOnClickListener {
val source = items[bindingAdapterPosition]
repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
}
} }
} }
} }

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="bou.amine.apps.readerforselfossv2.android.AddSourceActivity"> tools:context="bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -41,36 +41,20 @@
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintVertical_bias="0.0"> app:layout_constraintVertical_bias="0.0">
<TextView
android:text="@string/add_source"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView2"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textAlignment="center"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:gravity="center_horizontal" />
<EditText <EditText
android:id="@+id/nameInput"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:ems="10" android:ems="10"
android:id="@+id/nameInput" android:hint="@string/add_source_hint_name"
android:layout_marginTop="32dp" android:inputType="text"
app:layout_constraintTop_toBottomOf="@+id/textView2" android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
android:inputType="text" app:layout_constraintTop_toTopOf="parent" />
android:hint="@string/add_source_hint_name"
android:textColorHint="?android:textColorPrimary"
android:autofillHints="false" />
<EditText <EditText
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -20,6 +20,11 @@
android:orderInCategory="2" android:orderInCategory="2"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item android:id="@+id/action_sources"
android:title="@string/menu_home_sources"
android:orderInCategory="97"
app:showAsAction="never"/>
<item android:id="@+id/action_settings" <item android:id="@+id/action_settings"
android:title="@string/title_activity_settings" android:title="@string/title_activity_settings"
android:orderInCategory="98" android:orderInCategory="98"

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -6,4 +6,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"禁用自动错误报告 "</string> <string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
<string name="menu_home_filter">筛选器</string> <string name="menu_home_filter">筛选器</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -126,4 +126,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -129,4 +129,6 @@
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string> <string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string> <string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string> <string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
</resources> </resources>

View File

@ -231,7 +231,8 @@ class RepositoryTest {
listOf("tags"), listOf("tags"),
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png" "b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
SelfossModel.SourceParams("url")
)) ))
runBlocking { runBlocking {
repository.getNewerItems() repository.getNewerItems()
@ -551,7 +552,8 @@ class RepositoryTest {
listOf("Test", "second"), listOf("Test", "second"),
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"d8c92cdb1ef119ea85c4b9205c879ca7.png" "d8c92cdb1ef119ea85c4b9205c879ca7.png",
SelfossModel.SourceParams("url")
), ),
SelfossModel.Source( SelfossModel.Source(
2, 2,
@ -559,7 +561,8 @@ class RepositoryTest {
listOf("second"), listOf("second"),
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png" "b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
SelfossModel.SourceParams("url")
) )
) )
val sourcesDB = listOf( val sourcesDB = listOf(
@ -569,7 +572,8 @@ class RepositoryTest {
"Test,second", "Test,second",
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"d8c92cdb1ef119ea85c4b9205c879ca7.png" "d8c92cdb1ef119ea85c4b9205c879ca7.png",
"url"
), ),
SOURCE( SOURCE(
"2", "2",
@ -577,7 +581,8 @@ class RepositoryTest {
"second", "second",
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"b3aa8a664d08eb15d6ff1db2fa83e0d9.png" "b3aa8a664d08eb15d6ff1db2fa83e0d9.png",
"url"
) )
) )
@ -707,7 +712,7 @@ class RepositoryTest {
@Test @Test
fun create_source() { fun create_source() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository() initializeRepository()
@ -718,7 +723,6 @@ class RepositoryTest {
"https://test.com/feed", "https://test.com/feed",
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"Test, New", "Test, New",
""
) )
} }
@ -728,7 +732,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(),
) )
} }
assertSame(true, response) assertSame(true, response)
@ -736,7 +739,7 @@ class RepositoryTest {
@Test @Test
fun create_source_but_response_fails() { fun create_source_but_response_fails() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(false) SuccessResponse(false)
initializeRepository() initializeRepository()
@ -746,8 +749,7 @@ class RepositoryTest {
"test", "test",
"https://test.com/feed", "https://test.com/feed",
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"Test, New", "Test, New"
""
) )
} }
@ -756,8 +758,7 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(), any()
any(),
) )
} }
assertSame(false, response) assertSame(false, response)
@ -765,7 +766,7 @@ class RepositoryTest {
@Test @Test
fun create_source_without_connection() { fun create_source_without_connection() {
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
@ -775,8 +776,7 @@ class RepositoryTest {
"test", "test",
"https://test.com/feed", "https://test.com/feed",
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"Test, New", "Test, New"
""
) )
} }
@ -786,7 +786,6 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any()
) )
} }
assertSame(false, response) assertSame(false, response)
@ -1034,7 +1033,8 @@ class RepositoryTest {
listOf("Test", "second"), listOf("Test", "second"),
"spouts\\rss\\fulltextrss", "spouts\\rss\\fulltextrss",
"", "",
"d8c92cdb1ef119ea85c4b9205c879ca7.png" "d8c92cdb1ef119ea85c4b9205c879ca7.png",
SelfossModel.SourceParams("url")
) )
) )
repository.searchFilter = "search" repository.searchFilter = "search"

View File

@ -9,6 +9,7 @@ import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.encodeCollection
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
class SelfossModel { class SelfossModel {
@ -55,7 +56,12 @@ class SelfossModel {
val tags: List<String>, val tags: List<String>,
val spout: String, val spout: String,
val error: String, val error: String,
val icon: String? val icon: String?,
val params: SourceParams?
)
@Serializable
data class SourceParams(
val url: String
) )
@Serializable @Serializable
data class Item( data class Item(
@ -122,7 +128,7 @@ class SelfossModel {
object TagsListSerializer : KSerializer<List<String>> { object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> { override fun deserialize(decoder: Decoder): List<String> {
return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) { return when(val json = ((decoder as JsonDecoder).decodeJsonElement())) {
is JsonArray -> json.toList().map { it.toString() } is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
else -> json.toString().split(",") else -> json.toString().split(",")
} }
@ -132,7 +138,7 @@ class SelfossModel {
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING) get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: List<String>) { override fun serialize(encoder: Encoder, value: List<String>) {
TODO("Not yet implemented") encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
} }
} }

View File

@ -48,6 +48,7 @@ class Repository(
private var fetchedTags = false private var fetchedTags = false
private var _readerItems = ArrayList<SelfossModel.Item>() private var _readerItems = ArrayList<SelfossModel.Item>()
private var _selectedSource: SelfossModel.Source? = null
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter // TODO: Use the updatedSince parameter
@ -343,7 +344,6 @@ class Repository(
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String
): Boolean { ): Boolean {
var response = false var response = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
@ -352,13 +352,27 @@ class Repository(
url, url,
spout, spout,
tags, tags,
filter
).isSuccess == true ).isSuccess == true
} }
return response return response
} }
suspend fun updateSource(
id: Int,
title: String,
url: String,
spout: String,
tags: String
): Boolean {
var response = false
if (isNetworkAvailable()) {
response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
}
return response
}
suspend fun deleteSource(id: Int, title: String): Boolean { suspend fun deleteSource(id: Int, title: String): Boolean {
var success = false var success = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
@ -580,4 +594,16 @@ class Repository(
fun migrate(driverFactory: DriverFactory) { fun migrate(driverFactory: DriverFactory) {
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
} }
fun setSelectedSource(source: SelfossModel.Source) {
_selectedSource = source
}
fun unsetSelectedSource() {
_selectedSource = null
}
fun getSelectedSource(): SelfossModel.Source? {
return _selectedSource
}
} }

View File

@ -23,6 +23,9 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
return if (r != null && r.status.isSuccess()) { return if (r != null && r.status.isSuccess()) {
r.body() r.body()
} else { } else {
if (r != null) {
Napier.i("Error ${r.status}", tag = "maybeResponse")
}
SuccessResponse(false) SuccessResponse(false)
} }
} }

View File

@ -76,10 +76,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
// Api version was introduces after the POST login, so when there is a version, it should be available // Api version was introduces after the POST login, so when there is a version, it should be available
private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1 private fun shouldHavePostLogin() = appSettingsService.getApiVersion() != -1
private fun hasLoginInfo() = appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty() private fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
.isNotEmpty()
suspend fun login(): SuccessResponse = suspend fun login(): SuccessResponse =
if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword().isNotEmpty()) { if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
.isNotEmpty()
) {
if (shouldHavePostLogin()) { if (shouldHavePostLogin()) {
postLogin() postLogin()
} else { } else {
@ -99,7 +103,9 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
parameter("password", appSettingsService.getPassword()) parameter("password", appSettingsService.getPassword())
}) })
private fun shouldHaveNewLogout() = appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0 private fun shouldHaveNewLogout() =
appSettingsService.getApiVersion() >= 5 // We are missing 4.1.0
suspend fun logout(): SuccessResponse = suspend fun logout(): SuccessResponse =
if (shouldHaveNewLogout()) { if (shouldHaveNewLogout()) {
doLogout() doLogout()
@ -107,7 +113,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
maybeLogoutIfAvailable() maybeLogoutIfAvailable()
} }
private suspend fun maybeLogoutIfAvailable() = responseOrSuccessIf404(client.tryToGet(url("/logout"))) private suspend fun maybeLogoutIfAvailable() =
responseOrSuccessIf404(client.tryToGet(url("/logout")))
private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current"))) private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")))
@ -236,13 +243,12 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String,
filter: String
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (appSettingsService.getApiVersion() > 1) { if (appSettingsService.getApiVersion() > 1) {
createSource("tags[]", title, url, spout, tags, filter) createSource("tags[]", title, url, spout, tags)
} else { } else {
createSource("tags", title, url, spout, tags, filter) createSource("tags", title, url, spout, tags)
} }
) )
@ -251,8 +257,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String
filter: String
): HttpResponse? = ): HttpResponse? =
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source"), url = url("/source"),
@ -265,7 +270,43 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
append("url", url) append("url", url)
append("spout", spout) append("spout", spout)
append(tagsParamName, tags) append(tagsParamName, tags)
append("filter", filter) }
)
suspend fun updateSourceForVersion(
id: Int,
title: String,
url: String,
spout: String,
tags: String
): SuccessResponse =
maybeResponse(
if (appSettingsService.getApiVersion() > 1) {
updateSource(id, "tags[]", title, url, spout, tags)
} else {
updateSource(id, "tags", title, url, spout, tags)
}
)
private suspend fun updateSource(
id: Int,
tagsParamName: String,
title: String,
url: String,
spout: String,
tags: String,
): HttpResponse? =
client.tryToSubmitForm(
url = url("/source/$id"),
formParameters = Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
append("title", title)
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
} }
) )

View File

@ -19,7 +19,8 @@ fun SOURCE.toView(): SelfossModel.Source =
this.tags.split(","), this.tags.split(","),
this.spout, this.spout,
this.error, this.error,
this.icon this.icon,
if (this.url != null) SelfossModel.SourceParams(this.url) else null
) )
fun SelfossModel.Source.toEntity(): SOURCE = fun SelfossModel.Source.toEntity(): SOURCE =
@ -29,7 +30,8 @@ fun SelfossModel.Source.toEntity(): SOURCE =
this.tags.joinToString(","), this.tags.joinToString(","),
this.spout, this.spout,
this.error, this.error,
this.icon.orEmpty() this.icon.orEmpty(),
this.params?.url
) )
fun SelfossModel.Tag.toEntity(): TAG = fun SelfossModel.Tag.toEntity(): TAG =

View File

@ -0,0 +1 @@
ALTER TABLE SOURCE ADD COLUMN `url` TEXT;

View File

@ -5,6 +5,7 @@ CREATE TABLE SOURCE (
`spout` TEXT NOT NULL, `spout` TEXT NOT NULL,
`error` TEXT NOT NULL, `error` TEXT NOT NULL,
`icon` TEXT NOT NULL, `icon` TEXT NOT NULL,
`url` TEXT,
PRIMARY KEY(`id`) PRIMARY KEY(`id`)
); );