Compare commits

..

13 Commits

Author SHA1 Message Date
aminecmi
29d33687b4 fix: Fixing date formats.
Some checks are pending
continuous-integration/drone/pr Build is running
continuous-integration/drone/push Build is passing
2023-10-10 22:26:38 +02:00
aminecmi
33cca86383 chore: update versions 2023-10-10 21:41:34 +02:00
aminecmi
86a30f647d fix: Fixed ios build. 2023-10-10 21:32:28 +02:00
78d87ad52a Merge master
Some checks are pending
continuous-integration/drone/pr Build is running
2023-09-22 16:11:38 +02:00
2b82be61d0 Revert changes not related to the gradle upgrade
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-16 14:13:05 +02:00
20aab0ea62 Improve image handling
Some checks failed
continuous-integration/drone/pr Build is failing
2023-09-12 00:28:36 +02:00
dcb9b1cb1f Enable non transitive R class 2023-09-12 00:28:36 +02:00
776d5d36f6 Remove deprecated function calls 2023-09-12 00:28:36 +02:00
990a354229 Remove deprecated function and prevent errors in case of a null fragment name 2023-09-12 00:28:36 +02:00
bd6b96d09d Remove not required jcenter repository 2023-09-12 00:28:36 +02:00
d26f3979cd Upgrade all dependencies 2023-09-12 00:28:36 +02:00
b59b1abc6d Fix R8 compilation problem 2023-09-12 00:25:14 +02:00
baca7cffed Upgrade to gradle 8 2023-09-12 00:24:59 +02:00
60 changed files with 1347 additions and 1803 deletions

View File

@@ -13,10 +13,10 @@ steps:
- curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip - curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Linting..." - echo "Linting..."
- ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' || true - ktlint || true
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Detecting..." - echo "Detecting..."
- ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true - ./detekt-cli-1.23.1/bin/detekt-cli --all-rules || true
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
command_timeout: 1m command_timeout: 1m
- name: BuildAndTest - name: BuildAndTest
@@ -53,24 +53,20 @@ steps:
- git fetch --tags -p - git fetch --tags -p
- PREV=$(git describe --tags --abbrev=0) - PREV=$(git describe --tags --abbrev=0)
- ./build.sh --publish --from-ci - ./build.sh --publish --from-ci
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- VER=$(git describe --tags --abbrev=0) - VER=$(git describe --tags --abbrev=0)
- CHANGELOG=$(git log $PREV..HEAD --pretty="- %s") - CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
- echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md - echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
- git add CHANGELOG.md - git add CHANGELOG.md
- git commit -m "Changelog for $VER [CI SKIP]" - git commit -m "Changelog for $VER [CI SKIP]"
- git push pushing master
- git push pushing --tags
environment: environment:
TZ: Europe/Paris TZ: Europe/Paris
GITEA_USR:
- name: git-push from_secret: giteaUsr
image: appleboy/drone-git-push GITEA_PASS:
settings: from_secret: giteaPass
branch: master
remote:
from_secret: remoteUrl
followtags: true
ssh_key:
from_secret: privateKey
skip_verify: true
- name: scpFiles - name: scpFiles
image: appleboy/drone-scp image: appleboy/drone-scp

View File

@@ -1,11 +1,3 @@
**v123102841**
- chore: cleaning ci steps and upgrading dependencies.
- feat: Self signed ssl support.
- Changelog for v123061811 [CI SKIP]
--------------------------------------------------------------------
**v123061811** **v123061811**
- feat: Added confirmation dialog for disconnect item menu. - feat: Added confirmation dialog for disconnect item menu.

View File

@@ -43,7 +43,9 @@ import org.kodein.di.instance
import java.security.MessageDigest import java.security.MessageDigest
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware { class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
private var items: ArrayList<SelfossModel.Item> = ArrayList() private var items: ArrayList<SelfossModel.Item> = ArrayList()
private var elementsShown: ItemType = ItemType.UNREAD private var elementsShown: ItemType = ItemType.UNREAD
@@ -61,22 +63,22 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private var fromTabShortcut: Boolean = false private var fromTabShortcut: Boolean = false
private val settingsLauncher = private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { appSettingsService.refreshUserSettings()
appSettingsService.refreshUserSettings() }
}
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 = ActivityHomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
repository.offlineOverride = intent.getBooleanExtra("startOffline", false) repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
if (fromTabShortcut) { if (fromTabShortcut) {
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position)) elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
@@ -90,6 +92,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleSwipeRefreshLayout() handleSwipeRefreshLayout()
if (appSettingsService.isItemCachingEnabled()) { if (appSettingsService.isItemCachingEnabled()) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
@@ -101,7 +104,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.swipeRefreshLayout.setColorSchemeResources( binding.swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1, R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_2,
R.color.refresh_progress_3, R.color.refresh_progress_3
) )
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false repository.offlineOverride = false
@@ -112,41 +115,37 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
} }
val swipeDirs = val swipeDirs = if (appSettingsService.getPublicAccess()) {
if (appSettingsService.getPublicAccess()) { 0
0 } else {
} else { ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT }
}
val simpleItemTouchCallback = val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback( object : ItemTouchHelper.SimpleCallback(
0, 0,
swipeDirs, swipeDirs
) { ) {
override fun getSwipeDirs( override fun getSwipeDirs(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder
): Int = ): Int =
if (elementsShown == ItemType.STARRED) { if (elementsShown == ItemType.STARRED) {
0 0
} else { } else {
super.getSwipeDirs( super.getSwipeDirs(
recyclerView, recyclerView,
viewHolder, viewHolder
) )
} }
override fun onMove( override fun onMove(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder
): Boolean = false ): Boolean = false
override fun onSwiped( override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
viewHolder: RecyclerView.ViewHolder,
swipeDir: Int,
) {
val position = viewHolder.bindingAdapterPosition val position = viewHolder.bindingAdapterPosition
val i = items.elementAtOrNull(position) val i = items.elementAtOrNull(position)
@@ -163,7 +162,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
"Found null when swiping at positon $position.", "Found null when swiping at positon $position.",
Toast.LENGTH_LONG, Toast.LENGTH_LONG
).show() ).show()
} }
} }
@@ -172,10 +171,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView) ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
} }
private fun updateBottomBarBadgeCount( private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
badge: TextBadgeItem,
count: Int,
) {
if (count > 0) { if (count > 0) {
badge badge
.setText(count.toString()) .setText(count.toString())
@@ -186,18 +182,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleBottomBar() { private fun handleBottomBar() {
tabNewBadge =
TextBadgeItem() tabNewBadge = TextBadgeItem()
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .setHideOnSelect(false).hide(false)
tabArchiveBadge = tabArchiveBadge = TextBadgeItem()
TextBadgeItem() .setText("")
.setText("") .setHideOnSelect(false).hide(false)
.setHideOnSelect(false).hide(false) tabStarredBadge = TextBadgeItem()
tabStarredBadge = .setText("")
TextBadgeItem() .setHideOnSelect(false).hide(false)
.setText("")
.setHideOnSelect(false).hide(false)
if (appSettingsService.isDisplayUnreadCountEnabled()) { if (appSettingsService.isDisplayUnreadCountEnabled()) {
lifecycleScope.launch { lifecycleScope.launch {
@@ -224,19 +218,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
val tabNew = val tabNew =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp, R.drawable.ic_tab_fiber_new_black_24dp,
getString(R.string.tab_new), getString(R.string.tab_new)
) )
.setBadgeItem(tabNewBadge) .setBadgeItem(tabNewBadge)
val tabArchive = val tabArchive =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_archive_black_24dp, R.drawable.ic_tab_archive_black_24dp,
getString(R.string.tab_read), getString(R.string.tab_read)
) )
.setBadgeItem(tabArchiveBadge) .setBadgeItem(tabArchiveBadge)
val tabStarred = val tabStarred =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_favorite_black_24dp, R.drawable.ic_tab_favorite_black_24dp,
getString(R.string.tab_favs), getString(R.string.tab_favs)
).setActiveColorResource(R.color.pink) ).setActiveColorResource(R.color.pink)
.setBadgeItem(tabStarredBadge) .setBadgeItem(tabStarredBadge)
@@ -277,6 +271,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab() getElementsAccordingToTab()
} }
private fun handleGDPRDialog(GDPRShown: Boolean) { private fun handleGDPRDialog(GDPRShown: Boolean) {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray()) messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
@@ -286,7 +281,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
alertDialog.setMessage(getString(R.string.gdpr_dialog_message)) alertDialog.setMessage(getString(R.string.gdpr_dialog_message))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK"
) { dialog, _ -> ) { dialog, _ ->
appSettingsService.settings.putBoolean("GDPR_shown", true) appSettingsService.settings.putBoolean("GDPR_shown", true)
dialog.dismiss() dialog.dismiss()
@@ -303,41 +298,37 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
when (currentManager) { when (currentManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = GridLayoutManager(
GridLayoutManager( this,
this, calculateNoOfColumns()
calculateNoOfColumns(), )
)
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
is GridLayoutManager -> is GridLayoutManager ->
if (appSettingsService.isCardViewEnabled()) { if (appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = StaggeredGridLayoutManager(
StaggeredGridLayoutManager( calculateNoOfColumns(),
calculateNoOfColumns(), StaggeredGridLayoutManager.VERTICAL
StaggeredGridLayoutManager.VERTICAL, )
)
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
else -> else ->
if (currentManager == null) { if (currentManager == null) {
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = GridLayoutManager(
GridLayoutManager( this,
this, calculateNoOfColumns()
calculateNoOfColumns(), )
)
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} else { } else {
layoutManager = layoutManager = StaggeredGridLayoutManager(
StaggeredGridLayoutManager( calculateNoOfColumns(),
calculateNoOfColumns(), StaggeredGridLayoutManager.VERTICAL
StaggeredGridLayoutManager.VERTICAL, )
)
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
} }
@@ -345,40 +336,39 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleBottomBarActions() { private fun handleBottomBarActions() {
binding.bottomBar.setTabSelectedListener( binding.bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
object : BottomNavigationBar.OnTabSelectedListener { override fun onTabUnselected(position: Int) = Unit
override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) { override fun onTabReselected(position: Int) {
when (val layoutManager = binding.recyclerView.adapter) {
is StaggeredGridLayoutManager -> when (val layoutManager = binding.recyclerView.adapter) {
if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { is StaggeredGridLayoutManager ->
getElementsAccordingToTab() if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
} else { getElementsAccordingToTab()
layoutManager.scrollToPositionWithOffset(0, 0) } else {
} layoutManager.scrollToPositionWithOffset(0, 0)
is GridLayoutManager -> }
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) { is GridLayoutManager ->
getElementsAccordingToTab() if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
} else { getElementsAccordingToTab()
layoutManager.scrollToPositionWithOffset(0, 0) } else {
} layoutManager.scrollToPositionWithOffset(0, 0)
else -> Unit }
} else -> Unit
} }
}
override fun onTabSelected(position: Int) { override fun onTabSelected(position: Int) {
offset = 0 offset = 0
lastFetchDone = false lastFetchDone = false
elementsShown = ItemType.fromInt(position + 1) elementsShown = ItemType.fromInt(position + 1)
getElementsAccordingToTab() getElementsAccordingToTab()
binding.recyclerView.scrollToPosition(0) binding.recyclerView.scrollToPosition(0)
fetchOnEmptyList() fetchOnEmptyList()
} }
}, })
)
} }
fun fetchOnEmptyList() { fun fetchOnEmptyList() {
@@ -388,33 +378,27 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleInfiniteScroll() { private fun handleInfiniteScroll() {
recyclerViewScrollListener = recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
object : RecyclerView.OnScrollListener() { override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) {
override fun onScrolled( if (dy > 0) {
localRecycler: RecyclerView, val lastVisibleItem = getLastVisibleItem()
dx: Int,
dy: Int,
) {
if (dy > 0) {
val lastVisibleItem = getLastVisibleItem()
if (lastVisibleItem == (items.size - 1) && items.size < maxItemNumber()) { if (lastVisibleItem == (items.size - 1) && items.size < maxItemNumber()) {
getElementsAccordingToTab(appendResults = true) getElementsAccordingToTab(appendResults = true)
}
} }
} }
} }
}
binding.recyclerView.clearOnScrollListeners() binding.recyclerView.clearOnScrollListeners()
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener) binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
} }
private fun getLastVisibleItem(): Int { private fun getLastVisibleItem() : Int {
return when (val manager = binding.recyclerView.layoutManager) { return when (val manager = binding.recyclerView.layoutManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
manager.findLastCompletelyVisibleItemPositions( null
null, ).last()
).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition() is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0 else -> 0
} }
@@ -427,31 +411,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.emptyText.visibility = View.GONE binding.emptyText.visibility = View.GONE
} }
fun getElementsAccordingToTab(appendResults: Boolean = false) { fun getElementsAccordingToTab(
offset = appendResults: Boolean = false
if (appendResults && items.size > 0) { ) {
items.size - 1 offset = if (appendResults && items.size > 0) {
} else { items.size - 1
0 } else {
} 0
}
firstVisible = if (appendResults) firstVisible else 0 firstVisible = if (appendResults) firstVisible else 0
getItems(appendResults, elementsShown) getItems(appendResults, elementsShown)
} }
private fun getItems( private fun getItems(appendResults: Boolean, itemType: ItemType) {
appendResults: Boolean,
itemType: ItemType,
) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
binding.swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
repository.displayedItems = itemType repository.displayedItems = itemType
items = items = if (appendResults) {
if (appendResults) { repository.getOlderItems()
repository.getOlderItems() } else {
} else { repository.getNewerItems()
repository.getNewerItems() }
}
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
handleListResult() handleListResult()
} }
@@ -460,44 +441,43 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun handleListResult(appendResults: Boolean = false) { private fun handleListResult(appendResults: Boolean = false) {
if (appendResults) { if (appendResults) {
val oldManager = binding.recyclerView.layoutManager val oldManager = binding.recyclerView.layoutManager
firstVisible = firstVisible = when (oldManager) {
when (oldManager) { is StaggeredGridLayoutManager ->
is StaggeredGridLayoutManager -> oldManager.findFirstCompletelyVisibleItemPositions(null).last()
oldManager.findFirstCompletelyVisibleItemPositions(null).last() is GridLayoutManager ->
is GridLayoutManager -> oldManager.findFirstCompletelyVisibleItemPosition()
oldManager.findFirstCompletelyVisibleItemPosition() else -> 0
else -> 0 }
}
} }
if (recyclerAdapter == null) { if (recyclerAdapter == null) {
if (appSettingsService.isCardViewEnabled()) { if (appSettingsService.isCardViewEnabled()) {
recyclerAdapter = recyclerAdapter =
ItemCardAdapter( ItemCardAdapter(
this, this,
items, items,
) { ) {
updateItems(it) updateItems(it)
} }
} else { } else {
recyclerAdapter = recyclerAdapter =
ItemListAdapter( ItemListAdapter(
this, this,
items, items,
) { ) {
updateItems(it) updateItems(it)
} }
binding.recyclerView.addItemDecoration( binding.recyclerView.addItemDecoration(
DividerItemDecoration( DividerItemDecoration(
this@HomeActivity, this@HomeActivity,
DividerItemDecoration.VERTICAL, DividerItemDecoration.VERTICAL
), )
) )
} }
binding.recyclerView.adapter = recyclerAdapter binding.recyclerView.adapter = recyclerAdapter
} else { } else {
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items) (recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
} }
reloadBadges() reloadBadges()
@@ -549,11 +529,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true return true
} }
private fun needsConfirmation( private fun needsConfirmation(titleRes: Int, messageRes: Int, doFn: () -> Unit) {
titleRes: Int,
messageRes: Int,
doFn: () -> Unit,
) {
AlertDialog.Builder(this@HomeActivity) AlertDialog.Builder(this@HomeActivity)
.setMessage(messageRes) .setMessage(messageRes)
.setTitle(titleRes) .setTitle(titleRes)
@@ -578,15 +554,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
if (updatedRemote) { if (updatedRemote) {
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_success_response, R.string.refresh_success_response, Toast.LENGTH_LONG
Toast.LENGTH_LONG,
) )
.show() .show()
} else { } else {
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_failer_message, R.string.refresh_failer_message,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
@@ -604,7 +579,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_read, R.string.all_posts_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
tabNewBadge.removeBadge() tabNewBadge.removeBadge()
@@ -613,7 +588,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_not_read, R.string.all_posts_not_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
handleListResult() handleListResult()
@@ -660,12 +635,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun handleRecurringTask() { private fun handleRecurringTask() {
if (appSettingsService.isPeriodicRefreshEnabled()) { if (appSettingsService.isPeriodicRefreshEnabled()) {
val myConstraints = val myConstraints = Constraints.Builder()
Constraints.Builder() .setRequiresBatteryNotLow(true)
.setRequiresBatteryNotLow(true) .setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled()) .setRequiresStorageNotLow(true)
.setRequiresStorageNotLow(true) .build()
.build()
val backgroundWork = val backgroundWork =
PeriodicWorkRequestBuilder<LoadingWorker>(appSettingsService.getRefreshMinutes(), TimeUnit.MINUTES) PeriodicWorkRequestBuilder<LoadingWorker>(appSettingsService.getRefreshMinutes(), TimeUnit.MINUTES)
@@ -673,9 +647,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.addTag("selfoss-loading") .addTag("selfoss-loading")
.build() .build()
WorkManager.getInstance( WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
baseContext,
).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
} }
} }
} }

