Compare commits
12 Commits
b7b4ff1485
...
f7a29d66ca
Author | SHA1 | Date | |
---|---|---|---|
f7a29d66ca | |||
|
734b0b7112 | ||
|
dec620a409 | ||
|
4d29ee0b92 | ||
|
33333ca998 | ||
|
8d87eef0fc | ||
|
5a26513ed7 | ||
|
5b7f5225d8 | ||
|
03f53bf9c9 | ||
|
e06e6d580d | ||
|
63e8649512 | ||
|
6260c3fc06 |
13
.drone.yml
13
.drone.yml
@ -1,20 +1,21 @@
|
|||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
|
name: android
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build
|
|
||||||
image: mingc/android-build-box:latest
|
|
||||||
commands:
|
|
||||||
- ./gradlew build
|
|
||||||
|
|
||||||
- name: code-analysis
|
- name: code-analysis
|
||||||
image: mingc/android-build-box:latest
|
image: mingc/android-build-box:latest
|
||||||
failure: ignore
|
failure: ignore
|
||||||
commands:
|
commands:
|
||||||
- ls -la
|
- ls -la
|
||||||
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.sources=. -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
|
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
|
||||||
environment:
|
environment:
|
||||||
SONAR_HOST_URL:
|
SONAR_HOST_URL:
|
||||||
from_secret: sonarScannerHostUrl
|
from_secret: sonarScannerHostUrl
|
||||||
SONAR_LOGIN:
|
SONAR_LOGIN:
|
||||||
from_secret: sonarScannerLogin
|
from_secret: sonarScannerLogin
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
image: mingc/android-build-box:latest
|
||||||
|
commands:
|
||||||
|
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
|
@ -1,5 +1,7 @@
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
val ignoreGitVersion: String by project
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
kotlin("android")
|
kotlin("android")
|
||||||
@ -32,11 +34,19 @@ fun gitVersion(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun versionCodeFromGit(): Int {
|
fun versionCodeFromGit(): Int {
|
||||||
|
if (ignoreGitVersion == "true") {
|
||||||
|
// don't care
|
||||||
|
return 1
|
||||||
|
}
|
||||||
println("version code " + gitVersion())
|
println("version code " + gitVersion())
|
||||||
return gitVersion().toInt()
|
return gitVersion().toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun versionNameFromGit(): String {
|
fun versionNameFromGit(): String {
|
||||||
|
if (ignoreGitVersion == "true") {
|
||||||
|
// don't care
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
println("version name " + gitVersion())
|
println("version name " + gitVersion())
|
||||||
return gitVersion()
|
return gitVersion()
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// TODO
|
|
||||||
//package bou.amine.apps.readerforselfossv2.android.utils
|
|
||||||
//
|
|
||||||
//import bou.amine.apps.readerforselfossv2.android.utils.Config
|
|
||||||
//import bou.amine.apps.readerforselfossv2.android.utils.parseDate
|
|
||||||
//import org.junit.Test
|
|
||||||
//
|
|
||||||
//class DateUtilsTest {
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun parseDateV4() {
|
|
||||||
//
|
|
||||||
// Config.apiVersion = 4
|
|
||||||
// val dateString = "2013-04-07T13:43:00+01:00"
|
|
||||||
//
|
|
||||||
// val milliseconds = parseDate(dateString).toEpochMilli()
|
|
||||||
// val correctMilliseconds : Long = 1365338580000
|
|
||||||
//
|
|
||||||
// assert(milliseconds == correctMilliseconds)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Test
|
|
||||||
// fun parseDateV1() {
|
|
||||||
// Config.apiVersion = 0
|
|
||||||
// val dateString = "2013-04-07 13:43:00"
|
|
||||||
//
|
|
||||||
// val milliseconds = parseDate(dateString).toEpochMilli()
|
|
||||||
// val correctMilliseconds = 1365342180000
|
|
||||||
//
|
|
||||||
// assert(milliseconds == correctMilliseconds)
|
|
||||||
// }
|
|
||||||
//}
|
|
@ -1074,7 +1074,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
|
|
||||||
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) {
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val success = repository.markAllAsRead(items.map { it.id })
|
val success = repository.markAllAsRead(items)
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@HomeActivity,
|
this@HomeActivity,
|
||||||
@ -1153,10 +1153,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
|
|
||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
when {
|
when {
|
||||||
action.read -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action)
|
||||||
action.unread -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action)
|
||||||
action.starred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action)
|
||||||
action.unstarred -> doAndReportOnFail(repository.markAsRead(action.articleId.toInt()), action)
|
action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import bou.amine.apps.readerforselfossv2.DI.networkModule
|
|||||||
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
import bou.amine.apps.readerforselfossv2.android.utils.Config
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.NetworkStatus
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.ftinc.scoop.Scoop
|
import com.ftinc.scoop.Scoop
|
||||||
@ -25,7 +26,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
override val di by DI.lazy {
|
override val di by DI.lazy {
|
||||||
import(networkModule)
|
import(networkModule)
|
||||||
bind<Repository>() with singleton { Repository(instance(), instance()) }
|
bind<Repository>() with singleton { Repository(instance(), instance(), NetworkStatus(applicationContext)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var config: Config
|
private lateinit var config: Config
|
||||||
|
@ -112,7 +112,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
private fun readItem(item: SelfossModel.Item) {
|
private fun readItem(item: SelfossModel.Item) {
|
||||||
if (markOnScroll) {
|
if (markOnScroll) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.markAsRead(item.id)
|
repository.markAsRead(item)
|
||||||
// TODO: Handle failure
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,13 +207,13 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
R.id.star -> {
|
R.id.star -> {
|
||||||
if (allItems[binding.pager.currentItem].starred) {
|
if (allItems[binding.pager.currentItem].starred) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unstarr(allItems[binding.pager.currentItem].id)
|
repository.unstarr(allItems[binding.pager.currentItem])
|
||||||
// TODO: Handle failure
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
afterUnsave()
|
afterUnsave()
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.starr(allItems[binding.pager.currentItem].id)
|
repository.starr(allItems[binding.pager.currentItem])
|
||||||
// TODO: Handle failure
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
afterSave()
|
afterSave()
|
||||||
|
@ -113,14 +113,14 @@ class ItemCardAdapter(
|
|||||||
if (c.isNetworkAvailable()) {
|
if (c.isNetworkAvailable()) {
|
||||||
if (item.starred) {
|
if (item.starred) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unstarr(item.id)
|
repository.unstarr(item)
|
||||||
// TODO: Handle failure
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
item.starred = false
|
item.starred = false
|
||||||
binding.favButton.isSelected = false
|
binding.favButton.isSelected = false
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.starr(item.id)
|
repository.starr(item)
|
||||||
// TODO: Handle failure
|
// TODO: Handle failure
|
||||||
}
|
}
|
||||||
item.starred = true
|
item.starred = true
|
||||||
|
@ -79,7 +79,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
||||||
val i = items[position]
|
val i = items[position]
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.markAsRead(i.id)
|
repository.markAsRead(i)
|
||||||
}
|
}
|
||||||
if (repository.displayedItems == ItemType.UNREAD) {
|
if (repository.displayedItems == ItemType.UNREAD) {
|
||||||
items.remove(i)
|
items.remove(i)
|
||||||
@ -95,7 +95,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
|
|
||||||
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unmarkAsRead(items[position].id)
|
repository.unmarkAsRead(items[position])
|
||||||
// Todo: SharedItems.unreadItem(app, api, db, items[position])
|
// Todo: SharedItems.unreadItem(app, api, db, items[position])
|
||||||
// TODO: update db
|
// TODO: update db
|
||||||
|
|
||||||
|
@ -76,19 +76,19 @@ override fun doWork(): Result {
|
|||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
when {
|
when {
|
||||||
action.read -> doAndReportOnFail(
|
action.read -> doAndReportOnFail(
|
||||||
repository.markAsRead(action.articleId.toInt()),
|
repository.markAsReadById(action.articleId.toInt()),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.unread -> doAndReportOnFail(
|
action.unread -> doAndReportOnFail(
|
||||||
repository.unmarkAsRead(action.articleId.toInt()),
|
repository.unmarkAsReadById(action.articleId.toInt()),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.starred -> doAndReportOnFail(
|
action.starred -> doAndReportOnFail(
|
||||||
repository.starr(action.articleId.toInt()),
|
repository.starrById(action.articleId.toInt()),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
action.unstarred -> doAndReportOnFail(
|
action.unstarred -> doAndReportOnFail(
|
||||||
repository.unstarr(action.articleId.toInt()),
|
repository.unstarrById(action.articleId.toInt()),
|
||||||
action
|
action
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -169,7 +169,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
R.id.unread_action -> if (context != null) {
|
R.id.unread_action -> if (context != null) {
|
||||||
if (this@ArticleFragment.item.unread) {
|
if (this@ArticleFragment.item.unread) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.markAsRead(this@ArticleFragment.item.id)
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = false
|
this@ArticleFragment.item.unread = false
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
@ -179,7 +179,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
).show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.unmarkAsRead(this@ArticleFragment.item.id)
|
repository.unmarkAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = true
|
this@ArticleFragment.item.unread = true
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
@ -46,16 +46,16 @@ class AppColors(a: Activity) {
|
|||||||
|
|
||||||
colorBackground = if (isDarkTheme) {
|
colorBackground = if (isDarkTheme) {
|
||||||
a.setTheme(R.style.NoBarDark)
|
a.setTheme(R.style.NoBarDark)
|
||||||
R.color.darkBackground
|
a.resources.getColor(R.color.darkBackground)
|
||||||
} else {
|
} else {
|
||||||
a.setTheme(R.style.NoBar)
|
a.setTheme(R.style.NoBar)
|
||||||
R.color.grey_50
|
a.resources.getColor(R.color.grey_50)
|
||||||
}
|
}
|
||||||
|
|
||||||
textColor = if (isDarkTheme) {
|
textColor = if (isDarkTheme) {
|
||||||
R.color.white
|
a.resources.getColor(R.color.white)
|
||||||
} else {
|
} else {
|
||||||
R.color.grey_900
|
a.resources.getColor(R.color.grey_900)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,3 +18,5 @@ kotlin.native.enableDependencyPropagation=false
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||||
|
org.gradle.parallel=true
|
||||||
|
ignoreGitVersion=false
|
||||||
|
@ -36,6 +36,9 @@ kotlin {
|
|||||||
|
|
||||||
//Logging
|
//Logging
|
||||||
implementation("io.github.aakira:napier:2.6.1")
|
implementation("io.github.aakira:napier:2.6.1")
|
||||||
|
|
||||||
|
// Network information
|
||||||
|
implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
@ -86,4 +89,8 @@ android {
|
|||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 31
|
targetSdk = 31
|
||||||
}
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
actual class Platform actual constructor() {
|
|
||||||
actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
|
|
||||||
}
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.text.format.DateUtils
|
||||||
|
import java.time.Instant
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.OffsetDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
actual class DateUtils actual constructor(private val apiMajorVersion: Int) {
|
||||||
|
actual fun parseDate(dateString: String): Long {
|
||||||
|
|
||||||
|
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||||
|
|
||||||
|
return if (apiMajorVersion >= 4) {
|
||||||
|
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
|
||||||
|
} else {
|
||||||
|
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
|
||||||
|
ZoneOffset.UTC).toEpochMilli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun parseRelativeDate(dateString: String): String {
|
||||||
|
|
||||||
|
val date = parseDate(dateString)
|
||||||
|
|
||||||
|
return " " + DateUtils.getRelativeTimeSpanString(
|
||||||
|
date,
|
||||||
|
Instant.now().toEpochMilli(),
|
||||||
|
DateUtils.MINUTE_IN_MILLIS,
|
||||||
|
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.github.`ln-12`.library.ConnectivityStatus
|
||||||
|
|
||||||
|
actual class NetworkStatus(context: Context) {
|
||||||
|
private val connectivityStatus = ConnectivityStatus(context)
|
||||||
|
actual val current = connectivityStatus.isNetworkConnected
|
||||||
|
|
||||||
|
actual fun start() {
|
||||||
|
connectivityStatus.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun stop() {
|
||||||
|
connectivityStatus.stop()
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
import org.junit.Assert.assertTrue
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class AndroidGreetingTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testExample() {
|
|
||||||
assertTrue("Check Android is mentioned", Greeting().greeting().contains("Android"))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
class Greeting {
|
|
||||||
fun greeting(): String {
|
|
||||||
return "Hello, ${Platform().platform}!"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
expect class Platform() {
|
|
||||||
val platform: String
|
|
||||||
}
|
|
@ -5,16 +5,18 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
|||||||
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.NetworkStatus
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) {
|
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, networkStatus: NetworkStatus) {
|
||||||
val settings = Settings()
|
val settings = Settings()
|
||||||
|
|
||||||
var items = ArrayList<SelfossModel.Item>()
|
var items = ArrayList<SelfossModel.Item>()
|
||||||
|
private val isConnectionAvailable = networkStatus.current
|
||||||
|
|
||||||
var baseUrl = apiDetails.getBaseUrl()
|
var baseUrl = apiDetails.getBaseUrl()
|
||||||
lateinit var dateUtils: DateUtils
|
lateinit var dateUtils: DateUtils
|
||||||
@ -36,6 +38,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
networkStatus.start()
|
||||||
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
|
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
updateApiVersion()
|
updateApiVersion()
|
||||||
@ -45,40 +48,65 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
// TODO: Check connectivity, use the updatedSince parameter
|
// TODO: Use the updatedSince parameter
|
||||||
val fetchedItems = api.getItems(displayedItems.type,
|
if (isConnectionAvailable.value) {
|
||||||
|
val fetchedItems = api.getItems(
|
||||||
|
displayedItems.type,
|
||||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||||
offset = 0,
|
offset = 0,
|
||||||
tagFilter?.tag,
|
tagFilter?.tag,
|
||||||
sourceFilter?.id?.toLong(),
|
sourceFilter?.id?.toLong(),
|
||||||
searchFilter,
|
searchFilter,
|
||||||
null)
|
null
|
||||||
|
)
|
||||||
|
|
||||||
if (fetchedItems != null) {
|
if (fetchedItems != null) {
|
||||||
items = ArrayList(fetchedItems)
|
items = ArrayList(fetchedItems)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Provide an error message if the connection is not available.
|
||||||
|
// TODO: Get items from the database
|
||||||
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
|
||||||
// TODO: Check connectivity
|
if (isConnectionAvailable.value) {
|
||||||
val offset = items.size
|
val offset = items.size
|
||||||
val fetchedItems = api.getItems(displayedItems.type,
|
val fetchedItems = api.getItems(
|
||||||
|
displayedItems.type,
|
||||||
settings.getString("prefer_api_items_number", "200").toInt(),
|
settings.getString("prefer_api_items_number", "200").toInt(),
|
||||||
offset,
|
offset,
|
||||||
tagFilter?.tag,
|
tagFilter?.tag,
|
||||||
sourceFilter?.id?.toLong(),
|
sourceFilter?.id?.toLong(),
|
||||||
searchFilter,
|
searchFilter,
|
||||||
null)
|
null
|
||||||
|
)
|
||||||
|
|
||||||
if (fetchedItems != null) {
|
if (fetchedItems != null) {
|
||||||
appendItems(fetchedItems)
|
appendItems(fetchedItems)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Provide an error message
|
||||||
|
}
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? =
|
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? {
|
||||||
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null)
|
return if (isConnectionAvailable.value) {
|
||||||
|
api.getItems(
|
||||||
|
itemType.type,
|
||||||
|
200,
|
||||||
|
0,
|
||||||
|
tagFilter?.tag,
|
||||||
|
sourceFilter?.id?.toLong(),
|
||||||
|
searchFilter,
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
|
private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
|
||||||
// TODO: Store in DB if enabled by user
|
// TODO: Store in DB if enabled by user
|
||||||
@ -94,8 +122,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reloadBadges(): Boolean {
|
suspend fun reloadBadges(): Boolean {
|
||||||
// TODO: Check connectivity, calculate from DB
|
|
||||||
var success = false
|
var success = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
val response = api.stats()
|
val response = api.stats()
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
badgeUnread = response.unread
|
badgeUnread = response.unread
|
||||||
@ -103,72 +131,121 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
badgeStarred = response.starred
|
badgeStarred = response.starred
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Compute badges from database
|
||||||
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getTags(): List<SelfossModel.Tag>? {
|
suspend fun getTags(): List<SelfossModel.Tag>? {
|
||||||
// TODO: Check success, store in DB
|
// TODO: Store in DB
|
||||||
return api.tags()
|
return if (isConnectionAvailable.value) {
|
||||||
|
api.tags()
|
||||||
|
} else {
|
||||||
|
// TODO: Compute from database
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
|
||||||
// TODO: Check success, store in DB
|
// TODO: Store in DB
|
||||||
return api.spouts()
|
return if (isConnectionAvailable.value) {
|
||||||
|
api.spouts()
|
||||||
|
} else {
|
||||||
|
// TODO: Compute from database
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
suspend fun getSources(): ArrayList<SelfossModel.Source>? {
|
||||||
// TODO: Check success
|
// TODO: Store in DB
|
||||||
return api.sources()
|
return if (isConnectionAvailable.value) {
|
||||||
|
api.sources()
|
||||||
|
} else {
|
||||||
|
// TODO: Compute from database
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAsRead(id: Int): Boolean {
|
suspend fun markAsRead(item: SelfossModel.Item): Boolean {
|
||||||
|
val success = markAsReadById(item.id)
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
markAsReadLocally(item)
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun markAsReadById(id: Int): Boolean {
|
||||||
|
var success = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
|
success = api.markAsRead(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||||
// TODO: Check internet connection
|
// TODO: Check internet connection
|
||||||
val success = api.markAsRead(id.toString())?.isSuccess == true
|
val success = unmarkAsReadById(item.id)
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
markAsReadLocally(items.first {it.id == id})
|
unmarkAsReadLocally(item)
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unmarkAsRead(id: Int): Boolean {
|
suspend fun unmarkAsReadById(id: Int): Boolean {
|
||||||
// TODO: Check internet connection
|
// TODO: Check internet connection
|
||||||
val success = api.unmarkAsRead(id.toString())?.isSuccess == true
|
var success = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
if (success) {
|
success = api.unmarkAsRead(id.toString())?.isSuccess == true
|
||||||
unmarkAsReadLocally(items.first {it.id == id})
|
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun starr(id: Int): Boolean {
|
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||||
// TODO: Check success, store in DB
|
val success = starrById(item.id)
|
||||||
val success = api.starr(id.toString())?.isSuccess == true
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
starrLocally(items.first {it.id == id})
|
starrLocally(item)
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unstarr(id: Int): Boolean {
|
suspend fun starrById(id: Int): Boolean {
|
||||||
// TODO: Check internet connection
|
var success = false
|
||||||
val success = api.unstarr(id.toString())?.isSuccess == true
|
if (isConnectionAvailable.value) {
|
||||||
|
success = api.starr(id.toString())?.isSuccess == true
|
||||||
if (success) {
|
|
||||||
unstarrLocally(items.first {it.id == id})
|
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markAllAsRead(ids: List<Int>): Boolean {
|
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||||
// TODO: Check Internet connectivity, store in DB
|
val success = unstarrById(item.id)
|
||||||
|
|
||||||
val success = api.markAllAsRead(ids.map { it.toString() })?.isSuccess == true
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
val itemsToMark = items.filter { it.id in ids }
|
unstarrLocally(item)
|
||||||
for (item in itemsToMark) {
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun unstarrById(id: Int): Boolean {
|
||||||
|
var success = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
|
success = api.unstarr(id.toString())?.isSuccess == true
|
||||||
|
}
|
||||||
|
return success
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
|
var success = false
|
||||||
|
if (isConnectionAvailable) {
|
||||||
|
success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
for (item in items) {
|
||||||
markAsReadLocally(item)
|
markAsReadLocally(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,45 +291,51 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
tags: String,
|
tags: String,
|
||||||
filter: String
|
filter: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
// TODO: Check connectivity
|
var response = false
|
||||||
val response = api.createSourceForVersion(
|
if (isConnectionAvailable.value) {
|
||||||
|
response = api.createSourceForVersion(
|
||||||
title,
|
title,
|
||||||
url,
|
url,
|
||||||
spout,
|
spout,
|
||||||
tags,
|
tags,
|
||||||
filter,
|
filter,
|
||||||
apiMajorVersion
|
apiMajorVersion
|
||||||
)
|
)?.isSuccess == true
|
||||||
|
}
|
||||||
|
|
||||||
return response != null
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteSource(id: Int): Boolean {
|
suspend fun deleteSource(id: Int): Boolean {
|
||||||
// TODO: Check connectivity, store in DB
|
// TODO: Store in DB
|
||||||
var success = false
|
var success = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
val response = api.deleteSource(id)
|
val response = api.deleteSource(id)
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
success = response.isSuccess
|
success = response.isSuccess
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemote(): Boolean {
|
suspend fun updateRemote(): Boolean {
|
||||||
// TODO: Handle connectivity issues
|
var response = false
|
||||||
val response = api.update()
|
if (isConnectionAvailable.value) {
|
||||||
return response?.isSuccess ?: false
|
response = api.update()?.isSuccess == true
|
||||||
|
}
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun login(): Boolean {
|
suspend fun login(): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
try {
|
try {
|
||||||
val response = api.login()
|
val response = api.login()
|
||||||
if (response != null && response.isSuccess) {
|
result = response?.isSuccess == true
|
||||||
result = true
|
|
||||||
}
|
|
||||||
} catch (cause: Throwable) {
|
} catch (cause: Throwable) {
|
||||||
Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote")
|
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@ -271,13 +354,14 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateApiVersion() {
|
private suspend fun updateApiVersion() {
|
||||||
// TODO: Handle connectivity issues
|
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
||||||
|
|
||||||
|
if (isConnectionAvailable.value) {
|
||||||
val fetchedVersion = api.version()
|
val fetchedVersion = api.version()
|
||||||
if (fetchedVersion != null) {
|
if (fetchedVersion != null) {
|
||||||
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
apiMajorVersion = fetchedVersion.getApiMajorVersion()
|
||||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||||
} else {
|
}
|
||||||
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.text.Html
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
@ -13,7 +11,7 @@ class SelfossModel {
|
|||||||
val unread: Int
|
val unread: Int
|
||||||
) {
|
) {
|
||||||
fun getTitleDecoded(): String {
|
fun getTitleDecoded(): String {
|
||||||
return Html.fromHtml(tag).toString()
|
return tag // TODO Html.fromHtml(tag).toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,42 +1,16 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
//import android.text.format.DateUtils
|
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
import bou.amine.apps.readerforselfossv2.rest.SelfossModel
|
||||||
import java.time.Instant
|
|
||||||
import java.time.LocalDateTime
|
|
||||||
import java.time.OffsetDateTime
|
|
||||||
import java.time.ZoneOffset
|
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
fun SelfossModel.Item.parseDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): Instant =
|
|
||||||
|
fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long =
|
||||||
dateUtils.parseDate(this.datetime)
|
dateUtils.parseDate(this.datetime)
|
||||||
|
|
||||||
fun SelfossModel.Item.parseRelativeDate(dateUtils: bou.amine.apps.readerforselfossv2.utils.DateUtils): String =
|
fun SelfossModel.Item.parseRelativeDate(dateUtils: DateUtils): String =
|
||||||
dateUtils.parseRelativeDate(this.datetime)
|
dateUtils.parseRelativeDate(this.datetime)
|
||||||
|
|
||||||
class DateUtils(private val apiMajorVersion: Int) {
|
expect class DateUtils(apiMajorVersion: Int) {
|
||||||
fun parseDate(dateString: String): Instant {
|
fun parseDate(dateString: String): Long
|
||||||
|
|
||||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
fun parseRelativeDate(dateString: String): String
|
||||||
|
|
||||||
return if (apiMajorVersion >= 4) {
|
|
||||||
OffsetDateTime.parse(dateString).toInstant()
|
|
||||||
} else {
|
|
||||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(ZoneOffset.UTC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun parseRelativeDate(dateString: String): String {
|
|
||||||
|
|
||||||
val date = parseDate(dateString)
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// return " " + DateUtils.getRelativeTimeSpanString(
|
|
||||||
// date.toEpochMilli(),
|
|
||||||
// Instant.now().toEpochMilli(),
|
|
||||||
// 60000L, // DateUtils.MINUTE_IN_MILLIS,
|
|
||||||
// 262144 // DateUtils.FORMAT_ABBREV_RELATIVE
|
|
||||||
// )
|
|
||||||
return dateString
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
|
expect class NetworkStatus {
|
||||||
|
val current: MutableStateFlow<Boolean>
|
||||||
|
fun start()
|
||||||
|
fun stop()
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertTrue
|
|
||||||
|
|
||||||
class CommonGreetingTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testExample() {
|
|
||||||
assertTrue(Greeting().greeting().contains("Hello"), "Check 'Hello' is mentioned")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2
|
|
||||||
|
|
||||||
import platform.UIKit.UIDevice
|
|
||||||
|
|
||||||
actual class Platform actual constructor() {
|
|
||||||
actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
|
|
||||||
}
|
|
@ -0,0 +1,27 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import com.github.`ln-12`.library.ConnectivityStatus
|
||||||
|
import kotlinx.coroutines.MainScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
actual class NetworkStatus {
|
||||||
|
private val connectivityStatus: ConnectivityStatus = ConnectivityStatus()
|
||||||
|
actual val current: MutableStateFlow<Boolean> = connectivityStatus.isNetworkConnected
|
||||||
|
|
||||||
|
actual fun start() {
|
||||||
|
connectivityStatus.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun stop() {
|
||||||
|
connectivityStatus.stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStatus(success: (Boolean) -> Unit) {
|
||||||
|
MainScope().launch {
|
||||||
|
connectivityStatus.isNetworkConnected.collect { status ->
|
||||||
|
success(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user