Compare commits
	
		
			17 Commits
		
	
	
		
			v122123531
			...
			417a33eb25
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 417a33eb25 | ||
|  | 2e7f7f23b3 | ||
|  | e5e182761e | ||
|  | a094d88799 | ||
| e51915d1cd | |||
| 3a654f6ede | |||
| 5227751dca | |||
|  | 27eafe4ff4 | ||
|  | 8c83a9408b | ||
|  | fe2410f719 | ||
|  | a5e86bfb77 | ||
|  | 23be633798 | ||
|  | 813e0707d8 | ||
|  | 9ed9bf07fc | ||
|  | 47265c10d0 | ||
|  | 5cc633246a | ||
|  | 1f40385786 | 
| @@ -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: | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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?) { | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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(), | ||||
|   | ||||
| @@ -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(), | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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") | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|   | ||||
| @@ -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,21 +51,64 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|                 false | ||||
|             ) | ||||
|  | ||||
|         val context: Context? = context | ||||
|  | ||||
|         val tagGroup = binding.tagsGroup | ||||
|         val sourceGroup = binding.sourcesGroup | ||||
|  | ||||
|         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) | ||||
|                     val c = Chip(context) | ||||
|                     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 | ||||
|                     } | ||||
|  | ||||
|                     tagGroup.addView(c) | ||||
|                 } | ||||
|  | ||||
|                 repository.getSources().forEach { source -> | ||||
|                 val c = Chip(requireContext()) | ||||
|                     val c = Chip(context) | ||||
|  | ||||
|                 Glide.with(requireContext()) | ||||
|                     Glide.with(context) | ||||
|                         .load(source.getIcon(repository.baseUrl)) | ||||
|                         .listener(object : RequestListener<Drawable?> { | ||||
|                             override fun onLoadFailed( | ||||
| @@ -124,6 +163,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | ||||
|                 binding.progressBar2.visibility = GONE | ||||
|                 binding.filterView.visibility = VISIBLE | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         binding.floatingActionButton2.setOnClickListener { | ||||
|             (activity as HomeActivity).getElementsAccordingToTab() | ||||
| @@ -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" | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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" | ||||
| } | ||||
| @@ -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") } | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -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> { | ||||
|   | ||||
| @@ -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) | ||||
|     } | ||||
| } | ||||
| @@ -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 | ||||
|     ) | ||||
| @@ -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; | ||||
| @@ -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` = ?; | ||||
		Reference in New Issue
	
	Block a user