Compare commits

...

17 Commits

Author SHA1 Message Date
417a33eb25 Merge pull request 'Running migrations.' (#118) from fix-migration into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/118
2022-12-28 14:49:11 +00:00
2e7f7f23b3 No duplicate builds for PRs.
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-28 15:34:22 +01:00
e5e182761e Running migrations.
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/pr Build was killed
2022-12-28 15:27:17 +01:00
a094d88799 Merge pull request 'Make the author field nullable' (#117) from davidoskky/ReaderForSelfoss-multiplatform:author into master
Some checks reported errors
continuous-integration/drone/push Build was killed
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/117
2022-12-28 14:25:36 +00:00
e51915d1cd Include author field when updating the database
All checks were successful
continuous-integration/drone/pr Build is passing
2022-12-28 14:25:56 +01:00
3a654f6ede Migrate the database table 2022-12-28 14:25:34 +01:00
5227751dca Make the author field nullable
All checks were successful
continuous-integration/drone/pr Build is passing
2022-12-28 11:02:43 +01:00
27eafe4ff4 Delete sources from DB and reload items on source deletion.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-26 22:27:28 +01:00
8c83a9408b Drone should work better.
Some checks reported errors
continuous-integration/drone/push Build was killed
2022-12-26 22:26:28 +01:00
fe2410f719 Handling author field.
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
2022-12-26 21:49:55 +01:00
a5e86bfb77 Date format issues.
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is failing
2022-12-26 15:02:19 +01:00
23be633798 Add api version to the reports.
Some checks are pending
continuous-integration/drone/push Build is running
2022-12-25 22:45:12 +01:00
813e0707d8 Date format issue.
Some checks are pending
continuous-integration/drone/push Build is running
2022-12-25 22:41:34 +01:00
9ed9bf07fc Items in repository.
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-23 22:53:16 +01:00
47265c10d0 Trying nexus build.
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-23 14:59:58 +01:00
5cc633246a Debugging images issues.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-22 20:28:49 +01:00
1f40385786 Context should not be null, but handle the case for now.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2022-12-19 22:08:28 +01:00
22 changed files with 231 additions and 160 deletions

View File

@ -17,6 +17,7 @@ steps:
- echo "---------------------------------------------------------"
- ./gradlew koverMergedXmlReport
environment:
TZ: Europe/Paris
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
@ -34,7 +35,6 @@ steps:
trigger:
event:
- push
- pull_request
---
kind: pipeline
@ -50,6 +50,7 @@ steps:
- git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- git push pushing --tags
environment:
TZ: Europe/Paris
GITEA_USR:
from_secret: giteaUsr
GITEA_PASS:
@ -75,10 +76,7 @@ steps:
from_secret: privateKey
command_timeout: 2m
script:
- cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
- cd /home/ubuntu && sudo rm -rf /var/www/amine/version.txt && sudo chown www-data:www-data ./version.txt && sudo mv version.txt /var/www/amine/
trigger:
event:
@ -117,6 +115,7 @@ steps:
- echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment:
TZ: Europe/Paris
YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass
YOUR_KEY_ALIAS:

View File

@ -23,6 +23,7 @@ import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
@ -129,6 +130,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiVersion()
ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
}
val intent = Intent(this, HomeActivity::class.java)
startActivity(intent)

View File

@ -3,10 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.widget.ImageView
import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
@ -18,8 +15,6 @@ import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.github.ln_12.library.ConnectivityStatus
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
@ -83,6 +78,8 @@ class MyApp : MultiDexApplication(), DIAware {
}
}
}
repository.migrate(driverFactory)
}
override fun attachBaseContext(base: Context?) {

View File

@ -30,6 +30,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivityReaderBinding
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
override val di by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
@ -61,12 +63,14 @@ class ReaderActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
if (allItems.isEmpty()) {
currentItem = intent.getIntExtra("currentItem", 0)
allItems = repository.getReaderItems()
if (allItems.isEmpty() || currentItem > allItems.size) {
finish()
}
currentItem = intent.getIntExtra("currentItem", 0)
readItem(allItems[currentItem])
binding.pager.adapter = ScreenSlidePagerAdapter(this)
@ -214,8 +218,4 @@ class ReaderActivity : AppCompatActivity(), DIAware {
startActivity(intent)
overridePendingTransition(0, 0)
}
companion object {
var allItems: ArrayList<SelfossModel.Item> = ArrayList()
}
}

View File

@ -62,7 +62,7 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight
@ -132,8 +132,8 @@ class ItemCardAdapter(
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),

View File

@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
@ -84,8 +84,8 @@ class ItemListAdapter(
private fun handleLinkOpening() {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),

View File

@ -78,9 +78,9 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id) = items[bindingAdapterPosition]
val (id, title) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) {
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)

