Compare commits

..
6 Commits
Author SHA1 Message Date
davidoskky 566930ba84 Fix sourceDetails unit tests 2025-04-13 00:01:46 +02:00
davidoskky b1259fdc92 Support source filter editing
The API for filter is undocumented on the Selfoss API reference, but does exist. We can now allow users setting and modifying filters inside the application.
2025-04-12 23:58:12 +02:00
AmineB ceba58e98f Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
Check master code / build (push) Successful in 8m27s
Reviewed-on: #190
2025-03-16 13:14:44 +00:00
davidoskky c3ee07dd85 Refactor star icon handling
Check PR code / translations (pull_request) Successful in 1m4s
Check PR code / Lint (pull_request) Successful in 1m25s
Check PR code / build (pull_request) Successful in 11m52s
Extracted all favorite handling to two functions. Makes it a little bit more readable.
2025-03-12 16:07:49 +01:00
davidoskky 93d99192b3 Don't restart activity changing alignment
When changing alignment in the reader we were restarting the reader activity to reload. Doing this led to reloading the article which was initially opened every time you changed alignment. Now, when changing the alignment we retain all existing fragments but command all of them to update their alignment setting.
2025-03-12 16:07:49 +01:00
giteadrone 359dec2ca0 Changelog for v125030711 2025-03-12 11:39:57 +00:00
11 changed files with 120 additions and 105 deletions
+11
View File
@@ -1,3 +1,14 @@
**v125030711
- Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master
- chore: check changes for translations and android.
- fix: initial status loading issues.
- Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master
- chore: new connectivity dep. Closes #84.
- Changelog for v125030681
--------------------------------------------------------------------
**v125030681
- chore: do not send reports on simulators.
@@ -37,22 +37,6 @@ class ReaderActivity :
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
} else {
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
}
}
private fun canFavorite() {
showMenuItem(true)
}
private fun canRemoveFromFavorite() {
showMenuItem(false)
}
@Suppress("detekt:SwallowedException")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -73,14 +57,21 @@ class ReaderActivity :
finish()
}
try {
readItem(allItems[currentItem])
} catch (e: IndexOutOfBoundsException) {
finish()
}
readItem()
binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(currentItem, false)
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
currentItem = position
updateStarIcon()
readItem()
}
},
)
}
override fun onResume() {
@@ -89,14 +80,20 @@ class ReaderActivity :
binding.indicator.setViewPager(binding.pager)
}
private fun readItem(item: SelfossModel.Item) {
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) {
private fun readItem() {
val item = allItems.getOrNull(currentItem)
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess() && item != null) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item)
}
}
}
private fun updateStarIcon() {
val isStarred = allItems.getOrNull(currentItem)?.starred ?: false
toolbarMenu.findItem(R.id.star)?.icon?.setTint(if (isStarred) Color.RED else Color.WHITE)
}
override fun onSaveInstanceState(oldInstanceState: Bundle) {
super.onSaveInstanceState(oldInstanceState)
oldInstanceState.clear()
@@ -141,8 +138,7 @@ class ReaderActivity :
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.reader_menu, menu)
menuInflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu
alignmentMenu()
@@ -150,87 +146,50 @@ class ReaderActivity :
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.star)
} else {
if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (!allItems.isNullOrEmpty() && allItems.size >= position) {
if (allItems[position].starred) {
canRemoveFromFavorite()
} else {
canFavorite()
}
readItem(allItems[position])
}
}
},
)
updateStarIcon()
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
fun afterSave() {
allItems[binding.pager.currentItem] =
allItems[binding.pager.currentItem].toggleStar()
canRemoveFromFavorite()
}
fun afterUnsave() {
allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar()
canFavorite()
}
when (item.itemId) {
android.R.id.home -> {
onBackPressedDispatcher.onBackPressed()
return true
}
R.id.star -> {
if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(allItems[binding.pager.currentItem])
}
afterUnsave()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(allItems[binding.pager.currentItem])
}
afterSave()
}
}
R.id.align_left -> {
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
refreshFragment()
}
R.id.align_justify -> {
switchAlignmentSetting(AppSettingsService.JUSTIFY)
refreshFragment()
}
android.R.id.home -> onBackPressedDispatcher.onBackPressed()
R.id.star -> toggleFavorite()
R.id.align_left -> switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
R.id.align_justify -> switchAlignmentSetting(AppSettingsService.JUSTIFY)
}
return super.onOptionsItemSelected(item)
}
private fun switchAlignmentSetting(allignment: Int) {
appSettingsService.changeAllignment(allignment)
alignmentMenu()
private fun toggleFavorite() {
val item = allItems.getOrNull(currentItem) ?: return
val starred = item.starred
CoroutineScope(Dispatchers.IO).launch {
if (starred) {
repository.unstarr(item)
} else {
repository.starr(item)
}
}
item.toggleStar()
updateStarIcon()
}
private fun refreshFragment() {
finish()
overridePendingTransition(0, 0)
startActivity(intent)
overridePendingTransition(0, 0)
private fun switchAlignmentSetting(alignment: Int) {
appSettingsService.changeAllignment(alignment)
alignmentMenu()
val fragmentManager = supportFragmentManager
val fragments = fragmentManager.fragments
for (fragment in fragments) {
if (fragment is ArticleFragment) {
fragment.refreshAlignment()
}
}
}
}
@@ -65,6 +65,7 @@ class UpsertSourceActivity :
private fun initFields(items: Map<String, SelfossModel.Spout>) {
binding.nameInput.setText(existingSource!!.title)
binding.tags.setText(existingSource!!.tags?.joinToString(", "))
binding.filter.setText(existingSource!!.filter)
binding.sourceUri.setText(existingSource!!.params?.url)
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
binding.progress.visibility = View.GONE
@@ -169,6 +170,7 @@ class UpsertSourceActivity :
url,
mSpoutsValue!!,
binding.tags.text.toString(),
binding.filter.text.toString(),
)
} else {
repository.createSource(
@@ -176,6 +178,7 @@ class UpsertSourceActivity :
url,
mSpoutsValue!!,
binding.tags.text.toString(),
binding.filter.text.toString(),
)
}
if (successfullyAddedSource) {
@@ -263,13 +263,15 @@ class ArticleFragment :
)
}
private fun refreshAlignment() {
fun refreshAlignment() {
textAlignment =
when (appSettingsService.getActiveAllignment()) {
1 -> "justify"
2 -> "left"
else -> "justify"
}
htmlToWebview()
}
@Suppress("detekt:SwallowedException")
@@ -64,15 +64,28 @@
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_tags"
android:inputType="text"
android:minHeight="48dp"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceUri" />
<EditText
android:id="@+id/filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_filter"
android:inputType="text"
android:minHeight="48dp"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tags" />
<Spinner
android:id="@+id/spoutsSpinner"
android:layout_width="match_parent"
@@ -80,7 +93,7 @@
android:minHeight="48dp"
android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tags" />
app:layout_constraintTop_toBottomOf="@+id/filter" />
<Button
android:id="@+id/saveBtn"
@@ -17,6 +17,7 @@
<string name="add_source_hint_tags">"Tag1, Tag2, Tag3"</string>
<string name="add_source_hint_url">"Link"</string>
<string name="add_source_hint_name">"Name"</string>
<string name="add_source_hint_filter">/Filter/</string>
<string name="add_source">"Add a source"</string>
<string name="add_source_save">"Save"</string>
<string name="wrong_infos">"Check your details again."</string>
@@ -44,6 +44,8 @@ private const val FEED_URL = "https://test.com/feed"
private const val TAGS = "Test, New"
private const val FILTER = "/filter/"
private const val NUMBER_ARTICLES = 100
private const val NUMBER_UNREAD = 50
private const val NUMBER_STARRED = 20
@@ -836,7 +838,7 @@ class RepositoryTest {
@Test
fun create_source() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(true)
initializeRepository()
@@ -848,6 +850,7 @@ class RepositoryTest {
FEED_URL,
SPOUT,
TAGS,
FILTER,
)
}
@@ -857,6 +860,7 @@ class RepositoryTest {
any(),
any(),
any(),
any(),
)
}
assertSame(true, response)
@@ -864,7 +868,7 @@ class RepositoryTest {
@Test
fun create_source_but_response_fails() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(false)
initializeRepository()
@@ -876,6 +880,7 @@ class RepositoryTest {
FEED_URL,
SPOUT,
TAGS,
FILTER,
)
}
@@ -885,6 +890,7 @@ class RepositoryTest {
any(),
any(),
any(),
any(),
)
}
assertSame(false, response)
@@ -892,7 +898,7 @@ class RepositoryTest {
@Test
fun create_source_without_connection() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
coEvery { api.createSourceForVersion(any(), any(), any(), any(), any()) } returns
SuccessResponse(true)
initializeRepository(false)
@@ -904,6 +910,7 @@ class RepositoryTest {
FEED_URL,
SPOUT,
TAGS,
FILTER,
)
}
@@ -913,6 +920,7 @@ class RepositoryTest {
any(),
any(),
any(),
any(),
)
}
assertSame(false, response)
@@ -0,0 +1,8 @@
**v125030711**
- Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master
- chore: check changes for translations and android.
- fix: initial status loading issues.
- Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master
- chore: new connectivity dep. Closes #84.
- Changelog for v125030681
@@ -102,6 +102,7 @@ class SelfossModel {
override var error: String? = null,
override var icon: String? = null,
var params: SourceParams? = null,
var filter: String? = null,
) : Source
@Serializable
@@ -368,6 +368,7 @@ class Repository(
url: String,
spout: String,
tags: String,
filter: String,
): Boolean {
var response = false
if (connectivityService.isNetworkAvailable()) {
@@ -377,6 +378,7 @@ class Repository(
url,
spout,
tags,
filter,
).isSuccess == true
}
@@ -389,10 +391,11 @@ class Repository(
url: String,
spout: String,
tags: String,
filter: String,
): Boolean {
var response = false
if (connectivityService.isNetworkAvailable()) {
response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true
response = api.updateSourceForVersion(id, title, url, spout, tags, filter).isSuccess == true
}
return response
@@ -638,12 +638,13 @@ class SelfossApi(
url: String,
spout: String,
tags: String,
filter: String,
): SuccessResponse =
maybeResponse(
if (appSettingsService.getApiVersion() > 1) {
createSource("tags[]", title, url, spout, tags)
createSource("tags[]", title, url, spout, tags, filter)
} else {
createSource("tags", title, url, spout, tags)
createSource("tags", title, url, spout, tags, filter)
},
)
@@ -653,6 +654,7 @@ class SelfossApi(
url: String,
spout: String,
tags: String,
filter: String,
): HttpResponse? =
client.tryToSubmitForm(
url = url("/source"),
@@ -665,6 +667,7 @@ class SelfossApi(
append("title", title)
append("url", url)
append("spout", spout)
append("filter", filter)
append(tagsParamName, tags)
},
block = {
@@ -694,12 +697,13 @@ class SelfossApi(
url: String,
spout: String,
tags: String,
filter: String,
): SuccessResponse =
maybeResponse(
if (appSettingsService.getApiVersion() > 1) {
updateSource(id, "tags[]", title, url, spout, tags)
updateSource(id, "tags[]", title, url, spout, tags, filter)
} else {
updateSource(id, "tags", title, url, spout, tags)
updateSource(id, "tags", title, url, spout, tags, filter)
},
)
@@ -710,6 +714,7 @@ class SelfossApi(
url: String,
spout: String,
tags: String,
filter: String,
): HttpResponse? =
client.tryToSubmitForm(
url = url("/source/$id"),
@@ -723,6 +728,7 @@ class SelfossApi(
append("url", url)
append("spout", spout)
append(tagsParamName, tags)
append("filter", filter)
},
block = {
if (appSettingsService