Compare commits
	
		
			12 Commits
		
	
	
		
			ad88011454
			...
			v123030751
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 08f79cb148 | ||
| e21906e70d | |||
|  | 9d2cc32bc9 | ||
|  | d9d057c8dc | ||
|  | 1f3fa0c4a6 | ||
|  | dea3def385 | ||
|  | f72ef2f5d4 | ||
|  | f28cb759df | ||
|  | b9d69c3e64 | ||
|  | c2a1c9eaac | ||
|  | bf37209a15 | ||
|  | 2c558fe6fd | 
							
								
								
									
										33
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										33
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,36 @@ | |||||||
|  | **v123030681** | ||||||
|  |  | ||||||
|  | - fix: Unread and starred can be null. | ||||||
|  | - Fixed version number issue. | ||||||
|  | - Changelog for v123030621 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | **v123030621** | ||||||
|  |  | ||||||
|  | - fix: url required issue. | ||||||
|  | - fix: Canvas reused issue. | ||||||
|  | - Changelog for v123020572 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | **v123020572** | ||||||
|  |  | ||||||
|  | - fix: requirecontext issues ? | ||||||
|  | - debug: activity not found exception. | ||||||
|  | - Changelog for v123020571 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
|  | **v123020571** | ||||||
|  |  | ||||||
|  | - chore: remove errors logging. | ||||||
|  | - fix: quickfix for url param not provided for some sources. | ||||||
|  | - Update 'CHANGELOG.md' | ||||||
|  | - Changelog for v123020523 [CI SKIP] | ||||||
|  |  | ||||||
|  | -------------------------------------------------------------------- | ||||||
|  |  | ||||||
| **v123020523** | **v123020523** | ||||||
|  |  | ||||||
| - fix: Git changelog. | - fix: Git changelog. | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ fun gitVersion(): String { | |||||||
|     val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) |     val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) | ||||||
|     process = if (maybeTagOfCurrentCommit.isEmpty()) { |     process = if (maybeTagOfCurrentCommit.isEmpty()) { | ||||||
|         println("No tag on current commit. Will take the latest one.") |         println("No tag on current commit. Will take the latest one.") | ||||||
|         execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1") |         execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1") | ||||||
|     } else { |     } else { | ||||||
|         println("Tag found on current commit") |         println("Tag found on current commit") | ||||||
|         execWithOutput("git -C ../ describe --contains HEAD") |         execWithOutput("git -C ../ describe --contains HEAD") | ||||||
| @@ -145,8 +145,8 @@ dependencies { | |||||||
|     implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1") |     implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1") | ||||||
|  |  | ||||||
|     // glide |     // glide | ||||||
|     kapt("com.github.bumptech.glide:compiler:4.14.2") |     kapt("com.github.bumptech.glide:compiler:4.15.0") | ||||||
|     implementation("com.github.bumptech.glide:okhttp3-integration:4.14.2") |     implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0") | ||||||
|  |  | ||||||
|     // Themes |     // Themes | ||||||
|     implementation("com.github.rubensousa:floatingtoolbar:1.5.1") |     implementation("com.github.rubensousa:floatingtoolbar:1.5.1") | ||||||
|   | |||||||
| @@ -49,13 +49,13 @@ class SourcesActivity : AppCompatActivity(), DIAware { | |||||||
|         super.onResume() |         super.onResume() | ||||||
|         val mLayoutManager = LinearLayoutManager(this) |         val mLayoutManager = LinearLayoutManager(this) | ||||||
|  |  | ||||||
|         var items: ArrayList<SelfossModel.Source> |         var items: ArrayList<SelfossModel.SourceDetail> | ||||||
|  |  | ||||||
|         binding.recyclerView.setHasFixedSize(true) |         binding.recyclerView.setHasFixedSize(true) | ||||||
|         binding.recyclerView.layoutManager = mLayoutManager |         binding.recyclerView.layoutManager = mLayoutManager | ||||||
|  |  | ||||||
|         CoroutineScope(Dispatchers.Main).launch { |         CoroutineScope(Dispatchers.Main).launch { | ||||||
|             val response = repository.getSources() |             val response = repository.getSourcesDetails() | ||||||
|             if (response.isNotEmpty()) { |             if (response.isNotEmpty()) { | ||||||
|                 items = response |                 items = response | ||||||
|                 val mAdapter = SourcesListAdapter( |                 val mAdapter = SourcesListAdapter( | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ import org.kodein.di.instance | |||||||
|  |  | ||||||
| class UpsertSourceActivity : AppCompatActivity(), DIAware { | class UpsertSourceActivity : AppCompatActivity(), DIAware { | ||||||
|  |  | ||||||
|     private var existingSource: SelfossModel.Source? = null |     private var existingSource: SelfossModel.SourceDetail? = null | ||||||
|     private var mSpoutsValue: String? = null |     private var mSpoutsValue: String? = null | ||||||
|  |  | ||||||
|     private lateinit var binding: ActivityUpsertSourceBinding |     private lateinit var binding: ActivityUpsertSourceBinding | ||||||
| @@ -68,7 +68,7 @@ class UpsertSourceActivity : AppCompatActivity(), DIAware { | |||||||
|  |  | ||||||
|     private fun initFields(items: Map<String, SelfossModel.Spout>) { |     private fun initFields(items: Map<String, SelfossModel.Spout>) { | ||||||
|         binding.nameInput.setText(existingSource!!.title) |         binding.nameInput.setText(existingSource!!.title) | ||||||
|         binding.tags.setText(existingSource!!.tags.joinToString(", ")) |         binding.tags.setText(existingSource!!.tags?.joinToString(", ")) | ||||||
|         binding.sourceUri.setText(existingSource!!.params?.url) |         binding.sourceUri.setText(existingSource!!.params?.url) | ||||||
|         binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout)) |         binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout)) | ||||||
|         binding.progress.visibility = View.GONE |         binding.progress.visibility = View.GONE | ||||||
|   | |||||||
| @@ -31,7 +31,7 @@ import org.kodein.di.instance | |||||||
|  |  | ||||||
| class SourcesListAdapter( | class SourcesListAdapter( | ||||||
|     private val app: Activity, |     private val app: Activity, | ||||||
|     private val items: ArrayList<SelfossModel.Source> |     private val items: ArrayList<SelfossModel.SourceDetail> | ||||||
| ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware { | ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware { | ||||||
|     private val c: Context = app.baseContext |     private val c: Context = app.baseContext | ||||||
|     private val generator: ColorGenerator = ColorGenerator.MATERIAL |     private val generator: ColorGenerator = ColorGenerator.MATERIAL | ||||||
| @@ -61,7 +61,7 @@ class SourcesListAdapter( | |||||||
|             c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) |             c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (itm.error.isNotBlank()) { |         if (!itm.error.isNullOrBlank()) { | ||||||
|             binding.errorText.visibility = View.VISIBLE |             binding.errorText.visibility = View.VISIBLE | ||||||
|             binding.errorText.text = itm.error |             binding.errorText.text = itm.error | ||||||
|         } else { |         } else { | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| package bou.amine.apps.readerforselfossv2.android.fragments | package bou.amine.apps.readerforselfossv2.android.fragments | ||||||
|  |  | ||||||
|  | import android.content.ActivityNotFoundException | ||||||
| import android.content.Intent | import android.content.Intent | ||||||
| import android.content.res.ColorStateList | import android.content.res.ColorStateList | ||||||
| import android.content.res.TypedArray | import android.content.res.TypedArray | ||||||
| @@ -150,6 +151,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|  |  | ||||||
|         } catch (e: InflateException) { |         } catch (e: InflateException) { | ||||||
|             e.sendSilentlyWithAcraWithName("webview not available") |             e.sendSilentlyWithAcraWithName("webview not available") | ||||||
|  |             if (context != null) { | ||||||
|                 AlertDialog.Builder(requireContext()) |                 AlertDialog.Builder(requireContext()) | ||||||
|                     .setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) |                     .setMessage(requireContext().getString(R.string.webview_dialog_issue_message)) | ||||||
|                     .setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) |                     .setTitle(requireContext().getString(R.string.webview_dialog_issue_title)) | ||||||
| @@ -162,6 +164,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|                     .create() |                     .create() | ||||||
|                     .show() |                     .show() | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return binding.root |         return binding.root | ||||||
|     } |     } | ||||||
| @@ -304,10 +307,16 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|         binding.webcontent.webViewClient = object : WebViewClient() { |         binding.webcontent.webViewClient = object : WebViewClient() { | ||||||
|             @Deprecated("Deprecated in Java") |             @Deprecated("Deprecated in Java") | ||||||
|             override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { |             override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { | ||||||
|                 if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { |                 return if (context != null && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { | ||||||
|  |                     try { | ||||||
|                         requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) |                         requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) | ||||||
|  |                     } catch (e: ActivityNotFoundException) { | ||||||
|  |                         e.sendSilentlyWithAcraWithName("activityNotFound > $url") | ||||||
|  |                     } | ||||||
|  |                     true | ||||||
|  |                 } else { | ||||||
|  |                     false | ||||||
|                 } |                 } | ||||||
|                 return true |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             @Deprecated("Deprecated in Java") |             @Deprecated("Deprecated in Java") | ||||||
| @@ -359,7 +368,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun htmlToWebview() { |     private fun htmlToWebview() { | ||||||
|  |         if (context != null) { | ||||||
|             val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) |             val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) | ||||||
|             val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) |             val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) | ||||||
|  |  | ||||||
| @@ -397,7 +406,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|                 val itemUrl = URL(url) |                 val itemUrl = URL(url) | ||||||
|                 baseUrl = itemUrl.protocol + "://" + itemUrl.host |                 baseUrl = itemUrl.protocol + "://" + itemUrl.host | ||||||
|             } catch (e: MalformedURLException) { |             } catch (e: MalformedURLException) { | ||||||
|             e.sendSilentlyWithAcraWithName("htmlToWebview > item url") |                 e.sendSilentlyWithAcraWithName("htmlToWebview > $url") | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             val fontName = when (font) { |             val fontName = when (font) { | ||||||
| @@ -500,6 +509,7 @@ class ArticleFragment : Fragment(), DIAware { | |||||||
|                 null |                 null | ||||||
|             ) |             ) | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fun scrollDown() { |     fun scrollDown() { | ||||||
|         val height = binding.nestedScrollView.measuredHeight |         val height = binding.nestedScrollView.measuredHeight | ||||||
|   | |||||||
| @@ -20,10 +20,8 @@ import bou.amine.apps.readerforselfossv2.repository.Repository | |||||||
| import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | ||||||
| import bou.amine.apps.readerforselfossv2.utils.getIcon | import bou.amine.apps.readerforselfossv2.utils.getIcon | ||||||
| import com.bumptech.glide.Glide | import com.bumptech.glide.Glide | ||||||
| import com.bumptech.glide.load.DataSource | import com.bumptech.glide.request.target.ViewTarget | ||||||
| import com.bumptech.glide.load.engine.GlideException | import com.bumptech.glide.request.transition.Transition | ||||||
| import com.bumptech.glide.request.RequestListener |  | ||||||
| import com.bumptech.glide.request.target.Target |  | ||||||
| import com.google.android.material.bottomsheet.BottomSheetDialogFragment | import com.google.android.material.bottomsheet.BottomSheetDialogFragment | ||||||
| import com.google.android.material.chip.Chip | import com.google.android.material.chip.Chip | ||||||
| import kotlinx.coroutines.CoroutineScope | import kotlinx.coroutines.CoroutineScope | ||||||
| @@ -84,37 +82,25 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | |||||||
|     ) { |     ) { | ||||||
|         val sourceGroup = binding.sourcesGroup |         val sourceGroup = binding.sourcesGroup | ||||||
|  |  | ||||||
|         repository.getSources().forEach { source -> |         repository.getSourcesDetailsOrStats().forEach { source -> | ||||||
|             val c = Chip(context) |             val c = Chip(context) | ||||||
|             c.ellipsize = TextUtils.TruncateAt.END |             c.ellipsize = TextUtils.TruncateAt.END | ||||||
|  |  | ||||||
|             Glide.with(context) |             Glide.with(context) | ||||||
|                 .load(source.getIcon(repository.baseUrl)) |                 .load(source.getIcon(repository.baseUrl)) | ||||||
|                 .listener(object : RequestListener<Drawable?> { |                 .into(object : ViewTarget<Chip?, Drawable?>(c) { | ||||||
|                     override fun onLoadFailed( |  | ||||||
|                         e: GlideException?, |  | ||||||
|                         model: Any?, |  | ||||||
|                         target: Target<Drawable?>?, |  | ||||||
|                         isFirstResource: Boolean |  | ||||||
|                     ): Boolean { |  | ||||||
|                         return false |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     override fun onResourceReady( |                     override fun onResourceReady( | ||||||
|                         resource: Drawable?, |                         resource: Drawable, | ||||||
|                         model: Any?, |                         transition: Transition<in Drawable?>? | ||||||
|                         target: Target<Drawable?>?, |                     ) { | ||||||
|                         dataSource: DataSource?, |  | ||||||
|                         isFirstResource: Boolean |  | ||||||
|                     ): Boolean { |  | ||||||
|                         try { |                         try { | ||||||
|                             c.chipIcon = resource |                             c.chipIcon = resource | ||||||
|                         } catch (e: Exception) { |                         } catch (e: Exception) { | ||||||
|                             e.sendSilentlyWithAcraWithName("sources > onResourceReady") |                             e.sendSilentlyWithAcraWithName("sources > onResourceReady") | ||||||
|                         } |                         } | ||||||
|                         return false |  | ||||||
|                     } |                     } | ||||||
|                 }).preload() |  | ||||||
|  |                 }) | ||||||
|  |  | ||||||
|             c.text = source.title.getHtmlDecoded() |             c.text = source.title.getHtmlDecoded() | ||||||
|  |  | ||||||
| @@ -141,9 +127,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware { | |||||||
|                 selectedChip = c |                 selectedChip = c | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             c.isEnabled = source.error.isBlank() |             c.isEnabled = source.error.isNullOrBlank() | ||||||
|  |  | ||||||
|             if (source.error.isNotBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { |             if (!source.error.isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||||
|                 c.tooltipText = source.error |                 c.tooltipText = source.error | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -300,9 +300,10 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
|  |  | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         repository.setSourceFilter(SelfossModel.Source( |         repository.setSourceFilter(SelfossModel.SourceDetail( | ||||||
|             1, |             1, | ||||||
|             "Test", |             "Test", | ||||||
|  |             null, | ||||||
|             listOf("tags"), |             listOf("tags"), | ||||||
|             SPOUT, |             SPOUT, | ||||||
|             "", |             "", | ||||||
| @@ -609,30 +610,32 @@ class RepositoryTest { | |||||||
|     fun get_sources() { |     fun get_sources() { | ||||||
|         val (sources, sourcesDB) = prepareSources() |         val (sources, sourcesDB) = prepareSources() | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertSame(sources, testSources) |         assertEquals(sources, testSources) | ||||||
|         assertNotEquals(sourcesDB.map { it.toView() }, testSources) |         assertNotEquals(sourcesDB.map { it.toView() }, testSources) | ||||||
|         coVerify(exactly = 1) { api.sources() } |         coVerify(exactly = 1) { api.sourcesDetailed() } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun prepareSources(): Pair<ArrayList<SelfossModel.Source>, List<SOURCE>> { |     private fun prepareSources(): Pair<ArrayList<SelfossModel.SourceDetail>, List<SOURCE>> { | ||||||
|         val sources = arrayListOf( |         val sources = arrayListOf( | ||||||
|             SelfossModel.Source( |             SelfossModel.SourceDetail( | ||||||
|                 1, |                 1, | ||||||
|                 "First source", |                 "First source", | ||||||
|  |                 null, | ||||||
|                 listOf("Test", "second"), |                 listOf("Test", "second"), | ||||||
|                 SPOUT, |                 SPOUT, | ||||||
|                 "", |                 "", | ||||||
|                 IMAGE_URL_2, |                 IMAGE_URL_2, | ||||||
|                 SelfossModel.SourceParams("url") |                 SelfossModel.SourceParams("url") | ||||||
|             ), |             ), | ||||||
|             SelfossModel.Source( |             SelfossModel.SourceDetail( | ||||||
|                 2, |                 2, | ||||||
|                 "Second source", |                 "Second source", | ||||||
|  |                 null, | ||||||
|                 listOf("second"), |                 listOf("second"), | ||||||
|                 SPOUT, |                 SPOUT, | ||||||
|                 "", |                 "", | ||||||
| @@ -661,7 +664,7 @@ class RepositoryTest { | |||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         coEvery { api.sources() } returns StatusAndData(success = true, data = sources) |         coEvery { api.sourcesDetailed() } returns StatusAndData(success = true, data = sources) | ||||||
|         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB |         every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB | ||||||
|         return Pair(sources, sourcesDB) |         return Pair(sources, sourcesDB) | ||||||
|     } |     } | ||||||
| @@ -675,13 +678,13 @@ class RepositoryTest { | |||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source>? | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|             // Sources will be fetched from the database on the second call, thus testSources != sources |             // Sources will be fetched from the database on the second call, thus testSources != sources | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         coVerify(exactly = 1) { api.sources() } |         coVerify(exactly = 1) { api.sourcesDetailed() } | ||||||
|         assertNotSame(sources, testSources) |         assertNotEquals(sources, testSources) | ||||||
|         assertEquals(sourcesDB.map { it.toView() }, testSources) |         assertEquals(sourcesDB.map { it.toView() }, testSources) | ||||||
|         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } |         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } | ||||||
|     } |     } | ||||||
| @@ -693,13 +696,13 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertSame(sources, testSources) |         assertEquals(sources, testSources) | ||||||
|         coVerify(exactly = 1) { api.sources() } |         coVerify(exactly = 1) { api.sourcesDetailed() } | ||||||
|         verify(exactly = 0) { db.sourcesQueries } |         verify(exactly = 0) { db.sourcesQueries } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -710,13 +713,13 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         initializeRepository() |         initializeRepository() | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertSame(sources, testSources) |         assertEquals(sources, testSources) | ||||||
|         coVerify(exactly = 1) { api.sources() } |         coVerify(exactly = 1) { api.sourcesDetailed() } | ||||||
|         verify(atLeast = 1) { db.sourcesQueries } |         verify(atLeast = 1) { db.sourcesQueries } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -724,13 +727,13 @@ class RepositoryTest { | |||||||
|     fun get_sources_without_connection() { |     fun get_sources_without_connection() { | ||||||
|         val (_, sourcesDB) = prepareSources() |         val (_, sourcesDB) = prepareSources() | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertEquals(sourcesDB.map { it.toView() }, testSources) |         assertEquals(sourcesDB.map { it.toView() }, testSources) | ||||||
|         coVerify(exactly = 0) { api.sources() } |         coVerify(exactly = 0) { api.sourcesDetailed() } | ||||||
|         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } |         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -741,13 +744,13 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true |         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertEquals(emptyList<SelfossModel.Source>(), testSources) |         assertEquals(emptyList<SelfossModel.Source>(), testSources) | ||||||
|         coVerify(exactly = 0) { api.sources() } |         coVerify(exactly = 0) { api.sourcesDetailed() } | ||||||
|         verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() } |         verify(exactly = 0) { db.sourcesQueries.sources().executeAsList() } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -758,13 +761,13 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isItemCachingEnabled() } returns true |         every { appSettingsService.isItemCachingEnabled() } returns true | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertEquals(sourcesDB.map { it.toView() }, testSources) |         assertEquals(sourcesDB.map { it.toView() }, testSources) | ||||||
|         coVerify(exactly = 0) { api.sources() } |         coVerify(exactly = 0) { api.sourcesDetailed() } | ||||||
|         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } |         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -775,13 +778,13 @@ class RepositoryTest { | |||||||
|         every { appSettingsService.isItemCachingEnabled() } returns false |         every { appSettingsService.isItemCachingEnabled() } returns false | ||||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false |         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||||
|         initializeRepository(MutableStateFlow(false)) |         initializeRepository(MutableStateFlow(false)) | ||||||
|         var testSources: List<SelfossModel.Source>? |         var testSources: List<SelfossModel.Source> | ||||||
|         runBlocking { |         runBlocking { | ||||||
|             testSources = repository.getSources() |             testSources = repository.getSourcesDetails() | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         assertEquals(sourcesDB.map { it.toView() }, testSources) |         assertEquals(sourcesDB.map { it.toView() }, testSources) | ||||||
|         coVerify(exactly = 0) { api.sources() } |         coVerify(exactly = 0) { api.sourcesDetailed() } | ||||||
|         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } |         verify(atLeast = 1) { db.sourcesQueries.sources().executeAsList() } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -1102,9 +1105,10 @@ class RepositoryTest { | |||||||
|     private fun prepareSearch() { |     private fun prepareSearch() { | ||||||
|         repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0)) |         repository.setTagFilter(SelfossModel.Tag("Tag", "read", 0)) | ||||||
|         repository.setSourceFilter( |         repository.setSourceFilter( | ||||||
|             SelfossModel.Source( |             SelfossModel.SourceDetail( | ||||||
|                 1, |                 1, | ||||||
|                 "First source", |                 "First source", | ||||||
|  |                 5, | ||||||
|                 listOf("Test", "second"), |                 listOf("Test", "second"), | ||||||
|                 SPOUT, |                 SPOUT, | ||||||
|                 "", |                 "", | ||||||
|   | |||||||
| @@ -24,8 +24,8 @@ class SelfossModel { | |||||||
|     @Serializable |     @Serializable | ||||||
|     class Stats( |     class Stats( | ||||||
|         val total: Int, |         val total: Int, | ||||||
|         val unread: Int, |         val unread: Int?, | ||||||
|         val starred: Int |         val starred: Int? | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
| @@ -63,20 +63,39 @@ class SelfossModel { | |||||||
|         fun isPublicModeEnabled() = publicMode ?: false |         fun isPublicModeEnabled() = publicMode ?: false | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     interface Source { | ||||||
|  |         val id: Int | ||||||
|  |         var title: String | ||||||
|  |         var unread: Int? | ||||||
|  |         var error: String? | ||||||
|  |         var icon: String? | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
|     data class Source( |     data class SourceStats( | ||||||
|         val id: Int, |         override val id: Int, | ||||||
|         val title: String, |         override var title: String, | ||||||
|  |         override var unread: Int?, | ||||||
|  |         override var error: String? = null, | ||||||
|  |         override var icon: String? = null | ||||||
|  |         ) : Source | ||||||
|  |  | ||||||
|  |     @Serializable | ||||||
|  |     data class SourceDetail( | ||||||
|  |         override val id: Int, | ||||||
|  |         override var title: String, | ||||||
|  |         override var unread: Int? = null, | ||||||
|         @Serializable(with = TagsListSerializer::class) |         @Serializable(with = TagsListSerializer::class) | ||||||
|         val tags: List<String>, |         var tags: List<String>?, | ||||||
|         val spout: String, |         var spout: String?, | ||||||
|         val error: String, |         override var error: String?, | ||||||
|         val icon: String?, |         override var icon: String?, | ||||||
|         val params: SourceParams? |         var params: SourceParams? | ||||||
|     ) |     ) : Source | ||||||
|  |  | ||||||
|     @Serializable |     @Serializable | ||||||
|     data class SourceParams( |     data class SourceParams( | ||||||
|         val url: String? |         val url: String? = null | ||||||
|     ) |     ) | ||||||
|     @Serializable |     @Serializable | ||||||
|     data class Item( |     data class Item( | ||||||
|   | |||||||
| @@ -44,11 +44,11 @@ class Repository( | |||||||
|     private val _badgeStarred = MutableStateFlow(0) |     private val _badgeStarred = MutableStateFlow(0) | ||||||
|     val badgeStarred = _badgeStarred.asStateFlow() |     val badgeStarred = _badgeStarred.asStateFlow() | ||||||
|  |  | ||||||
|     private var fetchedSources = false |  | ||||||
|     private var fetchedTags = false |     private var fetchedTags = false | ||||||
|  |     private var fetchedSources = false | ||||||
|  |  | ||||||
|     private var _readerItems = ArrayList<SelfossModel.Item>() |     private var _readerItems = ArrayList<SelfossModel.Item>() | ||||||
|     private var _selectedSource: SelfossModel.Source? = null |     private var _selectedSource: SelfossModel.SourceDetail? = null | ||||||
|  |  | ||||||
|     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { |     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { | ||||||
|         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() |         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() | ||||||
| @@ -132,9 +132,9 @@ class Repository( | |||||||
|         if (isNetworkAvailable()) { |         if (isNetworkAvailable()) { | ||||||
|             val response = api.stats() |             val response = api.stats() | ||||||
|             if (response.success && response.data != null) { |             if (response.success && response.data != null) { | ||||||
|                 _badgeUnread.value = response.data.unread |                 _badgeUnread.value = response.data.unread ?: 0 | ||||||
|                 _badgeAll.value = response.data.total |                 _badgeAll.value = response.data.total | ||||||
|                 _badgeStarred.value = response.data.starred |                 _badgeStarred.value = response.data.starred ?: 0 | ||||||
|                 success = true |                 success = true | ||||||
|             } |             } | ||||||
|         } else if (appSettingsService.isItemCachingEnabled()) { |         } else if (appSettingsService.isItemCachingEnabled()) { | ||||||
| @@ -180,23 +180,46 @@ class Repository( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     suspend fun getSources(): ArrayList<SelfossModel.Source> { |     suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> { | ||||||
|  |         var sources = ArrayList<SelfossModel.Source>() | ||||||
|         val isDatabaseEnabled = |         val isDatabaseEnabled = | ||||||
|             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() |             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||||
|         return if (isNetworkAvailable() && !fetchedSources) { |         val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true | ||||||
|             val apiSources = api.sources() |         if (shouldFetch && isNetworkAvailable()) { | ||||||
|             if (apiSources.success && apiSources.data != null && isDatabaseEnabled) { |             if (appSettingsService.getPublicAccess()) { | ||||||
|                 resetDBSourcesWithData(apiSources.data) |                 val apiSources = api.sourcesStats() | ||||||
|                 if (!appSettingsService.isUpdateSourcesEnabled()) { |                 if (apiSources.success && apiSources.data != null) { | ||||||
|                     fetchedSources = true |                     fetchedSources = true | ||||||
|  |                     sources = apiSources.data as ArrayList<SelfossModel.Source> | ||||||
|                 } |                 } | ||||||
|             } |  | ||||||
|             apiSources.data ?: ArrayList() |  | ||||||
|         } else if (isDatabaseEnabled) { |  | ||||||
|             ArrayList(getDBSources().map { it.toView() }) |  | ||||||
|             } else { |             } else { | ||||||
|             ArrayList() |                 sources = getSourcesDetails() as ArrayList<SelfossModel.Source> | ||||||
|             } |             } | ||||||
|  |         } else if (isDatabaseEnabled) { | ||||||
|  |             sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.Source> | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return sources | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     suspend fun getSourcesDetails(): ArrayList<SelfossModel.SourceDetail> { | ||||||
|  |         var sources = ArrayList<SelfossModel.SourceDetail>() | ||||||
|  |         val isDatabaseEnabled = | ||||||
|  |             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||||
|  |         val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true | ||||||
|  |         if (shouldFetch && isNetworkAvailable()) { | ||||||
|  |             val apiSources = api.sourcesDetailed() | ||||||
|  |             if (apiSources.success && apiSources.data != null) { | ||||||
|  |                 fetchedSources = true | ||||||
|  |                 sources = apiSources.data | ||||||
|  |                 if (isDatabaseEnabled) { | ||||||
|  |                     resetDBSourcesWithData(sources) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } else if (isDatabaseEnabled) { | ||||||
|  |             sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail> | ||||||
|  |         } | ||||||
|  |         return sources | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     suspend fun markAsRead(item: SelfossModel.Item): Boolean { |     suspend fun markAsRead(item: SelfossModel.Item): Boolean { | ||||||
| @@ -482,7 +505,7 @@ class Repository( | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private fun resetDBSourcesWithData(sources: List<SelfossModel.Source>) { |     private fun resetDBSourcesWithData(sources: List<SelfossModel.SourceDetail>) { | ||||||
|         db.sourcesQueries.deleteAllSources() |         db.sourcesQueries.deleteAllSources() | ||||||
|  |  | ||||||
|         db.sourcesQueries.transaction { |         db.sourcesQueries.transaction { | ||||||
| @@ -592,7 +615,7 @@ class Repository( | |||||||
|         ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) |         ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun setSelectedSource(source: SelfossModel.Source) { |     fun setSelectedSource(source: SelfossModel.SourceDetail) { | ||||||
|         _selectedSource = source |         _selectedSource = source | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -600,7 +623,7 @@ class Repository( | |||||||
|         _selectedSource = null |         _selectedSource = null | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fun getSelectedSource(): SelfossModel.Source? { |     fun getSelectedSource(): SelfossModel.SourceDetail? { | ||||||
|         return _selectedSource |         return _selectedSource | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -183,7 +183,15 @@ class SelfossApi(private val appSettingsService: AppSettingsService) { | |||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|  |  | ||||||
|     suspend fun sources(): StatusAndData<ArrayList<SelfossModel.Source>> = |     suspend fun sourcesStats(): StatusAndData<ArrayList<SelfossModel.SourceStats>> = | ||||||
|  |         bodyOrFailure(client.tryToGet(url("/sources/stats")) { | ||||||
|  |             if (!shouldHavePostLogin()) { | ||||||
|  |                 parameter("username", appSettingsService.getUserName()) | ||||||
|  |                 parameter("password", appSettingsService.getPassword()) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |  | ||||||
|  |     suspend fun sourcesDetailed(): StatusAndData<ArrayList<SelfossModel.SourceDetail>> = | ||||||
|         bodyOrFailure(client.tryToGet(url("/sources/list")) { |         bodyOrFailure(client.tryToGet(url("/sources/list")) { | ||||||
|             if (!shouldHavePostLogin()) { |             if (!shouldHavePostLogin()) { | ||||||
|                 parameter("username", appSettingsService.getUserName()) |                 parameter("username", appSettingsService.getUserName()) | ||||||
|   | |||||||
| @@ -12,24 +12,25 @@ fun TAG.toView(): SelfossModel.Tag = | |||||||
|         this.unread.toInt() |         this.unread.toInt() | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
| fun SOURCE.toView(): SelfossModel.Source = | fun SOURCE.toView(): SelfossModel.SourceDetail = | ||||||
|     SelfossModel.Source( |     SelfossModel.SourceDetail( | ||||||
|         this.id.toInt(), |         this.id.toInt(), | ||||||
|         this.title, |         this.title, | ||||||
|         this.tags.split(","), |         null, | ||||||
|  |         this.tags?.split(","), | ||||||
|         this.spout, |         this.spout, | ||||||
|         this.error, |         this.error, | ||||||
|         this.icon, |         this.icon, | ||||||
|         SelfossModel.SourceParams(this.url) |         if (this.url != null) SelfossModel.SourceParams(this.url) else null | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
| fun SelfossModel.Source.toEntity(): SOURCE = | fun SelfossModel.SourceDetail.toEntity(): SOURCE = | ||||||
|     SOURCE( |     SOURCE( | ||||||
|         this.id.toString(), |         this.id.toString(), | ||||||
|         this.title.getHtmlDecoded(), |         this.title.getHtmlDecoded(), | ||||||
|         this.tags.joinToString(","), |         this.tags?.joinToString(",").orEmpty(), | ||||||
|         this.spout, |         this.spout.orEmpty(), | ||||||
|         this.error, |         this.error.orEmpty(), | ||||||
|         this.icon.orEmpty(), |         this.icon.orEmpty(), | ||||||
|         this.params?.url |         this.params?.url | ||||||
|     ) |     ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user