View File

@ -101,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText()
contentSource = item.sourceAuthorAndDate()
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()
@ -347,7 +347,7 @@ class ArticleFragment : Fragment(), DIAware {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
} catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg")
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > jpeg > $url")
}
}
else if (url.lowercase(Locale.US).contains(".png")) {
@ -355,7 +355,7 @@ class ArticleFragment : Fragment(), DIAware {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
} catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png")
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > png > $url")
}
}
else if (url.lowercase(Locale.US).contains(".webp")) {
@ -363,7 +363,7 @@ class ArticleFragment : Fragment(), DIAware {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
} catch ( e : ExecutionException) {
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp")
e.sendSilentlyWithAcraWithName("shouldInterceptRequest > webp > $url")
}
}

View File

@ -1,6 +1,6 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
@ -13,9 +13,7 @@ import android.view.ViewGroup
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide
@ -38,16 +36,14 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
override val di: DI by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private var selectedChip: Chip? = null
@SuppressLint("ResourceAsColor")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
): View {
val binding =
bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding.inflate(
inflater,
@ -55,74 +51,118 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
false
)
val context: Context? = context
val tagGroup = binding.tagsGroup
val sourceGroup = binding.sourcesGroup
CoroutineScope(Dispatchers.Main).launch {
val tags = repository.getTags()
if (context == null) {
dismiss()
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
} else {
CoroutineScope(Dispatchers.Main).launch {
val tags = repository.getTags()
tags.forEach { tag ->
val c = chipForTag(tag)
tagGroup.addView(c)
}
tags.forEach { tag ->
val c = Chip(context)
c.text = tag.tag
repository.getSources().forEach { source ->
val c = Chip(requireContext())
Glide.with(requireContext())
.load(source.getIcon(repository.baseUrl))
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
c.chipIcon = resource
return false
}
}).preload()
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setSourceFilter(source)
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
repository.setTagFilter(null)
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
tagGroup.addView(c)
}
repository.getSources().forEach { source ->
val c = Chip(context)
if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true
selectedChip = c
Glide.with(context)
.load(source.getIcon(repository.baseUrl))
.listener(object : RequestListener<Drawable?> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable?>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable?>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
c.chipIcon = resource
return false
}
}).preload()
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setSourceFilter(source)
repository.setTagFilter(null)
}
if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
sourceGroup.addView(c)
}
sourceGroup.addView(c)
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
}
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
}
binding.floatingActionButton2.setOnClickListener {
@ -133,49 +173,8 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
return binding.root
}
private fun chipForTag(tag: SelfossModel.Tag): Chip {
val c = Chip(requireContext())
c.text = tag.tag
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(tag.color)
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
return c
}
companion object {
const val TAG = "ModalBottomSheet"
const val TAG = "FilterModalBottomSheet"
}

View File