View File

@@ -11,8 +11,8 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityImageBindin
import bou.amine.apps.readerforselfossv2.android.fragments.ImageFragment import bou.amine.apps.readerforselfossv2.android.fragments.ImageFragment
class ImageActivity : AppCompatActivity() { class ImageActivity : AppCompatActivity() {
private lateinit var allImages: ArrayList<String> private lateinit var allImages : ArrayList<String>
private var position: Int = 0 private var position : Int = 0
private lateinit var binding: ActivityImageBinding private lateinit var binding: ActivityImageBinding
@@ -32,44 +32,27 @@ class ImageActivity : AppCompatActivity() {
binding.pager.adapter = ScreenSlidePagerAdapter(this) binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(position, false) binding.pager.setCurrentItem(position, false)
val transitionListener = val transitionListener = object : MotionLayout.TransitionListener {
object : MotionLayout.TransitionListener { override fun onTransitionStarted(motionLayout: MotionLayout?, startId: Int, endId: Int) {
override fun onTransitionStarted( // Nothing
motionLayout: MotionLayout?, }
startId: Int,
endId: Int,
) {
// Nothing
}
override fun onTransitionChange( override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float
motionLayout: MotionLayout?, ) {
startId: Int, // Nothing
endId: Int, }
progress: Float,
) {
// Nothing
}
override fun onTransitionCompleted( override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) {
motionLayout: MotionLayout?, if (motionLayout?.currentState == binding.root.endState) {
currentId: Int, onBackPressedDispatcher.onBackPressed()
) { overridePendingTransition(0, 0)
if (motionLayout?.currentState == binding.root.endState) {
onBackPressedDispatcher.onBackPressed()
overridePendingTransition(0, 0)
}
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float,
) {
// Nothing
} }
} }
override fun onTransitionTrigger(motionLayout: MotionLayout?, triggerId: Int, positive: Boolean, progress: Float) {
// Nothing
}
}
binding.root.setTransitionListener(transitionListener) binding.root.setTransitionListener(transitionListener)
} }
@@ -85,6 +68,7 @@ class ImageActivity : AppCompatActivity() {
} }
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allImages.size override fun getItemCount(): Int = allImages.size
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position]) override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])

View File

@@ -28,7 +28,9 @@ import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class LoginActivity : AppCompatActivity(), DIAware { class LoginActivity : AppCompatActivity(), DIAware {
private var inValidCount: Int = 0 private var inValidCount: Int = 0
private var isWithLogin = false private var isWithLogin = false
@@ -38,6 +40,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
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)
@@ -66,6 +69,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
} }
private fun handleActions() { private fun handleActions() {
binding.passwordView.setOnEditorActionListener( binding.passwordView.setOnEditorActionListener(
TextView.OnEditorActionListener { _, id, _ -> TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) { if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
@@ -73,7 +77,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
return@OnEditorActionListener true return@OnEditorActionListener true
} }
false false
}, }
) )
binding.signInButton.setOnClickListener { attemptLogin() } binding.signInButton.setOnClickListener { attemptLogin() }
@@ -94,7 +98,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
alertDialog.setMessage(getString(R.string.base_url_error)) alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK"
) { dialog, _ -> dialog.dismiss() } ) { dialog, _ -> dialog.dismiss() }
alertDialog.show() alertDialog.show()
} }
@@ -119,6 +123,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
} }
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
binding.urlView.error = null binding.urlView.error = null
binding.loginView.error = null binding.loginView.error = null
@@ -150,7 +155,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
R.string.application_selfoss_only, R.string.application_selfoss_only,
Toast.LENGTH_LONG, Toast.LENGTH_LONG
).show() ).show()
} }
preferenceError() preferenceError()
@@ -164,7 +169,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
private fun failLoginDetails( private fun failLoginDetails(
password: String, password: String,
login: String, login: String
) { ) {
var lastFocusedView: View? = null var lastFocusedView: View? = null
var cancel = false var cancel = false
@@ -197,7 +202,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
alertDialog.setMessage(getString(R.string.text_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK"
) { dialog, _ -> dialog.dismiss() } ) { dialog, _ -> dialog.dismiss() }
alertDialog.show() alertDialog.show()
inValidCount = 0 inValidCount = 0
@@ -206,10 +211,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
maybeCancelAndFocusView(cancel, focusView) maybeCancelAndFocusView(cancel, focusView)
} }
private fun maybeCancelAndFocusView( private fun maybeCancelAndFocusView(cancel: Boolean, focusView: View?) {
cancel: Boolean,
focusView: View?,
) {
if (cancel) { if (cancel) {
focusView?.requestFocus() focusView?.requestFocus()
} }
@@ -223,13 +225,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 0F else 1F, if (show) 0F else 1F
).setListener( ).setListener(object : AnimatorListenerAdapter() {
object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) {
override fun onAnimationEnd(animation: Animator) { binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE }
} }
},
) )
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
@@ -237,13 +238,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 1F else 0F, if (show) 1F else 0F
).setListener( ).setListener(object : AnimatorListenerAdapter() {
object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) {
override fun onAnimationEnd(animation: Animator) { binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE }
} }
},
) )
} }

View File

@@ -8,6 +8,7 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)

View File

@@ -32,20 +32,20 @@ import org.acra.sender.HttpSender
import org.kodein.di.* import org.kodein.di.*
class MyApp : MultiDexApplication(), DIAware { class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy { override val di by DI.lazy {
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) } bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) }
import(networkModule) import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) } bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) } bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with bind<Repository>() with singleton {
singleton { Repository(
Repository( instance(),
instance(), instance(),
instance(), isConnectionAvailable,
isConnectionAvailable, instance()
instance(), )
) }
}
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) } bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
} }
@@ -70,24 +70,23 @@ class MyApp : MultiDexApplication(), DIAware {
ProcessLifecycleOwner.get().lifecycle.addObserver( ProcessLifecycleOwner.get().lifecycle.addObserver(
AppLifeCycleObserver( AppLifeCycleObserver(
connectivityStatus, connectivityStatus,
repository, repository
), )
) )
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable -> viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = val toastMessage = if (networkAvailable) {
if (networkAvailable) { repository.handleDBActions()
repository.handleDBActions() R.string.network_connectivity_retrieved
R.string.network_connectivity_retrieved } else {
} else { R.string.network_connectivity_lost
R.string.network_connectivity_lost }
}
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
toastMessage, toastMessage,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
@@ -101,38 +100,37 @@ class MyApp : MultiDexApplication(), DIAware {
initAcra { initAcra {
reportFormat = StringFormat.JSON reportFormat = StringFormat.JSON
reportContent = reportContent = listOf(
listOf( ReportField.REPORT_ID,
ReportField.REPORT_ID, ReportField.INSTALLATION_ID,
ReportField.INSTALLATION_ID, ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_CODE, ReportField.APP_VERSION_NAME,
ReportField.APP_VERSION_NAME, ReportField.BUILD,
ReportField.BUILD, ReportField.ANDROID_VERSION,
ReportField.ANDROID_VERSION, ReportField.BRAND,
ReportField.BRAND, ReportField.PHONE_MODEL,
ReportField.PHONE_MODEL, ReportField.AVAILABLE_MEM_SIZE,
ReportField.AVAILABLE_MEM_SIZE, ReportField.TOTAL_MEM_SIZE,
ReportField.TOTAL_MEM_SIZE, ReportField.STACK_TRACE,
ReportField.STACK_TRACE, ReportField.APPLICATION_LOG,
ReportField.APPLICATION_LOG, ReportField.LOGCAT,
ReportField.LOGCAT, ReportField.INITIAL_CONFIGURATION,
ReportField.INITIAL_CONFIGURATION, ReportField.CRASH_CONFIGURATION,
ReportField.CRASH_CONFIGURATION, ReportField.IS_SILENT,
ReportField.IS_SILENT, ReportField.USER_APP_START_DATE,
ReportField.USER_APP_START_DATE, ReportField.USER_COMMENT,
ReportField.USER_COMMENT, ReportField.USER_CRASH_DATE,
ReportField.USER_CRASH_DATE, ReportField.USER_EMAIL,
ReportField.USER_EMAIL, ReportField.CUSTOM_DATA
ReportField.CUSTOM_DATA, )
)
toast { toast {
// required //required
text = getString(R.string.crash_toast_text) text = getString(R.string.crash_toast_text)
length = Toast.LENGTH_SHORT length = Toast.LENGTH_SHORT
} }
httpSender { httpSender {
uri = uri =
"https://bugs.amine-louveau.fr/report" // best guess, you may need to adjust this "https://bugs.amine-louveau.fr/report" /*best guess, you may need to adjust this*/
basicAuthLogin = "qMEscjj89Gwt6cPR" basicAuthLogin = "qMEscjj89Gwt6cPR"
basicAuthPassword = "Yo58QFlGzFaWlBzP" basicAuthPassword = "Yo58QFlGzFaWlBzP"
httpMethod = HttpSender.Method.POST httpMethod = HttpSender.Method.POST
@@ -150,12 +148,11 @@ class MyApp : MultiDexApplication(), DIAware {
val newItemsChannelname = getString(R.string.new_items_channel_sync) val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = val newItemsChannelmChannel = NotificationChannel(
NotificationChannel( AppSettingsService.newItemsChannelId,
AppSettingsService.newItemsChannelId, newItemsChannelname,
newItemsChannelname, newItemsChannelimportance
newItemsChannelimportance, )
)
notificationManager.createNotificationChannel(mChannel) notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel) notificationManager.createNotificationChannel(newItemsChannelmChannel)
@@ -166,11 +163,9 @@ class MyApp : MultiDexApplication(), DIAware {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler() val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e -> Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is NoClassDefFoundError && if (e is NoClassDefFoundError && e.stackTrace.asList().any {
e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug") it.toString().contains("android.view.ViewDebug")
} }) {
) {
// Nothing // Nothing
} else { } else {
oldHandler.uncaughtException(thread, e) oldHandler.uncaughtException(thread, e)
@@ -180,8 +175,9 @@ class MyApp : MultiDexApplication(), DIAware {
class AppLifeCycleObserver( class AppLifeCycleObserver(
val connectivityStatus: ConnectivityStatus, val connectivityStatus: ConnectivityStatus,
val repository: Repository, val repository: Repository
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) { override fun onResume(owner: LifecycleOwner) {
super.onResume(owner) super.onResume(owner)
repository.connectionMonitored = true repository.connectionMonitored = true

View File

@@ -23,6 +23,7 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class ReaderActivity : AppCompatActivity(), DIAware { class ReaderActivity : AppCompatActivity(), DIAware {
private var currentItem: Int = 0 private var currentItem: Int = 0
private lateinit var toolbarMenu: Menu private lateinit var toolbarMenu: Menu
@@ -101,15 +102,15 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
FragmentStateAdapter(fa) { FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allItems.size override fun getItemCount(): Int = allItems.size
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position]) override fun createFragment(position: Int): Fragment =
ArticleFragment.newInstance(allItems[position])
} }
override fun onKeyDown( override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
keyCode: Int,
event: KeyEvent?,
): Boolean {
return when (keyCode) { return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN -> {
val currentFragment = val currentFragment =
@@ -151,8 +152,10 @@ class ReaderActivity : AppCompatActivity(), DIAware {
canFavorite() canFavorite()
} }
binding.pager.registerOnPageChangeCallback( binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() { object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
@@ -163,7 +166,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
} }
readItem(allItems[position]) readItem(allItems[position])
} }
}, }
) )
} }

View File

@@ -18,10 +18,11 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class SourcesActivity : AppCompatActivity(), DIAware { class SourcesActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivitySourcesBinding private lateinit var binding: ActivitySourcesBinding
override val di by closestDI() override val di by closestDI()
private val repository: Repository by instance() private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivitySourcesBinding.inflate(layoutInflater) binding = ActivitySourcesBinding.inflate(layoutInflater)
@@ -57,18 +58,16 @@ class SourcesActivity : AppCompatActivity(), DIAware {
val response = repository.getSourcesDetails() val response = repository.getSourcesDetails()
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
items = response items = response
val mAdapter = val mAdapter = SourcesListAdapter(
SourcesListAdapter( this@SourcesActivity, items
this@SourcesActivity, )
items,
)
binding.recyclerView.adapter = mAdapter binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
} else { } else {
Toast.makeText( Toast.makeText(
this@SourcesActivity, this@SourcesActivity,
R.string.cant_get_sources, R.string.cant_get_sources,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }

View File

@@ -21,7 +21,9 @@ import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class UpsertSourceActivity : AppCompatActivity(), DIAware { class UpsertSourceActivity : AppCompatActivity(), DIAware {
private var existingSource: SelfossModel.SourceDetail? = null private var existingSource: SelfossModel.SourceDetail? = null
private var mSpoutsValue: String? = null private var mSpoutsValue: String? = null
@@ -56,6 +58,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.title = resources.getString(title) supportActionBar?.title = resources.getString(title)
maybeGetDetailsFromIntentSharing(intent) maybeGetDetailsFromIntentSharing(intent)
binding.saveBtn.setOnClickListener { binding.saveBtn.setOnClickListener {
@@ -85,30 +88,25 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
private fun handleSpoutsSpinner() { private fun handleSpoutsSpinner() {
val spoutsKV = HashMap<String, String>() val spoutsKV = HashMap<String, String>()
binding.spoutsSpinner.onItemSelectedListener = binding.spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
object : AdapterView.OnItemSelectedListener { override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
override fun onItemSelected( if (view != null) {
adapterView: AdapterView<*>, val spoutName = (view as TextView).text.toString()
view: View?, mSpoutsValue = spoutsKV[spoutName]
i: Int,
l: Long,
) {
if (view != null) {
val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName]
}
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
mSpoutsValue = null
} }
} }
override fun onNothingSelected(adapterView: AdapterView<*>) {
mSpoutsValue = null
}
}
fun handleSpoutFailure(networkIssue: Boolean = false) { fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText( Toast.makeText(
this@UpsertSourceActivity, 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()
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE
} }
@@ -129,7 +127,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
ArrayAdapter( ArrayAdapter(
this@UpsertSourceActivity, 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)
binding.spoutsSpinner.adapter = spinnerArrayAdapter binding.spoutsSpinner.adapter = spinnerArrayAdapter
@@ -146,7 +144,9 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
} }
} }
private fun maybeGetDetailsFromIntentSharing(intent: Intent) { private fun maybeGetDetailsFromIntentSharing(
intent: Intent
) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) { if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT)) binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE)) binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
@@ -172,30 +172,29 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware {
} }
else -> { else -> {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = val successfullyAddedSource = if (existingSource != null) {
if (existingSource != null) { repository.updateSource(
repository.updateSource( existingSource!!.id,
existingSource!!.id, binding.nameInput.text.toString(),
binding.nameInput.text.toString(), url,
url, mSpoutsValue!!,
mSpoutsValue!!, binding.tags.text.toString()
binding.tags.text.toString(), )
) } else {
} else { repository.createSource(
repository.createSource( binding.nameInput.text.toString(),
binding.nameInput.text.toString(), url,
url, mSpoutsValue!!,
mSpoutsValue!!, binding.tags.text.toString(),
binding.tags.text.toString(), )
) }
}
if (successfullyAddedSource) { if (successfullyAddedSource) {
finish() finish()
} else { } else {
Toast.makeText( Toast.makeText(
this@UpsertSourceActivity, this@UpsertSourceActivity,
R.string.cant_create_source, R.string.cant_create_source,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }

View File

@@ -32,7 +32,7 @@ import org.kodein.di.instance
class ItemCardAdapter( class ItemCardAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() { ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val imageMaxHeight: Int = private val imageMaxHeight: Int =
@@ -42,18 +42,12 @@ class ItemCardAdapter(
override val repository: Repository by instance() override val repository: Repository by instance()
override val appSettingsService: AppSettingsService by instance() override val appSettingsService: AppSettingsService by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding) return ViewHolder(binding)
} }
override fun onBindViewHolder( override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder: ViewHolder,
position: Int,
) {
with(holder) { with(holder) {
val itm = items[position] val itm = items[position]
@@ -103,6 +97,7 @@ class ItemCardAdapter(
} }
private fun handleClickListeners() { private fun handleClickListeners() {
binding.favButton.setOnClickListener { binding.favButton.setOnClickListener {
val item = items[bindingAdapterPosition] val item = items[bindingAdapterPosition]
if (item.starred) { if (item.starred) {
@@ -135,7 +130,7 @@ class ItemCardAdapter(
bindingAdapterPosition, bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(), appSettingsService.isArticleViewerEnabled(),
app, app
) )
} }
} }

View File

@@ -23,26 +23,20 @@ import org.kodein.di.instance
class ItemListAdapter( class ItemListAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemListAdapter.ViewHolder>() { ) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val c: Context = app.baseContext private val c: Context = app.baseContext
override val di: DI by closestDI(app) override val di: DI by closestDI(app)
override val repository: Repository by instance() override val repository : Repository by instance()
override val appSettingsService: AppSettingsService by instance() override val appSettingsService : AppSettingsService by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup,
viewType: Int,
): ViewHolder {
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding) return ViewHolder(binding)
} }
override fun onBindViewHolder( override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder: ViewHolder,
position: Int,
) {
with(holder) { with(holder) {
val itm = items[position] val itm = items[position]
@@ -55,6 +49,7 @@ class ItemListAdapter(
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate() binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) { if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded()) binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded())
} else { } else {
@@ -69,6 +64,7 @@ class ItemListAdapter(
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) { inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
init { init {
handleLinkOpening() handleLinkOpening()
} }
@@ -80,7 +76,7 @@ class ItemListAdapter(
bindingAdapterPosition, bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(), items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(), appSettingsService.isArticleViewerEnabled(),
app, app
) )
} }
} }

View File

@@ -28,20 +28,16 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items) updateItems(this.items)
} }
private fun unmarkSnackbar( private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item, val s = Snackbar
position: Int, .make(
) { app.findViewById(R.id.coordLayout),
val s = R.string.marked_as_read,
Snackbar Snackbar.LENGTH_LONG
.make( )
app.findViewById(R.id.coordLayout), .setAction(R.string.undo_string) {
R.string.marked_as_read, unreadItemAtIndex(item, position, false)
Snackbar.LENGTH_LONG, }
)
.setAction(R.string.undo_string) {
unreadItemAtIndex(item, position, false)
}
val view = s.view val view = s.view
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
@@ -49,20 +45,16 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
s.show() s.show()
} }
private fun markSnackbar( private fun markSnackbar(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item, val s = Snackbar
position: Int, .make(
) { app.findViewById(R.id.coordLayout),
val s = R.string.marked_as_unread,
Snackbar Snackbar.LENGTH_LONG
.make( )
app.findViewById(R.id.coordLayout), .setAction(R.string.undo_string) {
R.string.marked_as_unread, readItemAtIndex(item, position, false)
Snackbar.LENGTH_LONG, }
)
.setAction(R.string.undo_string) {
readItemAtIndex(item, position, false)
}
val view = s.view val view = s.view
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
@@ -78,11 +70,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
private fun readItemAtIndex( private fun readItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
item: SelfossModel.Item,
position: Int,
showSnackbar: Boolean = true,
) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item) repository.markAsRead(item)
} }
@@ -98,13 +86,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
private fun unreadItemAtIndex( private fun unreadItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
item: SelfossModel.Item,
position: Int,
showSnackbar: Boolean = true,
) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(item) repository.unmarkAsRead(item)
} }
notifyItemChanged(position) notifyItemChanged(position)
if (showSnackbar) { if (showSnackbar) {
@@ -112,13 +97,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
fun addItemAtIndex( fun addItemAtIndex(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item,
position: Int,
) {
items.add(position, item) items.add(position, item)
notifyItemInserted(position) notifyItemInserted(position)
updateItems(items) updateItems(items)
} }
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) { fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
@@ -126,5 +109,6 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
items.addAll(newItems) items.addAll(newItems)
notifyItemRangeInserted(oldSize, newItems.size) notifyItemRangeInserted(oldSize, newItems.size)
updateItems(items) updateItems(items)
} }
} }

