Compare commits

...

29 Commits

Author SHA1 Message Date
e36189e2e7 Merge pull request 'About config upgrade.' (#93) from aboutconfig into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/93
2022-11-05 21:20:17 +00:00
d6bdf510a4 About config upgrade. 2022-11-05 22:00:16 +01:00
a464e93370 Merge pull request 'Immediately update bottom badges after reading or starring articles' (#91) from davidoskky/ReaderForSelfoss-multiplatform:badges into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/91
2022-11-02 19:06:48 +00:00
4b63afe62a Update badges tests 2022-11-01 21:51:46 +01:00
ac4c4b9441 Merge branch 'master' into badges 2022-11-01 20:35:13 +00:00
16b10dc1b7 Merge pull request 'Show all sources in the sources list' (#90) from davidoskky/ReaderForSelfoss-multiplatform:sources into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/90
2022-11-01 20:30:30 +00:00
02d734eee8 Do not edit the repository items from outside the repository 2022-11-01 21:29:04 +01:00
c5cdfc0d53 Update bottom bar badges through a state flow 2022-11-01 21:28:14 +01:00
6d610ed61a Fix repeating items in recyclerview 2022-11-01 19:53:22 +01:00
792950be7c Remove unreachable condition 2022-11-01 19:52:43 +01:00
af8969ce4a Merge pull request 'Cleaning.' (#88) from cleaning into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/88
2022-10-31 20:42:20 +00:00
27c55e59a1 Cleaning still. 2022-10-31 21:28:11 +01:00
94a0747947 More cleaning. 2022-10-30 22:02:07 +01:00
d862bfba4f Still cleaning. 2022-10-30 21:38:04 +01:00
b0d1d9c29a No daemon 2022-10-30 21:27:53 +01:00
7b40a31979 Cleaning. 2022-10-30 21:21:43 +01:00
823a8c3692 Date formatter 2022-10-30 21:12:01 +01:00
5494978db8 Cleaning. 2022-10-29 22:58:25 +02:00
6076eb1cee Merge pull request 'chore/mock-multiplatform' (#86) from chore/mock-multiplatform into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/86
2022-10-29 12:10:51 +00:00
131101d2ee Date api issues. 2022-10-29 13:50:00 +02:00
62ad1f45ba Unit tests are on the android side. 2022-10-29 13:37:46 +02:00
402d18b889 Versions update. SettingsKey constants. 2022-10-28 20:32:23 +02:00
e32699c93f Merge pull request 'ios/init' (#85) from ios/init into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/85
2022-10-24 18:18:16 +00:00
059a237b99 Commenting tests for now. 2022-10-23 20:39:09 +02:00
d2bdbae6c8 Launching ios APP. 2022-10-23 20:38:43 +02:00
510fcbe47e Merge pull request 'Prevent crash when logging in' (#81) from davidoskky/ReaderForSelfoss-multiplatform:login_crash into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/81
2022-10-22 19:53:59 +00:00
667e9c1a5d Adjust tests to changes in the repository 2022-10-21 22:56:35 +02:00
53b1d1f8b2 Rework repository initialization 2022-10-21 22:42:32 +02:00
c25e8889a4 Prevent crash when logging in 2022-10-17 19:35:52 +02:00
32 changed files with 2164 additions and 1719 deletions

View File

@ -6,16 +6,19 @@ steps:
- name: AnylyseBuildTest - name: AnylyseBuildTest
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: 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 "---------------------------------------------------------"
- echo "Analysing..." - 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 "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Building..." - echo "Building..."
- ./gradlew :androidApp:build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false - ./gradlew build
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Testing..." - echo "Testing..."
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false - ./gradlew test
environment: environment:
SONAR_HOST_URL: SONAR_HOST_URL:
from_secret: sonarScannerHostUrl from_secret: sonarScannerHostUrl
@ -85,8 +88,12 @@ steps:
- name: build - name: build
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: 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" - 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 "---------------------------------------------------------"
- echo "Get Key" - echo "Get Key"
- wget https://amine-louveau.fr/key - wget https://amine-louveau.fr/key

View File

@ -6,6 +6,7 @@ plugins {
id("com.android.application") id("com.android.application")
kotlin("android") kotlin("android")
kotlin("kapt") kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
} }
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@ -54,11 +55,15 @@ fun versionNameFromGit(): String {
android { android {
compileOptions { compileOptions {
// Flag to enable support for the new language APIs // Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true sourceCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_1_8
} }
compileSdk = 31
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
}
compileSdk = 33
buildToolsVersion = "31.0.0" buildToolsVersion = "31.0.0"
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
@ -66,7 +71,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android" applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21 minSdk = 21
targetSdk = 31 targetSdk = 33
versionCode = versionCodeFromGit() versionCode = versionCodeFromGit()
versionName = versionNameFromGit() versionName = versionNameFromGit()
@ -79,6 +84,11 @@ android {
// tests // tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes { buildTypes {
getByName("release") { getByName("release") {
isMinifyEnabled = true isMinifyEnabled = true
@ -98,9 +108,6 @@ android {
dimension = "build" dimension = "build"
} }
} }
kotlinOptions {
jvmTarget = "1.8"
}
namespace = "bou.amine.apps.readerforselfossv2.android" namespace = "bou.amine.apps.readerforselfossv2.android"
} }
@ -114,13 +121,6 @@ dependencies {
implementation("androidx.preference:preference-ktx:1.1.1") 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"))) implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// Android Support // Android Support
@ -135,18 +135,12 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.3") implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jsoup:jsoup:1.14.3") implementation("org.jsoup:jsoup:1.14.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
//multidex //multidex
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
// About // About
implementation("com.mikepenz:aboutlibraries-core:8.9.4") implementation("com.mikepenz:aboutlibraries-core:10.5.1")
implementation("com.mikepenz:aboutlibraries:8.9.4") implementation("com.mikepenz:aboutlibraries:10.5.1")
implementation("com.mikepenz:aboutlibraries-definitions:8.9.4")
// Async
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
// Retrofit + http logging + okhttp // Retrofit + http logging + okhttp
implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:retrofit:2.9.0")
@ -188,14 +182,41 @@ dependencies {
implementation("androidx.core:core-ktx:1.8.0") 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") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
// Network information // Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT // 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
} }

View File

@ -90,3 +90,5 @@
# @Serializable and @Polymorphic are used at runtime for polymorphic serialization. # @Serializable and @Polymorphic are used at runtime for polymorphic serialization.
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault -keepattributes RuntimeVisibleAnnotations,AnnotationDefault
-dontwarn io.mockk.**
-keep class io.mockk.** { *; }

View File

@ -94,7 +94,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
try { try {
val items = repository.getSpouts() val items = repository.getSpouts()
if (items != null) { if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name } val itemsStrings = items.map { it.value.name }
for ((key, value) in items) { for ((key, value) in items) {
spoutsKV[value.name] = key spoutsKV[value.name] = key

View File

@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
@ -178,8 +179,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position) adapter.handleItemAtIndex(position)
reloadBadgeContent()
val tagHashes = i.tags.map { it.longHash() } val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map { tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) { if (tagHashes.contains(it.key)) {
@ -207,6 +206,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView) 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() { private fun handleBottomBar() {
tabNewBadge = TextBadgeItem() tabNewBadge = TextBadgeItem()
@ -219,6 +228,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .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 = val tabNew =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp, R.drawable.ic_tab_fiber_new_black_24dp,
@ -714,29 +745,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadges() { private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) { if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.reloadBadges() 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() { private fun reloadTagsBadges() {
tagsBadge.forEach { tagsBadge.forEach {
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString())) binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
@ -858,10 +872,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun maxItemNumber(): Int = private fun maxItemNumber(): Int =
when (elementsShown) { when (elementsShown) {
ItemType.UNREAD -> repository.badgeUnread ItemType.UNREAD -> repository.badgeUnread.value
ItemType.ALL -> repository.badgeAll ItemType.ALL -> repository.badgeAll.value
ItemType.STARRED -> repository.badgeStarred ItemType.STARRED -> repository.badgeStarred.value
else -> repository.badgeUnread // if !elementsShown then unread are fetched. else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
} }
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) { private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {

View File

@ -93,12 +93,15 @@ class LoginActivity : AppCompatActivity(), DIAware {
} }
private fun goToMain() { private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch {
repository.updateApiVersion()
}
val intent = Intent(this, HomeActivity::class.java) val intent = Intent(this, HomeActivity::class.java)
startActivity(intent) startActivity(intent)
finish() finish()
} }
private fun preferenceError(t: Throwable) { private fun preferenceError() {
appSettingsService.resetLoginInformation() appSettingsService.resetLoginInformation()
binding.urlView.error = getString(R.string.wrong_infos) binding.urlView.error = getString(R.string.wrong_infos)
@ -166,7 +169,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
goToMain() goToMain()
} else { } else {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
preferenceError(Exception("Not success")) preferenceError()
} }
} }
} }

View File

@ -14,6 +14,7 @@ import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDexApplication import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import bou.amine.apps.readerforselfossv2.DI.networkModule 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.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.dao.DriverFactory import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
@ -28,6 +29,7 @@ import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kodein.di.* import org.kodein.di.*
@ -37,7 +39,7 @@ class MyApp : MultiDexApplication(), DIAware {
import(networkModule) import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) } bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) } 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<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
} }
@ -47,6 +49,9 @@ class MyApp : MultiDexApplication(), DIAware {
private val connectivityStatus: ConnectivityStatus by instance() private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance() private val driverFactory: DriverFactory by instance()
// TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Napier.base(DebugAntilog()) Napier.base(DebugAntilog())

View File

@ -36,9 +36,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private fun showMenuItem(willAddToFavorite: Boolean) { private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) { if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE) toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
} else { } else {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED) toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
} }
} }

View File

@ -56,20 +56,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources() val response = repository.getSources()
if (response != null) { if (response.isNotEmpty()) {
items = response items = response
val mAdapter = SourcesListAdapter( val mAdapter = SourcesListAdapter(
this@SourcesActivity, items this@SourcesActivity, items
) )
binding.recyclerView.adapter = mAdapter binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
if (items.isEmpty()) {
Toast.makeText(
this@SourcesActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
} else { } else {
Toast.makeText( Toast.makeText(
this@SourcesActivity, this@SourcesActivity,

View File

@ -10,9 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString 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.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable 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.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@ -59,7 +62,7 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils) binding.sourceTitleAndDate.text = itm.sourceAndDateText()
if (!appSettingsService.isFullHeightCardsEnabled()) { if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight binding.itemImage.maxHeight = imageMaxHeight
@ -108,13 +111,11 @@ class ItemCardAdapter(
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item) repository.unstarr(item)
} }
item.starred = false
binding.favButton.isSelected = false binding.favButton.isSelected = false
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.starr(item) repository.starr(item)
} }
item.starred = true
binding.favButton.isSelected = true binding.favButton.isSelected = true
} }
} }