@ -16,7 +16,8 @@ fun SelfossModel.Item.toParcelable() : ParecelableItem =
this.icon,
this.link,
this.sourcetitle,
this.tags.joinToString(",")
this.tags.joinToString(","),
this.author
)
fun ParecelableItem.toModel() : SelfossModel.Item =
SelfossModel.Item(
@ -30,7 +31,8 @@ fun ParecelableItem.toModel() : SelfossModel.Item =
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
this.tags.split(","),
this.author
)
data class ParecelableItem(
val id: Int,
@ -43,7 +45,8 @@ data class ParecelableItem(
val icon: String?,
val link: String,
val sourcetitle: String,
val tags: String
val tags: String,
val author: String?
) : Parcelable {
companion object {
@ -65,7 +68,8 @@ data class ParecelableItem(
icon = source.readString(),
link = source.readString().orEmpty(),
sourcetitle = source.readString().orEmpty(),
tags = source.readString().orEmpty()
tags = source.readString().orEmpty(),
author = source.readString().orEmpty()
)
override fun describeContents() = 0
@ -82,5 +86,6 @@ data class ParecelableItem(
dest.writeString(link)
dest.writeString(sourcetitle)
dest.writeString(tags)
dest.writeString(author)
}
}

View File

@ -18,7 +18,6 @@ import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.openItemUrl(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int,
linkDecoded: String,
articleViewer: Boolean,
@ -33,7 +32,6 @@ fun Context.openItemUrl(
).show()
} else {
if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem)
app.startActivity(intent)

View File

@ -11,11 +11,14 @@ class DatesTest {
private val v3Date = "2013-04-07T13:43:00+01:00"
private val v4Date = "2013-04-07 13:43:00"
private val bug1Date = "2022-12-24T17:00:08+00"
@Test
fun v3_date_should_be_parsed() {
val date = DateUtils.parseDate(v3Date)
val expected = LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.of("UTC+1")) .toEpochMilliseconds()
val expected =
LocalDateTime(2013, 4, 7, 14, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
@ -30,4 +33,14 @@ class DatesTest {
assertEquals(date, expected)
}
@Test
fun bug1_date_should_be_parsed() {
val date = DateUtils.parseDate(bug1Date)
val expected =
LocalDateTime(2022, 12, 24, 18, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
}

View File

@ -58,6 +58,7 @@ class RepositoryTest {
data = SelfossModel.Stats(NUMBER_ARTICLES, NUMBER_UNREAD, NUMBER_STARRED)
)
every { db.itemsQueries.deleteItemsWhereSource(any()) } returns Unit
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
every { db.tagsQueries.deleteAllTags() } returns Unit
every { db.tagsQueries.transaction(any(), any()) } returns Unit
@ -798,10 +799,11 @@ class RepositoryTest {
initializeRepository()
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
response = repository.deleteSource(5, "src")
}
coVerify(exactly = 1) { api.deleteSource(5) }
coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(true, response)
}
@ -812,10 +814,11 @@ class RepositoryTest {
initializeRepository()
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
response = repository.deleteSource(5, "src")
}
coVerify(exactly = 1) { api.deleteSource(5) }
coVerify(exactly = 0) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(false, response)
}
@ -826,10 +829,11 @@ class RepositoryTest {
initializeRepository(MutableStateFlow(false))
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
response = repository.deleteSource(5, "src")
}
coVerify(exactly = 0) { api.deleteSource(5) }
coVerify(exactly = 1) { db.itemsQueries.deleteItemsWhereSource("src") }
assertSame(false, response)
}

View File

@ -17,7 +17,8 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags
tags = item.tags,
author = item.author
)
)
}
@ -35,7 +36,8 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags.split(',')
tags = item.tags.split(','),
author = item.author
)
)
}
@ -54,4 +56,5 @@ class FakeItemParameters {
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing"
var author = "Someone important"
}

View File

@ -17,10 +17,12 @@ plugins {
allprojects {
repositories {
google()
mavenCentral()
jcenter()
maven { url = uri("https://www.jitpack.io") }
maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
// IMPORTANT : Add back when new library added
// google()
// mavenCentral()
// jcenter()
// maven { url = uri("https://www.jitpack.io") }
}
}

View File

@ -2,16 +2,20 @@ val pushCache: String by settings
pluginManagement {
repositories {
google()
gradlePluginPortal()
mavenCentral()
maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
// IMPORTANT : Add back when new plugin added
// google()
// gradlePluginPortal()
// mavenCentral()
}
}
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven { url = uri("https://nexus.amine-louveau.fr/repository/maven-public/")}
// IMPORTANT : Add back when new library added
// google()
// mavenCentral()
}
}

View File

@ -10,7 +10,11 @@ actual class DateUtils {
return try {
Instant.parse(dateString).toEpochMilliseconds()
} catch (e: Exception) {
LocalDateTime.parse(dateString.replace(" ", "T")).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
var str = dateString.replace(" ", "T")
if (str.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{2}".toRegex())) {
str = str.split("+")[0]
}
LocalDateTime.parse(str).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
}
}

View File

