Compare commits

..

No commits in common. "a3253d5a7bf436dbef4194b7d87955bc02ba03ed" and "8cbb225e6d4ee394afba47e0a64eb536946fbd33" have entirely different histories.

27 changed files with 742 additions and 723 deletions

View File

@ -170,13 +170,6 @@ dependencies {
implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
//Dependency Injection
implementation("org.kodein.di:kodein-di:7.12.0")
implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0")
//Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
//PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0")

View File

@ -1,36 +1,47 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import androidx.appcompat.app.AppCompatActivity
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.repository.Repository
import com.ftinc.scoop.Scoop
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class AddSourceActivity : AppCompatActivity(), DIAware {
class AddSourceActivity : AppCompatActivity() {
private lateinit var apiDetailsService: ApiDetailsService
private var mSpoutsValue: String? = null
private lateinit var api: SelfossApi
private lateinit var appColors: AppColors
private lateinit var binding: ActivityAddSourceBinding
override val di by closestDI()
private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@AddSourceActivity)
@ -65,32 +76,45 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
try {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val settings =
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
apiDetailsService = AndroidApiDetailsService(this@AddSourceActivity)
api = SelfossApi(
// this,
// this@AddSourceActivity,
// settings.getBoolean("isSelfSignedCert", false),
// prefs.getString("api_timeout", "-1")!!.toLong()
apiDetailsService
)
} catch (e: IllegalArgumentException) {
mustLoginToAddSource()
}
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
binding.saveBtn.setTextColor(appColors.colorAccent)
binding.saveBtn.setOnClickListener {
handleSaveSource(
binding.tags,
binding.nameInput.text.toString(),
binding.sourceUri.text.toString()
)
handleSaveSource(binding.tags, binding.nameInput.text.toString(), binding.sourceUri.text.toString(), api)
}
}
override fun onResume() {
super.onResume()
val config = Config()
val config = Config(this)
if (config.baseUrl.isEmpty() || !config.baseUrl.isBaseUrlValid(this@AddSourceActivity)) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
handleSpoutsSpinner(binding.spoutsSpinner, api, binding.progress, binding.formContainer)
}
}
private fun handleSpoutsSpinner(
spoutsSpinner: Spinner,
api: SelfossApi?,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
@ -110,7 +134,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
val items = repository.getSpouts()
var items = api!!.spouts()
if (items != null) {
val itemsStrings = items.map { it.value.name }
@ -158,7 +182,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
finish()
}
private fun handleSaveSource(tags: EditText, title: String, url: String) {
private fun handleSaveSource(tags: EditText, title: String, url: String, api: SelfossApi) {
val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
@ -169,14 +193,15 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = repository.createSource(
title,
url,
mSpoutsValue!!,
tags.text.toString(),
"",
)
if (successfullyAddedSource) {
val response: SelfossModel.SuccessResponse? = api.createSourceForVersion(
title,
url,
mSpoutsValue!!,
tags.text.toString(),
"",
PreferenceManager.getDefaultSharedPreferences(this@AddSourceActivity).getInt("apiVersionMajor", 0)
)
if (response != null) {
finish()
} else {
Toast.makeText(

View File

@ -1,11 +1,14 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Bundle
import androidx.preference.PreferenceManager
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -34,9 +37,11 @@ import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDataba
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
@ -47,9 +52,13 @@ import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActiv
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity
import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.service.SelfossService
import bou.amine.apps.readerforselfossv2.utils.longHash
import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem
@ -71,27 +80,30 @@ import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.addStickyFooterItem
import com.mikepenz.materialdrawer.util.updateBadge
import com.mikepenz.materialdrawer.widget.AccountHeaderView
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import java.util.concurrent.TimeUnit
import kotlin.concurrent.thread
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener {
private lateinit var dataBase: AndroidDeviceDatabase
private lateinit var dbService: AndroidDeviceDatabaseService
private lateinit var searchService: SearchService
private lateinit var apiDetailsService: ApiDetailsService
private lateinit var service: SelfossService<AndroidItemEntity>
private val MENU_PREFERENCES = 12302
private val DRAWER_ID_TAGS = 100101L
private val DRAWER_ID_HIDDEN_TAGS = 101100L
private val DRAWER_ID_SOURCES = 100110L
private val DRAWER_ID_FILTERS = 100111L
private val UNREAD_SHOWN = 1
private val READ_SHOWN = 2
private val FAV_SHOWN = 3
private var items: ArrayList<SelfossModel.Item> = ArrayList()
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
private var internalBrowser = false
private var articleViewer = false
@ -100,13 +112,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private var displayAllCount = false
private var fullHeightCards: Boolean = false
private var itemsNumber: Int = 200
private var elementsShown: ItemType = ItemType.UNREAD
private var elementsShown: Int = 1
private var userIdentifier: String = ""
private var displayAccountHeader: Boolean = false
private var infiniteScroll: Boolean = false
private var lastFetchDone: Boolean = false
private var updateSources: Boolean = true
private var markOnScroll: Boolean = false
private var hiddenTags: List<String> = emptyList()
private var apiVersionMajor: Int = 0
private var periodicRefresh = false
private var refreshMinutes: Long = 360L
@ -115,12 +129,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private lateinit var tabNewBadge: TextBadgeItem
private lateinit var tabArchiveBadge: TextBadgeItem
private lateinit var tabStarredBadge: TextBadgeItem
private lateinit var api: SelfossApi
private lateinit var customTabActivityHelper: CustomTabActivityHelper
private lateinit var editor: SharedPreferences.Editor
private lateinit var sharedPref: SharedPreferences
private lateinit var appColors: AppColors
private var offset: Int = 0
private var firstVisible: Int = 0
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
private var settings = Settings()
private lateinit var settings: SharedPreferences
private lateinit var binding: ActivityHomeBinding
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
@ -134,9 +151,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private lateinit var config: Config
override val di by closestDI()
private val repository : Repository by instance()
data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?)
override fun onStart() {
@ -146,7 +160,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@HomeActivity)
config = Config()
config = Config(this@HomeActivity)
super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater)
@ -156,7 +170,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
offlineShortcut = intent.getBooleanExtra("startOffline", false)
if (fromTabShortcut) {
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
elementsShown = intent.getIntExtra("shortcutTab", UNREAD_SHOWN)
}
setContentView(view)
@ -178,14 +192,33 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
customTabActivityHelper = CustomTabActivityHelper()
sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
settings = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
apiDetailsService = AndroidApiDetailsService(applicationContext)
api = SelfossApi(
// this,
// this@HomeActivity,
// settings.getBoolean("isSelfSignedCert", false),
// sharedPref.getString("api_timeout", "-1")!!.toLong()
apiDetailsService
)
dataBase = AndroidDeviceDatabase(applicationContext)
searchService = SearchService(DateUtils(apiDetailsService))
dbService = AndroidDeviceDatabaseService(dataBase, searchService)
service = SelfossService(api, dbService, searchService)
items = ArrayList()
allItems = ArrayList()
handleBottomBar()
handleDrawer()
handleSwipeRefreshLayout()
handleSettings()
handleSharedPrefs()
getApiMajorVersion()
getElementsAccordingToTab()
}
@ -198,6 +231,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
)
binding.swipeRefreshLayout.setOnRefreshListener {
offlineShortcut = false
allItems = ArrayList()
lastFetchDone = false
handleDrawerItems()
CoroutineScope(Dispatchers.Main).launch {
@ -215,7 +249,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int =
if (elementsShown == ItemType.STARRED) {
if (elementsShown == FAV_SHOWN) {
0
} else {
super.getSwipeDirs(
@ -312,7 +346,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.bottomBar.setBackgroundStyle(BottomNavigationBar.BACKGROUND_STYLE_STATIC)
if (fromTabShortcut) {
binding.bottomBar.selectTab(elementsShown.position - 1)
binding.bottomBar.selectTab(elementsShown - 1)
}
}
private fun getApiMajorVersion() {
CoroutineScope(Dispatchers.IO).launch {
val version = api.version()
if (version != null) {
apiVersionMajor = version.getApiMajorVersion()
sharedPref.edit().putInt("apiVersionMajor", apiVersionMajor).apply()
}
}
}
@ -322,6 +366,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
// TODO: Make this the only appcolors init
appColors = AppColors(this@HomeActivity)
sharedPref = PreferenceManager.getDefaultSharedPreferences(this)
editor = settings.edit()
handleDrawerItems()
handleThemeUpdate()
@ -348,30 +396,34 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
customTabActivityHelper.unbindCustomTabsService(this)
}
private fun handleSettings() {
internalBrowser = settings.getBoolean("prefer_internal_browser", true)
articleViewer = settings.getBoolean("prefer_article_viewer", true)
shouldBeCardView = settings.getBoolean("card_view_active", false)
displayUnreadCount = settings.getBoolean("display_unread_count", true)
displayAllCount = settings.getBoolean("display_other_count", false)
fullHeightCards = settings.getBoolean("full_height_cards", false)
itemsNumber = settings.getString("prefer_api_items_number", "200").toInt()
displayAccountHeader = settings.getBoolean("account_header_displaying", false)
infiniteScroll = settings.getBoolean("infinite_loading", false)
updateSources = settings.getBoolean("update_sources", true)
markOnScroll = settings.getBoolean("mark_on_scroll", false)
hiddenTags = if (settings.getString("hidden_tags", "").isNotEmpty()) {
settings.getString("hidden_tags", "").replace("\\s".toRegex(), "").split(",")
private fun handleSharedPrefs() {
internalBrowser = sharedPref.getBoolean("prefer_internal_browser", true)
articleViewer = sharedPref.getBoolean("prefer_article_viewer", true)
shouldBeCardView = sharedPref.getBoolean("card_view_active", false)
displayUnreadCount = sharedPref.getBoolean("display_unread_count", true)
displayAllCount = sharedPref.getBoolean("display_other_count", false)
fullHeightCards = sharedPref.getBoolean("full_height_cards", false)
itemsNumber = sharedPref.getString("prefer_api_items_number", "200")!!.toInt()
userIdentifier = sharedPref.getString("unique_id", "")!!
displayAccountHeader = sharedPref.getBoolean("account_header_displaying", false)
infiniteScroll = sharedPref.getBoolean("infinite_loading", false)
searchService.itemsCaching = sharedPref.getBoolean("items_caching", false)
updateSources = sharedPref.getBoolean("update_sources", true)
markOnScroll = sharedPref.getBoolean("mark_on_scroll", false)
hiddenTags = if (sharedPref.getString("hidden_tags", "")!!.isNotEmpty()) {
sharedPref.getString("hidden_tags", "")!!.replace("\\s".toRegex(), "").split(",")
} else {
emptyList()
}
periodicRefresh = settings.getBoolean("periodic_refresh", false)
refreshWhenChargingOnly = settings.getBoolean("refresh_when_charging", false)
refreshMinutes = settings.getString("periodic_refresh_minutes", "360").toLong()
periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
refreshWhenChargingOnly = sharedPref.getBoolean("refresh_when_charging", false)
refreshMinutes = sharedPref.getString("periodic_refresh_minutes", "360")!!.toLong()
if (refreshMinutes <= 15) {
refreshMinutes = 15
}
apiVersionMajor = sharedPref.getInt("apiVersionMajor", 0)
}
private fun handleThemeBinding() {
@ -426,7 +478,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.drawerContainer.addDrawerListener(drawerListener)
displayAccountHeader =
settings.getBoolean("account_header_displaying", false)
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("account_header_displaying", false)
binding.mainDrawer.addStickyFooterItem(
PrimaryDrawerItem().apply {
@ -456,7 +509,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
attachToSliderView(binding.mainDrawer)
addProfiles(
ProfileDrawerItem().apply {
nameText = settings.getString("url", "")
nameText = settings.getString("url", "").toString()
setBackgroundResource(R.drawable.bg)
iconRes = R.mipmap.ic_launcher
selectionListEnabledForSingleProfile = false
@ -503,8 +556,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
textColor = ColorHolder.fromColor(Color.WHITE)
color = ColorHolder.fromColor(appColors.colorAccent) }
onDrawerItemClickListener = { _,_,_ ->
repository.tagFilter = it
repository.sourceFilter = null
allItems = ArrayList()
searchService.tagFilter = it.tag
searchService.sourceFilter = null
searchService.sourceIDFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -554,8 +609,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
textColor = ColorHolder.fromColor(Color.WHITE)
color = ColorHolder.fromColor(appColors.colorAccent) }
onDrawerItemClickListener = { _,_,_ ->
repository.tagFilter = it
repository.sourceFilter = null
allItems = ArrayList()
searchService.tagFilter = it.tag
searchService.sourceFilter = null
searchService.sourceIDFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -586,10 +643,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
val item = PrimaryDrawerItem().apply {
nameText = source.getTitleDecoded()
identifier = source.id.toLong()
iconUrl = source.getIcon(repository.baseUrl)
iconUrl = source.getIcon(apiDetailsService.getBaseUrl())
onDrawerItemClickListener = { _,_,_ ->
repository.sourceFilter = source
repository.tagFilter = null
allItems = ArrayList()
searchService.sourceIDFilter = source.id.toLong()
searchService.sourceFilter = source.title
searchService.tagFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
@ -609,8 +668,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
identifier = DRAWER_ID_FILTERS
badgeRes = R.string.drawer_action_clear
onDrawerItemClickListener = { _,_,_ ->
repository.sourceFilter = null
repository.tagFilter = null
allItems = ArrayList()
searchService.sourceFilter = null
searchService.sourceIDFilter = null
searchService.tagFilter = null
binding.mainDrawer.setSelectionAtPosition(-1)
getElementsAccordingToTab()
fetchOnEmptyList()
@ -711,7 +772,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
fun sourcesApiCall() {
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
val response = api.sources()
if (response != null) {
sources = response
val apiDrawerData = DrawerData(tags, sources)
@ -730,7 +791,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) {
CoroutineScope(Dispatchers.IO).launch {
tags = repository.getTags()
val response = api.tags()
if (response != null) {
tags = response
}
sourcesApiCall()
}
}
@ -825,7 +889,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
offset = 0
lastFetchDone = false
elementsShown = ItemType.fromInt(position + 1)
elementsShown = position + 1
getElementsAccordingToTab()
binding.recyclerView.scrollToPosition(0)
@ -878,6 +942,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun getElementsAccordingToTab(
appendResults: Boolean = false
) {
fun doGetAccordingToTab() {
when (elementsShown) {
UNREAD_SHOWN -> getUnRead(appendResults)
READ_SHOWN -> getRead(appendResults)
FAV_SHOWN -> getStarred(appendResults)
else -> getUnRead(appendResults)
}
}
offset = if (appendResults && items.size > 0) {
items.size - 1
} else {
@ -885,17 +958,45 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
firstVisible = if (appendResults) firstVisible else 0
getItems(appendResults, elementsShown)
doGetAccordingToTab()
}
private fun getItems(appendResults: Boolean, itemType: ItemType) {
private fun getUnRead(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch {
binding.swipeRefreshLayout.isRefreshing = true
repository.displayedItems = itemType
items = if (appendResults) {
repository.getOlderItems()
val apiItems = service.getUnreadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
if (appendResults) {
apiItems?.let { items.addAll(it) }
} else {
repository.getNewerItems()
items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
}
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
}
}
private fun getRead(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch {
binding.swipeRefreshLayout.isRefreshing = true
val apiItems = service.getReadItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
if (appendResults) {
apiItems?.let { items.addAll(it) }
} else {
items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
}
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
}
}
private fun getStarred(appendResults: Boolean = false) {
CoroutineScope(Dispatchers.Main).launch {
binding.swipeRefreshLayout.isRefreshing = true
val apiItems = service.getStarredItems(itemsNumber, offset, applicationContext.isNetworkAvailable())
if (appendResults) {
apiItems?.let { items.addAll(it) }
} else {
items = apiItems.orEmpty() as ArrayList<SelfossModel.Item>
}
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
@ -920,13 +1021,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemCardAdapter(
this,
items,
api,
apiDetailsService,
db,
customTabActivityHelper,
internalBrowser,
articleViewer,
fullHeightCards,
appColors,
config
userIdentifier,
config,
searchService
) {
updateItems(it)
}
@ -935,12 +1040,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemListAdapter(
this,
items,
api,
apiDetailsService,
db,
customTabActivityHelper,
internalBrowser,
articleViewer,
userIdentifier,
appColors,
config
config,
searchService
) {
updateItems(it)
}
@ -964,10 +1073,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadges() {
if (displayUnreadCount || displayAllCount) {
CoroutineScope(Dispatchers.Main).launch {
if (applicationContext.isNetworkAvailable()) {
repository.reloadBadges()
reloadBadgeContent()
}
service.reloadBadges(applicationContext.isNetworkAvailable())
reloadBadgeContent()
}
}
}
@ -975,15 +1082,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadgeContent() {
if (displayUnreadCount) {
tabNewBadge
.setText(repository.badgeUnread.toString())
.setText(searchService.badgeUnread.toString())
.maybeShow()
}
if (displayAllCount) {
tabArchiveBadge
.setText(repository.badgeAll.toString())
.setText(searchService.badgeAll.toString())
.maybeShow()
tabStarredBadge
.setText(repository.badgeStarred.toString())
.setText(searchService.badgeStarred.toString())
.maybeShow()
}
}
@ -1003,7 +1110,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onQueryTextChange(p0: String?): Boolean {
if (p0.isNullOrBlank()) {
repository.searchFilter = null
searchService.searchFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
}
@ -1011,7 +1118,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
override fun onQueryTextSubmit(p0: String?): Boolean {
repository.searchFilter = p0
searchService.searchFilter = p0
getElementsAccordingToTab()
fetchOnEmptyList()
return false
@ -1044,10 +1151,9 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
// TODO: Use Dispatchers.IO
CoroutineScope(Dispatchers.Main).launch {
val updatedRemote = repository.updateRemote()
if (updatedRemote) {
val status = api.update()
if (status != null && status.isSuccess) {
Toast.makeText(
this@HomeActivity,
R.string.refresh_success_response, Toast.LENGTH_LONG
@ -1068,13 +1174,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
}
R.id.readAll -> {
if (elementsShown == ItemType.UNREAD) {
if (elementsShown == UNREAD_SHOWN) {
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
binding.swipeRefreshLayout.isRefreshing = true
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
CoroutineScope(Dispatchers.Main).launch {
val success = repository.markAllAsRead(items.map { it.id })
val success = service.readAll(items.map { it.id.toString() }, applicationContext.isNetworkAvailable())
if (success) {
Toast.makeText(
this@HomeActivity,
@ -1102,7 +1208,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true
}
R.id.action_disconnect -> {
return Config.logoutAndRedirect(this, this@HomeActivity)
return Config.logoutAndRedirect(this, this@HomeActivity, editor)
}
else -> return super.onOptionsItemSelected(item)
}
@ -1110,10 +1216,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun maxItemNumber(): Int =
when (elementsShown) {
ItemType.UNREAD -> repository.badgeUnread
ItemType.ALL -> repository.badgeAll
ItemType.STARRED -> repository.badgeStarred
else -> repository.badgeUnread // if !elementsShown then unread are fetched.
UNREAD_SHOWN -> searchService.badgeUnread
READ_SHOWN -> searchService.badgeAll
FAV_SHOWN -> searchService.badgeStarred
else -> searchService.badgeUnread // if !elementsShown then unread are fetched.
}
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {
@ -1139,8 +1245,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
}
private fun handleOfflineActions() {
fun doAndReportOnFail(success: Boolean, action: ActionEntity) {
if (success) {
fun doAndReportOnFail(call: SelfossModel.SuccessResponse?, action: ActionEntity) {
if (call != null && call.isSuccess) {
thread {
db.actionsDao().delete(action)
}
@ -1153,10 +1259,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
actions.forEach { action ->
when {
action.read -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
action.unread -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
action.starred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
action.unstarred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
action.read -> doAndReportOnFail(api.markAsRead(action.articleId), action)
action.unread -> doAndReportOnFail(api.unmarkAsRead(action.articleId), action)
action.starred -> doAndReportOnFail(api.starr(action.articleId), action)
action.unstarred -> doAndReportOnFail(api.unstarr(action.articleId), action)
}
}
}

View File

@ -3,43 +3,48 @@ package bou.amine.apps.readerforselfossv2.android
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import android.text.TextUtils
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import androidx.work.Logger
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import com.mikepenz.aboutlibraries.LibsBuilder
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginActivity() : AppCompatActivity(), DIAware {
class LoginActivity : AppCompatActivity() {
private var inValidCount: Int = 0
private var isWithSelfSignedCert = false
private var isWithLogin = false
private var isWithHTTPLogin = false
private val settings = Settings()
private lateinit var settings: SharedPreferences
private lateinit var editor: SharedPreferences.Editor
private lateinit var userIdentifier: String
private lateinit var appColors: AppColors
private lateinit var binding: ActivityLoginBinding
override val di by closestDI()
private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@LoginActivity)
@ -53,7 +58,12 @@ class LoginActivity() : AppCompatActivity(), DIAware {
handleBaseUrlFail()
if (settings.getString("url", "").isNotEmpty()) {
settings = PreferenceManager.getDefaultSharedPreferences(applicationContext)
userIdentifier = settings.getString("unique_id", "")!!
editor = settings.edit()
if (settings.getString("url", "")!!.isNotEmpty()) {
goToMain()
}
@ -118,16 +128,18 @@ class LoginActivity() : AppCompatActivity(), DIAware {
}
private fun preferenceError(t: Throwable) {
settings.remove("url")
settings.remove("login")
settings.remove("httpUserName")
settings.remove("password")
settings.remove("httpPassword")
editor.remove("url")
editor.remove("login")
editor.remove("httpUserName")
editor.remove("password")
editor.remove("httpPassword")
editor.apply()
binding.urlView.error = getString(R.string.wrong_infos)
binding.loginView.error = getString(R.string.wrong_infos)
binding.passwordView.error = getString(R.string.wrong_infos)
binding.httpLoginView.error = getString(R.string.wrong_infos)
binding.httpPasswordView.error = getString(R.string.wrong_infos)
showProgress(false)
}
private fun attemptLogin() {
@ -200,21 +212,39 @@ class LoginActivity() : AppCompatActivity(), DIAware {
} else {
showProgress(true)
repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert)
editor.putString("url", url)
editor.putString("login", login)
editor.putString("httpUserName", httpLogin)
editor.putString("password", password)
editor.putString("httpPassword", httpPassword)
editor.putBoolean("isSelfSignedCert", isWithSelfSignedCert)
editor.apply()
val apiDetailsService = AndroidApiDetailsService(this@LoginActivity)
val api = SelfossApi(
// this,
// this@LoginActivity,
// isWithSelfSignedCert,
// -1L
apiDetailsService
)
if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) {
CoroutineScope(Dispatchers.IO).launch {
val result = repository.login()
if (result) {
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
try {
val result = api.login()
if (result != null && result.isSuccess) {
goToMain()
} else {
preferenceError(Exception("Not success"))
}
} catch (cause: Throwable) {
Log.e("1", "LOL")
}
}
} else {
showProgress(false)
}
showProgress(false)
}
}

View File

@ -6,35 +6,31 @@ import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import androidx.preference.PreferenceManager
import android.widget.ImageView
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.DI.networkModule
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.repository.Repository
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.russhwolf.settings.Settings
import org.kodein.di.*
class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy {
import(networkModule)
bind<Repository>() with singleton { Repository(instance(), instance()) }
}
import java.util.UUID.randomUUID
class MyApp : MultiDexApplication() {
private lateinit var config: Config
private lateinit var settings : Settings
override fun onCreate() {
super.onCreate()
config = Config()
settings = Settings()
config = Config(baseContext)
val prefs = getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (prefs.getString("unique_id", "")!!.isEmpty()) {
val editor = prefs.edit()
editor.putString("unique_id", randomUUID().toString())
editor.apply()
}
initDrawerImageLoader()

View File

@ -1,5 +1,7 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.view.KeyEvent
@ -8,6 +10,7 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import androidx.room.Room
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
@ -17,38 +20,38 @@ import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabas
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.toggleStar
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import com.ftinc.scoop.Scoop
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class ReaderActivity : AppCompatActivity(), DIAware {
class ReaderActivity : AppCompatActivity() {
private var markOnScroll: Boolean = false
private var currentItem: Int = 0
private lateinit var userIdentifier: String
private lateinit var appColors: AppColors
private lateinit var api: SelfossApi
private lateinit var toolbarMenu: Menu
private lateinit var db: AppDatabase
private lateinit var prefs: SharedPreferences
private lateinit var binding: ActivityReaderBinding
private var activeAlignment: Int = 1
private val JUSTIFY = 1
private val ALIGN_LEFT = 2
override val di by closestDI()
private val repository : Repository by instance()
private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE)
@ -65,7 +68,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
showMenuItem(false)
}
private var settings = Settings()
private lateinit var editor: SharedPreferences.Editor
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -88,8 +91,23 @@ class ReaderActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
markOnScroll = settings.getBoolean("mark_on_scroll", false)
activeAlignment = settings.getInt("text_align", JUSTIFY)
val settings =
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
prefs = PreferenceManager.getDefaultSharedPreferences(this)
editor = prefs.edit()
userIdentifier = prefs.getString("unique_id", "")!!
markOnScroll = prefs.getBoolean("mark_on_scroll", false)
activeAlignment = prefs.getInt("text_align", JUSTIFY)
api = SelfossApi(
// this,
// this@ReaderActivity,
// settings.getBoolean("isSelfSignedCert", false),
// prefs.getString("api_timeout", "-1")!!.toLong()
AndroidApiDetailsService(this@ReaderActivity)
)
if (allItems.isEmpty()) {
finish()
@ -112,8 +130,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private fun readItem(item: SelfossModel.Item) {
if (markOnScroll) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item.id)
// TODO: Handle failure
api.markAsRead(item.id.toString())
// TODO: update item in DB
}
}
}
@ -150,8 +168,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
}
}
private fun alignmentMenu() {
val showJustify = activeAlignment == ALIGN_LEFT
private fun alignmentMenu(showJustify: Boolean) {
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify
}
@ -166,7 +183,11 @@ class ReaderActivity : AppCompatActivity(), DIAware {
} else {
canFavorite()
}
alignmentMenu()
if (activeAlignment == JUSTIFY) {
alignmentMenu(false)
} else {
alignmentMenu(true)
}
binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() {
@ -207,37 +228,34 @@ class ReaderActivity : AppCompatActivity(), DIAware {
R.id.star -> {
if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(allItems[binding.pager.currentItem].id)
// TODO: Handle failure
api.unstarr(allItems[binding.pager.currentItem].id.toString())
// TODO: update in DB
}
afterUnsave()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(allItems[binding.pager.currentItem].id)
// TODO: Handle failure
api.starr(allItems[binding.pager.currentItem].id.toString())
// TODO: update in DB
}
afterSave()
}
}
R.id.align_left -> {
activeAlignment = ALIGN_LEFT
switchAlignmentSetting()
editor.putInt("text_align", ALIGN_LEFT)
editor.apply()
alignmentMenu(true)
refreshFragment()
}
R.id.align_justify -> {
activeAlignment = JUSTIFY
switchAlignmentSetting()
editor.putInt("text_align", JUSTIFY)
editor.apply()
alignmentMenu(false)
refreshFragment()
}
}
return super.onOptionsItemSelected(item)
}
private fun switchAlignmentSetting() {
settings.putInt("text_align", activeAlignment)
alignmentMenu()
}
private fun refreshFragment() {
finish()
overridePendingTransition(0, 0)

View File

@ -1,34 +1,37 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle
import android.widget.Toast
import androidx.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import android.widget.Toast
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import com.ftinc.scoop.Scoop
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.ArrayList
class SourcesActivity : AppCompatActivity(), DIAware {
class SourcesActivity : AppCompatActivity() {
private lateinit var appColors: AppColors
private lateinit var binding: ActivitySourcesBinding
override val di by closestDI()
private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(this@SourcesActivity)
binding = ActivitySourcesBinding.inflate(layoutInflater)
@ -59,6 +62,18 @@ class SourcesActivity : AppCompatActivity(), DIAware {
super.onResume()
val mLayoutManager = LinearLayoutManager(this)
val settings =
getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val apiDetailsService = AndroidApiDetailsService(this@SourcesActivity)
val api = SelfossApi(
// this,
// this@SourcesActivity,
// settings.getBoolean("isSelfSignedCert", false),
// prefs.getString("api_timeout", "-1")!!.toLong()
apiDetailsService
)
var items: ArrayList<SelfossModel.Source>
binding.recyclerView.setHasFixedSize(true)
@ -66,11 +81,11 @@ class SourcesActivity : AppCompatActivity(), DIAware {
if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
val response = api.sources()
if (response != null) {
items = response
val mAdapter = SourcesListAdapter(
this@SourcesActivity, items
val mAdapter = SourcesListAdapter(this@SourcesActivity, items, api,
apiDetailsService
)
binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()

View File

@ -17,28 +17,32 @@ import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActiv
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class ItemCardAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val api: SelfossApi,
override val apiDetailsService: ApiDetailsService,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
private val fullHeightCards: Boolean,
override val appColors: AppColors,
override val userIdentifier: String,
override val config: Config,
override val searchService: SearchService,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private val c: Context = app.baseContext
@ -46,9 +50,6 @@ class ItemCardAdapter(
private val imageMaxHeight: Int =
c.resources.getDimension(R.dimen.card_image_max_height).toInt()
override val di: DI by closestDI(app)
override val repository : Repository by instance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
@ -65,23 +66,23 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(appColors.colorAccent)
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText(DateUtils(apiDetailsService))
if (!fullHeightCards) {
binding.itemImage.maxHeight = imageMaxHeight
binding.itemImage.scaleType = ScaleType.CENTER_CROP
}
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getThumbnail(apiDetailsService.getBaseUrl()).isEmpty()) {
binding.itemImage.visibility = View.GONE
Glide.with(c).clear(binding.itemImage)
binding.itemImage.setImageDrawable(null)
} else {
binding.itemImage.visibility = View.VISIBLE
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
c.bitmapCenterCrop(config, itm.getThumbnail(apiDetailsService.getBaseUrl()), binding.itemImage)
}
if (itm.getIcon(repository.baseUrl).isEmpty()) {
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val drawable =
@ -91,7 +92,7 @@ class ItemCardAdapter(
.build(itm.getSourceTitle().toTextDrawableString(c), color)
binding.sourceImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.sourceImage)
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.sourceImage)
}
}
}
@ -113,15 +114,15 @@ class ItemCardAdapter(
if (c.isNetworkAvailable()) {
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item.id)
// TODO: Handle failure
api.unstarr(item.id.toString())
// TODO: save to db
}
item.starred = false
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item.id)
// TODO: Handle failure
api.starr(item.id.toString())
// TODO: save to db
}
item.starred = true
binding.favButton.isSelected = true
@ -151,7 +152,8 @@ class ItemCardAdapter(
customTabsIntent,
internalBrowser,
articleViewer,
app
app,
searchService
)
}
}

View File

@ -13,31 +13,32 @@ import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import org.kodein.di.DI
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class ItemListAdapter(
override val app: Activity,
override var items: ArrayList<SelfossModel.Item>,
override val api: SelfossApi,
override val apiDetailsService: ApiDetailsService,
override val db: AppDatabase,
private val helper: CustomTabActivityHelper,
private val internalBrowser: Boolean,
private val articleViewer: Boolean,
override val userIdentifier: String,
override val appColors: AppColors,
override val config: Config,
override val searchService: SearchService,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext
override val di: DI by closestDI(app)
override val repository : Repository by instance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding)
@ -53,11 +54,11 @@ class ItemListAdapter(
binding.title.setLinkTextColor(appColors.colorAccent)
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText(DateUtils(apiDetailsService))
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getThumbnail(apiDetailsService.getBaseUrl()).isEmpty()) {
if (itm.getIcon(repository.baseUrl).isEmpty()) {
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
val color = generator.getColor(itm.getSourceTitle())
val drawable =
@ -68,10 +69,10 @@ class ItemListAdapter(
binding.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.itemImage)
}
} else {
c.bitmapCenterCrop(config, itm.getThumbnail(repository.baseUrl), binding.itemImage)
c.bitmapCenterCrop(config, itm.getThumbnail(apiDetailsService.getBaseUrl()), binding.itemImage)
}
}
}
@ -96,7 +97,8 @@ class ItemListAdapter(
customTabsIntent,
internalBrowser,
articleViewer,
app
app,
searchService
)
}
}

View File

@ -8,22 +8,25 @@ import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>() {
abstract var items: ArrayList<SelfossModel.Item>
abstract val repository: Repository
abstract val api: SelfossApi
abstract val apiDetailsService: ApiDetailsService
abstract val db: AppDatabase
abstract val userIdentifier: String
abstract val app: Activity
abstract val appColors: AppColors
abstract val config: Config
abstract val searchService: SearchService
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
@ -79,15 +82,18 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
val i = items[position]
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(i.id)
}
if (repository.displayedItems == ItemType.UNREAD) {
items.remove(i)
notifyItemRemoved(position)
updateItems(items)
} else {
notifyItemChanged(position)
api.markAsRead(i.id.toString())
// TODO: update db
}
// Todo:
// if (SharedItems.displayedItems == "unread") {
// items.remove(i)
// notifyItemRemoved(position)
// updateItems(items)
// } else {
// notifyItemChanged(position)
// }
if (showSnackbar) {
unmarkSnackbar(i, position)
}
@ -95,7 +101,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(items[position].id)
api.unmarkAsRead(items[position].id.toString())
// Todo: SharedItems.unreadItem(app, api, db, items[position])
// TODO: update db

View File

@ -16,30 +16,26 @@ import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class SourcesListAdapter(
private val app: Activity,
private val items: ArrayList<SelfossModel.Source>
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
private val items: ArrayList<SelfossModel.Source>,
private val api: SelfossApi,
private val apiDetailsService: ApiDetailsService
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>() {
private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private lateinit var config: Config
private lateinit var binding: SourceListItemBinding
override val di: DI by closestDI(app)
private val repository : Repository by instance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding.root)
@ -47,9 +43,9 @@ class SourcesListAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itm = items[position]
config = Config()
config = Config(c)
if (itm.getIcon(repository.baseUrl).isEmpty()) {
if (itm.getIcon(apiDetailsService.getBaseUrl()).isEmpty()) {
val color = generator.getColor(itm.getTitleDecoded())
val drawable =
@ -59,7 +55,7 @@ class SourcesListAdapter(
.build(itm.getTitleDecoded().toTextDrawableString(c), color)
binding.itemImage.setImageDrawable(drawable)
} else {
c.circularBitmapDrawable(config, itm.getIcon(repository.baseUrl), binding.itemImage)
c.circularBitmapDrawable(config, itm.getIcon(apiDetailsService.getBaseUrl()), binding.itemImage)
}
binding.sourceTitle.text = itm.getTitleDecoded()
@ -81,8 +77,8 @@ class SourcesListAdapter(
if (c.isNetworkAvailable(null)) {
val (id) = items[adapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) {
val action = api.deleteSource(id)
if (action != null && action.isSuccess) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)

View File

@ -5,6 +5,7 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.preference.PreferenceManager
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.PRIORITY_DEFAULT
import androidx.core.app.NotificationCompat.PRIORITY_LOW
@ -12,39 +13,52 @@ import androidx.room.Room
import androidx.work.Worker
import androidx.work.WorkerParameters
import bou.amine.apps.readerforselfossv2.android.MainActivity
import bou.amine.apps.readerforselfossv2.android.MyApp
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.model.preloadImages
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.ActionEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.ItemType
import com.russhwolf.settings.Settings
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.service.SelfossService
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.instance
import java.util.*
import kotlin.concurrent.schedule
import kotlin.concurrent.thread
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params) {
lateinit var db: AppDatabase
override val di by lazy { (applicationContext as MyApp).di }
private val repository : Repository by instance()
override fun doWork(): Result {
val settings = Settings()
val periodicRefresh = settings.getBoolean("periodic_refresh", false)
val settings =
this.context.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val sharedPref = PreferenceManager.getDefaultSharedPreferences(this.context)
val periodicRefresh = sharedPref.getBoolean("periodic_refresh", false)
if (periodicRefresh) {
val apiDetailsService = AndroidApiDetailsService(this.context)
val api = SelfossApi(
// this.context,
// null,
// settings.getBoolean("isSelfSignedCert", false),
// sharedPref.getString("api_timeout", "-1")!!.toLong()
apiDetailsService
)
val dateUtils = DateUtils(apiDetailsService)
val searchService = SearchService(dateUtils)
val service = SelfossService(api, AndroidDeviceDatabaseService(AndroidDeviceDatabase(applicationContext), searchService), searchService)
if (context.isNetworkAvailable()) {
@ -63,7 +77,7 @@ override fun doWork(): Result {
notificationManager.notify(1, notification.build())
val notifyNewItems = settings.getBoolean("notify_new_items", false)
val notifyNewItems = sharedPref.getBoolean("notify_new_items", false)
db = Room.databaseBuilder(
applicationContext,
@ -76,19 +90,19 @@ override fun doWork(): Result {
actions.forEach { action ->
when {
action.read -> doAndReportOnFail(
repository.markAsRead(action.articleId.toInt()),
api.markAsRead(action.articleId),
action
)
action.unread -> doAndReportOnFail(
repository.unmarkAsRead(action.articleId.toInt()),
api.unmarkAsRead(action.articleId),
action
)
action.starred -> doAndReportOnFail(
repository.starr(action.articleId.toInt()),
api.starr(action.articleId),
action
)
action.unstarred -> doAndReportOnFail(
repository.unstarr(action.articleId.toInt()),
api.unstarr(action.articleId),
action
)
}
@ -97,10 +111,10 @@ override fun doWork(): Result {
if (context.isNetworkAvailable()) {
launch {
try {
val newItems = repository.allItems(ItemType.UNREAD)
val newItems = service.allNewItems()
handleNewItemsNotification(newItems, notifyNewItems, notificationManager)
val readItems = repository.allItems(ItemType.ALL)
val starredItems = repository.allItems(ItemType.STARRED)
val readItems = service.allReadItems()
val starredItems = service.allStarredItems()
// TODO: save all to DB
} catch (e: Throwable) {}
}
@ -159,9 +173,8 @@ override fun doWork(): Result {
}
}
private fun doAndReportOnFail(result: Boolean, action: ActionEntity) {
// TODO: Failures should be reported
if (result) {
private fun doAndReportOnFail(result: SelfossModel.SuccessResponse?, action: ActionEntity) {
if (result != null && result.isSuccess) {
thread {
db.actionsDao().delete(action)
}

View File

@ -1,6 +1,8 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Bitmap
@ -9,16 +11,14 @@ import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.view.*
import android.webkit.WebResourceResponse
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.*
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.room.Room
import bou.amine.apps.readerforselfossv2.android.ImageActivity
import bou.amine.apps.readerforselfossv2.android.R
@ -26,32 +26,35 @@ import bou.amine.apps.readerforselfossv2.android.api.mercury.MercuryApi
import bou.amine.apps.readerforselfossv2.android.api.mercury.ParsedContent
import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBinding
import bou.amine.apps.readerforselfossv2.android.model.*
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.AndroidDeviceDatabaseService
import bou.amine.apps.readerforselfossv2.android.persistence.database.AppDatabase
import bou.amine.apps.readerforselfossv2.android.persistence.entities.AndroidItemEntity
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_1_2
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3
import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4
import bou.amine.apps.readerforselfossv2.android.service.AndroidApiDetailsService
import bou.amine.apps.readerforselfossv2.android.themes.AppColors
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.service.SelfossService
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.github.rubensousa.floatingtoolbar.FloatingToolbar
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.russhwolf.settings.Settings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
@ -60,7 +63,10 @@ import java.net.URL
import java.util.*
import java.util.concurrent.ExecutionException
class ArticleFragment : Fragment(), DIAware {
class ArticleFragment : Fragment() {
private lateinit var dbService: AndroidDeviceDatabaseService
private lateinit var apiDetailsService: ApiDetailsService
private lateinit var service: SelfossService<AndroidItemEntity>
private var fontSize: Int = 16
private lateinit var item: SelfossModel.Item
private var mCustomTabActivityHelper: CustomTabActivityHelper? = null
@ -70,6 +76,7 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var contentImage: String
private lateinit var contentTitle: String
private lateinit var allImages : ArrayList<String>
private lateinit var editor: SharedPreferences.Editor
private lateinit var fab: FloatingActionButton
private lateinit var appColors: AppColors
private lateinit var db: AppDatabase
@ -78,10 +85,7 @@ class ArticleFragment : Fragment(), DIAware {
private var _binding: FragmentArticleBinding? = null
private val binding get() = _binding!!
override val di : DI by closestDI()
private val repository: Repository by instance()
private var settings = Settings()
private lateinit var prefs: SharedPreferences
private var typeface: Typeface? = null
private var resId: Int = 0
@ -97,10 +101,16 @@ class ArticleFragment : Fragment(), DIAware {
override fun onCreate(savedInstanceState: Bundle?) {
appColors = AppColors(requireActivity())
config = Config()
config = Config(requireActivity())
super.onCreate(savedInstanceState)
apiDetailsService = AndroidApiDetailsService(requireContext())
dbService = AndroidDeviceDatabaseService(AndroidDeviceDatabase(requireContext()), SearchService(DateUtils(apiDetailsService)))
service = SelfossService(SelfossApi(apiDetailsService), dbService, SearchService(DateUtils(apiDetailsService)))
val pi: ParecelableItem = requireArguments().getParcelable(ARG_ITEMS)!!
item = pi.toModel()
@ -122,14 +132,16 @@ class ArticleFragment : Fragment(), DIAware {
url = item.getLinkDecoded()
contentText = item.content
contentTitle = item.getTitleDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText(repository.dateUtils)
contentImage = item.getThumbnail(apiDetailsService.getBaseUrl())
contentSource = item.sourceAndDateText(DateUtils(apiDetailsService))
allImages = item.getImages()
fontSize = settings.getString("reader_font_size", "16").toInt()
staticBar = settings.getBoolean("reader_static_bar", false)
prefs = PreferenceManager.getDefaultSharedPreferences(activity)
editor = prefs.edit()
fontSize = prefs.getString("reader_font_size", "16")!!.toInt()
staticBar = prefs.getBoolean("reader_static_bar", false)
font = settings.getString("reader_font", "")
font = prefs.getString("reader_font", "")!!
if (font.isNotEmpty()) {
resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
typeface = try {
@ -143,6 +155,16 @@ class ArticleFragment : Fragment(), DIAware {
refreshAlignment()
val settings = requireActivity().getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
val api = SelfossApi(
// requireContext(),
// requireActivity(),
// settings.getBoolean("isSelfSignedCert", false),
// prefs.getString("api_timeout", "-1")!!.toLong()
apiDetailsService
)
fab = binding.fab
fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent)
@ -169,7 +191,8 @@ class ArticleFragment : Fragment(), DIAware {
R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item.id)
api.markAsRead(this@ArticleFragment.item.id.toString())
// TODO: Update in DB
}
this@ArticleFragment.item.unread = false
Toast.makeText(
@ -179,7 +202,8 @@ class ArticleFragment : Fragment(), DIAware {
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item.id)
api.unmarkAsRead(this@ArticleFragment.item.id.toString())
// TODO: Update in DB
}
this@ArticleFragment.item.unread = true
Toast.makeText(
@ -252,7 +276,10 @@ class ArticleFragment : Fragment(), DIAware {
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
) { _, _ ->
settings.putBoolean("prefer_article_viewer", false)
val sharedPref = PreferenceManager.getDefaultSharedPreferences(requireContext())
val editor = sharedPref.edit()
editor.putBoolean("prefer_article_viewer", false)
editor.apply()
requireActivity().finish()
}
.create()
@ -268,7 +295,7 @@ class ArticleFragment : Fragment(), DIAware {
}
private fun refreshAlignment() {
textAlignment = when (settings.getInt("text_align", 1)) {
textAlignment = when (prefs.getInt("text_align", 1)) {
1 -> "justify"
2 -> "left"
else -> "justify"

View File

@ -1,52 +1,48 @@
package bou.amine.apps.readerforselfossv2.service
package bou.amine.apps.readerforselfossv2.android.service
import com.russhwolf.settings.Settings
import io.github.aakira.napier.Napier
import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
class ApiDetailsServiceImpl : ApiDetailsService {
val settings: Settings = Settings()
class AndroidApiDetailsService(c: Context) : ApiDetailsService {
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
private var _apiVersion: Int = -1
private var _baseUrl: String = ""
private var _userName: String = ""
private var _password: String = ""
override fun logApiCalls(message: String) {
Napier.d(message, tag = "LogApiCalls")
Log.d("LogApiCalls", message)
}
override fun getApiVersion(): Int {
if (_apiVersion == -1) {
_apiVersion = settings.getInt("apiVersionMajor", -1)
return _apiVersion
_apiVersion = settings.getInt("apiVersionMajor", -1)!!
return settings.getInt("apiVersionMajor", -1)!!
}
return _apiVersion
}
override fun getBaseUrl(): String {
if (_baseUrl.isEmpty()) {
_baseUrl = settings.getString("url", "")
_baseUrl = settings.getString("url", "")!!
}
return _baseUrl
}
override fun getUserName(): String {
if (_userName.isEmpty()) {
_userName = settings.getString("login", "")
_userName = settings.getString("login", "")!!
}
return _userName
}
override fun getPassword(): String {
if (_password.isEmpty()) {
_password = settings.getString("password", "")
_password = settings.getString("password", "")!!
}
return _password
}
override fun refresh() {
_password = settings.getString("password", "")
_userName = settings.getString("login", "")
_baseUrl = settings.getString("url", "")
_apiVersion = settings.getInt("apiVersionMajor", -1)
}
}

View File

@ -17,7 +17,6 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBin
import bou.amine.apps.readerforselfossv2.android.themes.Toppings
import bou.amine.apps.readerforselfossv2.android.utils.Config
import com.ftinc.scoop.Scoop
import com.russhwolf.settings.Settings
import java.lang.NumberFormatException
private const val TITLE_TAG = "settingsActivityTitle"
@ -174,12 +173,14 @@ class SettingsActivity : AppCompatActivity(),
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.itemId
if (id == R.id.clear) {
val settings = Settings()
settings.remove("color_primary")
settings.remove("color_primary_dark")
settings.remove("color_accent")
settings.remove("color_accent_dark")
settings.remove("dark_theme")
val pref = PreferenceManager.getDefaultSharedPreferences(activity)
val editor = pref.edit()
editor.remove("color_primary")
editor.remove("color_primary_dark")
editor.remove("color_accent")
editor.remove("color_accent_dark")
editor.remove("dark_theme")
editor.apply()
requireActivity().recreate()
}
return super.onOptionsItemSelected(item)

View File

@ -2,8 +2,8 @@ package bou.amine.apps.readerforselfossv2.android.themes
import android.app.Activity
import androidx.annotation.ColorInt
import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.android.R
import com.russhwolf.settings.Settings
class AppColors(a: Activity) {
@ -16,30 +16,30 @@ class AppColors(a: Activity) {
val isDarkTheme: Boolean
init {
val settings = Settings()
val sharedPref = PreferenceManager.getDefaultSharedPreferences(a)
colorPrimary =
settings.getInt(
sharedPref.getInt(
"color_primary",
a.resources.getColor(R.color.colorPrimary)
)
colorPrimaryDark =
settings.getInt(
sharedPref.getInt(
"color_primary_dark",
a.resources.getColor(R.color.colorPrimaryDark)
)
colorAccent =
settings.getInt(
sharedPref.getInt(
"color_accent",
a.resources.getColor(R.color.colorAccent)
)
colorAccentDark =
settings.getInt(
sharedPref.getInt(
"color_accent_dark",
a.resources.getColor(R.color.colorAccentDark)
)
isDarkTheme =
settings.getBoolean(
sharedPref.getBoolean(
"dark_theme",
false
)

View File

@ -3,27 +3,28 @@ package bou.amine.apps.readerforselfossv2.android.utils
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.android.LoginActivity
import com.russhwolf.settings.Settings
class Config {
class Config(c: Context) {
val settings = Settings()
val settings: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(c)
val baseUrl: String
get() = settings.getString("url", "")
get() = settings.getString("url", "")!!
val userLogin: String
get() = settings.getString("login", "")
get() = settings.getString("login", "")!!
val userPassword: String
get() = settings.getString("password", "")
get() = settings.getString("password", "")!!
val httpUserLogin: String
get() = settings.getString("httpUserName", "")
get() = settings.getString("httpUserName", "")!!
val httpUserPassword: String
get() = settings.getString("httpPassword", "")
get() = settings.getString("httpPassword", "")!!
companion object {
const val settingsName = "paramsselfoss"
@ -46,10 +47,11 @@ class Config {
fun logoutAndRedirect(
c: Context,
callingActivity: Activity,
editor: SharedPreferences.Editor,
baseUrlFail: Boolean = false
): Boolean {
val settings = Settings()
settings.clear()
val settings = PreferenceManager.getDefaultSharedPreferences(c)
settings.edit().clear().commit()
val intent = Intent(c, LoginActivity::class.java)
if (baseUrlFail) {
intent.putExtra("baseUrlFail", baseUrlFail)

View File

@ -10,17 +10,18 @@ import android.net.Uri
import android.os.Build
import android.text.Spannable
import android.text.style.ClickableSpan
import androidx.browser.customtabs.CustomTabsIntent
import android.util.Patterns
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import android.widget.Toast
import androidx.browser.customtabs.CustomTabsIntent
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.ReaderActivity
import bou.amine.apps.readerforselfossv2.android.model.getLinkDecoded
import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.SearchService
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -78,10 +79,12 @@ fun Context.openItemUrlInternally(
linkDecoded: String,
customTabsIntent: CustomTabsIntent,
articleViewer: Boolean,
app: Activity
app: Activity,
searchService: SearchService
) {
if (articleViewer) {
ReaderActivity.allItems = allItems
searchService.position = currentItem
val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)
@ -120,7 +123,8 @@ fun Context.openItemUrl(
customTabsIntent: CustomTabsIntent,
internalBrowser: Boolean,
articleViewer: Boolean,
app: Activity
app: Activity,
searchService: SearchService
) {
if (!linkDecoded.isUrlValid()) {
@ -139,7 +143,8 @@ fun Context.openItemUrl(
linkDecoded,
customTabsIntent,
articleViewer,
app
app,
searchService
)
} else {
this.openItemUrlInternalBrowser(

View File

@ -1,13 +1,13 @@
package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context
import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.getUnsafeHttpClient
import com.bumptech.glide.Glide
import com.bumptech.glide.GlideBuilder
import com.bumptech.glide.Registry
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule
import com.russhwolf.settings.Settings
import java.io.InputStream
class SelfSignedGlideModule : GlideModule {
@ -18,8 +18,8 @@ class SelfSignedGlideModule : GlideModule {
override fun registerComponents(context: Context?, glide: Glide?, registry: Registry?) {
if (context != null) {
val settings = Settings()
if (settings.getBoolean("isSelfSignedCert", false)) {
val pref = context?.getSharedPreferences(Config.settingsName, Context.MODE_PRIVATE)
if (pref.getBoolean("isSelfSignedCert", false)) {
val client = getUnsafeHttpClient().build()
registry?.append(

View File

@ -27,15 +27,6 @@ kotlin {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("io.ktor:ktor-client-auth:2.0.1")
implementation("org.jsoup:jsoup:1.14.3")
//Dependency Injection
implementation("org.kodein.di:kodein-di:7.12.0")
//Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
//Logging
implementation("io.github.aakira:napier:2.6.1")
}
}
val commonTest by getting {

View File

@ -1,14 +0,0 @@
package bou.amine.apps.readerforselfossv2.DI
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.service.ApiDetailsServiceImpl
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.instance
import org.kodein.di.singleton
val networkModule by DI.Module {
bind<ApiDetailsService>() with singleton { ApiDetailsServiceImpl() }
bind<SelfossApi>() with singleton { SelfossApi(instance()) }
}

View File

@ -1,285 +0,0 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.ItemType
import com.russhwolf.settings.Settings
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) {
val settings = Settings()
var items = ArrayList<SelfossModel.Item>()
var baseUrl = apiDetails.getBaseUrl()
lateinit var dateUtils: DateUtils
var displayedItems = ItemType.UNREAD
var tagFilter: SelfossModel.Tag? = null
var sourceFilter: SelfossModel.Source? = null
var searchFilter: String? = null
var itemsCaching = settings.getBoolean("items_caching", false)
var apiMajorVersion = 0
var badgeUnread = 0
set(value) {field = if (value < 0) { 0 } else { value } }
var badgeAll = 0
set(value) {field = if (value < 0) { 0 } else { value } }
var badgeStarred = 0
set(value) {field = if (value < 0) { 0 } else { value } }
init {
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
CoroutineScope(Dispatchers.Main).launch {
updateApiVersion()
dateUtils = DateUtils(apiMajorVersion)
reloadBadges()
}
}
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Check connectivity, use the updatedSince parameter
val fetchedItems = api.getItems(displayedItems.type,
settings.getString("prefer_api_items_number", "200").toInt(),
offset = 0,
tagFilter?.tag,
sourceFilter?.id?.toLong(),
searchFilter,
null)
if (fetchedItems != null) {
items = ArrayList(fetchedItems)
}
return items
}
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
// TODO: Check connectivity
val offset = items.size
val fetchedItems = api.getItems(displayedItems.type,
settings.getString("prefer_api_items_number", "200").toInt(),
offset,
tagFilter?.tag,
sourceFilter?.id?.toLong(),
searchFilter,
null)
if (fetchedItems != null) {
appendItems(fetchedItems)
}
return items
}
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? =
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null)
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
// TODO: Store in DB if enabled by user
val fetchedIDS = fetchedItems.map { it.id }
val tmpItems = ArrayList(items.filterNot { it.id in fetchedIDS })
tmpItems.addAll(fetchedItems)
sortItems(tmpItems)
items = tmpItems
}
private fun sortItems(items: ArrayList<SelfossModel.Item>) {
items.sortByDescending { dateUtils.parseDate(it.datetime) }
}
suspend fun reloadBadges(): Boolean {
// TODO: Check connectivity, calculate from DB
var success = false
val response = api.stats()
if (response != null) {
badgeUnread = response.unread
badgeAll = response.total
badgeStarred = response.starred
success = true
}
return success
}
suspend fun getTags(): List<SelfossModel.Tag>? {
// TODO: Check success, store in DB
return api.tags()
}
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
// TODO: Check success, store in DB
return api.spouts()
}
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
// TODO: Check success
return api.sources()
}
suspend fun markAsRead(id: Int): Boolean {
// TODO: Check internet connection
val success = api.markAsRead(id.toString())?.isSuccess == true
if (success) {
markAsReadLocally(items.first {it.id == id})
}
return success
}
suspend fun unmarkAsRead(id: Int): Boolean {
// TODO: Check internet connection
val success = api.unmarkAsRead(id.toString())?.isSuccess == true
if (success) {
unmarkAsReadLocally(items.first {it.id == id})
}
return success
}
suspend fun starr(id: Int): Boolean {
// TODO: Check success, store in DB
val success = api.starr(id.toString())?.isSuccess == true
if (success) {
starrLocally(items.first {it.id == id})
}
return success
}
suspend fun unstarr(id: Int): Boolean {
// TODO: Check internet connection
val success = api.unstarr(id.toString())?.isSuccess == true
if (success) {
unstarrLocally(items.first {it.id == id})
}
return success
}
suspend fun markAllAsRead(ids: List<Int>): Boolean {
// TODO: Check Internet connectivity, store in DB
val success = api.markAllAsRead(ids.map { it.toString() })?.isSuccess == true
if (success) {
val itemsToMark = items.filter { it.id in ids }
for (item in itemsToMark) {
markAsReadLocally(item)
}
}
return success
}
private fun markAsReadLocally(item: SelfossModel.Item) {
// TODO: Mark also in the database
if (item.unread) {
item.unread = false
badgeUnread -= 1
}
}
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
// TODO: Mark also in the database
if (!item.unread) {
item.unread = true
badgeUnread += 1
}
}
private fun starrLocally(item: SelfossModel.Item) {
// TODO: Mark also in the database
if (!item.starred) {
item.starred = true
badgeStarred += 1
}
}
private fun unstarrLocally(item: SelfossModel.Item) {
// TODO: Mark also in the database
if (item.starred) {
item.starred = false
badgeStarred -= 1
}
}
suspend fun createSource(
title: String,
url: String,
spout: String,
tags: String,
filter: String
): Boolean {
// TODO: Check connectivity
val response = api.createSourceForVersion(
title,
url,
spout,
tags,
filter,
apiMajorVersion
)
return response != null
}
suspend fun deleteSource(id: Int): Boolean {
// TODO: Check connectivity, store in DB
var success = false
val response = api.deleteSource(id)
if (response != null) {
success = response.isSuccess
}
return success
}
suspend fun updateRemote(): Boolean {
// TODO: Handle connectivity issues
val response = api.update()
return response?.isSuccess ?: false
}
suspend fun login(): Boolean {
var result = false
try {
val response = api.login()
if (response != null && response.isSuccess) {
result = true
}
} catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote")
}
return result
}
fun refreshLoginInformation(url: String, login: String, password: String,
httpLogin: String, httpPassword: String,
isSelfSignedCert: Boolean) {
settings.putString("url", url)
settings.putString("login", login)
settings.putString("httpUserName", httpLogin)
settings.putString("password", password)
settings.putString("httpPassword", httpPassword)
settings.putBoolean("isSelfSignedCert", isSelfSignedCert)
baseUrl = url
api.refreshLoginInformation()
}
private suspend fun updateApiVersion() {
// TODO: Handle connectivity issues
val fetchedVersion = api.version()
if (fetchedVersion != null) {
apiMajorVersion = fetchedVersion.getApiMajorVersion()
settings.putInt("apiVersionMajor", apiMajorVersion)
} else {
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
}
}
// TODO: Handle offline actions
}

View File

@ -3,6 +3,10 @@ package bou.amine.apps.readerforselfossv2.rest
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
import io.ktor.client.engine.ProxyBuilder.http
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.plugins.cache.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
@ -14,51 +18,43 @@ import kotlinx.serialization.json.Json
class SelfossApi(private val apiDetailsService: ApiDetailsService) {
var client = createHttpClient()
private fun createHttpClient(): HttpClient {
return HttpClient {
install(ContentNegotiation) {
install(HttpCache)
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
apiDetailsService.logApiCalls(message)
}
}
level = LogLevel.ALL
}
/* TODO: Auth as basic
if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = apiDetailsService.getUserName(), password = apiDetailsService.getPassword())
}
sendWithoutRequest {
true
}
}
}
}*/
expectSuccess = false
private val client = HttpClient() {
install(ContentNegotiation) {
install(HttpCache)
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install(Logging) {
logger = object: Logger {
override fun log(message: String) {
apiDetailsService.logApiCalls(message)
}
}
level = LogLevel.ALL
}
/* TODO: Auth as basic
if (apiDetailsService.getUserName().isNotEmpty() && apiDetailsService.getPassword().isNotEmpty()) {
install(Auth) {
basic {
credentials {
BasicAuthCredentials(username = apiDetailsService.getUserName(), password = apiDetailsService.getPassword())
}
sendWithoutRequest {
true
}
}
}
}*/
expectSuccess = false
}
fun url(path: String) =
private fun url(path: String) =
"${apiDetailsService.getBaseUrl()}$path"
fun refreshLoginInformation() {
apiDetailsService.refresh()
client = createHttpClient()
}
suspend fun login(): SelfossModel.SuccessResponse? =
client.get(url("/login")) {
@ -70,10 +66,10 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
type: String,
items: Int,
offset: Int,
tag: String?,
source: Long?,
search: String?,
updatedSince: String?
tag: String? = "",
source: Long? = null,
search: String? = "",
updatedSince: String? = ""
): List<SelfossModel.Item>? =
client.get(url("/items")) {
parameter("username", apiDetailsService.getUserName())
@ -168,7 +164,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
createSource(title, url, spout, tags, filter)
}
suspend fun createSource(
private suspend fun createSource(
title: String,
url: String,
spout: String,
@ -186,7 +182,7 @@ class SelfossApi(private val apiDetailsService: ApiDetailsService) {
}
).body()
suspend fun createSource2(
private suspend fun createSource2(
title: String,
url: String,
spout: String,

View File

@ -6,5 +6,4 @@ interface ApiDetailsService {
fun getBaseUrl(): String
fun getUserName(): String
fun getPassword(): String
fun refresh()
}

View File

@ -0,0 +1,109 @@
package bou.amine.apps.readerforselfossv2.service
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import kotlinx.coroutines.*
class SelfossService<ItemEntity>(val api: SelfossApi, private val dbService: DeviceDataBaseService<ItemEntity>, private val searchService: SearchService) {
suspend fun getReadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) {
if (isNetworkAvailable) {
val apiItems = readItems( itemsNumber, offset)
// SAVE OR UPDATE IN DB
return@withContext apiItems
} else {
// GET FROM DB
return@withContext emptyList()
}
}
suspend fun getUnreadItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) {
if (isNetworkAvailable) {
val apiItems = newItems(itemsNumber, offset)
// SAVE OR UPDATE IN DB
return@withContext apiItems
} else {
// GET FROM DB
return@withContext emptyList()
}
}
suspend fun getStarredItems(itemsNumber: Int, offset: Int, isNetworkAvailable: Boolean): List<SelfossModel.Item>? = withContext(
Dispatchers.Default) {
if (isNetworkAvailable) {
val apiItems = starredItems(itemsNumber, offset)
// SAVE OR UPDATE IN DB
return@withContext apiItems
} else {
// GET FROM DB
return@withContext emptyList()
}
}
suspend fun readAll(ids: List<String>, isNetworkAvailable: Boolean): Boolean {
// Add ids params
var success = false
if (isNetworkAvailable) {
success = api.markAllAsRead(ids)?.isSuccess == true
// SAVE OR UPDATE IN DB
}
// refresh view
return success
}
suspend fun reloadBadges(isNetworkAvailable: Boolean) = withContext(Dispatchers.Default) {
if (isNetworkAvailable) {
try {
val response = api.stats()
if (response != null) {
searchService.badgeUnread = response.unread
searchService.badgeAll = response.total
searchService.badgeStarred = response.starred
}
} catch (e: Throwable) {}
} else {
dbService.computeBadges()
}
}
private fun enqueueArticles(response: List<SelfossModel.Item>?, clearDatabase: Boolean) {
if (response != null) {
if (clearDatabase) {
CoroutineScope(Dispatchers.Default).launch {
dbService.clearDBItems()
}
}
dbService.appendNewItems(response)
}
}
suspend fun allNewItems(): List<SelfossModel.Item>? =
readItems(200, 0)
suspend fun allReadItems(): List<SelfossModel.Item>? =
newItems(200, 0)
suspend fun allStarredItems(): List<SelfossModel.Item>? =
starredItems(200, 0)
private suspend fun readItems(
itemsNumber: Int,
offset: Int
): List<SelfossModel.Item>? =
api.getItems("read", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
private suspend fun newItems(
itemsNumber: Int,
offset: Int
): List<SelfossModel.Item>? =
api.getItems("unread", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
private suspend fun starredItems(
itemsNumber: Int,
offset: Int
): List<SelfossModel.Item>? =
api.getItems("starred", itemsNumber, offset, searchService.tagFilter, searchService.sourceIDFilter, searchService.searchFilter)
}

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
@ -14,12 +15,12 @@ fun SelfossModel.Item.parseDate(dateUtils: bou.amine.apps.readerforselfossv2.uti
fun SelfossModel.Item.parseRelativeDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): String =
dateUtils.parseRelativeDate(this.datetime)
class DateUtils(private val apiMajorVersion: Int) {
class DateUtils(private val apiDetailsService: ApiDetailsService) {
fun parseDate(dateString: String): Instant {
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
return if (apiMajorVersion >= 4) {
return if (apiDetailsService.getApiVersion() >= 4) {
OffsetDateTime.parse(dateString).toInstant()
} else {
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)

View File

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