View File

@@ -28,26 +28,20 @@ import org.kodein.di.instance
class SourcesListAdapter( class SourcesListAdapter(
private val app: Activity, private val app: Activity,
private val items: ArrayList<SelfossModel.SourceDetail>, private val items: ArrayList<SelfossModel.SourceDetail>
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware { ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private lateinit var binding: SourceListItemBinding private lateinit var binding: SourceListItemBinding
override val di: DI by closestDI(app) override val di: DI by closestDI(app)
private val repository: Repository by instance() private val repository : Repository by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup,
viewType: Int,
): ViewHolder {
binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding.root) return ViewHolder(binding.root)
} }
override fun onBindViewHolder( override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder: ViewHolder,
position: Int,
) {
val itm = items[position] val itm = items[position]
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
@@ -73,11 +67,13 @@ class SourcesListAdapter(
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) { inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
init { init {
handleClickListeners() handleClickListeners()
} }
private fun handleClickListeners() { private fun handleClickListeners() {
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener { deleteBtn.setOnClickListener {
@@ -92,7 +88,7 @@ class SourcesListAdapter(
Toast.makeText( Toast.makeText(
app, app,
R.string.can_delete_source, R.string.can_delete_source,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
@@ -103,6 +99,7 @@ class SourcesListAdapter(
repository.setSelectedSource(source) repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java)) app.startActivity(Intent(app, UpsertSourceActivity::class.java))
} }
} }
} }

View File

@@ -26,15 +26,16 @@ import org.kodein.di.instance
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
class LoadingWorker(val context: Context, params: WorkerParameters) : class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params),
Worker(context, params),
DIAware { DIAware {
override val di by lazy { (applicationContext as MyApp).di } override val di by lazy { (applicationContext as MyApp).di }
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 doWork(): Result { override fun doWork(): Result {
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) { if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val notificationManager = val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -66,37 +67,37 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
private fun handleNewItemsNotification( private fun handleNewItemsNotification(
newItems: List<SelfossModel.Item>?, newItems: List<SelfossModel.Item>?,
notificationManager: NotificationManager, notificationManager: NotificationManager
) { ) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val apiItems = newItems.orEmpty() val apiItems = newItems.orEmpty()
val newSize = apiItems.filter { it.unread }.size val newSize = apiItems.filter { it.unread }.size
if (newSize > 0) { if (newSize > 0) {
val intent =
Intent(context, MainActivity::class.java).apply { val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
} }
val pflags = val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_IMMUTABLE
PendingIntent.FLAG_IMMUTABLE } else {
} else { 0
0 }
}
val pendingIntent: PendingIntent = val pendingIntent: PendingIntent =
PendingIntent.getActivity(context, 0, intent, pflags) PendingIntent.getActivity(context, 0, intent, pflags)
val newItemsNotification = val newItemsNotification =
NotificationCompat.Builder( NotificationCompat.Builder(
applicationContext, applicationContext,
AppSettingsService.newItemsChannelId, AppSettingsService.newItemsChannelId
) )
.setContentTitle(context.getString(R.string.new_items_notification_title)) .setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText( .setContentText(
context.getString( context.getString(
R.string.new_items_notification_text, R.string.new_items_notification_text,
newSize, newSize
), )
) )
.setPriority(PRIORITY_DEFAULT) .setPriority(PRIORITY_DEFAULT)
.setChannelId(AppSettingsService.newItemsChannelId) .setChannelId(AppSettingsService.newItemsChannelId)

View File

@@ -57,6 +57,7 @@ import java.net.URL
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg" private const val IMAGE_JPG = "image/jpg"
class ArticleFragment : Fragment(), DIAware { class ArticleFragment : Fragment(), DIAware {
@@ -83,6 +84,7 @@ class ArticleFragment : Fragment(), DIAware {
private val mercuryApi: MercuryApi by instance() private val mercuryApi: MercuryApi by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -94,7 +96,7 @@ class ArticleFragment : Fragment(), DIAware {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?
): View { ): View {
try { try {
binding = FragmentArticleBinding.inflate(inflater, container, false) binding = FragmentArticleBinding.inflate(inflater, container, false)
@@ -144,8 +146,9 @@ class ArticleFragment : Fragment(), DIAware {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show() if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
} }
} }
}, }
) )
} catch (e: InflateException) { } catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available") e.sendSilentlyWithAcraWithName("webview not available")
if (context != null) { if (context != null) {
@@ -153,7 +156,7 @@ class ArticleFragment : Fragment(), DIAware {
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) .setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) .setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton( .setPositiveButton(
android.R.string.ok, android.R.string.ok
) { _, _ -> ) { _, _ ->
appSettingsService.disableArticleViewer() appSettingsService.disableArticleViewer()
requireActivity().finish() requireActivity().finish()
@@ -208,30 +211,29 @@ class ArticleFragment : Fragment(), DIAware {
when (item.itemId) { when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle) R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item) R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> R.id.unread_action -> if (context != null) {
if (context != null) { if (this@ArticleFragment.item.unread) {
if (this@ArticleFragment.item.unread) { CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.IO).launch { repository.markAsRead(this@ArticleFragment.item)
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG,
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
} }
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG
).show()
} }
}
else -> Unit else -> Unit
} }
} }
@@ -239,18 +241,17 @@ class ArticleFragment : Fragment(), DIAware {
override fun onItemLongClick(item: MenuItem?) { override fun onItemLongClick(item: MenuItem?) {
// We do nothing // We do nothing
} }
}, }
) )
return floatingToolbar return floatingToolbar
} }
private fun refreshAlignment() { private fun refreshAlignment() {
textAlignment = textAlignment = when (appSettingsService.getActiveAllignment()) {
when (appSettingsService.getActiveAllignment()) { 1 -> "justify"
1 -> "justify" 2 -> "left"
2 -> "left" else -> "justify"
else -> "justify" }
}
} }
private fun getContentFromMercury() { private fun getContentFromMercury() {
@@ -301,7 +302,7 @@ class ArticleFragment : Fragment(), DIAware {
.with(requireContext()) .with(requireContext())
.asBitmap() .asBitmap()
.load( .load(
lead_image_url, lead_image_url
) )
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding.imageView)
@@ -311,75 +312,67 @@ class ArticleFragment : Fragment(), DIAware {
} }
private fun handleImageLoading() { private fun handleImageLoading() {
binding.webcontent.webViewClient = binding.webcontent.webViewClient = object : WebViewClient() {
object : WebViewClient() { @Deprecated("Deprecated in Java")
@Deprecated("Deprecated in Java") override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
override fun shouldOverrideUrlLoading( return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
view: WebView?, try {
url: String, requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
): Boolean { } catch (e: ActivityNotFoundException) {
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { e.sendSilentlyWithAcraWithName("activityNotFound > $url")
try {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} catch (e: ActivityNotFoundException) {
e.sendSilentlyWithAcraWithName("activityNotFound > $url")
}
true
} else {
false
} }
} true
} else {
@Deprecated("Deprecated in Java") false
override fun shouldInterceptRequest(
view: WebView,
url: String,
): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US)
.contains(".jpeg")
) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
)
} catch (e: ExecutionException) {
// Do nothing
}
}
return super.shouldInterceptRequest(view, url)
} }
} }
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(view: WebView, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US)
.contains(".jpeg")
) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG)
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.PNG)
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP)
)
} catch (e: ExecutionException) {
// Do nothing
}
}
return super.shouldInterceptRequest(view, url)
}
}
} }
private fun htmlToWebview() { private fun htmlToWebview() {
@@ -387,6 +380,7 @@ class ArticleFragment : Fragment(), DIAware {
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE binding.webcontent.visibility = View.VISIBLE
@@ -403,14 +397,11 @@ class ArticleFragment : Fragment(), DIAware {
handleImageLoading() handleImageLoading()
val gestureDetector = val gestureDetector =
GestureDetector( GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
activity, override fun onSingleTapUp(e: MotionEvent): Boolean {
object : GestureDetector.SimpleOnGestureListener() { return performClick()
override fun onSingleTapUp(e: MotionEvent): Boolean { }
return performClick() })
}
},
)
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) } binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) }
@@ -426,31 +417,29 @@ class ArticleFragment : Fragment(), DIAware {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url") e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
} }
val fontName = val fontName = when (font) {
when (font) { getString(R.string.open_sans_font_id) -> "Open Sans"
getString(R.string.open_sans_font_id) -> "Open Sans" getString(R.string.roboto_font_id) -> "Roboto"
getString(R.string.roboto_font_id) -> "Roboto" getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
getString(R.string.source_code_pro_font_id) -> "Source Code Pro" else -> ""
else -> "" }
}
val fontLinkAndStyle = val fontLinkAndStyle = if (font.isNotEmpty()) {
if (font.isNotEmpty()) { """<link href="https://fonts.googleapis.com/css?family=${
"""<link href="https://fonts.googleapis.com/css?family=${ fontName.replace(
fontName.replace( " ",
" ", "+"
"+", )
) }" rel="stylesheet">
}" rel="stylesheet">
|<style> |<style>
| * { | * {
| font-family: '$fontName'; | font-family: '$fontName';
| } | }
|</style> |</style>
""".trimMargin() """.trimMargin()
} else { } else {
"" ""
} }
binding.webcontent.loadDataWithBaseURL( binding.webcontent.loadDataWithBaseURL(
baseUrl, baseUrl,
@@ -468,7 +457,7 @@ class ArticleFragment : Fragment(), DIAware {
| color: ${ | color: ${
String.format( String.format(
"#%06X", "#%06X",
0xFFFFFF and resources.getColor(R.color.colorAccent), 0xFFFFFF and resources.getColor(R.color.colorAccent)
) )
} !important; } !important;
| } | }
@@ -484,7 +473,7 @@ class ArticleFragment : Fragment(), DIAware {
| background-color: ${ | background-color: ${
String.format( String.format(
"#%06X", "#%06X",
0xFFFFFF and colorSurface.data, 0xFFFFFF and colorSurface.data
) )
}; };
| } | }
@@ -492,13 +481,13 @@ class ArticleFragment : Fragment(), DIAware {
| background-color: ${ | background-color: ${
String.format( String.format(
"#%06X", "#%06X",
0xFFFFFF and colorSurface.data, 0xFFFFFF and colorSurface.data
) )
} !important; } !important;
| border-color: ${ | border-color: ${
String.format( String.format(
"#%06X", "#%06X",
0xFFFFFF and colorSurface.data, 0xFFFFFF and colorSurface.data
) )
} !important; } !important;
| padding: 0 !important; | padding: 0 !important;
@@ -513,7 +502,7 @@ class ArticleFragment : Fragment(), DIAware {
| background-color: ${ | background-color: ${
String.format( String.format(
"#%06X", "#%06X",
0xFFFFFF and colorSurface.data, 0xFFFFFF and colorSurface.data
) )
}; };
| } | }
@@ -522,11 +511,10 @@ class ArticleFragment : Fragment(), DIAware {
|</head> |</head>
|<body> |<body>
| $contentText | $contentText
|</body> |</body>""".trimMargin(),
""".trimMargin(),
"text/html", "text/html",
"utf-8", "utf-8",
null, null
) )
} }
} }
@@ -547,13 +535,16 @@ class ArticleFragment : Fragment(), DIAware {
requireContext().openInBrowserAsNewTask(this@ArticleFragment.item) requireContext().openInBrowserAsNewTask(this@ArticleFragment.item)
} else { } else {
Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context") Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context")
} }
} }
companion object { companion object {
private const val ARG_ITEMS = "items" private const val ARG_ITEMS = "items"
fun newInstance(item: SelfossModel.Item): ArticleFragment { fun newInstance(
item: SelfossModel.Item
): ArticleFragment {
val fragment = ArticleFragment() val fragment = ArticleFragment()
val args = Bundle() val args = Bundle()
args.putParcelable(ARG_ITEMS, item.toParcelable()) args.putParcelable(ARG_ITEMS, item.toParcelable())
@@ -563,11 +554,10 @@ class ArticleFragment : Fragment(), DIAware {
} }
fun performClick(): Boolean { fun performClick(): Boolean {
if (allImages != null && ( if (allImages != null && (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE)
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
)
) { ) {
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra) val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
val intent = Intent(activity, ImageActivity::class.java) val intent = Intent(activity, ImageActivity::class.java)
@@ -578,4 +568,6 @@ class ArticleFragment : Fragment(), DIAware {
} }
return false return false
} }
} }

View File

@@ -32,7 +32,9 @@ import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI import org.kodein.di.android.x.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
private lateinit var binding: FilterFragmentBinding private lateinit var binding: FilterFragmentBinding
override val di: DI by closestDI() override val di: DI by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
@@ -42,17 +44,18 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?
): View { ): View {
binding = binding =
FilterFragmentBinding.inflate( FilterFragmentBinding.inflate(
inflater, inflater,
container, container,
false, false
) )
val context: Context? = context val context: Context? = context
if (context == null) { if (context == null) {
dismiss() dismiss()
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView") Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
@@ -74,7 +77,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
return binding.root return binding.root
} }
private suspend fun handleSourceChips(context: Context) { private suspend fun handleSourceChips(
context: Context
) {
val sourceGroup = binding.sourcesGroup val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEachIndexed { _, source -> repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
@@ -83,20 +88,19 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
Glide.with(context) Glide.with(context)
.load(source.getIcon(repository.baseUrl)) .load(source.getIcon(repository.baseUrl))
.into( .into(object : ViewTarget<Chip?, Drawable?>(c) {
object : ViewTarget<Chip?, Drawable?>(c) { override fun onResourceReady(
override fun onResourceReady( resource: Drawable,
resource: Drawable, transition: Transition<in Drawable?>?
transition: Transition<in Drawable?>?, ) {
) { try {
try { c.chipIcon = resource
c.chipIcon = resource } catch (e: Exception) {
} catch (e: Exception) { e.sendSilentlyWithAcraWithName("sources > onResourceReady")
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
}
} }
}, }
)
})
c.text = source.title.getHtmlDecoded() c.text = source.title.getHtmlDecoded()
@@ -117,6 +121,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
repository.setTagFilter(null) repository.setTagFilter(null)
} }
if (repository.sourceFilter.value?.equals(source) == true) { if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true c.isCloseIconVisible = true
selectedChip = c selectedChip = c
@@ -132,7 +137,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
} }
} }
private suspend fun handleTagChips(context: Context) { private suspend fun handleTagChips(
context: Context,
) {
val tagGroup = binding.tagsGroup val tagGroup = binding.tagsGroup
val tags = repository.getTags() val tags = repository.getTags()
@@ -145,13 +152,12 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
if (tag.color.isNotEmpty()) { if (tag.color.isNotEmpty()) {
try { try {
val gd = GradientDrawable() val gd = GradientDrawable()
val gdColor = val gdColor = try {
try { Color.parseColor(tag.color)
Color.parseColor(tag.color) } catch (e: IllegalArgumentException) {
} catch (e: IllegalArgumentException) { e.sendSilentlyWithAcraWithName("color issue " + tag.color)
e.sendSilentlyWithAcraWithName("color issue " + tag.color) resources.getColor(R.color.colorPrimary)
resources.getColor(R.color.colorPrimary) }
}
gd.setColor(gdColor) gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30) gd.setSize(30, 30)
@@ -191,4 +197,6 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
companion object { companion object {
const val TAG = "FilterModalBottomSheet" const val TAG = "FilterModalBottomSheet"
} }
} }

View File

@@ -11,7 +11,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
class ImageFragment : Fragment() { class ImageFragment : Fragment() {
private lateinit var imageUrl: String
private lateinit var imageUrl : String
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
private var _binding: FragmentImageBinding? = null private var _binding: FragmentImageBinding? = null
private val binding get() = _binding private val binding get() = _binding
@@ -22,20 +23,16 @@ class ImageFragment : Fragment() {
imageUrl = requireArguments().getString("imageUrl")!! imageUrl = requireArguments().getString("imageUrl")!!
} }
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = FragmentImageBinding.inflate(inflater, container, false) _binding = FragmentImageBinding.inflate(inflater, container, false)
val view = binding?.root val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE binding!!.photoView.visibility = View.VISIBLE
Glide.with(requireActivity()) Glide.with(requireActivity())
.asBitmap() .asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(imageUrl) .load(imageUrl)
.into(binding!!.photoView) .into(binding!!.photoView)
return view return view
} }
@@ -48,7 +45,9 @@ class ImageFragment : Fragment() {
companion object { companion object {
private const val ARG_IMAGE = "imageUrl" private const val ARG_IMAGE = "imageUrl"
fun newInstance(imageUrl: String): ImageFragment { fun newInstance(
imageUrl : String
): ImageFragment {
val fragment = ImageFragment() val fragment = ImageFragment()
val args = Bundle() val args = Bundle()
args.putString(ARG_IMAGE, imageUrl) args.putString(ARG_IMAGE, imageUrl)

View File

@@ -9,20 +9,21 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
fun SelfossModel.Item.preloadImages(context: Context): Boolean { fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
val imageUrls = this.getImages() val imageUrls = this.getImages()
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
try { try {
for (url in imageUrls) { for (url in imageUrls) {
if (URLUtil.isValidUrl(url)) { if ( URLUtil.isValidUrl(url)) {
Glide.with(context).asBitmap() Glide.with(context).asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(url).submit() .load(url).submit()
} }
} }
} catch (e: Error) { } catch (e : Error) {
e.sendSilentlyWithAcraWithName("preloadImages") e.sendSilentlyWithAcraWithName("preloadImages")
return false return false
} }

View File

@@ -4,7 +4,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun SelfossModel.Item.toParcelable(): ParecelableItem = fun SelfossModel.Item.toParcelable() : ParecelableItem =
ParecelableItem( ParecelableItem(
this.id, this.id,
this.datetime, this.datetime,
@@ -17,10 +17,9 @@ fun SelfossModel.Item.toParcelable(): ParecelableItem =
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.joinToString(","), this.tags.joinToString(","),
this.author, this.author
) )
fun ParecelableItem.toModel() : SelfossModel.Item =
fun ParecelableItem.toModel(): SelfossModel.Item =
SelfossModel.Item( SelfossModel.Item(
this.id, this.id,
this.datetime, this.datetime,
@@ -33,9 +32,8 @@ fun ParecelableItem.toModel(): SelfossModel.Item =
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.split(","), this.tags.split(","),
this.author, this.author
) )
data class ParecelableItem( data class ParecelableItem(
val id: Int, val id: Int,
val datetime: String, val datetime: String,
@@ -48,16 +46,15 @@ data class ParecelableItem(
val link: String, val link: String,
val sourcetitle: String, val sourcetitle: String,
val tags: String, val tags: String,
val author: String?, val author: String?
) : Parcelable { ) : Parcelable {
companion object { companion object {
@JvmField @JvmField
val CREATOR: Parcelable.Creator<ParecelableItem> = val CREATOR: Parcelable.Creator<ParecelableItem> = object : Parcelable.Creator<ParecelableItem> {
object : Parcelable.Creator<ParecelableItem> { override fun createFromParcel(source: Parcel): ParecelableItem = ParecelableItem(source)
override fun createFromParcel(source: Parcel): ParecelableItem = ParecelableItem(source) override fun newArray(size: Int): Array<ParecelableItem?> = arrayOfNulls(size)
}
override fun newArray(size: Int): Array<ParecelableItem?> = arrayOfNulls(size)
}
} }
constructor(source: Parcel) : this( constructor(source: Parcel) : this(
@@ -72,15 +69,12 @@ data class ParecelableItem(
link = source.readString().orEmpty(), link = source.readString().orEmpty(),
sourcetitle = source.readString().orEmpty(), sourcetitle = source.readString().orEmpty(),
tags = source.readString().orEmpty(), tags = source.readString().orEmpty(),
author = source.readString().orEmpty(), author = source.readString().orEmpty()
) )
override fun describeContents() = 0 override fun describeContents() = 0
override fun writeToParcel( override fun writeToParcel(dest: Parcel, flags: Int) {
dest: Parcel,
flags: Int,
) {
dest.writeInt(id) dest.writeInt(id)
dest.writeString(datetime) dest.writeString(datetime)
dest.writeString(title) dest.writeString(title)

View File

@@ -24,10 +24,8 @@ import org.kodein.di.android.closestDI
private const val TITLE_TAG = "settingsActivityTitle" private const val TITLE_TAG = "settingsActivityTitle"
class SettingsActivity : class SettingsActivity : AppCompatActivity(),
AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
DIAware {
override val di by closestDI() override val di by closestDI()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -37,9 +35,9 @@ class SettingsActivity :
setContentView(binding.root) setContentView(binding.root)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.settings, MainPreferenceFragment()) .replace(R.id.settings, MainPreferenceFragment())
.commit() .commit()
} else { } else {
title = savedInstanceState.getCharSequence(TITLE_TAG) title = savedInstanceState.getCharSequence(TITLE_TAG)
} }
@@ -73,67 +71,57 @@ class SettingsActivity :
} }
override fun onPreferenceStartFragment( override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat, caller: PreferenceFragmentCompat,
pref: Preference, pref: Preference
): Boolean { ): Boolean {
// Instantiate the new Fragment // Instantiate the new Fragment
val args = pref.extras val args = pref.extras
val fragment = val fragment = supportFragmentManager.fragmentFactory.instantiate(
supportFragmentManager.fragmentFactory.instantiate(
classLoader, classLoader,
pref.fragment.toString(), pref.fragment.toString()
).apply { ).apply {
arguments = args arguments = args
setTargetFragment(caller, 0) setTargetFragment(caller, 0)
} }
// Replace the existing Fragment with the new Fragment // Replace the existing Fragment with the new Fragment
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment) .replace(R.id.settings, fragment)
.addToBackStack(null) .addToBackStack(null)
.commit() .commit()
title = pref.title title = pref.title
supportActionBar?.title = title supportActionBar?.title = title
return true return true
} }
class MainPreferenceFragment : PreferenceFragmentCompat() { class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_main, rootKey) setPreferencesFromResource(R.xml.pref_main, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
Preference.OnPreferenceChangeListener { _, newValue -> AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯ true
true }
}
preferenceManager.findPreference<Preference>("action_about")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("action_about")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ ->
Preference.OnPreferenceClickListener { _ -> context?.let {
context?.let { LibsBuilder()
LibsBuilder() .withAboutIconShown(true)
.withAboutIconShown(true) .withAboutVersionShown(true)
.withAboutVersionShown(true) .start(it)
.start(it)
}
true
} }
true
}
} }
} }
class GeneralPreferenceFragment : PreferenceFragmentCompat() { class GeneralPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_general, rootKey) setPreferencesFromResource(R.xml.pref_general, rootKey)
val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number") val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number")
editTextPreference?.setOnBindEditTextListener { editText -> editTextPreference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.filters = editText.filters = arrayOf(
arrayOf(
InputFilter { source, _, _, dest, _, _ -> InputFilter { source, _, _, dest, _, _ ->
try { try {
val input: Int = (dest.toString() + source.toString()).toInt() val input: Int = (dest.toString() + source.toString()).toInt()
@@ -143,53 +131,35 @@ class SettingsActivity :
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
} }
"" ""
}, }
) )
} }
} }
} }
class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() { class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_viewer, rootKey) setPreferencesFromResource(R.xml.pref_viewer, rootKey)
val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size") val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size")
fontSize?.setOnBindEditTextListener { editText -> fontSize?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.addTextChangedListener { editText.addTextChangedListener { object : TextWatcher {
object : TextWatcher { override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
override fun beforeTextChanged( // We do nothing
charSequence: CharSequence, }
i: Int, override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
i1: Int, // We do nothing
i2: Int, }
) { override fun afterTextChanged(editable: Editable) {
// We do nothing try {
} editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) {
override fun onTextChanged( e.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > afterTextChanged")
charSequence: CharSequence,
i: Int,
i1: Int,
i2: Int,
) {
// We do nothing
}
override fun afterTextChanged(editable: Editable) {
try {
editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) {
e.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > afterTextChanged")
}
} }
} }
} } }
editText.filters = editText.filters = arrayOf(
arrayOf(
InputFilter { source, _, _, dest, _, _ -> InputFilter { source, _, _, dest, _, _ ->
try { try {
val input = (dest.toString() + source.toString()).toInt() val input = (dest.toString() + source.toString()).toInt()
@@ -198,33 +168,26 @@ class SettingsActivity :
nfe.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > filters") nfe.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > filters")
} }
"" ""
}, }
) )
} }
} }
} }
class OfflinePreferenceFragment : PreferenceFragmentCompat() { class OfflinePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_offline, rootKey) setPreferencesFromResource(R.xml.pref_offline, rootKey)
} }
} }
class ThemePreferenceFragment : PreferenceFragmentCompat() { class ThemePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_theme, rootKey) setPreferencesFromResource(R.xml.pref_theme, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
Preference.OnPreferenceChangeListener { _, newValue -> AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯ true
true }
}
} }
} }
@@ -234,37 +197,28 @@ class SettingsActivity :
startActivity(browserIntent) startActivity(browserIntent)
} }
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_links, rootKey) setPreferencesFromResource(R.xml.pref_links, rootKey)
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener { openUrl(Uri.parse(AppSettingsService.trackerUrl))
openUrl(Uri.parse(AppSettingsService.trackerUrl)) true
true }
}
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener { openUrl(Uri.parse(AppSettingsService.sourceUrl))
openUrl(Uri.parse(AppSettingsService.sourceUrl)) false
false }
}
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener { openUrl(Uri.parse(AppSettingsService.translationUrl))
openUrl(Uri.parse(AppSettingsService.translationUrl)) false
false }
}
} }
} }
class ExperimentalPreferenceFragment : PreferenceFragmentCompat() { class ExperimentalPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_experimental, rootKey) setPreferencesFromResource(R.xml.pref_experimental, rootKey)
} }
} }

