forked from Louvorg/ReaderForSelfoss-multiplatform
Compare commits
89 Commits
v122092701
...
v122113101
Author | SHA1 | Date | |
---|---|---|---|
e36189e2e7 | |||
d6bdf510a4 | |||
a464e93370 | |||
4b63afe62a | |||
ac4c4b9441 | |||
16b10dc1b7 | |||
02d734eee8 | |||
c5cdfc0d53 | |||
6d610ed61a | |||
792950be7c | |||
af8969ce4a | |||
27c55e59a1 | |||
94a0747947 | |||
d862bfba4f | |||
b0d1d9c29a | |||
7b40a31979 | |||
823a8c3692 | |||
5494978db8 | |||
6076eb1cee | |||
131101d2ee | |||
62ad1f45ba | |||
402d18b889 | |||
e32699c93f | |||
059a237b99 | |||
d2bdbae6c8 | |||
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 |
26
.drone.yml
26
.drone.yml
@ -3,28 +3,22 @@ type: docker
|
||||
name: test
|
||||
|
||||
steps:
|
||||
- name: Anylyse
|
||||
- name: AnylyseBuildTest
|
||||
image: mingc/android-build-box:latest
|
||||
failure: ignore
|
||||
detach: true
|
||||
commands:
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Configure gradle..."
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties
|
||||
- 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\""
|
||||
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
|
||||
- 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
|
||||
- ./gradlew build
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Testing..."
|
||||
- echo "---------------------------------------------------------"
|
||||
- ./gradlew test
|
||||
environment:
|
||||
SONAR_HOST_URL:
|
||||
from_secret: sonarScannerHostUrl
|
||||
@ -94,8 +88,12 @@ steps:
|
||||
- name: build
|
||||
image: mingc/android-build-box:latest
|
||||
commands:
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Configure gradle..."
|
||||
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Generate APK"
|
||||
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
|
||||
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
|
||||
- echo "---------------------------------------------------------"
|
||||
- echo "Get Key"
|
||||
- wget https://amine-louveau.fr/key
|
||||
|
@ -6,6 +6,7 @@ plugins {
|
||||
id("com.android.application")
|
||||
kotlin("android")
|
||||
kotlin("kapt")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
}
|
||||
|
||||
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
|
||||
@ -54,11 +55,15 @@ fun versionNameFromGit(): String {
|
||||
android {
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
compileSdk = 31
|
||||
|
||||
// For Kotlin projects
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
compileSdk = 33
|
||||
buildToolsVersion = "31.0.0"
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
@ -66,7 +71,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "bou.amine.apps.readerforselfossv2.android"
|
||||
minSdk = 21
|
||||
targetSdk = 31
|
||||
targetSdk = 33
|
||||
versionCode = versionCodeFromGit()
|
||||
versionName = versionNameFromGit()
|
||||
|
||||
@ -79,6 +84,11 @@ android {
|
||||
// tests
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
getByName("release") {
|
||||
isMinifyEnabled = true
|
||||
@ -98,9 +108,6 @@ android {
|
||||
dimension = "build"
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
namespace = "bou.amine.apps.readerforselfossv2.android"
|
||||
|
||||
}
|
||||
@ -114,13 +121,6 @@ dependencies {
|
||||
|
||||
implementation("androidx.preference:preference-ktx:1.1.1")
|
||||
|
||||
// Testing
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0-alpha02")
|
||||
androidTestImplementation("androidx.test:runner:1.3.1-alpha02")
|
||||
// Espresso-contrib for DatePicker, RecyclerView, Drawer actions, Accessibility checks, CountingIdlingResource
|
||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.4.0-alpha02")
|
||||
// Espresso-intents for validation and stubbing of Intents
|
||||
androidTestImplementation("androidx.test.espresso:espresso-intents:3.4.0-alpha02")
|
||||
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
|
||||
|
||||
// Android Support
|
||||
@ -135,18 +135,12 @@ dependencies {
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
|
||||
implementation("org.jsoup:jsoup:1.14.3")
|
||||
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
|
||||
//multidex
|
||||
implementation("androidx.multidex:multidex:2.0.1")
|
||||
|
||||
// About
|
||||
implementation("com.mikepenz:aboutlibraries-core:8.9.4")
|
||||
implementation("com.mikepenz:aboutlibraries:8.9.4")
|
||||
implementation("com.mikepenz:aboutlibraries-definitions:8.9.4")
|
||||
|
||||
// Async
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
|
||||
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
|
||||
implementation("com.mikepenz:aboutlibraries:10.5.1")
|
||||
|
||||
// Retrofit + http logging + okhttp
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
@ -188,14 +182,41 @@ dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.8.0")
|
||||
|
||||
// implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
||||
// implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
|
||||
// implementation("androidx.lifecycle:lifecycle-runtime:2.5.1")
|
||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||
|
||||
// Network information
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
|
||||
// SQLDELIGHT
|
||||
implementation("com.squareup.sqldelight:android-driver:1.5.3")
|
||||
implementation("com.squareup.sqldelight:android-driver:1.5.4")
|
||||
|
||||
//test
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
testImplementation("io.mockk:mockk:1.12.0")
|
||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
}
|
||||
|
||||
tasks.withType<Test> {
|
||||
outputs.upToDateWhen { false }
|
||||
useJUnit()
|
||||
testLogging {
|
||||
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
|
||||
events = setOf(
|
||||
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
|
||||
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
|
||||
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
|
||||
)
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
|
||||
aboutLibraries {
|
||||
offlineMode = true
|
||||
fetchRemoteLicense = false
|
||||
fetchRemoteFunding = false
|
||||
includePlatform = false
|
||||
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
|
||||
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
|
||||
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
|
||||
}
|
2
androidApp/proguard-rules.pro
vendored
2
androidApp/proguard-rules.pro
vendored
@ -90,3 +90,5 @@
|
||||
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
|
||||
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault
|
||||
|
||||
-dontwarn io.mockk.**
|
||||
-keep class io.mockk.** { *; }
|
||||
|
@ -94,7 +94,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val items = repository.getSpouts()
|
||||
if (items != null) {
|
||||
if (items.isNotEmpty()) {
|
||||
val itemsStrings = items.map { it.value.name }
|
||||
for ((key, value) in items) {
|
||||
spoutsKV[value.name] = key
|
||||
|
@ -15,12 +15,10 @@ 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
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.*
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
@ -98,8 +96,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)
|
||||
@ -183,8 +179,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
adapter.handleItemAtIndex(position)
|
||||
|
||||
reloadBadgeContent()
|
||||
|
||||
val tagHashes = i.tags.map { it.longHash() }
|
||||
tagsBadge = tagsBadge.map {
|
||||
if (tagHashes.contains(it.key)) {
|
||||
@ -212,6 +206,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
|
||||
}
|
||||
|
||||
private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
|
||||
if (count > 0) {
|
||||
badge
|
||||
.setText(count.toString())
|
||||
.maybeShow()
|
||||
} else {
|
||||
badge.removeBadge()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleBottomBar() {
|
||||
|
||||
tabNewBadge = TextBadgeItem()
|
||||
@ -224,6 +228,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
.setText("")
|
||||
.setHideOnSelect(false).hide(false)
|
||||
|
||||
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
||||
lifecycleScope.launch {
|
||||
repository.badgeUnread.collect {
|
||||
updateBottomBarBadgeCount(tabNewBadge, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (appSettingsService.isDisplayAllCountEnabled()) {
|
||||
lifecycleScope.launch {
|
||||
repository.badgeAll.collect {
|
||||
updateBottomBarBadgeCount(tabArchiveBadge, it)
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repository.badgeStarred.collect {
|
||||
updateBottomBarBadgeCount(tabStarredBadge, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val tabNew =
|
||||
BottomNavigationItem(
|
||||
R.drawable.ic_tab_fiber_new_black_24dp,
|
||||
@ -352,27 +378,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 +400,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 +425,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
|
||||
@ -731,29 +745,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private fun reloadBadges() {
|
||||
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.reloadBadges()
|
||||
reloadBadgeContent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadBadgeContent() {
|
||||
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
||||
tabNewBadge
|
||||
.setText(repository.badgeUnread.toString())
|
||||
.maybeShow()
|
||||
}
|
||||
if (appSettingsService.isDisplayAllCountEnabled()) {
|
||||
tabArchiveBadge
|
||||
.setText(repository.badgeAll.toString())
|
||||
.maybeShow()
|
||||
tabStarredBadge
|
||||
.setText(repository.badgeStarred.toString())
|
||||
.maybeShow()
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadTagsBadges() {
|
||||
tagsBadge.forEach {
|
||||
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
|
||||
@ -875,10 +872,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
||||
|
||||
private fun maxItemNumber(): Int =
|
||||
when (elementsShown) {
|
||||
ItemType.UNREAD -> repository.badgeUnread
|
||||
ItemType.ALL -> repository.badgeAll
|
||||
ItemType.STARRED -> repository.badgeStarred
|
||||
else -> repository.badgeUnread // if !elementsShown then unread are fetched.
|
||||
ItemType.UNREAD -> repository.badgeUnread.value
|
||||
ItemType.ALL -> repository.badgeAll.value
|
||||
ItemType.STARRED -> repository.badgeStarred.value
|
||||
else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
|
||||
}
|
||||
|
||||
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {
|
||||
|
@ -93,12 +93,15 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
}
|
||||
|
||||
private fun goToMain() {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
repository.updateApiVersion()
|
||||
}
|
||||
val intent = Intent(this, HomeActivity::class.java)
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun preferenceError(t: Throwable) {
|
||||
private fun preferenceError() {
|
||||
appSettingsService.resetLoginInformation()
|
||||
|
||||
binding.urlView.error = getString(R.string.wrong_infos)
|
||||
@ -163,11 +166,10 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val result = repository.login()
|
||||
if (result) {
|
||||
repository.updateApiVersion()
|
||||
goToMain()
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
preferenceError(Exception("Not success"))
|
||||
preferenceError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDexApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible
|
||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||
@ -28,6 +29,7 @@ import io.github.aakira.napier.DebugAntilog
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.kodein.di.*
|
||||
|
||||
@ -37,7 +39,7 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
import(networkModule)
|
||||
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
|
||||
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
|
||||
bind<Repository>() with singleton { Repository(instance(), instance(), connectivityStatus, instance()) }
|
||||
bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
|
||||
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
|
||||
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
|
||||
}
|
||||
@ -47,6 +49,9 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
private val connectivityStatus: ConnectivityStatus by instance()
|
||||
private val driverFactory: DriverFactory by instance()
|
||||
|
||||
// TODO: handle with the "previous" way
|
||||
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Napier.base(DebugAntilog())
|
||||
@ -75,7 +80,6 @@ class MyApp : MultiDexApplication(), DIAware {
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun handleNotificationChannels() {
|
||||
|
@ -36,9 +36,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private fun showMenuItem(willAddToFavorite: Boolean) {
|
||||
if (willAddToFavorite) {
|
||||
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE)
|
||||
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
|
||||
} else {
|
||||
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED)
|
||||
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,20 +56,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val response = repository.getSources()
|
||||
if (response != null) {
|
||||
if (response.isNotEmpty()) {
|
||||
items = response
|
||||
val mAdapter = SourcesListAdapter(
|
||||
this@SourcesActivity, items
|
||||
)
|
||||
binding.recyclerView.adapter = mAdapter
|
||||
mAdapter.notifyDataSetChanged()
|
||||
if (items.isEmpty()) {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
R.string.nothing_here,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@SourcesActivity,
|
||||
|
@ -10,9 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import bou.amine.apps.readerforselfossv2.android.R
|
||||
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
|
||||
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.*
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
@ -59,7 +62,7 @@ class ItemCardAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
|
||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (!appSettingsService.isFullHeightCardsEnabled()) {
|
||||
binding.itemImage.maxHeight = imageMaxHeight
|
||||
@ -108,13 +111,11 @@ class ItemCardAdapter(
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.unstarr(item)
|
||||
}
|
||||
item.starred = false
|
||||
binding.favButton.isSelected = false
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
repository.starr(item)
|
||||
}
|
||||
item.starred = true
|
||||
binding.favButton.isSelected = true
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ class ItemListAdapter(
|
||||
|
||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||
|
||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
|
||||
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
|
||||
|
||||
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,13 @@ class SourcesListAdapter(
|
||||
binding.sourceTitle.text = itm.title.getHtmlDecoded()
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int) = position.toLong()
|
||||
|
||||
override fun getItemViewType(position: Int) = position
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
||||
|
||||
init {
|
||||
handleClickListeners()
|
||||
@ -74,13 +78,13 @@ class SourcesListAdapter(
|
||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
||||
|
||||
deleteBtn.setOnClickListener {
|
||||
val (id) = items[adapterPosition]
|
||||
val (id) = items[bindingAdapterPosition]
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val successfullyDeletedSource = repository.deleteSource(id)
|
||||
if (successfullyDeletedSource) {
|
||||
items.removeAt(adapterPosition)
|
||||
notifyItemRemoved(adapterPosition)
|
||||
notifyItemRangeChanged(adapterPosition, itemCount)
|
||||
items.removeAt(bindingAdapterPosition)
|
||||
notifyItemRemoved(bindingAdapterPosition)
|
||||
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
app,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
contentText = item.content
|
||||
contentTitle = item.title.getHtmlDecoded()
|
||||
contentImage = item.getThumbnail(repository.baseUrl)
|
||||
contentSource = item.sourceAndDateText(repository.dateUtils)
|
||||
contentSource = item.sourceAndDateText()
|
||||
allImages = item.getImages()
|
||||
|
||||
fontSize = appSettingsService.getFontSize()
|
||||
@ -354,6 +354,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
binding.webcontent.settings.javaScriptEnabled = false
|
||||
|
||||
binding.webcontent.webViewClient = object : WebViewClient() {
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
|
||||
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
@ -361,6 +362,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
return true
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
|
||||
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
|
||||
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
|
||||
@ -387,7 +389,7 @@ class ArticleFragment : Fragment(), DIAware {
|
||||
}
|
||||
|
||||
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
|
||||
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
return performClick()
|
||||
}
|
||||
})
|
||||
|
@ -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>
|
||||
|
33
androidApp/src/test/kotlin/DatesTest.kt
Normal file
33
androidApp/src/test/kotlin/DatesTest.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package bou.amine.apps.readerforselfossv2.repository
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.toInstant
|
||||
import org.junit.Test
|
||||
|
||||
class DatesTest {
|
||||
|
||||
private val v3Date = "2013-04-07T13:43:00+01:00"
|
||||
private val v4Date = "2013-04-07 13:43: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()
|
||||
|
||||
assertEquals(date, expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun v4_date_should_be_parsed() {
|
||||
val date = DateUtils.parseDate(v4Date)
|
||||
val expected =
|
||||
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
|
||||
.toEpochMilliseconds()
|
||||
|
||||
assertEquals(date, expected)
|
||||
}
|
||||
|
||||
}
|
1368
androidApp/src/test/kotlin/RepositoryTest.kt
Normal file
1368
androidApp/src/test/kotlin/RepositoryTest.kt
Normal file
File diff suppressed because it is too large
Load Diff
57
androidApp/src/test/kotlin/TestUtils.kt
Normal file
57
androidApp/src/test/kotlin/TestUtils.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package bou.amine.apps.readerforselfossv2.repository
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
|
||||
|
||||
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
|
||||
return listOf(
|
||||
ITEM(
|
||||
id = item.id,
|
||||
datetime = item.datetime,
|
||||
title = item.title,
|
||||
content = item.content,
|
||||
unread = item.unread,
|
||||
starred = item.starred,
|
||||
thumbnail = item.thumbnail,
|
||||
icon = item.icon,
|
||||
link = item.link,
|
||||
sourcetitle = item.sourcetitle,
|
||||
tags = item.tags
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
|
||||
return listOf(
|
||||
SelfossModel.Item(
|
||||
id = item.id.toInt(),
|
||||
datetime = item.datetime,
|
||||
title = item.title,
|
||||
content = item.content,
|
||||
unread = item.unread,
|
||||
starred = item.starred,
|
||||
thumbnail = item.thumbnail,
|
||||
icon = item.icon,
|
||||
link = item.link,
|
||||
sourcetitle = item.sourcetitle,
|
||||
tags = item.tags.split(',')
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
class FakeItemParameters {
|
||||
var id = "20"
|
||||
var datetime = "2022-09-09T03:32:01-04:00"
|
||||
val title = "Etica della ricerca sotto i riflettori."
|
||||
val content =
|
||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>L’ultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui l’intelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne l’origine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dell’innovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto all’attenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>L’argomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>L’embrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nell’offerta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso l’Ateneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
|
||||
var unread = true
|
||||
var starred = true
|
||||
val thumbnail = null
|
||||
val icon = "ba79e238383ce83c23a169929c8906ef.png"
|
||||
val link =
|
||||
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
|
||||
var sourcetitle = "La Chimica e la Società"
|
||||
var tags = "Chimica, Testing"
|
||||
}
|
@ -1,21 +1,20 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
||||
classpath("com.android.tools.build:gradle:7.3.0")
|
||||
|
||||
// sonarquve
|
||||
classpath("org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.4.0.2513")
|
||||
|
||||
// SqlDelight
|
||||
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
|
||||
classpath("com.squareup.sqldelight:gradle-plugin:1.5.4")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
//trick: for the same plugin versions in all sub-modules
|
||||
id("com.android.application").version("7.3.1").apply(false)
|
||||
id("com.android.library").version("7.3.1").apply(false)
|
||||
kotlin("android").version("1.7.20").apply(false)
|
||||
kotlin("multiplatform").version("1.7.20").apply(false)
|
||||
id("org.sonarqube").version("3.4.0.2513").apply(false)
|
||||
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
|
||||
}
|
||||
|
||||
apply(plugin = "org.sonarqube")
|
||||
|
||||
allprojects {
|
||||
@ -27,6 +26,7 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,26 @@
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
#Tue Mar 22 16:50:00 CET 2022
|
||||
#Gradle
|
||||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
|
||||
#Kotlin
|
||||
kotlin.code.style=official
|
||||
kotlin.mpp.enableCInteropCommonization=true
|
||||
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
kotlin.native.enableDependencyPropagation=false
|
||||
|
||||
#Android
|
||||
android.useAndroidX=true
|
||||
kotlin.native.enableDependencyPropagation=false
|
||||
#android.nonTransitiveRClass=true
|
||||
android.enableJetifier=true
|
||||
|
||||
|
||||
#MPP
|
||||
kotlin.mpp.enableCInteropCommonization=true
|
||||
kotlin.mpp.enableGranularSourceSetsMetadata=true
|
||||
|
||||
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
ignoreGitVersion=false
|
||||
kotlin.native.cacheKind.iosX64=none
|
||||
pushCache=true
|
||||
|
@ -1,383 +1,383 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 50;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
|
||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
/* Begin PBXBuildFile section */
|
||||
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
|
||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; };
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
7555FFB4242A642300829871 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
7555FFB4242A642300829871 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
|
||||
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
/* Begin PBXFileReference section */
|
||||
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
|
||||
7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
7555FF78242A565900829871 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
7555FF78242A565900829871 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
058557D7273AAEEB004C7B11 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF72242A565900829871 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7555FF7D242A565900829871 /* iosApp */,
|
||||
7555FF7C242A565900829871 /* Products */,
|
||||
7555FFB0242A642200829871 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF7C242A565900829871 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7555FF7B242A565900829871 /* iosApp.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF7D242A565900829871 /* iosApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
058557BA273AAA24004C7B11 /* Assets.xcassets */,
|
||||
7555FF82242A565900829871 /* ContentView.swift */,
|
||||
7555FF8C242A565B00829871 /* Info.plist */,
|
||||
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
||||
058557D7273AAEEB004C7B11 /* Preview Content */,
|
||||
);
|
||||
path = iosApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FFB0242A642200829871 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
/* Begin PBXGroup section */
|
||||
058557D7273AAEEB004C7B11 /* Preview Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
|
||||
);
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF72242A565900829871 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7555FF7D242A565900829871 /* iosApp */,
|
||||
7555FF7C242A565900829871 /* Products */,
|
||||
7555FFB0242A642200829871 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF7C242A565900829871 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7555FF7B242A565900829871 /* iosApp.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FF7D242A565900829871 /* iosApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
058557BA273AAA24004C7B11 /* Assets.xcassets */,
|
||||
7555FF82242A565900829871 /* ContentView.swift */,
|
||||
7555FF8C242A565B00829871 /* Info.plist */,
|
||||
2152FB032600AC8F00CF470E /* iOSApp.swift */,
|
||||
058557D7273AAEEB004C7B11 /* Preview Content */,
|
||||
);
|
||||
path = iosApp;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7555FFB0242A642200829871 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
7555FF7A242A565900829871 /* iosApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
|
||||
buildPhases = (
|
||||
7555FFB5242A651A00829871 /* ShellScript */,
|
||||
7555FF77242A565900829871 /* Sources */,
|
||||
7555FF78242A565900829871 /* Frameworks */,
|
||||
7555FF79242A565900829871 /* Resources */,
|
||||
7555FFB4242A642300829871 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = iosApp;
|
||||
productName = iosApp;
|
||||
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
/* Begin PBXNativeTarget section */
|
||||
7555FF7A242A565900829871 /* iosApp */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
|
||||
buildPhases = (
|
||||
7555FFB5242A651A00829871 /* ShellScript */,
|
||||
7555FF77242A565900829871 /* Sources */,
|
||||
7555FF78242A565900829871 /* Frameworks */,
|
||||
7555FF79242A565900829871 /* Resources */,
|
||||
7555FFB4242A642300829871 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = iosApp;
|
||||
productName = iosApp;
|
||||
productReference = 7555FF7B242A565900829871 /* iosApp.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
7555FF73242A565900829871 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1130;
|
||||
LastUpgradeCheck = 1130;
|
||||
ORGANIZATIONNAME = orgName;
|
||||
TargetAttributes = {
|
||||
7555FF7A242A565900829871 = {
|
||||
CreatedOnToolsVersion = 11.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 7555FF72242A565900829871;
|
||||
productRefGroup = 7555FF7C242A565900829871 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
7555FF7A242A565900829871 /* iosApp */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
/* Begin PBXProject section */
|
||||
7555FF73242A565900829871 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 1130;
|
||||
LastUpgradeCheck = 1130;
|
||||
ORGANIZATIONNAME = orgName;
|
||||
TargetAttributes = {
|
||||
7555FF7A242A565900829871 = {
|
||||
CreatedOnToolsVersion = 11.3.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 7555FF72242A565900829871;
|
||||
productRefGroup = 7555FF7C242A565900829871 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
7555FF7A242A565900829871 /* iosApp */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
7555FF79242A565900829871 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
|
||||
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
7555FF79242A565900829871 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
|
||||
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
7555FFB5242A651A00829871 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
7555FFB5242A651A00829871 /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
7555FF77242A565900829871 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
7555FF77242A565900829871 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
|
||||
7555FF83242A565900829871 /* ContentView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
7555FFA3242A565B00829871 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7555FFA4242A565B00829871 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
7555FFA6242A565B00829871 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-framework",
|
||||
shared,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7555FFA7242A565B00829871 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-framework",
|
||||
shared,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
/* Begin XCBuildConfiguration section */
|
||||
7555FFA3242A565B00829871 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7555FFA4242A565B00829871 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
7555FFA6242A565B00829871 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-framework",
|
||||
shared,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
7555FFA7242A565B00829871 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
|
||||
INFOPLIST_FILE = iosApp/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-framework",
|
||||
shared,
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7555FFA3242A565B00829871 /* Debug */,
|
||||
7555FFA4242A565B00829871 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7555FFA6242A565B00829871 /* Debug */,
|
||||
7555FFA7242A565B00829871 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 7555FF73242A565900829871 /* Project object */;
|
||||
}
|
||||
/* Begin XCConfigurationList section */
|
||||
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7555FFA3242A565B00829871 /* Debug */,
|
||||
7555FFA4242A565B00829871 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
7555FFA6242A565B00829871 /* Debug */,
|
||||
7555FFA7242A565B00829871 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 7555FF73242A565900829871 /* Project object */;
|
||||
}
|
||||
|
@ -2,12 +2,9 @@ import SwiftUI
|
||||
import shared
|
||||
|
||||
struct ContentView: View {
|
||||
let greet = Greeting().greeting()
|
||||
|
||||
let toto = SelfossApi().getItems()
|
||||
|
||||
var body: some View {
|
||||
Text(greet)
|
||||
Text("ototot")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,14 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildCache {
|
||||
remote<HttpBuildCache> {
|
||||
url = uri("http://18.0.0.7:3071/cache/")
|
||||
|
@ -1,7 +1,7 @@
|
||||
object SqlDelight {
|
||||
const val runtime = "com.squareup.sqldelight:runtime:1.5.3"
|
||||
const val android = "com.squareup.sqldelight:android-driver:1.5.3"
|
||||
const val native = "com.squareup.sqldelight:native-driver:1.5.3"
|
||||
const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
|
||||
const val android = "com.squareup.sqldelight:android-driver:1.5.4"
|
||||
const val native = "com.squareup.sqldelight:native-driver:1.5.4"
|
||||
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ kotlin {
|
||||
listOf(
|
||||
iosX64(),
|
||||
iosArm64(),
|
||||
//iosSimulatorArm64() sure all ios dependencies support this target
|
||||
// iosSimulatorArm64()
|
||||
).forEach {
|
||||
it.binaries.framework {
|
||||
baseName = "shared"
|
||||
@ -40,14 +40,11 @@ kotlin {
|
||||
implementation("org.kodein.di:kodein-di:7.12.0")
|
||||
|
||||
//Settings
|
||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
||||
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
|
||||
|
||||
//Logging
|
||||
implementation("io.github.aakira:napier:2.6.1")
|
||||
|
||||
// Network information
|
||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||
|
||||
// Sql
|
||||
implementation(SqlDelight.runtime)
|
||||
}
|
||||
@ -61,6 +58,7 @@ kotlin {
|
||||
val androidMain by getting {
|
||||
dependencies {
|
||||
implementation("io.ktor:ktor-client-okhttp:2.1.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
|
||||
// Sql
|
||||
implementation(SqlDelight.android)
|
||||
@ -74,39 +72,36 @@ kotlin {
|
||||
}
|
||||
val iosX64Main by getting
|
||||
val iosArm64Main by getting
|
||||
//val iosSimulatorArm64Main by getting
|
||||
// val iosSimulatorArm64Main by getting
|
||||
val iosMain by creating {
|
||||
dependsOn(commonMain)
|
||||
iosX64Main.dependsOn(this)
|
||||
iosArm64Main.dependsOn(this)
|
||||
//iosSimulatorArm64Main.dependsOn(this)
|
||||
// iosSimulatorArm64Main.dependsOn(this)
|
||||
|
||||
// Sql
|
||||
dependencies {
|
||||
implementation(SqlDelight.native)
|
||||
implementation("io.ktor:ktor-client-ios:2.1.1")
|
||||
}
|
||||
}
|
||||
val iosX64Test by getting
|
||||
val iosArm64Test by getting
|
||||
//val iosSimulatorArm64Test by getting
|
||||
// val iosSimulatorArm64Test by getting
|
||||
val iosTest by creating {
|
||||
dependsOn(commonTest)
|
||||
iosX64Test.dependsOn(this)
|
||||
iosArm64Test.dependsOn(this)
|
||||
dependencies {
|
||||
implementation("io.ktor:ktor-client-ios:2.1.1")
|
||||
}
|
||||
//iosSimulatorArm64Test.dependsOn(this)
|
||||
// iosSimulatorArm64Test.dependsOn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 31
|
||||
compileSdk = 32
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
targetSdk = 31
|
||||
targetSdk = 32
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
@ -115,10 +110,11 @@ android {
|
||||
namespace = "bou.amine.apps.readerforselfossv2"
|
||||
}
|
||||
|
||||
|
||||
|
||||
sqldelight {
|
||||
database("ReaderForSelfossDB") {
|
||||
packageName = "bou.amine.apps.readerforselfossv2.dao"
|
||||
sourceFolders = listOf("sqldelight")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,36 +1,29 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlinx.datetime.*
|
||||
|
||||
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
|
||||
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
return try {
|
||||
Instant.parse(dateString).toEpochMilliseconds()
|
||||
} catch (e: Exception) {
|
||||
LocalDateTime.parse(dateString.replace(" ", "T")).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||
}
|
||||
}
|
||||
|
||||
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
||||
return if (appSettingsService.getApiVersion() >= 4) {
|
||||
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
|
||||
} else {
|
||||
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
|
||||
ZoneOffset.UTC).toEpochMilli()
|
||||
val date = parseDate(dateString)
|
||||
|
||||
return " " + DateUtils.getRelativeTimeSpanString(
|
||||
date,
|
||||
Clock.System.now().toEpochMilliseconds(),
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
|
||||
val date = parseDate(dateString)
|
||||
|
||||
return " " + DateUtils.getRelativeTimeSpanString(
|
||||
date,
|
||||
Instant.now().toEpochMilli(),
|
||||
DateUtils.MINUTE_IN_MILLIS,
|
||||
DateUtils.FORMAT_ABBREV_RELATIVE
|
||||
)
|
||||
}
|
||||
}
|
@ -108,8 +108,8 @@ class SelfossModel {
|
||||
return stringUrl
|
||||
}
|
||||
|
||||
fun sourceAndDateText(dateUtils: DateUtils): String =
|
||||
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime)
|
||||
fun sourceAndDateText(): String =
|
||||
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
|
||||
|
||||
fun toggleStar(): Item {
|
||||
this.starred = !this.starred
|
||||
@ -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
|
||||
|
@ -6,20 +6,19 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
import bou.amine.apps.readerforselfossv2.utils.*
|
||||
import com.github.ln_12.library.ConnectivityStatus
|
||||
import io.github.aakira.napier.Napier
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, connectivityStatus: ConnectivityStatus, private val db: ReaderForSelfossDB) {
|
||||
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) {
|
||||
|
||||
var items = ArrayList<SelfossModel.Item>()
|
||||
val isConnectionAvailable = connectivityStatus.isNetworkConnected
|
||||
var connectionMonitored = false
|
||||
|
||||
var baseUrl = appSettingsService.getBaseUrl()
|
||||
lateinit var dateUtils: DateUtils
|
||||
|
||||
var displayedItems = ItemType.UNREAD
|
||||
|
||||
@ -29,21 +28,15 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
|
||||
var offlineOverride = false
|
||||
|
||||
var badgeUnread = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
var badgeAll = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
var badgeStarred = 0
|
||||
set(value) {field = if (value < 0) { 0 } else { value } }
|
||||
private val _badgeUnread = MutableStateFlow(0)
|
||||
val badgeUnread = _badgeUnread.asStateFlow()
|
||||
private val _badgeAll = MutableStateFlow(0)
|
||||
val badgeAll = _badgeAll.asStateFlow()
|
||||
private val _badgeStarred = MutableStateFlow(0)
|
||||
val badgeStarred = _badgeStarred.asStateFlow()
|
||||
|
||||
init {
|
||||
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
updateApiVersion()
|
||||
dateUtils = DateUtils(appSettingsService)
|
||||
reloadBadges()
|
||||
}
|
||||
}
|
||||
private var fetchedSources = false
|
||||
private var fetchedTags = false
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
// TODO: Use the updatedSince parameter
|
||||
@ -61,12 +54,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() }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -74,7 +74,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
if (fetchedItems.success && fetchedItems.data != null) {
|
||||
items = ArrayList(fetchedItems.data!!)
|
||||
if (fromDB) {
|
||||
items.sortByDescending { dateUtils.parseDate(it.datetime) }
|
||||
items.sortByDescending { DateUtils.parseDate(it.datetime) }
|
||||
}
|
||||
}
|
||||
return items
|
||||
@ -105,9 +105,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
|
||||
)
|
||||
@ -126,37 +126,45 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
if (isNetworkAvailable()) {
|
||||
val response = api.stats()
|
||||
if (response.success && response.data != null) {
|
||||
badgeUnread = response.data.unread
|
||||
badgeAll = response.data.total
|
||||
badgeStarred = response.data.starred
|
||||
_badgeUnread.value = response.data.unread
|
||||
_badgeAll.value = response.data.total
|
||||
_badgeStarred.value = 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
|
||||
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
||||
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
||||
_badgeAll.value = 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 +174,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 +204,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 +223,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 +242,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 +261,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)
|
||||
@ -266,7 +284,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||
if (item.unread) {
|
||||
item.unread = false
|
||||
badgeUnread -= 1
|
||||
_badgeUnread.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@ -277,7 +295,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||
if (!item.unread) {
|
||||
item.unread = true
|
||||
badgeUnread += 1
|
||||
_badgeUnread.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@ -288,7 +306,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
private fun starrLocally(item: SelfossModel.Item) {
|
||||
if (!item.starred) {
|
||||
item.starred = true
|
||||
badgeStarred += 1
|
||||
_badgeStarred.value += 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@ -299,7 +317,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||
if (item.starred) {
|
||||
item.starred = false
|
||||
badgeStarred -= 1
|
||||
_badgeStarred.value -= 1
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
@ -323,7 +341,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
tags,
|
||||
filter,
|
||||
appSettingsService.getApiVersion()
|
||||
)?.isSuccess == true
|
||||
).isSuccess == true
|
||||
}
|
||||
|
||||
return response
|
||||
@ -333,9 +351,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 +359,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 +370,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")
|
||||
}
|
||||
@ -390,9 +403,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 +443,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 +456,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO: Add tests
|
||||
suspend fun handleDBActions() {
|
||||
|
||||
val actions: List<ACTION> = getDBActions()
|
||||
|
@ -54,8 +54,8 @@ class AppSettingsService {
|
||||
return _apiVersion
|
||||
}
|
||||
|
||||
fun refreshApiVersion() {
|
||||
_apiVersion = settings.getInt("apiVersionMajor", -1)
|
||||
private fun refreshApiVersion() {
|
||||
_apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
||||
}
|
||||
|
||||
fun getBaseUrl(): String {
|
||||
@ -86,8 +86,8 @@ class AppSettingsService {
|
||||
return _itemsNumber!!
|
||||
}
|
||||
|
||||
fun refreshItemsNumber() {
|
||||
_itemsNumber = settings.getString("prefer_api_items_number", "20").toInt()
|
||||
private fun refreshItemsNumber() {
|
||||
_itemsNumber = settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||
}
|
||||
|
||||
fun getApiTimeout(): Long {
|
||||
@ -98,24 +98,24 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshApiTimeout() {
|
||||
val settingsTimeout = settings.getLong("api_timeout", HttpTimeout.INFINITE_TIMEOUT_MS)
|
||||
val settingsTimeout = settings.getLong(API_TIMEOUT, HttpTimeout.INFINITE_TIMEOUT_MS)
|
||||
_apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS
|
||||
}
|
||||
|
||||
private fun refreshBaseUrl() {
|
||||
_baseUrl = settings.getString("url", "")
|
||||
_baseUrl = settings.getString(BASE_URL, "")
|
||||
}
|
||||
|
||||
private fun refreshUsername() {
|
||||
_userName = settings.getString("login", "")
|
||||
_userName = settings.getString(LOGIN, "")
|
||||
}
|
||||
|
||||
private fun refreshPassword() {
|
||||
_password = settings.getString("password", "")
|
||||
_password = settings.getString(PASSWORD, "")
|
||||
}
|
||||
|
||||
private fun refreshArticleViewerEnabled() {
|
||||
_articleViewer = settings.getBoolean("prefer_article_viewer", true)
|
||||
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||
}
|
||||
|
||||
fun isArticleViewerEnabled(): Boolean {
|
||||
@ -125,7 +125,7 @@ class AppSettingsService {
|
||||
return _articleViewer == true
|
||||
}
|
||||
private fun refreshShouldBeCardViewEnabled() {
|
||||
_shouldBeCardView = settings.getBoolean("card_view_active", false)
|
||||
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
||||
}
|
||||
|
||||
fun isCardViewEnabled(): Boolean {
|
||||
@ -135,7 +135,7 @@ class AppSettingsService {
|
||||
return _shouldBeCardView == true
|
||||
}
|
||||
private fun refreshDisplayUnreadCountEnabled() {
|
||||
_displayUnreadCount = settings.getBoolean("display_unread_count", true)
|
||||
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
||||
}
|
||||
|
||||
fun isDisplayUnreadCountEnabled(): Boolean {
|
||||
@ -145,7 +145,7 @@ class AppSettingsService {
|
||||
return _displayUnreadCount == true
|
||||
}
|
||||
private fun refreshDisplayAllCountEnabled() {
|
||||
_displayAllCount = settings.getBoolean("display_other_count", false)
|
||||
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
||||
}
|
||||
|
||||
fun isDisplayAllCountEnabled(): Boolean {
|
||||
@ -155,7 +155,7 @@ class AppSettingsService {
|
||||
return _displayAllCount == true
|
||||
}
|
||||
private fun refreshFullHeightCardsEnabled() {
|
||||
_fullHeightCards = settings.getBoolean("full_height_cards", false)
|
||||
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
||||
}
|
||||
|
||||
fun isFullHeightCardsEnabled(): Boolean {
|
||||
@ -165,7 +165,7 @@ class AppSettingsService {
|
||||
return _fullHeightCards == true
|
||||
}
|
||||
private fun refreshUpdateSourcesEnabled() {
|
||||
_updateSources = settings.getBoolean("update_sources", true)
|
||||
_updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
||||
}
|
||||
|
||||
fun isUpdateSourcesEnabled(): Boolean {
|
||||
@ -175,7 +175,7 @@ class AppSettingsService {
|
||||
return _updateSources == true
|
||||
}
|
||||
private fun refreshPeriodicRefreshEnabled() {
|
||||
_periodicRefresh = settings.getBoolean("periodic_refresh", false)
|
||||
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
||||
}
|
||||
|
||||
fun isPeriodicRefreshEnabled(): Boolean {
|
||||
@ -186,7 +186,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
||||
_refreshWhenChargingOnly = settings.getBoolean("refresh_when_charging", false)
|
||||
_refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
||||
}
|
||||
|
||||
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
||||
@ -197,22 +197,22 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshRefreshMinutes() {
|
||||
_refreshMinutes = settings.getString("periodic_refresh_minutes", "360").toLong()
|
||||
_refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||
if (_refreshMinutes <= 15) {
|
||||
_refreshMinutes = 15
|
||||
}
|
||||
}
|
||||
|
||||
fun getRefreshMinutes(): Long {
|
||||
if (_refreshMinutes != null) {
|
||||
if (_refreshMinutes != 360L) {
|
||||
refreshRefreshMinutes()
|
||||
}
|
||||
return _refreshMinutes
|
||||
}
|
||||
|
||||
private fun refreshHiddenTags() {
|
||||
if (settings.getString("hidden_tags", "").isNotEmpty()) {
|
||||
_hiddenTags = settings.getString("hidden_tags", "").replace("\\s".toRegex(), "").split(",")
|
||||
if (settings.getString(HIDDEN_TAGS, "").isNotEmpty()) {
|
||||
_hiddenTags = settings.getString(HIDDEN_TAGS, "").replace("\\s".toRegex(), "").split(",")
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshInfiniteLoadingEnabled() {
|
||||
_infiniteLoading = settings.getBoolean("infinite_loading", false)
|
||||
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
||||
}
|
||||
|
||||
fun isInfiniteLoadingEnabled(): Boolean {
|
||||
@ -235,7 +235,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshItemCachingEnabled() {
|
||||
_itemsCaching = settings.getBoolean("items_caching", false)
|
||||
_itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
||||
}
|
||||
|
||||
fun isItemCachingEnabled(): Boolean {
|
||||
@ -246,7 +246,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshNotifyNewItemsEnabled() {
|
||||
_notifyNewItems = settings.getBoolean("notify_new_items", false)
|
||||
_notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
||||
}
|
||||
|
||||
fun isNotifyNewItemsEnabled(): Boolean {
|
||||
@ -258,7 +258,7 @@ class AppSettingsService {
|
||||
|
||||
|
||||
private fun refreshMarkOnScrollEnabled() {
|
||||
_markOnScroll = settings.getBoolean("mark_on_scroll", false)
|
||||
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
||||
}
|
||||
|
||||
fun isMarkOnScrollEnabled(): Boolean {
|
||||
@ -270,7 +270,7 @@ class AppSettingsService {
|
||||
|
||||
|
||||
private fun refreshActiveAllignment() {
|
||||
_activeAlignment = settings.getInt("text_align", JUSTIFY)
|
||||
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
||||
}
|
||||
|
||||
fun getActiveAllignment(): Int {
|
||||
@ -281,12 +281,12 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
fun changeAllignment(allignment: Int) {
|
||||
settings.putInt("text_align", allignment)
|
||||
settings.putInt(TEXT_ALIGN, allignment)
|
||||
_activeAlignment = allignment
|
||||
}
|
||||
|
||||
private fun refreshFontSize() {
|
||||
_fontSize = settings.getString("reader_font_size", "16").toInt()
|
||||
_fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
||||
}
|
||||
|
||||
fun getFontSize(): Int {
|
||||
@ -297,7 +297,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshStaticBarEnabled() {
|
||||
_staticBar = settings.getBoolean("reader_static_bar", false)
|
||||
_staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||
}
|
||||
|
||||
fun isStaticBarEnabled(): Boolean {
|
||||
@ -308,11 +308,11 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
private fun refreshFont() {
|
||||
_font = settings.getString("reader_font", "")
|
||||
_font = settings.getString(READER_FONT, "")
|
||||
}
|
||||
|
||||
fun getFont(): String {
|
||||
if (_font != null) {
|
||||
if (_font.isEmpty()) {
|
||||
refreshFont()
|
||||
}
|
||||
return _font
|
||||
@ -353,21 +353,21 @@ class AppSettingsService {
|
||||
login: String,
|
||||
password: String
|
||||
) {
|
||||
settings.putString("url", url)
|
||||
settings.putString("login", login)
|
||||
settings.putString("password", password)
|
||||
settings.putString(BASE_URL, url)
|
||||
settings.putString(LOGIN, login)
|
||||
settings.putString(PASSWORD, password)
|
||||
refreshApiSettings()
|
||||
}
|
||||
|
||||
fun resetLoginInformation() {
|
||||
settings.remove("url")
|
||||
settings.remove("login")
|
||||
settings.remove("password")
|
||||
settings.remove(BASE_URL)
|
||||
settings.remove(LOGIN)
|
||||
settings.remove(PASSWORD)
|
||||
refreshApiSettings()
|
||||
}
|
||||
|
||||
fun updateApiVersion(apiMajorVersion: Int) {
|
||||
settings.putInt("apiVersionMajor", apiMajorVersion)
|
||||
settings.putInt(API_VERSION_MAJOR, apiMajorVersion)
|
||||
refreshApiVersion()
|
||||
}
|
||||
|
||||
@ -378,7 +378,7 @@ class AppSettingsService {
|
||||
}
|
||||
|
||||
fun disableArticleViewer() {
|
||||
settings.putBoolean("prefer_article_viewer", false)
|
||||
settings.putBoolean(PREFER_ARTICLE_VIEWER, false)
|
||||
refreshArticleViewerEnabled()
|
||||
}
|
||||
|
||||
@ -396,5 +396,53 @@ class AppSettingsService {
|
||||
const val JUSTIFY = 1
|
||||
|
||||
const val ALIGN_LEFT = 2
|
||||
|
||||
const val API_VERSION_MAJOR = "apiVersionMajor"
|
||||
|
||||
const val API_ITEMS_NUMBER = "prefer_api_items_number"
|
||||
|
||||
const val API_TIMEOUT = "api_timeout"
|
||||
|
||||
const val BASE_URL = "url"
|
||||
|
||||
const val LOGIN = "login"
|
||||
|
||||
const val PASSWORD = "password"
|
||||
|
||||
const val PREFER_ARTICLE_VIEWER = "prefer_article_viewer"
|
||||
|
||||
const val CARD_VIEW_ACTIVE = "card_view_active"
|
||||
|
||||
const val DISPLAY_UNREAD_COUNT = "display_unread_count"
|
||||
|
||||
const val DISPLAY_OTHER_COUNT = "display_other_count"
|
||||
|
||||
const val FULL_HEIGHT_CARDS = "full_height_cards"
|
||||
|
||||
const val UPDATE_SOURCES = "update_sources"
|
||||
|
||||
const val PERIODIC_REFRESH = "periodic_refresh"
|
||||
|
||||
const val REFRESH_WHEN_CHARGING = "refresh_when_charging"
|
||||
|
||||
const val READER_FONT = "reader_font"
|
||||
|
||||
const val READER_STATIC_BAR = "reader_static_bar"
|
||||
|
||||
const val READER_FONT_SIZE = "reader_font_size"
|
||||
|
||||
const val TEXT_ALIGN = "text_align"
|
||||
|
||||
const val MARK_ON_SCROLL = "mark_on_scroll"
|
||||
|
||||
const val NOTIFY_NEW_ITEMS = "notify_new_items"
|
||||
|
||||
const val PERIODIC_REFRESH_MINUTES = "periodic_refresh_minutes"
|
||||
|
||||
const val HIDDEN_TAGS = "hidden_tags"
|
||||
|
||||
const val INFINITE_LOADING = "infinite_loading"
|
||||
|
||||
const val ITEMS_CACHING = "items_caching"
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2.service
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
|
||||
class SearchService(val dateUtils: DateUtils) {
|
||||
var displayedItems: String = "unread"
|
||||
set(value) {
|
||||
field = when (value) {
|
||||
"all" -> "all"
|
||||
"unread" -> "unread"
|
||||
"read" -> "read"
|
||||
"starred" -> "starred"
|
||||
else -> "all"
|
||||
}
|
||||
}
|
||||
|
||||
var position = 0
|
||||
var searchFilter: String? = null
|
||||
var sourceIDFilter: Long? = null
|
||||
var sourceFilter: String? = null
|
||||
var tagFilter: String? = null
|
||||
var itemsCaching = false
|
||||
|
||||
var badgeUnread = -1
|
||||
var badgeAll = -1
|
||||
var badgeStarred = -1
|
||||
}
|
@ -1,16 +1,9 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
expect class DateUtils() {
|
||||
companion object {
|
||||
fun parseDate(dateString: String): Long
|
||||
|
||||
|
||||
fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long =
|
||||
dateUtils.parseDate(this.datetime)
|
||||
|
||||
expect class DateUtils constructor(appSettingsService: AppSettingsService) {
|
||||
val appSettingsService: AppSettingsService // This is needed because of https://stackoverflow.com/a/65249085
|
||||
|
||||
fun parseDate(dateString: String): Long
|
||||
|
||||
fun parseRelativeDate(dateString: String): String
|
||||
fun parseRelativeDate(dateString: String): String
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +65,6 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
||||
this.thumbnail,
|
||||
this.icon,
|
||||
this.link,
|
||||
this.title.getHtmlDecoded(),
|
||||
this.sourcetitle.getHtmlDecoded(),
|
||||
this.tags.joinToString(",")
|
||||
)
|
@ -1,14 +1,13 @@
|
||||
package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
package bou.amine.apps.readerforselfossv2
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class IosGreetingTest {
|
||||
|
||||
@Test
|
||||
fun testExample() {
|
||||
assertTrue(Greeting().greeting().contains("iOS"), "Check iOS is mentioned")
|
||||
}
|
||||
}
|
@ -2,13 +2,15 @@ package bou.amine.apps.readerforselfossv2.utils
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||
|
||||
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
actual class DateUtils {
|
||||
actual companion object {
|
||||
actual fun parseDate(dateString: String): Long {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
actual fun parseRelativeDate(dateString: String): String {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user