Compare commits
	
		
			64 Commits
		
	
	
		
			v122092701
			...
			510fcbe47e
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 510fcbe47e | ||
| 667e9c1a5d | |||
| 53b1d1f8b2 | |||
| c25e8889a4 | |||
|  | 8b0bbe71c9 | ||
| 8bfe14c019 | |||
| 208babbce3 | |||
| 02098a7aa9 | |||
| d0a982f385 | |||
| 1d1c121aab | |||
| fe12819163 | |||
|  | 023a30c008 | ||
|  | a2862a2587 | ||
|  | 054e936657 | ||
| 1d2e5069b8 | |||
| a147646743 | |||
| 32e7fc0038 | |||
| c15bf44032 | |||
| 0bcd55bd4e | |||
| ebef0b3511 | |||
| 713ceb05bf | |||
| dc8381b661 | |||
| b5b820c64b | |||
| f7055626d9 | |||
|  | 6ec3e96909 | ||
| 22da30eaa8 | |||
| 79fd115f5e | |||
| 8dc3d319cd | |||
| 27bb056397 | |||
| f9ba13dc32 | |||
| 6f60ef4346 | |||
| 28b950f467 | |||
| a9c7ec3dc1 | |||
| 920d4ac1ef | |||
| 0e96d313ec | |||
| 7211fdb1a3 | |||
|  | 381d6acc82 | ||
| d311c2cdeb | |||
| 219cae5d74 | |||
| 2968aee309 | |||
| 6cb4b35c93 | |||
| 15ec0f2d26 | |||
| 4781e30da2 | |||
| c8759cc035 | |||
| cb4f2f02ef | |||
| 7517626ab7 | |||
| 41c951b659 | |||
| e2afff0b8e | |||
| a382fc89ea | |||
| 3f0a3903ae | |||
| f46f98cef0 | |||
| bf6f1a917e | |||
| 71c0a4d340 | |||
| 63c550ead3 | |||
| 366b2e10f1 | |||
| d2436bb976 | |||
| ef994460c1 | |||
| 758708e18d | |||
| c0381144d1 | |||
| cda3ba6cb4 | |||
| a4636cc0c8 | |||
| 60c24fc75a | |||
| 5853a19937 | |||
| 99f2c04bf6 | 
							
								
								
									
										13
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -3,28 +3,19 @@ type: docker | ||||
| name: test | ||||
|  | ||||
| steps: | ||||
|   - name: Anylyse | ||||
|   - name: AnylyseBuildTest | ||||
|     image: mingc/android-build-box:latest | ||||
|     failure: ignore | ||||
|     detach: true | ||||
|     commands: | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Analysing..." | ||||
|       - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" | ||||
|       - echo "---------------------------------------------------------" | ||||
|     environment: | ||||
|       SONAR_HOST_URL: | ||||
|         from_secret: sonarScannerHostUrl | ||||
|       SONAR_LOGIN: | ||||
|         from_secret: sonarScannerLogin | ||||
|   - name: BuildAndTest | ||||
|     image: mingc/android-build-box:latest | ||||
|     commands: | ||||
|       - echo "Building..." | ||||
|       - ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - echo "Testing..." | ||||
|       - echo "---------------------------------------------------------" | ||||
|       - ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false | ||||
|     environment: | ||||
|       SONAR_HOST_URL: | ||||
|         from_secret: sonarScannerHostUrl | ||||
|   | ||||
| @@ -15,9 +15,6 @@ import androidx.activity.result.contract.ActivityResultContracts | ||||
| import androidx.appcompat.app.ActionBarDrawerToggle | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.appcompat.app.AppCompatDelegate | ||||
| import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO | ||||
| import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES | ||||
| import androidx.appcompat.widget.SearchView | ||||
| import androidx.core.view.doOnNextLayout | ||||
| import androidx.drawerlayout.widget.DrawerLayout | ||||
| @@ -98,8 +95,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|     private val repository : Repository by instance() | ||||
|     private val appSettingsService : AppSettingsService by instance() | ||||
|  | ||||
|     data class DrawerData(val tags: List<SelfossModel.Tag>?, val sources: List<SelfossModel.Source>?) | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         binding = ActivityHomeBinding.inflate(layoutInflater) | ||||
| @@ -352,27 +347,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|         ) | ||||
|  | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             val drawerData = DrawerData(repository.getDBTags().map { it.toView() }, | ||||
|                                         repository.getDBSources().map { it.toView() }) | ||||
|             val tags = repository.getTags() | ||||
|             val sources = repository.getSources() | ||||
|             runOnUiThread { | ||||
|                 // Only refresh if there is no data in the DB, or if the `UpdateSources` setting is enabled | ||||
|                 if (drawerData.sources?.isEmpty() == true || appSettingsService.isUpdateSourcesEnabled()) { | ||||
|                     drawerApiCalls(drawerData) | ||||
|                 } else { | ||||
|                     handleDrawerData(drawerData, loadedFromCache = true) | ||||
|                 } | ||||
|                 handleDrawerData(tags, sources) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun drawerApiCalls(drawerData: DrawerData) { | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             val apiDrawerData = DrawerData(repository.getTags(), repository.getSources()) | ||||
|             handleDrawerData(if (drawerData != apiDrawerData) apiDrawerData else drawerData) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun handleDrawerData(drawerData: DrawerData, loadedFromCache: Boolean = false) { | ||||
|     private fun handleDrawerData(tags: List<SelfossModel.Tag>, sources: List<SelfossModel.Source>) { | ||||
|         binding.mainDrawer.itemAdapter.clear() | ||||
|  | ||||
|         // Filters title with clear action | ||||
| @@ -386,24 +369,24 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|         } | ||||
|  | ||||
|         // Hidden tags | ||||
|         if (drawerData.tags != null && drawerData.tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) { | ||||
|         if (tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) { | ||||
|             secondaryItem( | ||||
|                 withDivider = true, | ||||
|                 R.string.drawer_item_hidden_tags, | ||||
|                 DRAWER_ID_HIDDEN_TAGS | ||||
|             ) | ||||
|             handleHiddenTags(drawerData.tags) | ||||
|             handleHiddenTags(tags) | ||||
|         } | ||||
|  | ||||
|         // Tags | ||||
|         secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS) | ||||
|         if (drawerData.tags == null && !loadedFromCache) { | ||||
|         if (tags.isEmpty()) { | ||||
|             binding.mainDrawer.itemAdapter.add( | ||||
|                 SecondaryDrawerItem() | ||||
|                     .apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false } | ||||
|             ) | ||||
|         } else { | ||||
|             handleTags(drawerData.tags!!) | ||||
|             handleTags(tags) | ||||
|         } | ||||
|  | ||||
|         // Sources | ||||
| @@ -411,15 +394,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar | ||||
|             startActivity(Intent(v!!.context, SourcesActivity::class.java)) | ||||
|             false | ||||
|         } | ||||
|         if (drawerData.sources == null && !loadedFromCache) { | ||||
|         if (sources.isEmpty()) { | ||||
|             binding.mainDrawer.itemAdapter.add( | ||||
|                 SecondaryDrawerItem().apply { | ||||
|                     nameRes = R.string.drawer_error_loading_tags | ||||
|                     nameRes = R.string.drawer_error_loading_sources | ||||
|                     isSelectable = false | ||||
|                 } | ||||
|             ) | ||||
|         } else { | ||||
|             handleSources(drawerData.sources!!) | ||||
|             handleSources(sources) | ||||
|         } | ||||
|  | ||||
|         // About action | ||||
|   | ||||
| @@ -93,6 +93,9 @@ class LoginActivity : AppCompatActivity(), DIAware { | ||||
|     } | ||||
|  | ||||
|     private fun goToMain() { | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             repository.updateApiVersion() | ||||
|         } | ||||
|         val intent = Intent(this, HomeActivity::class.java) | ||||
|         startActivity(intent) | ||||
|         finish() | ||||
| @@ -163,7 +166,6 @@ class LoginActivity : AppCompatActivity(), DIAware { | ||||
|             CoroutineScope(Dispatchers.IO).launch { | ||||
|                 val result = repository.login() | ||||
|                 if (result) { | ||||
|                     repository.updateApiVersion() | ||||
|                     goToMain() | ||||
|                 } else { | ||||
|                     CoroutineScope(Dispatchers.Main).launch { | ||||
|   | ||||
| @@ -75,7 +75,6 @@ class MyApp : MultiDexApplication(), DIAware { | ||||
|                 ).show() | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     private fun handleNotificationChannels() { | ||||
|   | ||||
| @@ -28,7 +28,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         updateItems(this.items) | ||||
|     } | ||||
|  | ||||
|     private fun unmarkSnackbar(position: Int) { | ||||
|     private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) { | ||||
|         val s = Snackbar | ||||
|             .make( | ||||
|                 app.findViewById(R.id.coordLayout), | ||||
| @@ -37,7 +37,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|             ) | ||||
|             .setAction(R.string.undo_string) { | ||||
|                 CoroutineScope(Dispatchers.IO).launch { | ||||
|                     unreadItemAtIndex(position, false) | ||||
|                     unreadItemAtIndex(item, position, false) | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -47,7 +47,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|         s.show() | ||||
|     } | ||||
|  | ||||
|     private fun markSnackbar(position: Int) { | ||||
|     private fun markSnackbar(item: SelfossModel.Item, position: Int) { | ||||
|         val s = Snackbar | ||||
|             .make( | ||||
|                 app.findViewById(R.id.coordLayout), | ||||
| @@ -55,7 +55,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|                 Snackbar.LENGTH_LONG | ||||
|             ) | ||||
|             .setAction(R.string.undo_string) { | ||||
|                 readItemAtIndex(position) | ||||
|                 readItemAtIndex(item, position, false) | ||||
|             } | ||||
|  | ||||
|         val view = s.view | ||||
| @@ -66,37 +66,36 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte | ||||
|  | ||||
|     fun handleItemAtIndex(position: Int) { | ||||
|         if (items[position].unread) { | ||||
|             readItemAtIndex(position) | ||||
|             readItemAtIndex(items[position], position) | ||||
|         } else { | ||||
|             unreadItemAtIndex(position) | ||||
|             unreadItemAtIndex(items[position], position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun readItemAtIndex(position: Int, showSnackbar: Boolean = true) { | ||||
|         val i = items[position] | ||||
|     private fun readItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) { | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             repository.markAsRead(i) | ||||
|             repository.markAsRead(item) | ||||
|         } | ||||
|         if (repository.displayedItems == ItemType.UNREAD) { | ||||
|             items.remove(i) | ||||
|             items.remove(item) | ||||
|             notifyItemRemoved(position) | ||||
|             updateItems(items) | ||||
|         } else { | ||||
|             notifyItemChanged(position) | ||||
|         } | ||||
|         if (showSnackbar) { | ||||
|             unmarkSnackbar(position) | ||||
|             unmarkSnackbar(item, position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun unreadItemAtIndex(position: Int, showSnackbar: Boolean = true) { | ||||
|     private fun unreadItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) { | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             repository.unmarkAsRead(items[position]) | ||||
|             repository.unmarkAsRead(item) | ||||
|  | ||||
|         } | ||||
|         notifyItemChanged(position) | ||||
|         if (showSnackbar) { | ||||
|             markSnackbar(position) | ||||
|             markSnackbar(item, position) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -52,11 +52,13 @@ override fun doWork(): Result { | ||||
|  | ||||
|             repository.handleDBActions() | ||||
|  | ||||
|             val apiItems = repository.tryToCacheItemsAndGetNewOnes() | ||||
|             if (appSettingsService.isNotifyNewItemsEnabled()) { | ||||
|                 launch { | ||||
|                     handleNewItemsNotification(repository.tryToCacheItemsAndGetNewOnes(), notificationManager) | ||||
|                     handleNewItemsNotification(apiItems, notificationManager) | ||||
|                 } | ||||
|             } | ||||
|             apiItems.map { it.preloadImages(context) } | ||||
|         } | ||||
|     } | ||||
|     return Result.success() | ||||
| @@ -66,6 +68,7 @@ override fun doWork(): Result { | ||||
|         newItems: List<SelfossModel.Item>?, | ||||
|         notificationManager: NotificationManager | ||||
|     ) { | ||||
|         // TODO: Check if this coroutine is actually required | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|                 val apiItems = newItems.orEmpty() | ||||
|  | ||||
| @@ -102,7 +105,6 @@ override fun doWork(): Result { | ||||
|                         notificationManager.notify(2, newItemsNotification.build()) | ||||
|                     } | ||||
|                 } | ||||
|                 apiItems.map { it.preloadImages(context) } | ||||
|             Timer("", false).schedule(4000) { | ||||
|                 notificationManager.cancel(1) | ||||
|             } | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Thème sombre</string> | ||||
|     <string name="mode_system">Utiliser les paramètres système</string> | ||||
|     <string name="mode_light">Thème clair</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">深色模式</string> | ||||
|     <string name="mode_system">遵循系统设置</string> | ||||
|     <string name="mode_light">浅色模式</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -132,4 +132,5 @@ | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -63,6 +63,7 @@ | ||||
|     <string name="card_height_off">Card height will be fixed</string> | ||||
|     <string name="source_code">Source code</string> | ||||
|     <string name="drawer_error_loading_tags">Error loading tags…</string> | ||||
|     <string name="drawer_error_loading_sources">Error loading sources…</string> | ||||
|     <string name="drawer_item_filters">Filters</string> | ||||
|     <string name="drawer_action_clear">clear</string> | ||||
|     <string name="drawer_item_tags">Tags</string> | ||||
| @@ -109,7 +110,7 @@ | ||||
|     <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> | ||||
|     <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> | ||||
|     <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> | ||||
|     <string name="loading_notification_title">Loading ...</string> | ||||
|     <string name="loading_notification_title">Loading …</string> | ||||
|     <string name="loading_notification_text">Selfoss is syncing your articles</string> | ||||
|     <string name="notification_channel_sync">Sync notification</string> | ||||
|     <string name="new_items_channel_sync">New items notification</string> | ||||
|   | ||||
| @@ -56,6 +56,8 @@ kotlin { | ||||
|             dependencies { | ||||
|                 implementation(kotlin("test-common")) | ||||
|                 implementation(kotlin("test-annotations-common")) | ||||
|                 implementation("io.mockk:mockk:1.12.0") | ||||
|                 implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") | ||||
|             } | ||||
|         } | ||||
|         val androidMain by getting { | ||||
|   | ||||
| @@ -138,7 +138,11 @@ class SelfossModel { | ||||
|     object BooleanSerializer : KSerializer<Boolean> { | ||||
|         override fun deserialize(decoder: Decoder): Boolean { | ||||
|             val json = ((decoder as JsonDecoder).decodeJsonElement()).jsonPrimitive | ||||
|             return json.booleanOrNull ?: json.int == 1 | ||||
|             return if (json.booleanOrNull != null) { | ||||
|                 json.boolean | ||||
|             } else { | ||||
|                 json.int == 1 | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         override val descriptor: SerialDescriptor | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import io.github.aakira.napier.Napier | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| import kotlinx.coroutines.runBlocking | ||||
|  | ||||
| class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) { | ||||
|  | ||||
| @@ -36,13 +37,11 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|     var badgeStarred = 0 | ||||
|     set(value) {field = if (value < 0) { 0 } else { value } } | ||||
|  | ||||
|     private var fetchedSources = false | ||||
|     private var fetchedTags = false | ||||
|  | ||||
|     init { | ||||
|         // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             updateApiVersion() | ||||
|             dateUtils = DateUtils(appSettingsService) | ||||
|             reloadBadges() | ||||
|         } | ||||
|         connectivityStatus.start() | ||||
|     } | ||||
|  | ||||
|     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { | ||||
| @@ -61,12 +60,19 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|         } else { | ||||
|             if (appSettingsService.isItemCachingEnabled()) { | ||||
|                 fromDB = true | ||||
|                 var dbItems = getDBItems().filter { | ||||
|                     displayedItems == ItemType.ALL || | ||||
|                             (it.unread && displayedItems == ItemType.UNREAD) || | ||||
|                             (it.starred && displayedItems == ItemType.STARRED) | ||||
|                 } | ||||
|                 if (tagFilter != null) { | ||||
|                     dbItems = dbItems.filter { it.tags.split(',').contains(tagFilter!!.tag) } | ||||
|                 } | ||||
|                 if (sourceFilter != null) { | ||||
|                     dbItems = dbItems.filter { it.sourcetitle == sourceFilter!!.title } | ||||
|                 } | ||||
|                 fetchedItems = SelfossModel.StatusAndData.succes( | ||||
|                     getDBItems().filter { | ||||
|                         displayedItems == ItemType.ALL || | ||||
|                                 (it.unread && displayedItems == ItemType.UNREAD) || | ||||
|                                 (it.starred && displayedItems == ItemType.STARRED) | ||||
|                     }.map { it.toView() } | ||||
|                     dbItems.map { it.toView() } | ||||
|                 ) | ||||
|             } | ||||
|         } | ||||
| @@ -105,9 +111,9 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|             val items = api.getItems( | ||||
|                 itemType.type, | ||||
|                 0, | ||||
|                 tagFilter?.tag, | ||||
|                 sourceFilter?.id?.toLong(), | ||||
|                 searchFilter, | ||||
|                 null, | ||||
|                 null, | ||||
|                 null, | ||||
|                 null, | ||||
|                 200 | ||||
|             ) | ||||
| @@ -131,32 +137,40 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|                 badgeStarred = response.data.starred | ||||
|                 success = true | ||||
|             } | ||||
|         } else { | ||||
|         } else if (appSettingsService.isItemCachingEnabled()) { | ||||
|             // TODO: do this differently, because it's not efficient | ||||
|             val dbItems = getDBItems() | ||||
|             badgeUnread = dbItems.filter { item -> item.unread }.size | ||||
|             badgeStarred = dbItems.filter { item -> item.starred }.size | ||||
|             badgeAll = items.size | ||||
|             badgeAll = dbItems.size | ||||
|             success = true | ||||
|         } | ||||
|         return success | ||||
|     } | ||||
|  | ||||
|     suspend fun getTags(): List<SelfossModel.Tag>? { | ||||
|         return if (isNetworkAvailable()) { | ||||
|     suspend fun getTags(): List<SelfossModel.Tag> { | ||||
|         val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||
|         return if (isNetworkAvailable() && !fetchedTags) { | ||||
|             val apiTags = api.tags() | ||||
|             if (apiTags.success && apiTags.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { | ||||
|             if (apiTags.success && apiTags.data != null && isDatabaseEnabled) { | ||||
|                 resetDBTagsWithData(apiTags.data) | ||||
|                 if (!appSettingsService.isUpdateSourcesEnabled()) { | ||||
|                     fetchedTags = true | ||||
|                 } | ||||
|             } | ||||
|             apiTags.data | ||||
|         } else { | ||||
|             apiTags.data ?: emptyList() | ||||
|         } else if (isDatabaseEnabled) { | ||||
|             getDBTags().map { it.toView() } | ||||
|         } else { | ||||
|             emptyList() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     suspend fun getSpouts(): Map<String, SelfossModel.Spout>? { | ||||
|     // TODO: Add tests | ||||
|     suspend fun getSpouts(): Map<String, SelfossModel.Spout> { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             val spouts = api.spouts() | ||||
|             return if (spouts.success && spouts.data != null) { | ||||
|             if (spouts.success && spouts.data != null) { | ||||
|                 spouts.data | ||||
|             } else { | ||||
|                 emptyMap() // TODO: do something here | ||||
| @@ -166,18 +180,25 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     suspend fun getSources(): ArrayList<SelfossModel.Source>? { | ||||
|         return if (isNetworkAvailable()) { | ||||
|     suspend fun getSources(): ArrayList<SelfossModel.Source> { | ||||
|         val isDatabaseEnabled = appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||
|         return if (isNetworkAvailable() && !fetchedSources) { | ||||
|             val apiSources = api.sources() | ||||
|             if (apiSources.success && apiSources.data != null && (appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled())) { | ||||
|             if (apiSources.success && apiSources.data != null && isDatabaseEnabled) { | ||||
|                 resetDBSourcesWithData(apiSources.data) | ||||
|                 if (!appSettingsService.isUpdateSourcesEnabled()) { | ||||
|                     fetchedSources = true | ||||
|                 } | ||||
|             } | ||||
|             apiSources.data | ||||
|         } else { | ||||
|             apiSources.data ?: ArrayList() | ||||
|         } else if (isDatabaseEnabled) { | ||||
|             ArrayList(getDBSources().map { it.toView() }) | ||||
|         } else { | ||||
|             ArrayList() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun markAsRead(item: SelfossModel.Item): Boolean { | ||||
|         val success = markAsReadById(item.id) | ||||
|  | ||||
| @@ -189,14 +210,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|  | ||||
|     private suspend fun markAsReadById(id: Int): Boolean { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             api.markAsRead(id.toString())?.isSuccess | ||||
|             api.markAsRead(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), read = true) | ||||
|             true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean { | ||||
|         val success = unmarkAsReadById(item.id) | ||||
|  | ||||
| @@ -208,13 +229,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|  | ||||
|     private suspend fun unmarkAsReadById(id: Int): Boolean { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             api.unmarkAsRead(id.toString())?.isSuccess | ||||
|             api.unmarkAsRead(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), unread = true) | ||||
|             true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun starr(item: SelfossModel.Item): Boolean { | ||||
|         val success = starrById(item.id) | ||||
|  | ||||
| @@ -226,13 +248,14 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|  | ||||
|     private suspend fun starrById(id: Int): Boolean { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             api.starr(id.toString())?.isSuccess | ||||
|             api.starr(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), starred = true) | ||||
|             true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun unstarr(item: SelfossModel.Item): Boolean { | ||||
|         val success = unstarrById(item.id) | ||||
|  | ||||
| @@ -244,17 +267,18 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|  | ||||
|     private suspend fun unstarrById(id: Int): Boolean { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             api.unstarr(id.toString())?.isSuccess | ||||
|             api.unstarr(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), starred = true) | ||||
|             true | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean { | ||||
|         var success = false | ||||
|  | ||||
|         if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess) { | ||||
|         if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess) { | ||||
|             success = true | ||||
|             for (item in items) { | ||||
|                 markAsReadLocally(item) | ||||
| @@ -323,7 +347,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|                 tags, | ||||
|                 filter, | ||||
|                 appSettingsService.getApiVersion() | ||||
|             )?.isSuccess == true | ||||
|             ).isSuccess == true | ||||
|         } | ||||
|  | ||||
|         return response | ||||
| @@ -333,9 +357,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|         var success = false | ||||
|         if (isNetworkAvailable()) { | ||||
|             val response = api.deleteSource(id) | ||||
|             if (response != null) { | ||||
|                 success = response.isSuccess | ||||
|             } | ||||
|             success = response.isSuccess | ||||
|         } | ||||
|  | ||||
|         return success | ||||
| @@ -343,7 +365,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|  | ||||
|     suspend fun updateRemote(): Boolean { | ||||
|         return if (isNetworkAvailable()) { | ||||
|             api.update()?.equals("finished") | ||||
|             api.update().data.equals("finished") | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
| @@ -354,10 +376,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|         if (isNetworkAvailable()) { | ||||
|             try { | ||||
|                 val response = api.login() | ||||
|                 result = response?.isSuccess == true | ||||
|                 if (result) { | ||||
|                     updateApiVersion() | ||||
|                 } | ||||
|                 result = response.isSuccess == true | ||||
|             } catch (cause: Throwable) { | ||||
|                 Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote") | ||||
|             } | ||||
| @@ -380,6 +399,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|                 appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion()) | ||||
|             } | ||||
|         } | ||||
|         dateUtils = DateUtils(appSettingsService) | ||||
|     } | ||||
|  | ||||
|     fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride | ||||
| @@ -390,9 +410,9 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|     private fun deleteDBAction(action: ACTION) = | ||||
|         db.actionsQueries.deleteAction(action.id) | ||||
|  | ||||
|     fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList() | ||||
|     private fun getDBTags(): List<TAG> = db.tagsQueries.tags().executeAsList() | ||||
|  | ||||
|     fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList() | ||||
|     private fun getDBSources(): List<SOURCE> = db.sourcesQueries.sources().executeAsList() | ||||
|  | ||||
|     private fun resetDBTagsWithData(tagEntities: List<SelfossModel.Tag>) { | ||||
|         db.tagsQueries.deleteAllTags() | ||||
| @@ -430,8 +450,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|     private fun updateDBItem(item: SelfossModel.Item) = | ||||
|         db.itemsQueries.updateItem(item.datetime, item.title.getHtmlDecoded(), item.content, item.unread, item.starred, item.thumbnail, item.icon, item.link, item.sourcetitle, item.tags.joinToString(","), item.id.toString()) | ||||
|  | ||||
|  | ||||
|     suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item>? { | ||||
|     suspend fun tryToCacheItemsAndGetNewOnes(): List<SelfossModel.Item> { | ||||
|         try { | ||||
|             val newItems = getMaxItemsForBackground(ItemType.UNREAD) | ||||
|             val allItems = getMaxItemsForBackground(ItemType.ALL) | ||||
| @@ -444,6 +463,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap | ||||
|         return emptyList() | ||||
|     } | ||||
|  | ||||
|     // TODO: Add tests | ||||
|     suspend fun handleDBActions() { | ||||
|  | ||||
|         val actions: List<ACTION> = getDBActions() | ||||
|   | ||||
| @@ -65,6 +65,6 @@ fun SelfossModel.Item.toEntity(): ITEM = | ||||
|         this.thumbnail, | ||||
|         this.icon, | ||||
|         this.link, | ||||
|         this.title.getHtmlDecoded(), | ||||
|         this.sourcetitle.getHtmlDecoded(), | ||||
|         this.tags.joinToString(",") | ||||
|     ) | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user