View File

@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) 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()) { if (itm.getThumbnail(repository.baseUrl).isEmpty()) {

View File

@ -61,9 +61,13 @@ class SourcesListAdapter(
binding.sourceTitle.text = itm.title.getHtmlDecoded() 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 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 { init {
handleClickListeners() handleClickListeners()
@ -74,13 +78,13 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener { deleteBtn.setOnClickListener {
val (id) = items[adapterPosition] val (id) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id) val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) { if (successfullyDeletedSource) {
items.removeAt(adapterPosition) items.removeAt(bindingAdapterPosition)
notifyItemRemoved(adapterPosition) notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount) notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else { } else {
Toast.makeText( Toast.makeText(
app, app,

View File

@ -101,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content contentText = item.content
contentTitle = item.title.getHtmlDecoded() contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl) contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText(repository.dateUtils) contentSource = item.sourceAndDateText()
allImages = item.getImages() allImages = item.getImages()
fontSize = appSettingsService.getFontSize() fontSize = appSettingsService.getFontSize()
@ -354,6 +354,7 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.settings.javaScriptEnabled = false binding.webcontent.settings.javaScriptEnabled = false
binding.webcontent.webViewClient = object : WebViewClient() { binding.webcontent.webViewClient = object : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean { override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
@ -361,6 +362,7 @@ class ArticleFragment : Fragment(), DIAware {
return true return true
} }
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? { override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) { 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() { val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick() return performClick()
} }
}) })

View 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)
}
}

File diff suppressed because it is too large Load Diff

View 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>Letica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>Lultimo è 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 lintelligenza 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 lorigine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dellinnovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto allattenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>Largomento, 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>Lembrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nellofferta 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 lAteneo 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"
}

View File

@ -1,21 +1,20 @@
buildscript { buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies { 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 // 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") apply(plugin = "org.sonarqube")
allprojects { allprojects {
@ -27,6 +26,7 @@ allprojects {
} }
} }
tasks.register("clean", Delete::class) { tasks.register("clean", Delete::class) {
delete(rootProject.buildDir) delete(rootProject.buildDir)
} }

View File

@ -11,14 +11,26 @@
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true # org.gradle.parallel=true
#Tue Mar 22 16:50:00 CET 2022 #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.code.style=official
kotlin.mpp.enableCInteropCommonization=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" #Android
kotlin.native.enableDependencyPropagation=false
android.useAndroidX=true android.useAndroidX=true
kotlin.native.enableDependencyPropagation=false
#android.nonTransitiveRClass=true
android.enableJetifier=true android.enableJetifier=true
#MPP
kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.mpp.enableGranularSourceSetsMetadata=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
ignoreGitVersion=false ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none
pushCache=true pushCache=true

View File

@ -1,383 +1,383 @@
// !$*UTF8*$! // !$*UTF8*$!
{ {
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 50; objectVersion = 50;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; };
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview 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 */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
7555FFB4242A642300829871 /* Embed Frameworks */ = { 7555FFB4242A642300829871 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
dstPath = ""; dstPath = "";
dstSubfolderSpec = 10; dstSubfolderSpec = 10;
files = ( files = (
); );
name = "Embed Frameworks"; name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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; }; 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>"; }; 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>"; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
7555FF78242A565900829871 /* Frameworks */ = { 7555FF78242A565900829871 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
058557D7273AAEEB004C7B11 /* Preview Content */ = { 058557D7273AAEEB004C7B11 /* Preview Content */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */,
); );
path = "Preview Content"; path = "Preview Content";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7555FF72242A565900829871 = { 7555FF72242A565900829871 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7555FF7D242A565900829871 /* iosApp */, 7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */, 7555FF7C242A565900829871 /* Products */,
7555FFB0242A642200829871 /* Frameworks */, 7555FFB0242A642200829871 /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7555FF7C242A565900829871 /* Products */ = { 7555FF7C242A565900829871 /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
7555FF7B242A565900829871 /* iosApp.app */, 7555FF7B242A565900829871 /* iosApp.app */,
); );
name = Products; name = Products;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7555FF7D242A565900829871 /* iosApp */ = { 7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
058557BA273AAA24004C7B11 /* Assets.xcassets */, 058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */, 7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */, 7555FF8C242A565B00829871 /* Info.plist */,
2152FB032600AC8F00CF470E /* iOSApp.swift */, 2152FB032600AC8F00CF470E /* iOSApp.swift */,
058557D7273AAEEB004C7B11 /* Preview Content */, 058557D7273AAEEB004C7B11 /* Preview Content */,
); );
path = iosApp; path = iosApp;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
7555FFB0242A642200829871 /* Frameworks */ = { 7555FFB0242A642200829871 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
); );
name = Frameworks; name = Frameworks;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
/* End PBXGroup section */ /* End PBXGroup section */
/* Begin PBXNativeTarget section */ /* Begin PBXNativeTarget section */
7555FF7A242A565900829871 /* iosApp */ = { 7555FF7A242A565900829871 /* iosApp */ = {
isa = PBXNativeTarget; isa = PBXNativeTarget;
buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
buildPhases = ( buildPhases = (
7555FFB5242A651A00829871 /* ShellScript */, 7555FFB5242A651A00829871 /* ShellScript */,
7555FF77242A565900829871 /* Sources */, 7555FF77242A565900829871 /* Sources */,
7555FF78242A565900829871 /* Frameworks */, 7555FF78242A565900829871 /* Frameworks */,
7555FF79242A565900829871 /* Resources */, 7555FF79242A565900829871 /* Resources */,
7555FFB4242A642300829871 /* Embed Frameworks */, 7555FFB4242A642300829871 /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
dependencies = ( dependencies = (
); );
name = iosApp; name = iosApp;
productName = iosApp; productName = iosApp;
productReference = 7555FF7B242A565900829871 /* iosApp.app */; productReference = 7555FF7B242A565900829871 /* iosApp.app */;
productType = "com.apple.product-type.application"; productType = "com.apple.product-type.application";
}; };
/* End PBXNativeTarget section */ /* End PBXNativeTarget section */
/* Begin PBXProject section */ /* Begin PBXProject section */
7555FF73242A565900829871 /* Project object */ = { 7555FF73242A565900829871 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1130; LastSwiftUpdateCheck = 1130;
LastUpgradeCheck = 1130; LastUpgradeCheck = 1130;
ORGANIZATIONNAME = orgName; ORGANIZATIONNAME = orgName;
TargetAttributes = { TargetAttributes = {
7555FF7A242A565900829871 = { 7555FF7A242A565900829871 = {
CreatedOnToolsVersion = 11.3.1; CreatedOnToolsVersion = 11.3.1;
}; };
}; };
}; };
buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
compatibilityVersion = "Xcode 9.3"; compatibilityVersion = "Xcode 9.3";
developmentRegion = en; developmentRegion = en;
hasScannedForEncodings = 0; hasScannedForEncodings = 0;
knownRegions = ( knownRegions = (
en, en,
Base, Base,
); );
mainGroup = 7555FF72242A565900829871; mainGroup = 7555FF72242A565900829871;
productRefGroup = 7555FF7C242A565900829871 /* Products */; productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = ""; projectDirPath = "";
projectRoot = ""; projectRoot = "";
targets = ( targets = (
7555FF7A242A565900829871 /* iosApp */, 7555FF7A242A565900829871 /* iosApp */,
); );
}; };
/* End PBXProject section */ /* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */ /* Begin PBXResourcesBuildPhase section */
7555FF79242A565900829871 /* Resources */ = { 7555FF79242A565900829871 /* Resources */ = {
isa = PBXResourcesBuildPhase; isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */,
058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXResourcesBuildPhase section */ /* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */
7555FFB5242A651A00829871 /* ShellScript */ = { 7555FFB5242A651A00829871 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputFileListPaths = ( inputFileListPaths = (
); );
inputPaths = ( inputPaths = (
); );
outputFileListPaths = ( outputFileListPaths = (
); );
outputPaths = ( outputPaths = (
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n"; shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */
7555FF77242A565900829871 /* Sources */ = { 7555FF77242A565900829871 /* Sources */ = {
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */,
7555FF83242A565900829871 /* ContentView.swift in Sources */, 7555FF83242A565900829871 /* ContentView.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
/* End PBXSourcesBuildPhase section */ /* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */ /* Begin XCBuildConfiguration section */
7555FFA3242A565B00829871 /* Debug */ = { 7555FFA3242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES; ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1", "DEBUG=1",
"$(inherited)", "$(inherited)",
); );
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
}; };
7555FFA4242A565B00829871 /* Release */ = { 7555FFA4242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO; ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++"; CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES; CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES; CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 14.1; IPHONEOS_DEPLOYMENT_TARGET = 14.1;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
}; };
name = Release; name = Release;
}; };
7555FFA6242A565B00829871 /* Debug */ = { 7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-framework", "-framework",
shared, shared,
); );
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Debug; name = Debug;
}; };
7555FFA7242A565B00829871 /* Release */ = { 7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)"; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)";
INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_FILE = iosApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
OTHER_LDFLAGS = ( OTHER_LDFLAGS = (
"$(inherited)", "$(inherited)",
"-framework", "-framework",
shared, shared,
); );
PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp; PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosApp;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
}; };
name = Release; name = Release;
}; };
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
7555FFA3242A565B00829871 /* Debug */, 7555FFA3242A565B00829871 /* Debug */,
7555FFA4242A565B00829871 /* Release */, 7555FFA4242A565B00829871 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
7555FFA6242A565B00829871 /* Debug */, 7555FFA6242A565B00829871 /* Debug */,
7555FFA7242A565B00829871 /* Release */, 7555FFA7242A565B00829871 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
}; };
/* End XCConfigurationList section */ /* End XCConfigurationList section */
}; };
rootObject = 7555FF73242A565900829871 /* Project object */; rootObject = 7555FF73242A565900829871 /* Project object */;
} }