@ -57,7 +57,6 @@ class SelfossModel {
val error: String,
val icon: String?
)
@Serializable
data class Item(
val id: Int,
@ -73,7 +72,8 @@ class SelfossModel {
val link: String,
val sourcetitle: String,
@Serializable(with = TagsListSerializer::class)
val tags: List<String>
val tags: List<String>,
val author: String?
) {
// TODO: maybe find a better way to handle these kind of urls
fun getLinkDecoded(): String {
@ -102,8 +102,14 @@ class SelfossModel {
return stringUrl
}
fun sourceAndDateText(): String =
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
fun sourceAuthorAndDate(): String {
var txt = this.sourcetitle.getHtmlDecoded()
if (!this.author.isNullOrBlank()) {
txt += " (by ${this.author}) "
}
txt += DateUtils.parseRelativeDate(this.datetime)
return txt
}
fun toggleStar(): Item {
this.starred = !this.starred
@ -111,6 +117,7 @@ class SelfossModel {
}
}
// TODO: this seems to be super slow.
object TagsListSerializer : KSerializer<List<String>> {
override fun deserialize(decoder: Decoder): List<String> {

View File

@ -47,6 +47,8 @@ class Repository(
private var fetchedSources = false
private var fetchedTags = false
private var _readerItems = ArrayList<SelfossModel.Item>()
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
@ -357,13 +359,20 @@ class Repository(
return response
}
suspend fun deleteSource(id: Int): Boolean {
suspend fun deleteSource(id: Int, title: String): Boolean {
var success = false
if (isNetworkAvailable()) {
val response = api.deleteSource(id)
success = response.isSuccess
}
// We filter on success or if the network isn't available
if (success || !isNetworkAvailable()) {
items = ArrayList(items.filter { it.sourcetitle != title })
setReaderItems(items)
db.itemsQueries.deleteItemsWhereSource(title)
}
return success
}
@ -502,6 +511,7 @@ class Repository(
item.link,
item.sourcetitle,
item.tags.joinToString(","),
item.author,
item.id.toString()
)
@ -558,4 +568,16 @@ class Repository(
fun setSourceFilter(source: SelfossModel.Source?) {
_sourceFilter.value = source
}
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
_readerItems = readerItems
}
fun getReaderItems(): ArrayList<SelfossModel.Item> {
return _readerItems
}
fun migrate(driverFactory: DriverFactory) {
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
}
}

View File

@ -51,7 +51,8 @@ fun ITEM.toView(): SelfossModel.Item =
this.icon,
this.link,
this.sourcetitle,
this.tags.split(",")
this.tags.split(","),
this.author
)
fun SelfossModel.Item.toEntity(): ITEM =
@ -66,5 +67,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
this.icon,
this.link,
this.sourcetitle.getHtmlDecoded(),
this.tags.joinToString(",")
this.tags.joinToString(","),
this.author
)

View File

@ -0,0 +1,6 @@
CREATE TABLE ITEM_BACKUP AS SELECT `id`, `datetime`, `title`, `content`,
`unread`, `starred`, `thumbnail`, `icon`, `link`, `sourcetitle`,
`tags` FROM ITEM;
ALTER TABLE ITEM_BACKUP ADD COLUMN `author` TEXT;
DROP TABLE ITEM;
ALTER TABLE ITEM_BACKUP RENAME TO ITEM;

View File

@ -10,6 +10,7 @@ CREATE TABLE ITEM (
`link` TEXT NOT NULL,
`sourcetitle` TEXT NOT NULL,
`tags` TEXT NOT NULL,
`author` TEXT,
PRIMARY KEY(`id`)
);
@ -26,5 +27,8 @@ INSERT OR REPLACE INTO ITEM VALUES ?;
deleteItem:
DELETE FROM ITEM WHERE `id` = ?;
deleteItemsWhereSource:
DELETE FROM ITEM WHERE `sourcetitle` = ?;
updateItem:
UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ? WHERE `id` = ?;
UPDATE ITEM SET `datetime` = ?, `title` = ?, `content` = ?, `unread` = ?, `starred` = ?, `thumbnail` = ?, `icon` = ?, `link` = ?, `sourcetitle` = ?, `tags` = ?, `author` = ? WHERE `id` = ?;