View File

@@ -5,10 +5,7 @@ import android.content.Intent
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
fun Context.shareLink( fun Context.shareLink(itemUrl: String, itemTitle: String) {
itemUrl: String,
itemTitle: String,
) {
val sendIntent = Intent() val sendIntent = Intent()
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND sendIntent.action = Intent.ACTION_SEND
@@ -18,7 +15,7 @@ fun Context.shareLink(
startActivity( startActivity(
Intent.createChooser( Intent.createChooser(
sendIntent, sendIntent,
getString(R.string.share), getString(R.string.share)
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
) )
} }

View File

@@ -12,54 +12,51 @@ import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import kotlin.math.abs import kotlin.math.abs
class CircleImageView class CircleImageView @JvmOverloads constructor(
@JvmOverloads context: Context,
constructor( attrs: AttributeSet? = null,
context: Context, defStyleAttr: Int = 0
attrs: AttributeSet? = null, ) : RelativeLayout(context, attrs, defStyleAttr) {
defStyleAttr: Int = 0, val view: View
) : RelativeLayout(context, attrs, defStyleAttr) { val imageView: ShapeableImageView
val view: View val textView: TextView
val imageView: ShapeableImageView
val textView: TextView
private val colorScheme = private val colorScheme = listOf(
listOf( -0x1a8c8d,
-0x1a8c8d, -0xf9d6e,
-0xf9d6e, -0x459738,
-0x459738, -0x6a8a33,
-0x6a8a33, -0x867935,
-0x867935, -0x9b4a0a,
-0x9b4a0a, -0xb03c09,
-0xb03c09, -0xb22f1f,
-0xb22f1f, -0xb24954,
-0xb24954, -0x7e387c,
-0x7e387c, -0x512a7f,
-0x512a7f, -0x759b,
-0x759b, -0x2b1ea9,
-0x2b1ea9, -0x2ab1,
-0x2ab1, -0x48b3,
-0x48b3, -0x5e7781,
-0x5e7781, -0x6f5b52
-0x6f5b52, )
)
init { init {
view = LayoutInflater.from(context).inflate(R.layout.circle_image_view, this, true) view = LayoutInflater.from(context).inflate(R.layout.circle_image_view, this, true)
imageView = view.findViewById(R.id.circleImage) imageView = view.findViewById(R.id.circleImage)
textView = view.findViewById(R.id.circleText) textView = view.findViewById(R.id.circleText)
}
fun setBackgroundAndText(text: String) {
val circleDrawable = GradientDrawable()
val color = colorFromIdentifier(text)
circleDrawable.setColor(color)
imageView.setImageDrawable(circleDrawable)
textView.text = text.toTextDrawableString()
}
private fun colorFromIdentifier(key: String): Int {
return colorScheme[abs(key.hashCode()) % colorScheme.size]
}
} }
fun setBackgroundAndText(text: String) {
val circleDrawable = GradientDrawable()
val color = colorFromIdentifier(text)
circleDrawable.setColor(color)
imageView.setImageDrawable(circleDrawable)
textView.text = text.toTextDrawableString()
}
private fun colorFromIdentifier(key: String): Int {
return colorScheme[abs(key.hashCode()) % colorScheme.size]
}
}

View File

@@ -21,13 +21,14 @@ fun Context.openItemUrl(
currentItem: Int, currentItem: Int,
linkDecoded: String, linkDecoded: String,
articleViewer: Boolean, articleViewer: Boolean,
app: Activity, app: Activity
) { ) {
if (!linkDecoded.isUrlValid()) { if (!linkDecoded.isUrlValid()) {
Toast.makeText( Toast.makeText(
this, this,
this.getString(R.string.cant_open_invalid_url), this.getString(R.string.cant_open_invalid_url),
Toast.LENGTH_LONG, Toast.LENGTH_LONG
).show() ).show()
} else { } else {
if (articleViewer) { if (articleViewer) {
@@ -43,7 +44,8 @@ fun Context.openItemUrl(
} }
} }
fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlInvalid(): Boolean { fun String.isBaseUrlInvalid(): Boolean {
val baseUrl = this.toHttpUrlOrNull() val baseUrl = this.toHttpUrlOrNull()
@@ -64,10 +66,7 @@ fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
} }
class LinkOnTouchListener : View.OnTouchListener { class LinkOnTouchListener : View.OnTouchListener {
override fun onTouch( override fun onTouch(v: View?, event: MotionEvent?): Boolean {
v: View?,
event: MotionEvent?,
): Boolean {
var ret = false var ret = false
val widget: TextView = v as TextView val widget: TextView = v as TextView
val text: CharSequence = widget.text val text: CharSequence = widget.text

View File

@@ -8,4 +8,5 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
return this return this
} }
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this fun TextBadgeItem.maybeShow(): TextBadgeItem =
if (this.isHidden) this.show() else this

View File

@@ -10,30 +10,22 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
fun Context.bitmapCenterCrop( fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
url: String, Glide.with(this)
iv: ImageView, .asBitmap()
) = Glide.with(this) .load(url)
.asBitmap() .apply(RequestOptions.centerCropTransform())
.load(url) .into(iv)
.apply(RequestOptions.centerCropTransform())
.into(iv)
fun Context.circularDrawable( fun Context.circularDrawable(url: String, view: CircleImageView) {
url: String, view.textView.text =""
view: CircleImageView,
) {
view.textView.text = ""
Glide.with(this) Glide.with(this)
.load(url) .load(url)
.into(view.imageView) .into(view.imageView)
} }
fun getBitmapInputStream( fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
bitmap: Bitmap,
compressFormat: Bitmap.CompressFormat,
): InputStream {
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(compressFormat, 80, byteArrayOutputStream) bitmap.compress(compressFormat, 80, byteArrayOutputStream)
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()

View File

@@ -19,11 +19,10 @@ class AppViewModel(private val repository: Repository) : ViewModel() {
if (isConnected && !wasConnected && repository.connectionMonitored) { if (isConnected && !wasConnected && repository.connectionMonitored) {
_networkAvailableProvider.emit(true) _networkAvailableProvider.emit(true)
wasConnected = true wasConnected = true
} else if (!isConnected && wasConnected && repository.connectionMonitored) } else if (!isConnected && wasConnected && repository.connectionMonitored){
{ _networkAvailableProvider.emit(false)
_networkAvailableProvider.emit(false) wasConnected = false
wasConnected = false }
}
} }
} }
} }

View File

@@ -8,10 +8,11 @@ import kotlinx.datetime.toInstant
import org.junit.Test import org.junit.Test
class DatesTest { class DatesTest {
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00" private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val oldVersionDate = "2013-05-07 13:46:00" private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000" private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
@Test @Test
fun new_version_date_should_be_parsed() { fun new_version_date_should_be_parsed() {
@@ -52,4 +53,5 @@ class DatesTest {
assertEquals(expected, date) assertEquals(expected, date)
} }
} }

View File

@@ -42,11 +42,11 @@ class RepositoryTest {
private val NUMBER_STARRED = 20 private val NUMBER_STARRED = 20
private lateinit var repository: Repository private lateinit var repository: Repository
private fun initializeRepository( private fun initializeRepository(
isConnectionAvailable: MutableStateFlow<Boolean> = isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(
MutableStateFlow( true
true, )
),
) { ) {
repository = Repository(api, appSettingsService, isConnectionAvailable, db) repository = Repository(api, appSettingsService, isConnectionAvailable, db)
@@ -64,16 +64,14 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns false every { appSettingsService.isUpdateSourcesEnabled() } returns false
coEvery { api.apiInformation() } returns coEvery { api.apiInformation() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true))
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true)), )
) coEvery { api.stats() } returns StatusAndData(
coEvery { api.stats() } returns success = true,
StatusAndData( data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED)
success = true, )
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED),
)
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems() every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
@@ -103,7 +101,7 @@ class RepositoryTest {
fun get_api_4_date_with_api_1_version_stored() { fun get_api_4_date_with_api_1_version_stored() {
every { appSettingsService.getApiVersion() } returns 1 every { appSettingsService.getApiVersion() } returns 1
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
every { appSettingsService.updateApiVersion(any()) } returns Unit every { appSettingsService.updateApiVersion(any()) } returns Unit
initializeRepository() initializeRepository()
@@ -118,11 +116,10 @@ class RepositoryTest {
@Test @Test
fun get_public_access() { fun get_public_access() {
every { appSettingsService.updatePublicAccess(any()) } returns Unit every { appSettingsService.updatePublicAccess(any()) } returns Unit
coEvery { api.apiInformation() } returns coEvery { api.apiInformation() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true))
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true)), )
)
every { appSettingsService.getUserName() } returns "" every { appSettingsService.getUserName() } returns ""
initializeRepository() initializeRepository()
@@ -134,11 +131,10 @@ class RepositoryTest {
@Test @Test
fun get_public_access_username_not_empty() { fun get_public_access_username_not_empty() {
every { appSettingsService.updatePublicAccess(any()) } returns Unit every { appSettingsService.updatePublicAccess(any()) } returns Unit
coEvery { api.apiInformation() } returns coEvery { api.apiInformation() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true))
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true)), )
)
every { appSettingsService.getUserName() } returns "username" every { appSettingsService.getUserName() } returns "username"
initializeRepository() initializeRepository()
@@ -150,11 +146,10 @@ class RepositoryTest {
@Test @Test
fun get_public_access_no_auth() { fun get_public_access_no_auth() {
every { appSettingsService.updatePublicAccess(any()) } returns Unit every { appSettingsService.updatePublicAccess(any()) } returns Unit
coEvery { api.apiInformation() } returns coEvery { api.apiInformation() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, false))
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, false)), )
)
every { appSettingsService.getUserName() } returns "" every { appSettingsService.getUserName() } returns ""
initializeRepository() initializeRepository()
@@ -166,11 +161,10 @@ class RepositoryTest {
@Test @Test
fun get_public_access_disabled() { fun get_public_access_disabled() {
every { appSettingsService.updatePublicAccess(any()) } returns Unit every { appSettingsService.updatePublicAccess(any()) } returns Unit
coEvery { api.apiInformation() } returns coEvery { api.apiInformation() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true))
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true)), )
)
every { appSettingsService.getUserName() } returns "" every { appSettingsService.getUserName() } returns ""
initializeRepository() initializeRepository()
@@ -186,10 +180,10 @@ class RepositoryTest {
val itemParameters = FakeItemParameters() val itemParameters = FakeItemParameters()
itemParameters.datetime = "2021-04-23 11:45:32" itemParameters.datetime = "2021-04-23 11:45:32"
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData( StatusAndData(
success = true, success = true,
data = generateTestApiItem(itemParameters), data = generateTestApiItem(itemParameters)
) )
initializeRepository() initializeRepository()
runBlocking { runBlocking {
@@ -202,7 +196,7 @@ class RepositoryTest {
@Test @Test
fun get_newer_items() { fun get_newer_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
runBlocking { runBlocking {
@@ -217,7 +211,7 @@ class RepositoryTest {
@Test @Test
fun get_all_newer_items() { fun get_all_newer_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.displayedItems = ItemType.ALL repository.displayedItems = ItemType.ALL
@@ -233,7 +227,7 @@ class RepositoryTest {
@Test @Test
fun get_newer_starred_items() { fun get_newer_starred_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.displayedItems = ItemType.STARRED repository.displayedItems = ItemType.STARRED
@@ -270,10 +264,10 @@ class RepositoryTest {
itemParameter3.tags = "Other, Tag" itemParameter3.tags = "Other, Tag"
itemParameter3.id = "3" itemParameter3.id = "3"
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems( coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
itemParameter1, itemParameter1
) + ) +
generateTestDBItems(itemParameter2) + generateTestDBItems(itemParameter2) +
generateTestDBItems(itemParameter3) generateTestDBItems(itemParameter3)
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
@@ -298,26 +292,24 @@ class RepositoryTest {
itemParameter3.sourcetitle = "Other" itemParameter3.sourcetitle = "Other"
itemParameter3.id = "3" itemParameter3.id = "3"
coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems( coEvery { db.itemsQueries.items().executeAsList() } returns generateTestDBItems(
itemParameter1, itemParameter1
) + ) +
generateTestDBItems(itemParameter2) + generateTestDBItems(itemParameter2) +
generateTestDBItems(itemParameter3) generateTestDBItems(itemParameter3)
every { appSettingsService.isItemCachingEnabled() } returns true every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
repository.setSourceFilter( repository.setSourceFilter(SelfossModel.SourceDetail(
SelfossModel.SourceDetail( 1,
1, "Test",
"Test", null,
null, listOf("tags"),
listOf("tags"), SPOUT,
SPOUT, "",
"", IMAGE_URL,
IMAGE_URL, SelfossModel.SourceParams("url")
SelfossModel.SourceParams("url"), ))
),
)
runBlocking { runBlocking {
repository.getNewerItems() repository.getNewerItems()
} }
@@ -330,7 +322,7 @@ class RepositoryTest {
@Test @Test
fun get_older_items() { fun get_older_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.items = ArrayList(generateTestApiItem()) repository.items = ArrayList(generateTestApiItem())
@@ -346,7 +338,7 @@ class RepositoryTest {
@Test @Test
fun get_all_older_items() { fun get_all_older_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.items = ArrayList(generateTestApiItem()) repository.items = ArrayList(generateTestApiItem())
@@ -363,7 +355,7 @@ class RepositoryTest {
@Test @Test
fun get_older_starred_items() { fun get_older_starred_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = true, data = generateTestApiItem()) StatusAndData(success = true, data = generateTestApiItem())
initializeRepository() initializeRepository()
repository.displayedItems = ItemType.STARRED repository.displayedItems = ItemType.STARRED
@@ -600,16 +592,14 @@ class RepositoryTest {
} }
private fun prepareTags(): Pair<List<SelfossModel.Tag>, List<TAG>> { private fun prepareTags(): Pair<List<SelfossModel.Tag>, List<TAG>> {
val tags = val tags = listOf(
listOf( SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("test", "red", 6), SelfossModel.Tag("second", "yellow", 0)
SelfossModel.Tag("second", "yellow", 0), )
) val tagsDB = listOf(
val tagsDB = TAG("test_DB", "red", 6),
listOf( TAG("second_DB", "yellow", 0)
TAG("test_DB", "red", 6), )
TAG("second_DB", "yellow", 0),
)
coEvery { api.tags() } returns StatusAndData(success = true, data = tags) coEvery { api.tags() } returns StatusAndData(success = true, data = tags)
coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB coEvery { db.tagsQueries.tags().executeAsList() } returns tagsDB
@@ -631,50 +621,48 @@ class RepositoryTest {
} }
private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> { private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> {
val sources = val sources = arrayListOf(
arrayListOf( SelfossModel.SourceDetail(
SelfossModel.SourceDetail( 1,
1, "First source",
"First source", null,
null, listOf("Test", "second"),
listOf("Test", "second"), SPOUT,
SPOUT, "",
"", IMAGE_URL_2,
IMAGE_URL_2, SelfossModel.SourceParams("url")
SelfossModel.SourceParams("url"), ),
), SelfossModel.SourceDetail(
SelfossModel.SourceDetail( 2,
2, "Second source",
"Second source", null,
null, listOf("second"),
listOf("second"), SPOUT,
SPOUT, "",
"", IMAGE_URL,
IMAGE_URL, SelfossModel.SourceParams("url")
SelfossModel.SourceParams("url"),
),
) )
val sourcesDB = )
listOf( val sourcesDB = listOf(
SOURCE( SOURCE(
"1", "1",
"First DB source", "First DB source",
"Test,second", "Test,second",
SPOUT, SPOUT,
"", "",
IMAGE_URL_2, IMAGE_URL_2,
"url", "url"
), ),
SOURCE( SOURCE(
"2", "2",
"Second source", "Second source",
"second", "second",
SPOUT, SPOUT,
"", "",
IMAGE_URL, IMAGE_URL,
"url", "url"
),
) )
)
coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources) coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
@@ -803,18 +791,17 @@ class RepositoryTest {
@Test @Test
fun create_source() { fun create_source() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = response = repository.createSource(
repository.createSource( "test",
"test", FEED_URL,
FEED_URL, SPOUT,
SPOUT, TAGS,
TAGS, )
)
} }
coVerify(exactly = 1) { coVerify(exactly = 1) {
@@ -831,18 +818,17 @@ class RepositoryTest {
@Test @Test
fun create_source_but_response_fails() { fun create_source_but_response_fails() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(false) SuccessResponse(false)
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = response = repository.createSource(
repository.createSource( "test",
"test", FEED_URL,
FEED_URL, SPOUT,
SPOUT, TAGS
TAGS, )
)
} }
coVerify(exactly = 1) { coVerify(exactly = 1) {
@@ -850,7 +836,7 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(), any()
) )
} }
assertSame(false, response) assertSame(false, response)
@@ -859,18 +845,17 @@ class RepositoryTest {
@Test @Test
fun create_source_without_connection() { fun create_source_without_connection() {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true) SuccessResponse(true)
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var response: Boolean var response: Boolean
runBlocking { runBlocking {
response = response = repository.createSource(
repository.createSource( "test",
"test", FEED_URL,
FEED_URL, SPOUT,
SPOUT, TAGS
TAGS, )
)
} }
coVerify(exactly = 0) { coVerify(exactly = 0) {
@@ -931,11 +916,10 @@ class RepositoryTest {
@Test @Test
fun update_remote() { fun update_remote() {
coEvery { api.update() } returns coEvery { api.update() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = "finished"
data = "finished", )
)
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
@@ -949,11 +933,10 @@ class RepositoryTest {
@Test @Test
fun update_remote_but_response_fails() { fun update_remote_but_response_fails() {
coEvery { api.update() } returns coEvery { api.update() } returns StatusAndData(
StatusAndData( success = false,
success = false, data = "unallowed access"
data = "unallowed access", )
)
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
@@ -967,11 +950,10 @@ class RepositoryTest {
@Test @Test
fun update_remote_with_unallowed_access() { fun update_remote_with_unallowed_access() {
coEvery { api.update() } returns coEvery { api.update() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = "unallowed access"
data = "unallowed access", )
)
initializeRepository() initializeRepository()
var response: Boolean var response: Boolean
@@ -985,11 +967,10 @@ class RepositoryTest {
@Test @Test
fun update_remote_without_connection() { fun update_remote_without_connection() {
coEvery { api.update() } returns coEvery { api.update() } returns StatusAndData(
StatusAndData( success = true,
success = true, data = "undocumented..."
data = "undocumented...", )
)
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
var response: Boolean var response: Boolean
@@ -1056,7 +1037,7 @@ class RepositoryTest {
appSettingsService.refreshLoginInformation( appSettingsService.refreshLoginInformation(
BASE_URL, BASE_URL,
"login", "login",
"password", "password"
) )
} }
} }
@@ -1076,14 +1057,13 @@ class RepositoryTest {
any(), any(),
any(), any(),
any(), any(),
any(), any()
)
} returnsMany
listOf(
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
) )
} returnsMany listOf(
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
StatusAndData(success = true, data = generateTestApiItem(itemParameter2)),
StatusAndData(success = true, data = generateTestApiItem(itemParameter1)),
)
initializeRepository() initializeRepository()
prepareSearch() prepareSearch()
@@ -1097,7 +1077,7 @@ class RepositoryTest {
@Test @Test
fun cache_items_but_response_fails() { fun cache_items_but_response_fails() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = false, data = generateTestApiItem()) StatusAndData(success = false, data = generateTestApiItem())
initializeRepository() initializeRepository()
prepareSearch() prepareSearch()
@@ -1111,7 +1091,7 @@ class RepositoryTest {
@Test @Test
fun cache_items_without_connection() { fun cache_items_without_connection() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = false, data = generateTestApiItem()) StatusAndData(success = false, data = generateTestApiItem())
initializeRepository(MutableStateFlow(false)) initializeRepository(MutableStateFlow(false))
prepareSearch() prepareSearch()
@@ -1133,8 +1113,8 @@ class RepositoryTest {
SPOUT, SPOUT,
"", "",
IMAGE_URL_2, IMAGE_URL_2,
SelfossModel.SourceParams("url"), SelfossModel.SourceParams("url")
), )
) )
repository.searchFilter = "search" repository.searchFilter = "search"
} }

View File

@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> { fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
return listOf( return listOf(
ITEM( ITEM(
@@ -17,8 +18,8 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags, tags = item.tags,
author = item.author, author = item.author
), )
) )
} }
@@ -36,8 +37,8 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags.split(','), tags = item.tags.split(','),
author = item.author, author = item.author
), )
) )
} }

View File

@@ -5,15 +5,9 @@ import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager import javax.net.ssl.X509TrustManager
class NaiveTrustManager : X509TrustManager { class NaiveTrustManager : X509TrustManager {
override fun checkClientTrusted( override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
chain: Array<out X509Certificate>?,
authType: String?,
) {}
override fun checkServerTrusted( override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {}
chain: Array<out X509Certificate>?,
authType: String?,
) {}
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf() override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
} }

View File

@@ -3,40 +3,40 @@ package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils import android.text.format.DateUtils
import kotlinx.datetime.* import kotlinx.datetime.*
actual class DateUtils { actual class DateUtils {
actual companion object { actual companion object {
// Possible formats are // Possible formats are
// yyyy-mm-dd hh:mm:ss format // yyyy-mm-dd hh:mm:ss format
private val oldVersionFormat = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(.()\\d*)?".toRegex() private val oldVersionFormat = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(.()\\d*)?".toRegex()
// yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX (RFC3339) // yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX (RFC3339)
private val newVersionFormat = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}(:\\d{2})?".toRegex() private val newVersionFormat = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}(:\\d{2})?".toRegex()
// We may need to consider moving the formatting to platform specific code, even if the tests are doubled // We may need to consider moving the formatting to platform specific code, even if the tests are doubled
// For now, we handle this in a hacky way, because kotlin only accepts iso formats // For now, we handle this in a hacky way, because kotlin only accepts iso formats
actual fun parseDate(dateString: String): Long { actual fun parseDate(dateString: String): Long {
var isoDateString: String = var isoDateString: String = if (dateString.matches(oldVersionFormat)) {
if (dateString.matches(oldVersionFormat)) { dateString.replace(" ", "T")
dateString.replace(" ", "T") } else if (dateString.matches(newVersionFormat)) {
} else if (dateString.matches(newVersionFormat)) { dateString.split("+")[0]
dateString.split("+")[0] } else {
} else { throw Exception("Unrecognized format for $dateString")
throw Exception("Unrecognized format for $dateString") }
}
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds() return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
actual fun parseRelativeDate(dateString: String): String { actual fun parseRelativeDate(dateString: String): String {
val date = parseDate(dateString) val date = parseDate(dateString)
return " " + return " " + DateUtils.getRelativeTimeSpanString(
DateUtils.getRelativeTimeSpanString( date,
date, Clock.System.now().toEpochMilliseconds(),
Clock.System.now().toEpochMilliseconds(), DateUtils.MINUTE_IN_MILLIS,
DateUtils.MINUTE_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE
DateUtils.FORMAT_ABBREV_RELATIVE, )
)
} }
} }
} }

View File

@@ -21,13 +21,13 @@ actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
actual fun SelfossModel.Item.getImages(): ArrayList<String> { actual fun SelfossModel.Item.getImages(): ArrayList<String> {
val allImages = ArrayList<String>() val allImages = ArrayList<String>()
for (image in Jsoup.parse(content).getElementsByTag("img")) { for ( image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src") val url = image.attr("src")
if (url.lowercase(Locale.US).contains(".jpg") || if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") || url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") || url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp") url.lowercase(Locale.US).contains(".webp"))
) { {
allImages.add(url) allImages.add(url)
} }
} }
@@ -38,11 +38,7 @@ actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon) return constructUrl(baseUrl, "favicons", icon)
} }
actual fun constructUrl( actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
baseUrl: String,
path: String,
file: String?,
): String {
return if (file == null || file == "null" || file.isEmpty()) { return if (file == null || file == "null" || file.isEmpty()) {
"" ""
} else { } else {

View File

@@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.DI
import bou.amine.apps.readerforselfossv2.rest.MercuryApi import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.rest.SelfossApi import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.bind import org.kodein.di.bind
import org.kodein.di.instance import org.kodein.di.instance

View File

@@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.model
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
class MercuryModel { class MercuryModel {
@Serializable @Serializable
class ParsedContent( class ParsedContent(
val title: String? = null, val title: String? = null,
@@ -11,6 +12,6 @@ class MercuryModel {
val url: String? = null, val url: String? = null,
val error: Boolean? = null, val error: Boolean? = null,
val message: String? = null, val message: String? = null,
val failed: Boolean? = null, val failed: Boolean? = null
) )
} }

View File

@@ -13,31 +13,32 @@ import kotlinx.serialization.encoding.encodeCollection
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
class SelfossModel { class SelfossModel {
@Serializable @Serializable
data class Tag( data class Tag(
val tag: String, val tag: String,
val color: String, val color: String,
val unread: Int, val unread: Int
) )
@Serializable @Serializable
class Stats( class Stats(
val total: Int, val total: Int,
val unread: Int? = null, val unread: Int? = null,
val starred: Int? = null, val starred: Int? = null
) )
@Serializable @Serializable
data class Spout( data class Spout(
val name: String, val name: String,
val description: String, val description: String
) )
@Serializable @Serializable
data class ApiInformation( data class ApiInformation(
val version: String? = null, val version: String? = null,
val apiversion: String? = null, val apiversion: String? = null,
val configuration: ApiConfiguration? = null, val configuration: ApiConfiguration? = null
) { ) {
fun getApiMajorVersion(): Int { fun getApiMajorVersion(): Int {
var versionNumber = 0 var versionNumber = 0
@@ -55,7 +56,7 @@ class SelfossModel {
@Serializable(with = BooleanSerializer::class) @Serializable(with = BooleanSerializer::class)
val publicMode: Boolean? = null, val publicMode: Boolean? = null,
@Serializable(with = BooleanSerializer::class) @Serializable(with = BooleanSerializer::class)
val authEnabled: Boolean? = null, val authEnabled: Boolean? = null
) { ) {
fun isAuthEnabled() = authEnabled ?: true fun isAuthEnabled() = authEnabled ?: true
@@ -76,8 +77,8 @@ class SelfossModel {
override var title: String, override var title: String,
override var unread: Int? = null, override var unread: Int? = null,
override var error: String? = null, override var error: String? = null,
override var icon: String? = null, override var icon: String? = null
) : Source ) : Source
@Serializable @Serializable
data class SourceDetail( data class SourceDetail(
@@ -89,14 +90,13 @@ class SelfossModel {
var spout: String? = null, var spout: String? = null,
override var error: String? = null, override var error: String? = null,
override var icon: String? = null, override var icon: String? = null,
var params: SourceParams? = null, var params: SourceParams? = null
) : Source ) : Source
@Serializable @Serializable
data class SourceParams( data class SourceParams(
val url: String? = null, val url: String? = null
) )
@Serializable @Serializable
data class Item( data class Item(
val id: Int, val id: Int,
@@ -113,16 +113,15 @@ class SelfossModel {
val sourcetitle: String, val sourcetitle: String,
@Serializable(with = TagsListSerializer::class) @Serializable(with = TagsListSerializer::class)
val tags: List<String>, val tags: List<String>,
val author: String? = null, val author: String? = null
) { ) {
fun getLinkDecoded(): String { fun getLinkDecoded(): String {
var stringUrl: String var stringUrl: String
stringUrl = stringUrl = if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) {
if (link.contains("//news.google.com/news/") && link.contains("&amp;url=")) { link.substringAfter("&amp;url=")
link.substringAfter("&amp;url=") } else {
} else { this.link.replace("&amp;", "&")
this.link.replace("&amp;", "&") }
}
// handle :443 => https // handle :443 => https
if (stringUrl.contains(":443")) { if (stringUrl.contains(":443")) {
@@ -152,22 +151,21 @@ class SelfossModel {
} }
} }
// TODO: this seems to be super slow. // TODO: this seems to be super slow.
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().replace("^\"|\"$".toRegex(), "") } is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
else -> json.toString().split(",") else -> json.toString().split(",")
} }
} }
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING) get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
override fun serialize( override fun serialize(encoder: Encoder, value: List<String>) {
encoder: Encoder,
value: List<String>,
) {
encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() } encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
} }
} }
@@ -185,10 +183,7 @@ class SelfossModel {
override val descriptor: SerialDescriptor override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN) get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)
override fun serialize( override fun serialize(encoder: Encoder, value: Boolean) {
encoder: Encoder,
value: Boolean,
) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }

View File

@@ -19,8 +19,9 @@ class Repository(
private val api: SelfossApi, private val api: SelfossApi,
private val appSettingsService: AppSettingsService, private val appSettingsService: AppSettingsService,
val isConnectionAvailable: MutableStateFlow<Boolean>, val isConnectionAvailable: MutableStateFlow<Boolean>,
private val db: ReaderForSelfossDB, private val db: ReaderForSelfossDB
) { ) {
var items = ArrayList<SelfossModel.Item>() var items = ArrayList<SelfossModel.Item>()
var connectionMonitored = false var connectionMonitored = false
@@ -52,22 +53,20 @@ class Repository(
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
fetchedItems = fetchedItems = api.getItems(
api.getItems( displayedItems.type,
displayedItems.type, offset = 0,
offset = 0, tagFilter.value?.tag,
tagFilter.value?.tag, sourceFilter.value?.id?.toLong(),
sourceFilter.value?.id?.toLong(), searchFilter,
searchFilter, null
null, )
)
} else if (appSettingsService.isItemCachingEnabled()) { } else if (appSettingsService.isItemCachingEnabled()) {
var dbItems = var dbItems = getDBItems().filter {
getDBItems().filter { displayedItems == ItemType.ALL ||
displayedItems == ItemType.ALL ||
(it.unread && displayedItems == ItemType.UNREAD) || (it.unread && displayedItems == ItemType.UNREAD) ||
(it.starred && displayedItems == ItemType.STARRED) (it.starred && displayedItems == ItemType.STARRED)
} }
if (tagFilter.value != null) { if (tagFilter.value != null) {
dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter.value!!.tag) } dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter.value!!.tag) }
} }
@@ -76,10 +75,9 @@ class Repository(
} }
val itemsList = ArrayList(dbItems.map { it.toView() }) val itemsList = ArrayList(dbItems.map { it.toView() })
itemsList.sortByDescending { DateUtils.parseDate(it.datetime) } itemsList.sortByDescending { DateUtils.parseDate(it.datetime) }
fetchedItems = fetchedItems = StatusAndData.succes(
StatusAndData.succes( itemsList
itemsList, )
)
} }
if (fetchedItems.success && fetchedItems.data != null) { if (fetchedItems.success && fetchedItems.data != null) {
@@ -92,15 +90,14 @@ class Repository(
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val offset = items.size val offset = items.size
fetchedItems = fetchedItems = api.getItems(
api.getItems( displayedItems.type,
displayedItems.type, offset,
offset, tagFilter.value?.tag,
tagFilter.value?.tag, sourceFilter.value?.id?.toLong(),
sourceFilter.value?.id?.toLong(), searchFilter,
searchFilter, null
null, )
)
} // When using the db cache, we load everything the first time, so there should be nothing more to load. } // When using the db cache, we load everything the first time, so there should be nothing more to load.
if (fetchedItems.success && fetchedItems.data != null) { if (fetchedItems.success && fetchedItems.data != null) {
@@ -111,16 +108,15 @@ class Repository(
private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> { private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> {
return if (isNetworkAvailable()) { return if (isNetworkAvailable()) {
val items = val items = api.getItems(
api.getItems( itemType.type,
itemType.type, 0,
0, null,
null, null,
null, null,
null, null,
null, 200
200, )
)
return if (items.success && items.data != null) { return if (items.success && items.data != null) {
items.data items.data
} else { } else {
@@ -378,7 +374,7 @@ class Repository(
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String
): Boolean { ): Boolean {
var response = false var response = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
@@ -388,10 +384,7 @@ class Repository(
return response return response
} }
suspend fun deleteSource( suspend fun deleteSource(id: Int, title: String): Boolean {
id: Int,
title: String,
): Boolean {
var success = false var success = false
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val response = api.deleteSource(id) val response = api.deleteSource(id)
@@ -463,11 +456,7 @@ class Repository(
} }
} }
fun refreshLoginInformation( fun refreshLoginInformation(url: String, login: String, password: String) {
url: String,
login: String,
password: String,
) {
appSettingsService.refreshLoginInformation(url, login, password) appSettingsService.refreshLoginInformation(url, login, password)
baseUrl = url baseUrl = url
api.refreshLoginInformation() api.refreshLoginInformation()
@@ -485,10 +474,9 @@ class Repository(
// Check if we're accessing the instance in public mode // Check if we're accessing the instance in public mode
// This happens when auth and public mode are enabled but // This happens when auth and public mode are enabled but
// no credentials are provided to login // no credentials are provided to login
if (appSettingsService.getUserName().isEmpty() && if (appSettingsService.getUserName().isEmpty()
fetchedInformation.data.getApiConfiguration().isAuthEnabled() && && fetchedInformation.data.getApiConfiguration().isAuthEnabled()
fetchedInformation.data.getApiConfiguration().isPublicModeEnabled() && fetchedInformation.data.getApiConfiguration().isPublicModeEnabled()) {
) {
appSettingsService.updatePublicAccess(true) appSettingsService.updatePublicAccess(true)
} }
} }
@@ -497,9 +485,11 @@ class Repository(
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
private fun getDBActions(): List<ACTION> = db.actionsQueries.actions().executeAsList() private fun getDBActions(): List<ACTION> =
db.actionsQueries.actions().executeAsList()
private fun deleteDBAction(action: ACTION) = db.actionsQueries.deleteAction(action.id) private fun deleteDBAction(action: ACTION) =
db.actionsQueries.deleteAction(action.id)
private fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList() private fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList()
@@ -540,8 +530,9 @@ class Repository(
read: Boolean = false, read: Boolean = false,
unread: Boolean = false, unread: Boolean = false,
starred: Boolean = false, starred: Boolean = false,
unstarred: Boolean = false, unstarred: Boolean = false
) = db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred) ) =
db.actionsQueries.insertAction(articleid, read, unread, starred, unstarred)
private fun updateDBItem(item: SelfossModel.Item) = private fun updateDBItem(item: SelfossModel.Item) =
db.itemsQueries.updateItem( db.itemsQueries.updateItem(
@@ -556,7 +547,7 @@ class Repository(
item.sourcetitle, item.sourcetitle,
item.tags.joinToString(","), item.tags.joinToString(","),
item.author, item.author,
item.id.toString(), item.id.toString()
) )
suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> { suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> {
@@ -573,38 +564,32 @@ class Repository(
} }
suspend fun handleDBActions() { suspend fun handleDBActions() {
val actions: List<ACTION> = getDBActions() val actions: List<ACTION> = getDBActions()
actions.forEach { action -> actions.forEach { action ->
when { when {
action.read -> action.read -> doAndReportOnFail(
doAndReportOnFail( markAsReadById(action.articleid.toInt()),
markAsReadById(action.articleid.toInt()), action
action, )
) action.unread -> doAndReportOnFail(
action.unread -> unmarkAsReadById(action.articleid.toInt()),
doAndReportOnFail( action
unmarkAsReadById(action.articleid.toInt()), )
action, action.starred -> doAndReportOnFail(
) starrById(action.articleid.toInt()),
action.starred -> action
doAndReportOnFail( )
starrById(action.articleid.toInt()), action.unstarred -> doAndReportOnFail(
action, unstarrById(action.articleid.toInt()),
) action
action.unstarred -> )
doAndReportOnFail(
unstarrById(action.articleid.toInt()),
action,
)
} }
} }
} }
private fun doAndReportOnFail( private fun doAndReportOnFail(result: Boolean, action: ACTION) {
result: Boolean,
action: ACTION,
) {
if (result) { if (result) {
deleteDBAction(action) deleteDBAction(action)
} }

View File

@@ -11,27 +11,25 @@ import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class MercuryApi() { class MercuryApi() {
var client = createHttpClient() var client = createHttpClient()
private fun createHttpClient(): HttpClient { private fun createHttpClient(): HttpClient {
return HttpClient { return HttpClient {
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache) install(HttpCache)
json( json(Json {
Json { prettyPrint = true
prettyPrint = true isLenient = true
isLenient = true ignoreUnknownKeys = true
ignoreUnknownKeys = true })
},
)
} }
install(Logging) { install(Logging) {
logger = logger = object : Logger {
object : Logger { override fun log(message: String) {
override fun log(message: String) { Napier.d(message, tag = "LogMercuryCalls")
Napier.d(message, tag = "LogMercuryCalls")
}
} }
}
level = LogLevel.INFO level = LogLevel.INFO
} }
expectSuccess = false expectSuccess = false
@@ -39,9 +37,7 @@ class MercuryApi() {
} }
suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> = suspend fun query(url: String): StatusAndData<MercuryModel.ParsedContent> =
bodyOrFailure( bodyOrFailure(client.get("https://amine-louveau.fr/parser.php") {
client.get("https://amine-louveau.fr/parser.php") { parameter("link", url)
parameter("link", url) })
},
)
} }

View File

@@ -10,6 +10,7 @@ import io.ktor.client.request.forms.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse { suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
return if (r != null && r.status === HttpStatusCode.NotFound) { return if (r != null && r.status === HttpStatusCode.NotFound) {
SuccessResponse(true) SuccessResponse(true)
@@ -39,7 +40,7 @@ suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T>
inline fun tryToRequest( inline fun tryToRequest(
requestType: String, requestType: String,
fn: () -> HttpResponse, fn: () -> HttpResponse
): HttpResponse? { ): HttpResponse? {
var response: HttpResponse? = null var response: HttpResponse? = null
try { try {
@@ -52,42 +53,26 @@ inline fun tryToRequest(
suspend inline fun HttpClient.tryToGet( suspend inline fun HttpClient.tryToGet(
urlString: String, urlString: String,
crossinline block: HttpRequestBuilder.() -> Unit = {}, crossinline block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = ): HttpResponse? = tryToRequest("Get") { return this.get { url(urlString); block() } }
tryToRequest("Get") {
return this.get {
url(urlString)
block()
}
}
suspend inline fun HttpClient.tryToPost( suspend inline fun HttpClient.tryToPost(
urlString: String, urlString: String,
block: HttpRequestBuilder.() -> Unit = {}, block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = ): HttpResponse? = tryToRequest("Post") { return this.post { url(urlString); block() } }
tryToRequest("Post") {
return this.post {
url(urlString)
block()
}
}
suspend inline fun HttpClient.tryToDelete( suspend inline fun HttpClient.tryToDelete(
urlString: String, urlString: String,
block: HttpRequestBuilder.() -> Unit = {}, block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = ): HttpResponse? = tryToRequest("Delete") { return this.delete { url(urlString); block() } }
tryToRequest("Delete") {
return this.delete {
url(urlString)
block()
}
}
suspend fun HttpClient.tryToSubmitForm( suspend fun HttpClient.tryToSubmitForm(
url: String, url: String,
formParameters: Parameters = Parameters.Empty, formParameters: Parameters = Parameters.Empty,
encodeInQuery: Boolean = false, encodeInQuery: Boolean = false,
block: HttpRequestBuilder.() -> Unit = {}, block: HttpRequestBuilder.() -> Unit = {}
): HttpResponse? = ): HttpResponse? =
tryToRequest("SubmitForm") { tryToRequest("SubmitForm") {
return this.submitForm(formParameters, encodeInQuery) { return this.submitForm(formParameters, encodeInQuery) {

View File

@@ -36,8 +36,8 @@ import kotlinx.serialization.json.Json
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig) expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
class SelfossApi(private val appSettingsService: AppSettingsService) { class SelfossApi(private val appSettingsService: AppSettingsService) {
var client = createHttpClient()
var client = createHttpClient()
fun createHttpClient() = fun createHttpClient() =
HttpClient(CIO) { HttpClient(CIO) {
if (appSettingsService.getSelfSigned()) { if (appSettingsService.getSelfSigned()) {
@@ -47,22 +47,19 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
} }
install(ContentNegotiation) { install(ContentNegotiation) {
install(HttpCache) install(HttpCache)
json( json(Json {
Json { prettyPrint = true
prettyPrint = true isLenient = true
isLenient = true ignoreUnknownKeys = true
ignoreUnknownKeys = true explicitNulls = false
explicitNulls = false })
},
)
} }
install(Logging) { install(Logging) {
logger = logger = object : Logger {
object : Logger { override fun log(message: String) {
override fun log(message: String) { Napier.d(message, tag = "LogApiCalls")
Napier.d(message, tag = "LogApiCalls")
}
} }
}
level = LogLevel.INFO level = LogLevel.INFO
} }
install(HttpTimeout) { install(HttpTimeout) {
@@ -86,7 +83,8 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
expectSuccess = false expectSuccess = false
} }
fun url(path: String) = "${appSettingsService.getBaseUrl()}$path" fun url(path: String) =
"${appSettingsService.getBaseUrl()}$path"
fun refreshLoginInformation() { fun refreshLoginInformation() {
appSettingsService.refreshApiSettings() appSettingsService.refreshApiSettings()
@@ -102,15 +100,12 @@ 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() = private fun hasLoginInfo() =
appSettingsService.getUserName().isNotEmpty() && appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
appSettingsService.getPassword() .isNotEmpty()
.isNotEmpty()
suspend fun login(): SuccessResponse = suspend fun login(): SuccessResponse =
if (appSettingsService.getUserName().isNotEmpty() && if (appSettingsService.getUserName().isNotEmpty() && appSettingsService.getPassword()
appSettingsService.getPassword()
.isNotEmpty() .isNotEmpty()
) { ) {
if (shouldHavePostLogin()) { if (shouldHavePostLogin()) {
@@ -122,49 +117,30 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
SuccessResponse(true) SuccessResponse(true)
} }
private suspend fun getLogin() = private suspend fun getLogin() = maybeResponse(client.tryToGet(url("/login")) {
maybeResponse( parameter("username", appSettingsService.getUserName())
client.tryToGet(url("/login")) { parameter("password", appSettingsService.getPassword())
parameter("username", appSettingsService.getUserName()) if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
parameter("password", appSettingsService.getPassword()) headers {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
headers { )
append( }
HttpHeaders.Authorization, }
constructBasicAuthValue( })
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
private suspend fun postLogin() = private suspend fun postLogin() = maybeResponse(client.tryToPost(url("/login")) {
maybeResponse( parameter("username", appSettingsService.getUserName())
client.tryToPost(url("/login")) { parameter("password", appSettingsService.getPassword())
parameter("username", appSettingsService.getUserName()) if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
parameter("password", appSettingsService.getPassword()) headers {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
headers { )
append( }
HttpHeaders.Authorization, }
constructBasicAuthValue( })
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
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()) {
@@ -174,42 +150,23 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
} }
private suspend fun maybeLogoutIfAvailable() = private suspend fun maybeLogoutIfAvailable() =
responseOrSuccessIf404( responseOrSuccessIf404(client.tryToGet(url("/logout")) {
client.tryToGet(url("/logout")) { if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { headers {
headers { append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
append( )
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
} }
}, }
) })
private suspend fun doLogout() = private suspend fun doLogout() = maybeResponse(client.tryToDelete(url("/api/session/current")) {
maybeResponse( if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
client.tryToDelete(url("/api/session/current")) { headers {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
headers { )
append( }
HttpHeaders.Authorization, }
constructBasicAuthValue( })
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun getItems( suspend fun getItems(
type: String, type: String,
@@ -218,340 +175,213 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
source: Long?, source: Long?,
search: String?, search: String?,
updatedSince: String?, updatedSince: String?,
items: Int? = null, items: Int? = null
): StatusAndData<List<SelfossModel.Item>> = ): StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/items")) {
client.tryToGet(url("/items")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
parameter("type", type)
parameter("tag", tag)
parameter("source", source)
parameter("search", search)
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
parameter("type", type) }
parameter("tag", tag) })
parameter("source", source)
parameter("search", search)
parameter("updatedsince", updatedSince)
parameter("items", items ?: appSettingsService.getItemsNumber())
parameter("offset", offset)
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> = suspend fun getItemsWithoutCatch(): StatusAndData<List<SelfossModel.Item>> =
bodyOrFailure( bodyOrFailure(client.get(url("/items")) {
client.get(url("/items")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
parameter("type", "all")
parameter("items", 1)
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
parameter("type", "all") }
parameter("items", 1) })
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun stats(): StatusAndData<SelfossModel.Stats> = suspend fun stats(): StatusAndData<SelfossModel.Stats> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/stats")) {
client.tryToGet(url("/stats")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> = suspend fun tags(): StatusAndData<List<SelfossModel.Tag>> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/tags")) {
client.tryToGet(url("/tags")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun update(): StatusAndData<String> = suspend fun update(): StatusAndData<String> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/update")) {
client.tryToGet(url("/update")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> = suspend fun spouts(): StatusAndData<Map<String, SelfossModel.Spout>> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/sources/spouts")) {
client.tryToGet(url("/sources/spouts")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> = suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/sources/stats")) {
client.tryToGet(url("/sources/stats")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> = suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/sources/list")) {
client.tryToGet(url("/sources/list")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> = suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
bodyOrFailure( bodyOrFailure(client.tryToGet(url("/api/about")) {
client.tryToGet(url("/api/about")) { if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { headers {
headers { append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
append( )
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
} }
}, }
) })
suspend fun markAsRead(id: String): SuccessResponse = suspend fun markAsRead(id: String): SuccessResponse =
maybeResponse( maybeResponse(client.tryToPost(url("/mark/$id")) {
client.tryToPost(url("/mark/$id")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun unmarkAsRead(id: String): SuccessResponse = suspend fun unmarkAsRead(id: String): SuccessResponse =
maybeResponse( maybeResponse(client.tryToPost(url("/unmark/$id")) {
client.tryToPost(url("/unmark/$id")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun starr(id: String): SuccessResponse = suspend fun starr(id: String): SuccessResponse =
maybeResponse( maybeResponse(client.tryToPost(url("/starr/$id")) {
client.tryToPost(url("/starr/$id")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { }
headers { })
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
)
suspend fun unstarr(id: String): SuccessResponse = suspend fun unstarr(id: String): SuccessResponse =
maybeResponse( maybeResponse(client.tryToPost(url("/unstarr/$id")) {
client.tryToPost(url("/unstarr/$id")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
)
} }
}
})
suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
maybeResponse(client.tryToSubmitForm(
url = url("/mark"),
formParameters = Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
ids.map { append("ids[]", it) }
},
block = {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers { headers {
append( append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
) )
} }
} }
}, }
) ))
suspend fun markAllAsRead(ids: List<String>): SuccessResponse =
maybeResponse(
client.tryToSubmitForm(
url = url("/mark"),
formParameters =
Parameters.build {
if (!shouldHavePostLogin()) {
append("username", appSettingsService.getUserName())
append("password", appSettingsService.getPassword())
}
ids.map { append("ids[]", it) }
},
block = {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers {
append(
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
)
}
}
},
),
)
suspend fun createSourceForVersion( suspend fun createSourceForVersion(
title: String, title: String,
@@ -564,7 +394,7 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
createSource("tags[]", title, url, spout, tags) createSource("tags[]", title, url, spout, tags)
} else { } else {
createSource("tags", title, url, spout, tags) createSource("tags", title, url, spout, tags)
}, }
) )
private suspend fun createSource( private suspend fun createSource(
@@ -572,36 +402,28 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String
): HttpResponse? = ): HttpResponse? =
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source"), url = url("/source"),
formParameters = formParameters = Parameters.build {
Parameters.build { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { append("username", appSettingsService.getUserName())
append("username", appSettingsService.getUserName()) append("password", appSettingsService.getPassword())
append("password", appSettingsService.getPassword()) }
} append("title", title)
append("title", title) append("url", url)
append("url", url) append("spout", spout)
append("spout", spout) append(tagsParamName, tags)
append(tagsParamName, tags) },
},
block = { block = {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers { headers {
append( append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
) )
} }
} }
}, }
) )
suspend fun updateSourceForVersion( suspend fun updateSourceForVersion(
@@ -609,14 +431,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
title: String, title: String,
url: String, url: String,
spout: String, spout: String,
tags: String, tags: String
): SuccessResponse = ): SuccessResponse =
maybeResponse( maybeResponse(
if (appSettingsService.getApiVersion() > 1) { if (appSettingsService.getApiVersion() > 1) {
updateSource(id, "tags[]", title, url, spout, tags) updateSource(id, "tags[]", title, url, spout, tags)
} else { } else {
updateSource(id, "tags", title, url, spout, tags) updateSource(id, "tags", title, url, spout, tags)
}, }
) )
private suspend fun updateSource( private suspend fun updateSource(
@@ -629,54 +451,44 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
): HttpResponse? = ): HttpResponse? =
client.tryToSubmitForm( client.tryToSubmitForm(
url = url("/source/$id"), url = url("/source/$id"),
formParameters = formParameters = Parameters.build {
Parameters.build { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { append("username", appSettingsService.getUserName())
append("username", appSettingsService.getUserName()) append("password", appSettingsService.getPassword())
append("password", appSettingsService.getPassword()) }
} append("title", title)
append("title", title) append("url", url)
append("url", url) append("spout", spout)
append("spout", spout) append(tagsParamName, tags)
append(tagsParamName, tags) },
},
block = { block = {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
headers { headers {
append( append(HttpHeaders.Authorization, constructBasicAuthValue(BasicAuthCredentials(username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()))
HttpHeaders.Authorization,
constructBasicAuthValue(
BasicAuthCredentials(
username = appSettingsService.getBasicUserName(),
password = appSettingsService.getBasicPassword(),
),
),
) )
} }
} }
}, }
) )
suspend fun deleteSource(id: Int): SuccessResponse = suspend fun deleteSource(id: Int): SuccessResponse =
maybeResponse( maybeResponse(client.tryToDelete(url("/source/$id")) {
client.tryToDelete(url("/source/$id")) { if (!shouldHavePostLogin()) {
if (!shouldHavePostLogin()) { parameter("username", appSettingsService.getUserName())
parameter("username", appSettingsService.getUserName()) parameter("password", appSettingsService.getPassword())
parameter("password", appSettingsService.getPassword()) }
} if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) { headers {
headers { append(
append( HttpHeaders.Authorization,
HttpHeaders.Authorization, constructBasicAuthValue(
constructBasicAuthValue( BasicAuthCredentials(
BasicAuthCredentials( username = appSettingsService.getBasicUserName(),
username = appSettingsService.getBasicUserName(), password = appSettingsService.getBasicPassword()
password = appSettingsService.getBasicPassword(), )
),
),
) )
} )
} }
}, }
) })
} }

View File

@@ -13,93 +13,58 @@ class ACRASettings : Settings {
// Nothing // Nothing
} }
override fun getBoolean( override fun getBoolean(key: String, defaultValue: Boolean): Boolean = false
key: String,
defaultValue: Boolean,
): Boolean = false
override fun getBooleanOrNull(key: String): Boolean? = null override fun getBooleanOrNull(key: String): Boolean? = null
override fun getDouble( override fun getDouble(key: String, defaultValue: Double): Double = 0.0
key: String,
defaultValue: Double,
): Double = 0.0
override fun getDoubleOrNull(key: String): Double? = null override fun getDoubleOrNull(key: String): Double? = null
override fun getFloat( override fun getFloat(key: String, defaultValue: Float): Float = 0.0F
key: String,
defaultValue: Float,
): Float = 0.0F
override fun getFloatOrNull(key: String): Float? = null override fun getFloatOrNull(key: String): Float? = null
override fun getInt( override fun getInt(key: String, defaultValue: Int): Int = 0
key: String,
defaultValue: Int,
): Int = 0
override fun getIntOrNull(key: String): Int? = null override fun getIntOrNull(key: String): Int? = null
override fun getLong( override fun getLong(key: String, defaultValue: Long): Long = 0
key: String,
defaultValue: Long,
): Long = 0
override fun getLongOrNull(key: String): Long? = null override fun getLongOrNull(key: String): Long? = null
override fun getString( override fun getString(key: String, defaultValue: String): String = "0"
key: String,
defaultValue: String,
): String = "0"
override fun getStringOrNull(key: String): String? = null override fun getStringOrNull(key: String): String? = null
override fun hasKey(key: String): Boolean = false override fun hasKey(key: String): Boolean = false
override fun putBoolean( override fun putBoolean(key: String, value: Boolean) {
key: String,
value: Boolean,
) {
// Nothing // Nothing
} }
override fun putDouble( override fun putDouble(key: String, value: Double) {
key: String,
value: Double,
) {
// Nothing // Nothing
} }
override fun putFloat( override fun putFloat(key: String, value: Float) {
key: String,
value: Float,
) {
// Nothing // Nothing
} }
override fun putInt( override fun putInt(key: String, value: Int) {
key: String,
value: Int,
) {
// Nothing // Nothing
} }
override fun putLong( override fun putLong(key: String, value: Long) {
key: String,
value: Long,
) {
// Nothing // Nothing
} }
override fun putString( override fun putString(key: String, value: String) {
key: String,
value: String,
) {
// Nothing // Nothing
} }
override fun remove(key: String) { override fun remove(key: String) {
// Nothing // Nothing
} }
} }

View File

@@ -3,12 +3,7 @@ package bou.amine.apps.readerforselfossv2.service
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
class AppSettingsService(acraSenderServiceProcess: Boolean = false) { class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
val settings: Settings = val settings: Settings = if (acraSenderServiceProcess) { ACRASettings() } else { Settings() }
if (acraSenderServiceProcess) {
ACRASettings()
} else {
Settings()
}
// Api related // Api related
private var _apiVersion: Int = -1 private var _apiVersion: Int = -1
@@ -43,6 +38,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
private var _font: String = "" private var _font: String = ""
private var _theme: Int? = null private var _theme: Int? = null
init { init {
refreshApiSettings() refreshApiSettings()
refreshUserSettings() refreshUserSettings()
@@ -56,6 +52,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
return _apiVersion return _apiVersion
} }
fun updateApiVersion(apiMajorVersion: Int) { fun updateApiVersion(apiMajorVersion: Int) {
settings.putInt(API_VERSION_MAJOR, apiMajorVersion) settings.putInt(API_VERSION_MAJOR, apiMajorVersion)
refreshApiVersion() refreshApiVersion()
@@ -140,13 +137,13 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
private fun refreshItemsNumber() { private fun refreshItemsNumber() {
_itemsNumber = _itemsNumber = try {
try { settings.getString(API_ITEMS_NUMBER, "20").toInt()
settings.getString(API_ITEMS_NUMBER, "20").toInt() } catch (e: Exception) {
} catch (e: Exception) { settings.remove(API_ITEMS_NUMBER)
settings.remove(API_ITEMS_NUMBER) 20
20 }
}
} }
fun getApiTimeout(): Long { fun getApiTimeout(): Long {
@@ -159,21 +156,18 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
private fun secToMs(n: Long) = n * 1000 private fun secToMs(n: Long) = n * 1000
private fun refreshApiTimeout() { private fun refreshApiTimeout() {
_apiTimeout = _apiTimeout = secToMs(try {
secToMs( val settingsTimeout = settings.getString(API_TIMEOUT, "60")
try { if (settingsTimeout.toLong() > 0) {
val settingsTimeout = settings.getString(API_TIMEOUT, "60") settingsTimeout.toLong()
if (settingsTimeout.toLong() > 0) { } else {
settingsTimeout.toLong() settings.remove(API_TIMEOUT)
} else { 60
settings.remove(API_TIMEOUT) }
60 } catch (e: Exception) {
} settings.remove(API_TIMEOUT)
} catch (e: Exception) { 60
settings.remove(API_TIMEOUT) })
60
},
)
} }
private fun refreshBaseUrl() { private fun refreshBaseUrl() {
@@ -206,7 +200,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _articleViewer == true return _articleViewer == true
} }
private fun refreshShouldBeCardViewEnabled() { private fun refreshShouldBeCardViewEnabled() {
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false) _shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
} }
@@ -217,7 +210,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _shouldBeCardView == true return _shouldBeCardView == true
} }
private fun refreshDisplayUnreadCountEnabled() { private fun refreshDisplayUnreadCountEnabled() {
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true) _displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
} }
@@ -228,7 +220,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _displayUnreadCount == true return _displayUnreadCount == true
} }
private fun refreshDisplayAllCountEnabled() { private fun refreshDisplayAllCountEnabled() {
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false) _displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
} }
@@ -239,7 +230,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _displayAllCount == true return _displayAllCount == true
} }
private fun refreshFullHeightCardsEnabled() { private fun refreshFullHeightCardsEnabled() {
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false) _fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
} }
@@ -250,7 +240,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _fullHeightCards == true return _fullHeightCards == true
} }
private fun refreshUpdateSourcesEnabled() { private fun refreshUpdateSourcesEnabled() {
_updateSources = settings.getBoolean(UPDATE_SOURCES, true) _updateSources = settings.getBoolean(UPDATE_SOURCES, true)
} }
@@ -261,7 +250,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
} }
return _updateSources == true return _updateSources == true
} }
private fun refreshPeriodicRefreshEnabled() { private fun refreshPeriodicRefreshEnabled() {
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false) _periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
} }
@@ -331,6 +319,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
return _notifyNewItems == true return _notifyNewItems == true
} }
private fun refreshMarkOnScrollEnabled() { private fun refreshMarkOnScrollEnabled() {
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false) _markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
} }
@@ -342,6 +331,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
return _markOnScroll == true return _markOnScroll == true
} }
private fun refreshActiveAllignment() { private fun refreshActiveAllignment() {
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY) _activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
} }
@@ -439,7 +429,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
fun refreshLoginInformation( fun refreshLoginInformation(
url: String, url: String,
login: String, login: String,
password: String, password: String
) { ) {
val regex = """\/\/(\D+):(\D+)@""".toRegex() val regex = """\/\/(\D+):(\D+)@""".toRegex()
val matchResult = regex.find(url) val matchResult = regex.find(url)
@@ -547,5 +537,6 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
const val ITEMS_CACHING = "items_caching" const val ITEMS_CACHING = "items_caching"
const val CURRENT_THEME = "currentMode" const val CURRENT_THEME = "currentMode"
} }
} }

View File

@@ -9,7 +9,7 @@ fun TAG.toView(): SelfossModel.Tag =
SelfossModel.Tag( SelfossModel.Tag(
this.name, this.name,
this.color, this.color,
this.unread.toInt(), this.unread.toInt()
) )
fun SOURCE.toView(): SelfossModel.SourceDetail = fun SOURCE.toView(): SelfossModel.SourceDetail =
@@ -21,7 +21,7 @@ fun SOURCE.toView(): SelfossModel.SourceDetail =
this.spout, this.spout,
this.error, this.error,
this.icon, this.icon,
if (this.url != null) SelfossModel.SourceParams(this.url) else null, if (this.url != null) SelfossModel.SourceParams(this.url) else null
) )
fun SelfossModel.SourceDetail.toEntity(): SOURCE = fun SelfossModel.SourceDetail.toEntity(): SOURCE =
@@ -32,14 +32,14 @@ fun SelfossModel.SourceDetail.toEntity(): SOURCE =
this.spout.orEmpty(), this.spout.orEmpty(),
this.error.orEmpty(), this.error.orEmpty(),
this.icon.orEmpty(), this.icon.orEmpty(),
this.params?.url, this.params?.url
) )
fun SelfossModel.Tag.toEntity(): TAG = fun SelfossModel.Tag.toEntity(): TAG =
TAG( TAG(
this.tag, this.tag,
this.color, this.color,
this.unread.toLong(), this.unread.toLong()
) )
fun ITEM.toView(): SelfossModel.Item = fun ITEM.toView(): SelfossModel.Item =
@@ -55,7 +55,7 @@ fun ITEM.toView(): SelfossModel.Item =
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.split(","), this.tags.split(","),
this.author, this.author
) )
fun SelfossModel.Item.toEntity(): ITEM = fun SelfossModel.Item.toEntity(): ITEM =
@@ -71,5 +71,5 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.link, this.link,
this.sourcetitle.getHtmlDecoded(), this.sourcetitle.getHtmlDecoded(),
this.tags.joinToString(","), this.tags.joinToString(","),
this.author, this.author
) )

View File

@@ -3,8 +3,7 @@ package bou.amine.apps.readerforselfossv2.utils
enum class ItemType(val position: Int, val type: String) { enum class ItemType(val position: Int, val type: String) {
UNREAD(1, "unread"), UNREAD(1, "unread"),
ALL(2, "all"), ALL(2, "all"),
STARRED(3, "starred"), STARRED(3, "starred");
;
companion object { companion object {
fun fromInt(value: Int) = values().first { it.position == value } fun fromInt(value: Int) = values().first { it.position == value }

View File

@@ -12,8 +12,4 @@ expect fun SelfossModel.Item.getImages(): ArrayList<String>
expect fun SelfossModel.Source.getIcon(baseUrl: String): String expect fun SelfossModel.Source.getIcon(baseUrl: String): String
expect fun constructUrl( expect fun constructUrl(baseUrl: String, path: String, file: String?): String
baseUrl: String,
path: String,
file: String?,
): String

View File

@@ -1,6 +1,7 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
fun String?.isEmptyOrNullOrNullString(): Boolean = this == null || this == "null" || this.isEmpty() fun String?.isEmptyOrNullOrNullString(): Boolean =
this == null || this == "null" || this.isEmpty()
fun String.longHash(): Long { fun String.longHash(): Long {
var h = 98764321261L var h = 98764321261L

View File

@@ -22,10 +22,6 @@ actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
actual fun constructUrl( actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
baseUrl: String,
path: String,
file: String?,
): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@@ -1,5 +1,7 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
actual class DateUtils { actual class DateUtils {
actual companion object { actual companion object {
actual fun parseDate(dateString: String): Long { actual fun parseDate(dateString: String): Long {
@@ -10,4 +12,5 @@ actual class DateUtils {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }
} }

View File

@@ -22,10 +22,6 @@ actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
actual fun constructUrl( actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
baseUrl: String,
path: String,
file: String?,
): String {
TODO("Not yet implemented") TODO("Not yet implemented")
} }