View File

@ -2,12 +2,9 @@ import SwiftUI
import shared import shared
struct ContentView: View { struct ContentView: View {
let greet = Greeting().greeting()
let toto = SelfossApi().getItems()
var body: some View { var body: some View {
Text(greet) Text("ototot")
} }
} }

View File

@ -8,6 +8,14 @@ pluginManagement {
} }
} }
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
buildCache { buildCache {
remote<HttpBuildCache> { remote<HttpBuildCache> {
url = uri("http://18.0.0.7:3071/cache/") url = uri("http://18.0.0.7:3071/cache/")

View File

@ -1,7 +1,7 @@
object SqlDelight { object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.3" const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "com.squareup.sqldelight:android-driver:1.5.3" const val android = "com.squareup.sqldelight:android-driver:1.5.4"
const val native = "com.squareup.sqldelight:native-driver:1.5.3" const val native = "com.squareup.sqldelight:native-driver:1.5.4"
} }
@ -18,7 +18,7 @@ kotlin {
listOf( listOf(
iosX64(), iosX64(),
iosArm64(), iosArm64(),
//iosSimulatorArm64() sure all ios dependencies support this target // iosSimulatorArm64()
).forEach { ).forEach {
it.binaries.framework { it.binaries.framework {
baseName = "shared" baseName = "shared"
@ -40,14 +40,11 @@ kotlin {
implementation("org.kodein.di:kodein-di:7.12.0") implementation("org.kodein.di:kodein-di:7.12.0")
//Settings //Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9") implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
//Logging //Logging
implementation("io.github.aakira:napier:2.6.1") implementation("io.github.aakira:napier:2.6.1")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// Sql // Sql
implementation(SqlDelight.runtime) implementation(SqlDelight.runtime)
} }
@ -56,13 +53,12 @@ kotlin {
dependencies { dependencies {
implementation(kotlin("test-common")) implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common")) implementation(kotlin("test-annotations-common"))
implementation("io.mockk:mockk:1.12.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
} }
} }
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation("io.ktor:ktor-client-okhttp:2.1.1") implementation("io.ktor:ktor-client-okhttp:2.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Sql // Sql
implementation(SqlDelight.android) implementation(SqlDelight.android)
@ -76,39 +72,36 @@ kotlin {
} }
val iosX64Main by getting val iosX64Main by getting
val iosArm64Main by getting val iosArm64Main by getting
//val iosSimulatorArm64Main by getting // val iosSimulatorArm64Main by getting
val iosMain by creating { val iosMain by creating {
dependsOn(commonMain) dependsOn(commonMain)
iosX64Main.dependsOn(this) iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this) iosArm64Main.dependsOn(this)
//iosSimulatorArm64Main.dependsOn(this) // iosSimulatorArm64Main.dependsOn(this)
// Sql
dependencies { dependencies {
implementation(SqlDelight.native) implementation(SqlDelight.native)
implementation("io.ktor:ktor-client-ios:2.1.1")
} }
} }
val iosX64Test by getting val iosX64Test by getting
val iosArm64Test by getting val iosArm64Test by getting
//val iosSimulatorArm64Test by getting // val iosSimulatorArm64Test by getting
val iosTest by creating { val iosTest by creating {
dependsOn(commonTest) dependsOn(commonTest)
iosX64Test.dependsOn(this) iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this) iosArm64Test.dependsOn(this)
dependencies { // iosSimulatorArm64Test.dependsOn(this)
implementation("io.ktor:ktor-client-ios:2.1.1")
}
//iosSimulatorArm64Test.dependsOn(this)
} }
} }
} }
android { android {
compileSdk = 31 compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig { defaultConfig {
minSdk = 21 minSdk = 21
targetSdk = 31 targetSdk = 32
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
@ -117,10 +110,11 @@ android {
namespace = "bou.amine.apps.readerforselfossv2" namespace = "bou.amine.apps.readerforselfossv2"
} }
sqldelight { sqldelight {
database("ReaderForSelfossDB") { database("ReaderForSelfossDB") {
packageName = "bou.amine.apps.readerforselfossv2.dao" packageName = "bou.amine.apps.readerforselfossv2.dao"
sourceFolders = listOf("sqldelight") sourceFolders = listOf("sqldelight")
} }
} }

View File

@ -1,36 +1,29 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils import android.text.format.DateUtils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import kotlinx.datetime.*
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
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) { val date = parseDate(dateString)
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
} else { return " " + DateUtils.getRelativeTimeSpanString(
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant( date,
ZoneOffset.UTC).toEpochMilli() 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
)
}
} }

View File

@ -108,8 +108,8 @@ class SelfossModel {
return stringUrl return stringUrl
} }
fun sourceAndDateText(dateUtils: DateUtils): String = fun sourceAndDateText(): String =
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime) this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
fun toggleStar(): Item { fun toggleStar(): Item {
this.starred = !this.starred this.starred = !this.starred

View File

@ -6,21 +6,19 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.rest.SelfossApi import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.* import bou.amine.apps.readerforselfossv2.utils.*
import com.github.ln_12.library.ConnectivityStatus
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
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>() var items = ArrayList<SelfossModel.Item>()
val isConnectionAvailable = connectivityStatus.isNetworkConnected
var connectionMonitored = false var connectionMonitored = false
var baseUrl = appSettingsService.getBaseUrl() var baseUrl = appSettingsService.getBaseUrl()
lateinit var dateUtils: DateUtils
var displayedItems = ItemType.UNREAD var displayedItems = ItemType.UNREAD
@ -30,26 +28,16 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var offlineOverride = false var offlineOverride = false
var badgeUnread = 0 private val _badgeUnread = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeUnread = _badgeUnread.asStateFlow()
var badgeAll = 0 private val _badgeAll = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeAll = _badgeAll.asStateFlow()
var badgeStarred = 0 private val _badgeStarred = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeStarred = _badgeStarred.asStateFlow()
private var fetchedSources = false private var fetchedSources = false
private var fetchedTags = false private var fetchedTags = false
init {
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
connectivityStatus.start()
runBlocking {
updateApiVersion()
dateUtils = DateUtils(appSettingsService)
reloadBadges()
}
}
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Use the updatedSince parameter // TODO: Use the updatedSince parameter
var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error() var fetchedItems: SelfossModel.StatusAndData<List<SelfossModel.Item>> = SelfossModel.StatusAndData.error()
@ -86,7 +74,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (fetchedItems.success && fetchedItems.data != null) { if (fetchedItems.success && fetchedItems.data != null) {
items = ArrayList(fetchedItems.data!!) items = ArrayList(fetchedItems.data!!)
if (fromDB) { if (fromDB) {
items.sortByDescending { dateUtils.parseDate(it.datetime) } items.sortByDescending { DateUtils.parseDate(it.datetime) }
} }
} }
return items return items
@ -138,17 +126,17 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val response = api.stats() val response = api.stats()
if (response.success && response.data != null) { if (response.success && response.data != null) {
badgeUnread = response.data.unread _badgeUnread.value = response.data.unread
badgeAll = response.data.total _badgeAll.value = response.data.total
badgeStarred = response.data.starred _badgeStarred.value = response.data.starred
success = true success = true
} }
} else if (appSettingsService.isItemCachingEnabled()) { } else if (appSettingsService.isItemCachingEnabled()) {
// TODO: do this differently, because it's not efficient // TODO: do this differently, because it's not efficient
val dbItems = getDBItems() val dbItems = getDBItems()
badgeUnread = dbItems.filter { item -> item.unread }.size _badgeUnread.value = dbItems.filter { item -> item.unread }.size
badgeStarred = dbItems.filter { item -> item.starred }.size _badgeStarred.value = dbItems.filter { item -> item.starred }.size
badgeAll = dbItems.size _badgeAll.value = dbItems.size
success = true success = true
} }
return success return success
@ -296,7 +284,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun markAsReadLocally(item: SelfossModel.Item) { private fun markAsReadLocally(item: SelfossModel.Item) {
if (item.unread) { if (item.unread) {
item.unread = false item.unread = false
badgeUnread -= 1 _badgeUnread.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -307,7 +295,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unmarkAsReadLocally(item: SelfossModel.Item) { private fun unmarkAsReadLocally(item: SelfossModel.Item) {
if (!item.unread) { if (!item.unread) {
item.unread = true item.unread = true
badgeUnread += 1 _badgeUnread.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -318,7 +306,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun starrLocally(item: SelfossModel.Item) { private fun starrLocally(item: SelfossModel.Item) {
if (!item.starred) { if (!item.starred) {
item.starred = true item.starred = true
badgeStarred += 1 _badgeStarred.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -329,7 +317,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unstarrLocally(item: SelfossModel.Item) { private fun unstarrLocally(item: SelfossModel.Item) {
if (item.starred) { if (item.starred) {
item.starred = false item.starred = false
badgeStarred -= 1 _badgeStarred.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -383,9 +371,6 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
try { try {
val response = api.login() val response = api.login()
result = response.isSuccess == true result = response.isSuccess == true
if (result) {
updateApiVersion()
}
} catch (cause: Throwable) { } catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote") Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
} }
@ -399,7 +384,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
api.refreshLoginInformation() api.refreshLoginInformation()
} }
private suspend fun updateApiVersion() { suspend fun updateApiVersion() {
val apiMajorVersion = appSettingsService.getApiVersion() val apiMajorVersion = appSettingsService.getApiVersion()
if (isNetworkAvailable()) { if (isNetworkAvailable()) {

View File

@ -54,8 +54,8 @@ class AppSettingsService {
return _apiVersion return _apiVersion
} }
fun refreshApiVersion() { private fun refreshApiVersion() {
_apiVersion = settings.getInt("apiVersionMajor", -1) _apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
} }
fun getBaseUrl(): String { fun getBaseUrl(): String {
@ -86,8 +86,8 @@ class AppSettingsService {
return _itemsNumber!! return _itemsNumber!!
} }
fun refreshItemsNumber() { private fun refreshItemsNumber() {
_itemsNumber = settings.getString("prefer_api_items_number", "20").toInt() _itemsNumber = settings.getString(API_ITEMS_NUMBER, "20").toInt()
} }
fun getApiTimeout(): Long { fun getApiTimeout(): Long {
@ -98,24 +98,24 @@ class AppSettingsService {
} }
private fun refreshApiTimeout() { 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 _apiTimeout = if (settingsTimeout > 0) settingsTimeout else HttpTimeout.INFINITE_TIMEOUT_MS
} }
private fun refreshBaseUrl() { private fun refreshBaseUrl() {
_baseUrl = settings.getString("url", "") _baseUrl = settings.getString(BASE_URL, "")
} }
private fun refreshUsername() { private fun refreshUsername() {
_userName = settings.getString("login", "") _userName = settings.getString(LOGIN, "")
} }
private fun refreshPassword() { private fun refreshPassword() {
_password = settings.getString("password", "") _password = settings.getString(PASSWORD, "")
} }
private fun refreshArticleViewerEnabled() { private fun refreshArticleViewerEnabled() {
_articleViewer = settings.getBoolean("prefer_article_viewer", true) _articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
} }
fun isArticleViewerEnabled(): Boolean { fun isArticleViewerEnabled(): Boolean {
@ -125,7 +125,7 @@ class AppSettingsService {
return _articleViewer == true return _articleViewer == true
} }
private fun refreshShouldBeCardViewEnabled() { private fun refreshShouldBeCardViewEnabled() {
_shouldBeCardView = settings.getBoolean("card_view_active", false) _shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
} }
fun isCardViewEnabled(): Boolean { fun isCardViewEnabled(): Boolean {
@ -135,7 +135,7 @@ class AppSettingsService {
return _shouldBeCardView == true return _shouldBeCardView == true
} }
private fun refreshDisplayUnreadCountEnabled() { private fun refreshDisplayUnreadCountEnabled() {
_displayUnreadCount = settings.getBoolean("display_unread_count", true) _displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
} }
fun isDisplayUnreadCountEnabled(): Boolean { fun isDisplayUnreadCountEnabled(): Boolean {
@ -145,7 +145,7 @@ class AppSettingsService {
return _displayUnreadCount == true return _displayUnreadCount == true
} }
private fun refreshDisplayAllCountEnabled() { private fun refreshDisplayAllCountEnabled() {
_displayAllCount = settings.getBoolean("display_other_count", false) _displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
} }
fun isDisplayAllCountEnabled(): Boolean { fun isDisplayAllCountEnabled(): Boolean {
@ -155,7 +155,7 @@ class AppSettingsService {
return _displayAllCount == true return _displayAllCount == true
} }
private fun refreshFullHeightCardsEnabled() { private fun refreshFullHeightCardsEnabled() {
_fullHeightCards = settings.getBoolean("full_height_cards", false) _fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
} }
fun isFullHeightCardsEnabled(): Boolean { fun isFullHeightCardsEnabled(): Boolean {
@ -165,7 +165,7 @@ class AppSettingsService {
return _fullHeightCards == true return _fullHeightCards == true
} }
private fun refreshUpdateSourcesEnabled() { private fun refreshUpdateSourcesEnabled() {
_updateSources = settings.getBoolean("update_sources", true) _updateSources = settings.getBoolean(UPDATE_SOURCES, true)
} }
fun isUpdateSourcesEnabled(): Boolean { fun isUpdateSourcesEnabled(): Boolean {
@ -175,7 +175,7 @@ class AppSettingsService {
return _updateSources == true return _updateSources == true
} }
private fun refreshPeriodicRefreshEnabled() { private fun refreshPeriodicRefreshEnabled() {
_periodicRefresh = settings.getBoolean("periodic_refresh", false) _periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
} }
fun isPeriodicRefreshEnabled(): Boolean { fun isPeriodicRefreshEnabled(): Boolean {
@ -186,7 +186,7 @@ class AppSettingsService {
} }
private fun refreshRefreshWhenChargingOnlyEnabled() { private fun refreshRefreshWhenChargingOnlyEnabled() {
_refreshWhenChargingOnly = settings.getBoolean("refresh_when_charging", false) _refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
} }
fun isRefreshWhenChargingOnlyEnabled(): Boolean { fun isRefreshWhenChargingOnlyEnabled(): Boolean {
@ -197,22 +197,22 @@ class AppSettingsService {
} }
private fun refreshRefreshMinutes() { private fun refreshRefreshMinutes() {
_refreshMinutes = settings.getString("periodic_refresh_minutes", "360").toLong() _refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
if (_refreshMinutes <= 15) { if (_refreshMinutes <= 15) {
_refreshMinutes = 15 _refreshMinutes = 15
} }
} }
fun getRefreshMinutes(): Long { fun getRefreshMinutes(): Long {
if (_refreshMinutes != null) { if (_refreshMinutes != 360L) {
refreshRefreshMinutes() refreshRefreshMinutes()
} }
return _refreshMinutes return _refreshMinutes
} }
private fun refreshHiddenTags() { private fun refreshHiddenTags() {
if (settings.getString("hidden_tags", "").isNotEmpty()) { if (settings.getString(HIDDEN_TAGS, "").isNotEmpty()) {
_hiddenTags = settings.getString("hidden_tags", "").replace("\\s".toRegex(), "").split(",") _hiddenTags = settings.getString(HIDDEN_TAGS, "").replace("\\s".toRegex(), "").split(",")
} }
} }
@ -224,7 +224,7 @@ class AppSettingsService {
} }
private fun refreshInfiniteLoadingEnabled() { private fun refreshInfiniteLoadingEnabled() {
_infiniteLoading = settings.getBoolean("infinite_loading", false) _infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
} }
fun isInfiniteLoadingEnabled(): Boolean { fun isInfiniteLoadingEnabled(): Boolean {
@ -235,7 +235,7 @@ class AppSettingsService {
} }
private fun refreshItemCachingEnabled() { private fun refreshItemCachingEnabled() {
_itemsCaching = settings.getBoolean("items_caching", false) _itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
} }
fun isItemCachingEnabled(): Boolean { fun isItemCachingEnabled(): Boolean {
@ -246,7 +246,7 @@ class AppSettingsService {
} }
private fun refreshNotifyNewItemsEnabled() { private fun refreshNotifyNewItemsEnabled() {
_notifyNewItems = settings.getBoolean("notify_new_items", false) _notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
} }
fun isNotifyNewItemsEnabled(): Boolean { fun isNotifyNewItemsEnabled(): Boolean {
@ -258,7 +258,7 @@ class AppSettingsService {
private fun refreshMarkOnScrollEnabled() { private fun refreshMarkOnScrollEnabled() {
_markOnScroll = settings.getBoolean("mark_on_scroll", false) _markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
} }
fun isMarkOnScrollEnabled(): Boolean { fun isMarkOnScrollEnabled(): Boolean {
@ -270,7 +270,7 @@ class AppSettingsService {
private fun refreshActiveAllignment() { private fun refreshActiveAllignment() {
_activeAlignment = settings.getInt("text_align", JUSTIFY) _activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
} }
fun getActiveAllignment(): Int { fun getActiveAllignment(): Int {
@ -281,12 +281,12 @@ class AppSettingsService {
} }
fun changeAllignment(allignment: Int) { fun changeAllignment(allignment: Int) {
settings.putInt("text_align", allignment) settings.putInt(TEXT_ALIGN, allignment)
_activeAlignment = allignment _activeAlignment = allignment
} }
private fun refreshFontSize() { private fun refreshFontSize() {
_fontSize = settings.getString("reader_font_size", "16").toInt() _fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
} }
fun getFontSize(): Int { fun getFontSize(): Int {
@ -297,7 +297,7 @@ class AppSettingsService {
} }
private fun refreshStaticBarEnabled() { private fun refreshStaticBarEnabled() {
_staticBar = settings.getBoolean("reader_static_bar", false) _staticBar = settings.getBoolean(READER_STATIC_BAR, false)
} }
fun isStaticBarEnabled(): Boolean { fun isStaticBarEnabled(): Boolean {
@ -308,11 +308,11 @@ class AppSettingsService {
} }
private fun refreshFont() { private fun refreshFont() {
_font = settings.getString("reader_font", "") _font = settings.getString(READER_FONT, "")
} }
fun getFont(): String { fun getFont(): String {
if (_font != null) { if (_font.isEmpty()) {
refreshFont() refreshFont()
} }
return _font return _font
@ -353,21 +353,21 @@ class AppSettingsService {
login: String, login: String,
password: String password: String
) { ) {
settings.putString("url", url) settings.putString(BASE_URL, url)
settings.putString("login", login) settings.putString(LOGIN, login)
settings.putString("password", password) settings.putString(PASSWORD, password)
refreshApiSettings() refreshApiSettings()
} }
fun resetLoginInformation() { fun resetLoginInformation() {
settings.remove("url") settings.remove(BASE_URL)
settings.remove("login") settings.remove(LOGIN)
settings.remove("password") settings.remove(PASSWORD)
refreshApiSettings() refreshApiSettings()
} }
fun updateApiVersion(apiMajorVersion: Int) { fun updateApiVersion(apiMajorVersion: Int) {
settings.putInt("apiVersionMajor", apiMajorVersion) settings.putInt(API_VERSION_MAJOR, apiMajorVersion)
refreshApiVersion() refreshApiVersion()
} }
@ -378,7 +378,7 @@ class AppSettingsService {
} }
fun disableArticleViewer() { fun disableArticleViewer() {
settings.putBoolean("prefer_article_viewer", false) settings.putBoolean(PREFER_ARTICLE_VIEWER, false)
refreshArticleViewerEnabled() refreshArticleViewerEnabled()
} }
@ -396,5 +396,53 @@ class AppSettingsService {
const val JUSTIFY = 1 const val JUSTIFY = 1
const val ALIGN_LEFT = 2 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"
} }
} }

View File

@ -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
}

View File

@ -1,16 +1,9 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.model.SelfossModel expect class DateUtils() {
import bou.amine.apps.readerforselfossv2.service.AppSettingsService companion object {
fun parseDate(dateString: String): Long
fun parseRelativeDate(dateString: String): String
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
} }

View File

@ -1,14 +1,13 @@
package bou.amine.apps.readerforselfossv2.utils 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 parseRelativeDate(dateString: String): String {
actual fun parseDate(dateString: String): Long { TODO("Not yet implemented")
TODO("Not yet implemented") }
} }
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
} }

View File

@ -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")
}
}

View File

@ -2,13 +2,15 @@ package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) { actual class DateUtils {
actual fun parseDate(dateString: String): Long { actual companion object {
TODO("Not yet implemented") actual fun parseDate(dateString: String): Long {
} TODO("Not yet implemented")
}
actual fun parseRelativeDate(dateString: String): String { actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented") TODO("Not yet implemented")
}
} }
} }