Compare commits
	
		
			16 Commits
		
	
	
		
			v125020581
			...
			v125030711
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 62354ec70a | |||
| 18a17251ac | |||
| 5e91724ee2 | |||
| 212d259a33 | |||
| 3bf60f1146 | |||
|  | ef13e300f0 | ||
| f170d1157d | |||
| af4752f0f0 | |||
| f0fa1a17b6 | |||
| bb84d1541c | |||
| c9227b2c1c | |||
| 6eaad0c7c5 | |||
| a1c98aa7d0 | |||
| d5ec118679 | |||
| a1c0241a58 | |||
|  | f38936f9b4 | 
							
								
								
									
										10
									
								
								.gitea/workflows/assets/crowdin.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.gitea/workflows/assets/crowdin.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| project_id_env: CROWDIN_PROJECT_ID | ||||
| api_token_env: CROWDIN_PERSONAL_TOKEN | ||||
| base_path: "../../../" | ||||
|  | ||||
| files: | ||||
|   - source: /androidApp/src/main/res/values/strings.xml | ||||
|     translation: /androidApp/src/main/res/values-%android_code%/%original_file_name% | ||||
|     translate_attributes: '0' | ||||
|     content_segmentation: '0' | ||||
| preserve_hierarchy: true | ||||
| @@ -10,30 +10,44 @@ jobs: | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: "Check android app changes" | ||||
|         id: check-android-changes | ||||
|         uses: tj-actions/changed-files@v45 | ||||
|         with: | ||||
|           files: | | ||||
|             androidApp/src/** | ||||
|       - name: Fetch tags | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         run: git fetch --tags -p | ||||
|       - uses: actions/setup-java@v4 | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         with: | ||||
|           distribution: 'temurin' | ||||
|           java-version: '17' | ||||
|           cache: gradle | ||||
|       - uses: gradle/actions/setup-gradle@v3 | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|       - uses: android-actions/setup-android@v3 | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|       - name: Configure gradle... | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties | ||||
|       - name: Build and test | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest # These tests will be done | ||||
|       - uses: KengoTODA/actions-setup-docker-compose@v1 | ||||
|         with: | ||||
|           version: "2.23.3" | ||||
|       # TESTS ARE RUN LOCALLY | ||||
|       #      - uses: KengoTODA/actions-setup-docker-compose@v1 | ||||
|       #        with: | ||||
|       #          version: "2.23.3" | ||||
|       #      - name: run selfoss | ||||
|       #        run: | | ||||
|       #          docker compose -f .gitea/workflows/assets/docker-compose.yml up -d | ||||
|       - name: coverage | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         run: | | ||||
|           ./gradlew :koverHtmlReport | ||||
|       - uses: actions/upload-artifact@v3 | ||||
|         if: steps.check-android-changes.outputs.any_modified == 'true' | ||||
|         with: | ||||
|           name: coverage | ||||
|           path: build/reports/kover/html | ||||
|   | ||||
| @@ -3,6 +3,7 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - master | ||||
|       - chore-crowdin-ci | ||||
|  | ||||
| jobs: | ||||
|   Lint: | ||||
| @@ -23,6 +24,68 @@ jobs: | ||||
|         run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' | ||||
|       - name: Detecting... | ||||
|         run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt' | ||||
|   translations: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out repository code | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: "Check translations changes" | ||||
|         id: check-translations-changes | ||||
|         uses: tj-actions/changed-files@v45 | ||||
|         with: | ||||
|           files: | | ||||
|             androidApp/src/main/res/values/strings.xml | ||||
|       - name: upload translation sources | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' | ||||
|         uses: crowdin/github-action@v2 | ||||
|         with: | ||||
|           config: './.gitea/workflows/assets/crowdin.yml' | ||||
|           upload_sources: true | ||||
|           upload_translations: false | ||||
|           download_translations: false | ||||
|           create_pull_request: false | ||||
|           push_translations: false | ||||
|         env: | ||||
|           CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} | ||||
|           CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} | ||||
|       - name: wait | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' | ||||
|         run: sleep 10s | ||||
|       - name: download translations | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' | ||||
|         uses: crowdin/github-action@v2 | ||||
|         with: | ||||
|           config: './.gitea/workflows/assets/crowdin.yml' | ||||
|           upload_sources: false | ||||
|           upload_translations: false | ||||
|           download_translations: true | ||||
|           create_pull_request: false | ||||
|           push_translations: false | ||||
|         env: | ||||
|           CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} | ||||
|           CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} | ||||
|       - name: Check for uncommitted changes | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' | ||||
|         id: check-changes | ||||
|         uses: mskri/check-uncommitted-changes-action@v1.0.1 | ||||
|       - name: Commit Changes | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != '' | ||||
|         run: | | ||||
|           git config --global user.email aminecmi+giteadrone@pm.me | ||||
|           git config --global user.name giteadrone | ||||
|           git add ./androidApp/src/main/res/* | ||||
|           git commit -m "translation: translation files" | ||||
|       - name: Push changes | ||||
|         if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != '' | ||||
|         uses: appleboy/git-push-action@v1.0.0 | ||||
|         with: | ||||
|           author_name: giteadrone | ||||
|           author_email: aminecmi+giteadrone@pm.me | ||||
|           remote: ${{ secrets.REMOTE_URL }} | ||||
|           ssh_key: ${{ secrets.PRIVATE_KEY }} | ||||
|           branch: ${{ github.head_ref || github.ref_name }} | ||||
|   build: | ||||
|     needs: Lint | ||||
|     uses: ./.gitea/workflows/common_build.yml | ||||
|   | ||||
							
								
								
									
										22
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,25 @@ | ||||
| **v125030681 | ||||
|  | ||||
| - chore: do not send reports on simulators. | ||||
| - Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master | ||||
| - chore: do not send reports on simulators. | ||||
| - Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master | ||||
| - Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master | ||||
| - chore: we don't need to check if the url is valid in upsert screen. | ||||
| - fix: Url validation was not failing login. Added tests. | ||||
| - chore: crowding ci integration. | ||||
| - Show a confirmation dialog before deleting sources (#185) | ||||
| - Changelog for v125020581 | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **v125020581 | ||||
|  | ||||
| - fix: url can be empty ? | ||||
| - Changelog for v125020471 | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **v125020471 | ||||
|  | ||||
| - chore: no more docker-compose. | ||||
|   | ||||
| @@ -12,8 +12,12 @@ plugins { | ||||
|     id("app.cash.sqldelight") version "2.0.2" | ||||
| } | ||||
|  | ||||
| fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | ||||
|     val result: String = ByteArrayOutputStream().use { outputStream -> | ||||
| fun Project.execWithOutput( | ||||
|     cmd: String, | ||||
|     ignore: Boolean = false, | ||||
| ): String { | ||||
|     val result: String = | ||||
|         ByteArrayOutputStream().use { outputStream -> | ||||
|             project.exec { | ||||
|                 commandLine = cmd.split(" ") | ||||
|                 standardOutput = outputStream | ||||
| @@ -26,14 +30,20 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | ||||
|  | ||||
| fun gitVersion(): String { | ||||
|     val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) | ||||
|     val process = if (maybeTagOfCurrentCommit.isEmpty()) { | ||||
|     val process = | ||||
|         if (maybeTagOfCurrentCommit.isEmpty()) { | ||||
|             println("No tag on current commit. Will take the latest one.") | ||||
|             execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1") | ||||
|         } else { | ||||
|             println("Tag found on current commit") | ||||
|             execWithOutput("git -C ../ describe --contains HEAD") | ||||
|         } | ||||
|     return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim() | ||||
|     return process | ||||
|         .replace("^0", "") | ||||
|         .replace("'", "") | ||||
|         .substring(1) | ||||
|         .replace("\\.", "") | ||||
|         .trim() | ||||
| } | ||||
|  | ||||
| fun versionCodeFromGit(): Int { | ||||
| @@ -116,7 +126,6 @@ android { | ||||
|             isIncludeAndroidResources = true | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
| @@ -141,7 +150,7 @@ dependencies { | ||||
|     implementation("androidx.constraintlayout:constraintlayout:2.2.0") | ||||
|     implementation("org.jsoup:jsoup:1.18.3") | ||||
|  | ||||
|     //multidex | ||||
|     // multidex | ||||
|     implementation("androidx.multidex:multidex:2.0.1") | ||||
|  | ||||
|     // About | ||||
| @@ -162,31 +171,28 @@ dependencies { | ||||
|     implementation("me.relex:circleindicator:2.1.6") | ||||
|     implementation("androidx.viewpager2:viewpager2:1.1.0") | ||||
|  | ||||
|     //Dependency Injection | ||||
|     // Dependency Injection | ||||
|     implementation("org.kodein.di:kodein-di:7.23.1") | ||||
|     implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1") | ||||
|     implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1") | ||||
|  | ||||
|     //Settings | ||||
|     // Settings | ||||
|     implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0") | ||||
|  | ||||
|     //Logging | ||||
|     // Logging | ||||
|     implementation("io.github.aakira:napier:2.7.1") | ||||
|  | ||||
|     //PhotoView | ||||
|     // PhotoView | ||||
|     implementation("com.github.chrisbanes:PhotoView:2.3.0") | ||||
|  | ||||
|     implementation("androidx.core:core-ktx:1.15.0") | ||||
|  | ||||
|     implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") | ||||
|  | ||||
|     // Network information | ||||
|     implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") | ||||
|  | ||||
|     // SQLDELIGHT | ||||
|     implementation("app.cash.sqldelight:android-driver:2.0.2") | ||||
|  | ||||
|     //test | ||||
|     // test | ||||
|     testImplementation("junit:junit:4.13.2") | ||||
|     testImplementation("io.mockk:mockk:1.13.14") | ||||
|     testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1") | ||||
| @@ -210,10 +216,11 @@ tasks.withType<Test> { | ||||
|     useJUnit() | ||||
|     testLogging { | ||||
|         exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL | ||||
|         events = setOf( | ||||
|         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 | ||||
|                 org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR, | ||||
|             ) | ||||
|         showStandardStreams = true | ||||
|     } | ||||
|   | ||||
| @@ -60,9 +60,23 @@ class LoginActivityTest { | ||||
|     fun urlError() { | ||||
|         performLogin("10.0.2.2:8888") | ||||
|         onView(withId(R.id.urlView)).perform(click()) | ||||
|         onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem))) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun connectError() { | ||||
|         performLogin("http://10.0.2.2:8889") | ||||
|         onView(withId(R.id.urlView)).perform(click()) | ||||
|         onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos))) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun urlSlashError() { | ||||
|         performLogin("https://google.fr/toto") | ||||
|         onView(withId(R.id.urlView)).perform(click()) | ||||
|         onView(withId(R.id.urlView)).check(matches(withError(R.string.login_url_problem))) | ||||
|     } | ||||
|  | ||||
|     @Test | ||||
|     fun multiError() { | ||||
|         onView(withId(R.id.signInButton)).perform(click()) | ||||
|   | ||||
| @@ -149,9 +149,10 @@ class LoginActivity : | ||||
|                 .toString() | ||||
|                 .trim() | ||||
|  | ||||
|         failInvalidUrl(url) | ||||
|         failLoginDetails(password, login) | ||||
|  | ||||
|         val cancelUrl = failInvalidUrl(url) | ||||
|         if (cancelUrl) return | ||||
|         val cancelDetails = failLoginDetails(password, login) | ||||
|         if (cancelDetails) return | ||||
|         showProgress(true) | ||||
|  | ||||
|         appSettingsService.updateSelfSigned(binding.selfSigned.isChecked) | ||||
| @@ -193,7 +194,7 @@ class LoginActivity : | ||||
|     private fun failLoginDetails( | ||||
|         password: String, | ||||
|         login: String, | ||||
|     ) { | ||||
|     ): Boolean { | ||||
|         var lastFocusedView: View? = null | ||||
|         var cancel = false | ||||
|         if (isWithLogin) { | ||||
| @@ -210,9 +211,10 @@ class LoginActivity : | ||||
|             } | ||||
|         } | ||||
|         maybeCancelAndFocusView(cancel, lastFocusedView) | ||||
|         return cancel | ||||
|     } | ||||
|  | ||||
|     private fun failInvalidUrl(url: String) { | ||||
|     private fun failInvalidUrl(url: String): Boolean { | ||||
|         val focusView = binding.urlView | ||||
|         var cancel = false | ||||
|         if (url.isBaseUrlInvalid()) { | ||||
| @@ -232,6 +234,7 @@ class LoginActivity : | ||||
|             } | ||||
|         } | ||||
|         maybeCancelAndFocusView(cancel, focusView) | ||||
|         return cancel | ||||
|     } | ||||
|  | ||||
|     private fun maybeCancelAndFocusView( | ||||
|   | ||||
| @@ -10,18 +10,16 @@ import androidx.lifecycle.LifecycleOwner | ||||
| import androidx.lifecycle.ProcessLifecycleOwner | ||||
| import androidx.multidex.MultiDexApplication | ||||
| import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper | ||||
| import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel | ||||
| import bou.amine.apps.readerforselfossv2.dao.DriverFactory | ||||
| import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB | ||||
| import bou.amine.apps.readerforselfossv2.di.networkModule | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import com.github.ln_12.library.ConnectivityStatus | ||||
| import bou.amine.apps.readerforselfossv2.service.ConnectivityService | ||||
| 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.acra.ACRA | ||||
| import org.acra.ReportField | ||||
| @@ -44,27 +42,21 @@ class MyApp : | ||||
|         import(networkModule) | ||||
|         bind<DriverFactory>() with singleton { DriverFactory(applicationContext) } | ||||
|         bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) } | ||||
|         bind<ConnectivityService>() with singleton { ConnectivityService() } | ||||
|         bind<Repository>() with | ||||
|             singleton { | ||||
|                 Repository( | ||||
|                     instance(), | ||||
|                     instance(), | ||||
|                     isConnectionAvailable, | ||||
|                     instance(), | ||||
|                     instance(), | ||||
|                 ) | ||||
|             } | ||||
|         bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) } | ||||
|         bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } | ||||
|     } | ||||
|  | ||||
|     private val repository: Repository by instance() | ||||
|     private val viewModel: AppViewModel by instance() | ||||
|     private val connectivityStatus: ConnectivityStatus by instance() | ||||
|     private val driverFactory: DriverFactory by instance() | ||||
|  | ||||
|     @Suppress("detekt:ForbiddenComment") | ||||
|     // TODO: handle with the "previous" way | ||||
|     private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true) | ||||
|     private val connectivityService: ConnectivityService by instance() | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
| @@ -77,13 +69,12 @@ class MyApp : | ||||
|  | ||||
|             ProcessLifecycleOwner.get().lifecycle.addObserver( | ||||
|                 AppLifeCycleObserver( | ||||
|                     connectivityStatus, | ||||
|                     repository, | ||||
|                     connectivityService, | ||||
|                 ), | ||||
|             ) | ||||
|  | ||||
|             CoroutineScope(Dispatchers.Main).launch { | ||||
|                 viewModel.networkAvailableProvider.collect { networkAvailable -> | ||||
|                 connectivityService.networkAvailableProvider.collect { networkAvailable -> | ||||
|                     val toastMessage = | ||||
|                         if (networkAvailable) { | ||||
|                             repository.handleDBActions() | ||||
| @@ -109,6 +100,7 @@ class MyApp : | ||||
|         super.attachBaseContext(base) | ||||
|  | ||||
|         initAcra { | ||||
|             sendReportsInDevMode = false | ||||
|             reportFormat = StringFormat.JSON | ||||
|             reportContent = | ||||
|                 listOf( | ||||
| @@ -188,18 +180,15 @@ class MyApp : | ||||
|     } | ||||
|  | ||||
|     class AppLifeCycleObserver( | ||||
|         val connectivityStatus: ConnectivityStatus, | ||||
|         val repository: Repository, | ||||
|         val connectivityService: ConnectivityService, | ||||
|     ) : DefaultLifecycleObserver { | ||||
|         override fun onResume(owner: LifecycleOwner) { | ||||
|             super.onResume(owner) | ||||
|             repository.connectionMonitored = true | ||||
|             connectivityStatus.start() | ||||
|             connectivityService.start() | ||||
|         } | ||||
|  | ||||
|         override fun onPause(owner: LifecycleOwner) { | ||||
|             repository.connectionMonitored = false | ||||
|             connectivityStatus.stop() | ||||
|             connectivityService.stop() | ||||
|             super.onPause(owner) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -9,11 +9,9 @@ import android.widget.TextView | ||||
| import android.widget.Toast | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding | ||||
| import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid | ||||
| import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException | ||||
| import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.launch | ||||
| @@ -31,7 +29,6 @@ class UpsertSourceActivity : | ||||
|  | ||||
|     override val di by closestDI() | ||||
|     private val repository: Repository by instance() | ||||
|     private val appSettingsService: AppSettingsService by instance() | ||||
|  | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
| @@ -76,14 +73,8 @@ class UpsertSourceActivity : | ||||
|  | ||||
|     override fun onResume() { | ||||
|         super.onResume() | ||||
|  | ||||
|         val baseUrl = appSettingsService.getBaseUrl() | ||||
|         if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) { | ||||
|             mustLoginToAddSource() | ||||
|         } else { | ||||
|         handleSpoutsSpinner() | ||||
|     } | ||||
|     } | ||||
|  | ||||
|     @Suppress("detekt:SwallowedException") | ||||
|     private fun handleSpoutsSpinner() { | ||||
| @@ -157,13 +148,6 @@ class UpsertSourceActivity : | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun mustLoginToAddSource() { | ||||
|         Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show() | ||||
|         val i = Intent(this, LoginActivity::class.java) | ||||
|         startActivity(i) | ||||
|         finish() | ||||
|     } | ||||
|  | ||||
|     private fun handleSaveSource() { | ||||
|         val url = binding.sourceUri.text.toString() | ||||
|  | ||||
|   | ||||
| @@ -6,9 +6,8 @@ import android.content.Intent | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Button | ||||
| import android.widget.Toast | ||||
| import androidx.constraintlayout.widget.ConstraintLayout | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import bou.amine.apps.readerforselfossv2.android.R | ||||
| import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity | ||||
| @@ -32,33 +31,82 @@ class SourcesListAdapter( | ||||
|     private val items: ArrayList<SelfossModel.SourceDetail>, | ||||
| ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), | ||||
|     DIAware { | ||||
|     private val c: Context = app.baseContext | ||||
|     private lateinit var binding: SourceListItemBinding | ||||
|  | ||||
|     override val di: DI by closestDI(app) | ||||
|     private val repository: Repository by instance() | ||||
|     private val appSettingsService: AppSettingsService by instance() | ||||
|  | ||||
|     override fun onCreateViewHolder( | ||||
|         parent: ViewGroup, | ||||
|         viewType: Int, | ||||
|     ): ViewHolder { | ||||
|         binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||||
|         return ViewHolder(binding.root) | ||||
|         val binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) | ||||
|         return ViewHolder(binding) | ||||
|     } | ||||
|  | ||||
|     override fun onBindViewHolder( | ||||
|         holder: ViewHolder, | ||||
|         position: Int, | ||||
|     ) { | ||||
|         val itm = items[position] | ||||
|         holder.bind(items[position], position) | ||||
|     } | ||||
|  | ||||
|         val deleteBtn: Button = holder.mView.findViewById(R.id.deleteBtn) | ||||
|     override fun getItemId(position: Int) = position.toLong() | ||||
|  | ||||
|         deleteBtn.setOnClickListener { | ||||
|             val (id, title) = items[position] | ||||
|     override fun getItemViewType(position: Int) = position | ||||
|  | ||||
|     override fun getItemCount(): Int = items.size | ||||
|  | ||||
|     inner class ViewHolder( | ||||
|         val binding: SourceListItemBinding, | ||||
|     ) : RecyclerView.ViewHolder(binding.root) { | ||||
|         private val context: Context = app.applicationContext | ||||
|         private val repository: Repository by instance() | ||||
|         private val appSettingsService: AppSettingsService by instance() | ||||
|  | ||||
|         fun bind( | ||||
|             source: SelfossModel.SourceDetail, | ||||
|             position: Int, | ||||
|         ) { | ||||
|             binding.apply { | ||||
|                 sourceTitle.text = source.title.getHtmlDecoded() | ||||
|                 if (source.getIcon(repository.baseUrl).isEmpty()) { | ||||
|                     itemImage.setBackgroundAndText(source.title.getHtmlDecoded()) | ||||
|                 } else { | ||||
|                     context.circularDrawable(source.getIcon(repository.baseUrl), itemImage, appSettingsService) | ||||
|                 } | ||||
|  | ||||
|                 errorText.apply { | ||||
|                     visibility = if (!source.error.isNullOrBlank()) View.VISIBLE else View.GONE | ||||
|                     text = source.error | ||||
|                 } | ||||
|  | ||||
|                 deleteBtn.setOnClickListener { showDeleteConfirmationDialog(source, position) } | ||||
|  | ||||
|                 root.setOnClickListener { | ||||
|                     repository.setSelectedSource(source) | ||||
|                     app.startActivity(Intent(app, UpsertSourceActivity::class.java)) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private fun showDeleteConfirmationDialog( | ||||
|             source: SelfossModel.SourceDetail, | ||||
|             position: Int, | ||||
|         ) { | ||||
|             AlertDialog | ||||
|                 .Builder(app) | ||||
|                 .setTitle(app.getString(R.string.confirm_delete_title)) | ||||
|                 .setMessage(app.getString(R.string.confirm_delete_message, source.title)) | ||||
|                 .setPositiveButton(android.R.string.ok) { _, _ -> deleteSource(source, position) } | ||||
|                 .setNegativeButton(android.R.string.cancel, null) | ||||
|                 .show() | ||||
|         } | ||||
|  | ||||
|         private fun deleteSource( | ||||
|             source: SelfossModel.SourceDetail, | ||||
|             position: Int, | ||||
|         ) { | ||||
|             CoroutineScope(Dispatchers.IO).launch { | ||||
|                 val successfullyDeletedSource = repository.deleteSource(id, title) | ||||
|                 val successfullyDeletedSource = repository.deleteSource(source.id, source.title) | ||||
|                 launch(Dispatchers.Main) { | ||||
|                     if (successfullyDeletedSource) { | ||||
|                         items.removeAt(position) | ||||
|                         notifyItemRemoved(position) | ||||
| @@ -73,37 +121,6 @@ class SourcesListAdapter( | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|         holder.mView.setOnClickListener { | ||||
|             val source = items[position] | ||||
|  | ||||
|             repository.setSelectedSource(source) | ||||
|             app.startActivity(Intent(app, UpsertSourceActivity::class.java)) | ||||
|         } | ||||
|  | ||||
|         if (itm.getIcon(repository.baseUrl).isEmpty()) { | ||||
|             binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded()) | ||||
|         } else { | ||||
|             c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage, appSettingsService) | ||||
|     } | ||||
|  | ||||
|         if (!itm.error.isNullOrBlank()) { | ||||
|             binding.errorText.visibility = View.VISIBLE | ||||
|             binding.errorText.text = itm.error | ||||
|         } else { | ||||
|             binding.errorText.visibility = View.GONE | ||||
|         } | ||||
|  | ||||
|         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( | ||||
|         val mView: ConstraintLayout, | ||||
|     ) : RecyclerView.ViewHolder(mView) | ||||
| } | ||||
|   | ||||
| @@ -42,6 +42,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.rest.MercuryApi | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import bou.amine.apps.readerforselfossv2.service.ConnectivityService | ||||
| import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | ||||
| import bou.amine.apps.readerforselfossv2.utils.getImages | ||||
| import bou.amine.apps.readerforselfossv2.utils.getThumbnail | ||||
| @@ -88,6 +89,7 @@ class ArticleFragment : | ||||
|     override val di: DI by closestDI() | ||||
|     private val repository: Repository by instance() | ||||
|     private val appSettingsService: AppSettingsService by instance() | ||||
|     private val connectivityService: ConnectivityService by instance() | ||||
|  | ||||
|     private var typeface: Typeface? = null | ||||
|     private var resId: Int = 0 | ||||
| @@ -168,7 +170,7 @@ class ArticleFragment : | ||||
|  | ||||
|     private fun handleContent() { | ||||
|         if (contentText.isEmptyOrNullOrNullString()) { | ||||
|             if (repository.isNetworkAvailable() && url.isUrlValid()) { | ||||
|             if (connectivityService.isNetworkAvailable() && url.isUrlValid()) { | ||||
|                 getContentFromMercury(url!!) | ||||
|             } | ||||
|         } else { | ||||
|   | ||||
| @@ -22,5 +22,5 @@ class AcraReportingAdministrator : ReportingAdministrator { | ||||
|         context: Context, | ||||
|         config: CoreConfiguration, | ||||
|         crashReportData: CrashReportData, | ||||
|     ): Boolean = crashReportData.get("BRAND") != "redroid" | ||||
|     ): Boolean = crashReportData.get("BRAND") != "redroid" && !crashReportData.get("PHONE_MODEL").toString().startsWith("sdk_gphone") | ||||
| } | ||||
|   | ||||
| @@ -1,32 +0,0 @@ | ||||
| package bou.amine.apps.readerforselfossv2.android.viewmodel | ||||
|  | ||||
| import androidx.lifecycle.ViewModel | ||||
| import androidx.lifecycle.viewModelScope | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||
| import kotlinx.coroutines.launch | ||||
|  | ||||
| class AppViewModel( | ||||
|     private val repository: Repository, | ||||
| ) : ViewModel() { | ||||
|     private val _networkAvailableProvider = MutableSharedFlow<Boolean>() | ||||
|     val networkAvailableProvider = _networkAvailableProvider.asSharedFlow() | ||||
|     private var wasConnected = true | ||||
|  | ||||
|     init { | ||||
|         viewModelScope.launch { | ||||
|             repository.isConnectionAvailable.collect { isConnected -> | ||||
|                 if (repository.connectionMonitored) { | ||||
|                     if (isConnected && !wasConnected && repository.connectionMonitored) { | ||||
|                         _networkAvailableProvider.emit(true) | ||||
|                         wasConnected = true | ||||
|                     } else if (!isConnected && wasConnected && repository.connectionMonitored) { | ||||
|                         _networkAvailableProvider.emit(false) | ||||
|                         wasConnected = false | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string> | ||||
|     <string name="all_posts_read">"S'han llegit totes les publicacions"</string> | ||||
|     <string name="undo_string">"Desfés"</string> | ||||
|     <string name="addStringNoUrl">"Inicieu la sessió per afegir fonts."</string> | ||||
|     <string name="cant_get_sources">"No es pot obtenir la llista de fonts."</string> | ||||
|     <string name="cant_create_source">"No es pot crear la font."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Quant a"</string> | ||||
|     <string name="marked_as_read">"Element llegit"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string> | ||||
|     <string name="all_posts_read">"Alle Beiträge wurden gelesen"</string> | ||||
|     <string name="undo_string">"Rückgängig"</string> | ||||
|     <string name="addStringNoUrl">"Melde dich an um Quellen hinzuzufügen."</string> | ||||
|     <string name="cant_get_sources">"Quellen können nicht abgerufen werden."</string> | ||||
|     <string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Fehler beim Laden der Spouts-Liste aufgrund von Netzwerkproblemen."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Blocksatz</string> | ||||
|     <string name="settings_reader_font">Schriftgröße im Lesemodus</string> | ||||
|     <string name="remove_source">Quelle entfernen</string> | ||||
|     <string name="pref_theme_title">Heller/Dunkler Modus</string> | ||||
|     <string name="mode_dark">Dunkler Modus</string> | ||||
|     <string name="mode_system">Systemeinstellungen übernehmen</string> | ||||
|     <string name="mode_light">Heller Modus</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Über"</string> | ||||
|     <string name="marked_as_read">"Artikel gelesen"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string> | ||||
|     <string name="all_posts_read">"Todas las publicaciones fueron leídas"</string> | ||||
|     <string name="undo_string">"Deshacer"</string> | ||||
|     <string name="addStringNoUrl">"Iniciar sesión para añadir fuentes."</string> | ||||
|     <string name="cant_get_sources">"No se puede obtener la lista de fuentes."</string> | ||||
|     <string name="cant_create_source">"No se puede crear la fuente."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justificado</string> | ||||
|     <string name="settings_reader_font">Modo lectura</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Acerca de"</string> | ||||
|     <string name="marked_as_read">"Artículo leído"</string> | ||||
|     <string name="marked_as_unread">"Artículo no leído"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string> | ||||
|     <string name="all_posts_read">"Tous les posts sont lus"</string> | ||||
|     <string name="undo_string">"Annuler"</string> | ||||
|     <string name="addStringNoUrl">"Identifiez-vous pour ajouter une source."</string> | ||||
|     <string name="cant_get_sources">"Impossible de récupérer la liste des sources."</string> | ||||
|     <string name="cant_create_source">"Impossible de créer la source."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justifier le texte</string> | ||||
|     <string name="settings_reader_font">Police du lecteur d\'articles</string> | ||||
|     <string name="remove_source">Supprimer la source</string> | ||||
|     <string name="pref_theme_title">Thème Clair/Sombre</string> | ||||
|     <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> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"À propos"</string> | ||||
|     <string name="marked_as_read">"Marqué comme lu"</string> | ||||
|     <string name="marked_as_unread">"Marqué comme non lu"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Non se leron todas as publicacións"</string> | ||||
|     <string name="all_posts_read">"Leronse todas as publicacións"</string> | ||||
|     <string name="undo_string">"Desfacer"</string> | ||||
|     <string name="addStringNoUrl">"Accede pra engadir fontes."</string> | ||||
|     <string name="cant_get_sources">"Non se pode obter a lista de fontes."</string> | ||||
|     <string name="cant_create_source">"Non se pode crear unha fonte."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Xustificado</string> | ||||
|     <string name="settings_reader_font">Modo lector</string> | ||||
|     <string name="remove_source">Eliminar fonte</string> | ||||
|     <string name="pref_theme_title">Modo Claro/Escuro</string> | ||||
|     <string name="mode_dark">Modo escuro</string> | ||||
|     <string name="mode_system">Seguir axustes do sistema</string> | ||||
|     <string name="mode_light">Modo claro</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Acerca de"</string> | ||||
|     <string name="marked_as_read">"Elemento lido"</string> | ||||
|     <string name="marked_as_unread">"Elemento non lido"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Semua pos belum dibaca"</string> | ||||
|     <string name="all_posts_read">"Semua pos sudah dibaca"</string> | ||||
|     <string name="undo_string">"Urung"</string> | ||||
|     <string name="addStringNoUrl">"Masuk untuk menambah sumber."</string> | ||||
|     <string name="cant_get_sources">"Tidak bisa mendapatkan daftar sumber."</string> | ||||
|     <string name="cant_create_source">"Tidak dapat membuat sumber."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Tentang"</string> | ||||
|     <string name="marked_as_read">"Membaca item"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"All posts weren't read"</string> | ||||
|     <string name="all_posts_read">"Tutti i messaggi sono stati letti"</string> | ||||
|     <string name="undo_string">"Annulla"</string> | ||||
|     <string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string> | ||||
|     <string name="cant_get_sources">"Can't get sources list."</string> | ||||
|     <string name="cant_create_source">"Can't create source."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Informazioni"</string> | ||||
|     <string name="marked_as_read">"Articolo letto"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string> | ||||
|     <string name="all_posts_read">"모든 게시물을 읽었습니다."</string> | ||||
|     <string name="undo_string">"실행 취소"</string> | ||||
|     <string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string> | ||||
|     <string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string> | ||||
|     <string name="cant_create_source">"소스를 만들 수 없습니다."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"정보"</string> | ||||
|     <string name="marked_as_read">"항목 읽기"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Fout bij markeren als gelezen"</string> | ||||
|     <string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string> | ||||
|     <string name="undo_string">"Ongedaan maken"</string> | ||||
|     <string name="addStringNoUrl">"Login om bronnen toe te voegen"</string> | ||||
|     <string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string> | ||||
|     <string name="cant_create_source">"Kan bron niet creëeren"</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Over"</string> | ||||
|     <string name="marked_as_read">"Artikel gelezen"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Nenhum post foi lido"</string> | ||||
|     <string name="all_posts_read">"Todos os posts foram lidos"</string> | ||||
|     <string name="undo_string">"Desfazer"</string> | ||||
|     <string name="addStringNoUrl">"Faça login para adicionar fontes."</string> | ||||
|     <string name="cant_get_sources">"Não é possível obter a lista de fontes."</string> | ||||
|     <string name="cant_create_source">"Não é possível criar fonte."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Sobre"</string> | ||||
|     <string name="marked_as_read">"Item lido"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Todas as postagens não foram lidas"</string> | ||||
|     <string name="all_posts_read">"Todas as postagens foram lidas"</string> | ||||
|     <string name="undo_string">"Desfazer"</string> | ||||
|     <string name="addStringNoUrl">"Logar para adicionar fontes."</string> | ||||
|     <string name="cant_get_sources">"Não é possível obter a lista de fontes."</string> | ||||
|     <string name="cant_create_source">"Não é possível criar a fonte."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Sobre"</string> | ||||
|     <string name="marked_as_read">"Item lido"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"All posts weren't read"</string> | ||||
|     <string name="all_posts_read">"All posts were read"</string> | ||||
|     <string name="undo_string">"Undo"</string> | ||||
|     <string name="addStringNoUrl">"Log in to add sources."</string> | ||||
|     <string name="cant_get_sources">"Can't get sources list."</string> | ||||
|     <string name="cant_create_source">"Can't create source."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"මේ ගැන"</string> | ||||
|     <string name="marked_as_read">"Item read"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"Tüm mesajlar okunmadı"</string> | ||||
|     <string name="all_posts_read">"Tüm mesajlar okundu"</string> | ||||
|     <string name="undo_string">"Geri al"</string> | ||||
|     <string name="addStringNoUrl">"Kaynakları eklemek için giriş yapın."</string> | ||||
|     <string name="cant_get_sources">"Kaynakları listesi alınamıyor."</string> | ||||
|     <string name="cant_create_source">"Kaynak oluşturulamıyor."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"Hakkında"</string> | ||||
|     <string name="marked_as_read">"Öğeleri oku"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"所有帖子都未读"</string> | ||||
|     <string name="all_posts_read">"所有帖子已读"</string> | ||||
|     <string name="undo_string">"撤销"</string> | ||||
|     <string name="addStringNoUrl">"登录以添加数据源。"</string> | ||||
|     <string name="cant_get_sources">"无法获取数据列表。"</string> | ||||
|     <string name="cant_create_source">"无法创建源数据。"</string> | ||||
|     <string name="cant_get_spouts_no_network">"由于网络问题,无法获取 spouts 列表。"</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">左右对齐</string> | ||||
|     <string name="settings_reader_font">阅读器字体</string> | ||||
|     <string name="remove_source">删除源</string> | ||||
|     <string name="pref_theme_title">浅色/深色模式</string> | ||||
|     <string name="mode_dark">深色模式</string> | ||||
|     <string name="mode_system">遵循系统设置</string> | ||||
|     <string name="mode_light">浅色模式</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"关于我们"</string> | ||||
|     <string name="marked_as_read">"已读"</string> | ||||
|     <string name="marked_as_unread">"未读条目"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -24,7 +24,6 @@ | ||||
|     <string name="all_posts_not_read">"所有帖子都未读"</string> | ||||
|     <string name="all_posts_read">"所有帖子已读"</string> | ||||
|     <string name="undo_string">"撤销"</string> | ||||
|     <string name="addStringNoUrl">"登录以添加数据源。"</string> | ||||
|     <string name="cant_get_sources">"无法获取数据列表。"</string> | ||||
|     <string name="cant_create_source">"无法创建源数据。"</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -107,7 +106,6 @@ | ||||
|     <string name="reader_text_align_justify">Justify</string> | ||||
|     <string name="settings_reader_font">Reader font</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -129,4 +127,6 @@ | ||||
|     <string name="action_about">"关于我们"</string> | ||||
|     <string name="marked_as_read">"已读"</string> | ||||
|     <string name="marked_as_unread">"未讀項目"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -23,7 +23,6 @@ | ||||
|     <string name="all_posts_not_read">"All posts weren't read"</string> | ||||
|     <string name="all_posts_read">"All posts were read"</string> | ||||
|     <string name="undo_string">"Undo"</string> | ||||
|     <string name="addStringNoUrl">"Log in to add sources."</string> | ||||
|     <string name="cant_get_sources">"Can't get sources list."</string> | ||||
|     <string name="cant_create_source">"Can't create source."</string> | ||||
|     <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string> | ||||
| @@ -109,7 +108,6 @@ | ||||
|     <string name="open_sans_font_id" translatable="false">open_sans</string> | ||||
|     <string name="roboto_font_id" translatable="false">roboto</string> | ||||
|     <string name="remove_source">Remove source</string> | ||||
|     <string name="pref_theme_title">Light/Dark mode</string> | ||||
|     <string name="mode_dark">Dark mode</string> | ||||
|     <string name="mode_system">Follow the system setting</string> | ||||
|     <string name="mode_light">Light mode</string> | ||||
| @@ -131,4 +129,6 @@ | ||||
|     <string name="action_about">"About"</string> | ||||
|     <string name="marked_as_read">"Item read"</string> | ||||
|     <string name="marked_as_unread">"Item unread"</string> | ||||
|     <string name="confirm_delete_title">Confirm Deletion</string> | ||||
|     <string name="confirm_delete_message">Are you sure you want to delete the following source?\n%s</string> | ||||
| </resources> | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import bou.amine.apps.readerforselfossv2.model.SuccessResponse | ||||
| import bou.amine.apps.readerforselfossv2.repository.Repository | ||||
| import bou.amine.apps.readerforselfossv2.rest.SelfossApi | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import bou.amine.apps.readerforselfossv2.service.ConnectivityService | ||||
| import bou.amine.apps.readerforselfossv2.utils.ItemType | ||||
| import bou.amine.apps.readerforselfossv2.utils.toView | ||||
| import io.mockk.clearAllMocks | ||||
| @@ -24,7 +25,6 @@ import junit.framework.TestCase.assertFalse | ||||
| import junit.framework.TestCase.assertNotSame | ||||
| import junit.framework.TestCase.assertSame | ||||
| import junit.framework.TestCase.assertTrue | ||||
| import kotlinx.coroutines.flow.MutableStateFlow | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import org.junit.Assert.assertNotEquals | ||||
| import org.junit.Before | ||||
| @@ -52,15 +52,12 @@ class RepositoryTest { | ||||
|     private val db = mockk<ReaderForSelfossDB>(relaxed = true) | ||||
|     private val appSettingsService = mockk<AppSettingsService>() | ||||
|     private val api = mockk<SelfossApi>() | ||||
|     private val connectivityService = mockk<ConnectivityService>() | ||||
|     private lateinit var repository: Repository | ||||
|  | ||||
|     private fun initializeRepository( | ||||
|         isConnectionAvailable: MutableStateFlow<Boolean> = | ||||
|             MutableStateFlow( | ||||
|                 true, | ||||
|             ), | ||||
|     ) { | ||||
|         repository = Repository(api, appSettingsService, isConnectionAvailable, db) | ||||
|     private fun initializeRepository(isNetworkAvailable: Boolean = true) { | ||||
|         every { connectivityService.isNetworkAvailable() } returns isNetworkAvailable | ||||
|         repository = Repository(api, appSettingsService, connectivityService, db) | ||||
|  | ||||
|         runBlocking { | ||||
|             repository.updateApiInformation() | ||||
| @@ -110,7 +107,7 @@ class RepositoryTest { | ||||
|     fun instantiate_repository_without_api_version() { | ||||
|         every { appSettingsService.getApiVersion() } returns -1 | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|  | ||||
|         coVerify(exactly = 0) { api.apiInformation() } | ||||
|         coVerify(exactly = 0) { api.stats() } | ||||
| @@ -287,7 +284,7 @@ class RepositoryTest { | ||||
|     fun get_newer_items_without_connectivity() { | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         runBlocking { | ||||
|             repository.getNewerItems() | ||||
|         } | ||||
| @@ -314,7 +311,7 @@ class RepositoryTest { | ||||
|  | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         repository.setTagFilter(SelfossModel.Tag("Test", "red", 3)) | ||||
|         runBlocking { | ||||
|             repository.getNewerItems() | ||||
| @@ -342,7 +339,7 @@ class RepositoryTest { | ||||
|  | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         repository.setSourceFilter( | ||||
|             SelfossModel.SourceDetail( | ||||
|                 1, | ||||
| @@ -457,7 +454,7 @@ class RepositoryTest { | ||||
|  | ||||
|         var success: Boolean | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         runBlocking { | ||||
|             success = repository.reloadBadges() | ||||
|         } | ||||
| @@ -477,7 +474,7 @@ class RepositoryTest { | ||||
|  | ||||
|         var success: Boolean | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         runBlocking { | ||||
|             success = repository.reloadBadges() | ||||
|         } | ||||
| @@ -572,7 +569,7 @@ class RepositoryTest { | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testTags: List<SelfossModel.Tag> | ||||
|         runBlocking { | ||||
|             testTags = repository.getTags() | ||||
| @@ -590,7 +587,7 @@ class RepositoryTest { | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns false | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testTags: List<SelfossModel.Tag> | ||||
|         runBlocking { | ||||
|             testTags = repository.getTags() | ||||
| @@ -607,7 +604,7 @@ class RepositoryTest { | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testTags: List<SelfossModel.Tag> | ||||
|         runBlocking { | ||||
|             testTags = repository.getTags() | ||||
| @@ -625,7 +622,7 @@ class RepositoryTest { | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns false | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testTags: List<SelfossModel.Tag> | ||||
|         runBlocking { | ||||
|             testTags = repository.getTags() | ||||
| @@ -775,7 +772,7 @@ class RepositoryTest { | ||||
|     @Test | ||||
|     fun get_sources_without_connection() { | ||||
|         val (_, sourcesDB) = prepareSources() | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testSources: List<SelfossModel.Source> | ||||
|         runBlocking { | ||||
|             testSources = repository.getSourcesDetails() | ||||
| @@ -792,7 +789,7 @@ class RepositoryTest { | ||||
|  | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns false | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns true | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testSources: List<SelfossModel.Source> | ||||
|         runBlocking { | ||||
|             testSources = repository.getSourcesDetails() | ||||
| @@ -809,7 +806,7 @@ class RepositoryTest { | ||||
|  | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns true | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testSources: List<SelfossModel.Source> | ||||
|         runBlocking { | ||||
|             testSources = repository.getSourcesDetails() | ||||
| @@ -826,7 +823,7 @@ class RepositoryTest { | ||||
|  | ||||
|         every { appSettingsService.isItemCachingEnabled() } returns false | ||||
|         every { appSettingsService.isUpdateSourcesEnabled() } returns false | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var testSources: List<SelfossModel.Source> | ||||
|         runBlocking { | ||||
|             testSources = repository.getSourcesDetails() | ||||
| @@ -898,7 +895,7 @@ class RepositoryTest { | ||||
|         coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns | ||||
|             SuccessResponse(true) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = | ||||
| @@ -955,7 +952,7 @@ class RepositoryTest { | ||||
|     fun delete_source_without_connection() { | ||||
|         coEvery { api.deleteSource(any()) } returns SuccessResponse(false) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.deleteSource(5, "src") | ||||
| @@ -1028,7 +1025,7 @@ class RepositoryTest { | ||||
|                 data = "undocumented...", | ||||
|             ) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.updateRemote() | ||||
| @@ -1070,7 +1067,7 @@ class RepositoryTest { | ||||
|     fun login_but_without_connection() { | ||||
|         coEvery { api.login() } returns SuccessResponse(success = true) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         var response: Boolean | ||||
|         runBlocking { | ||||
|             response = repository.login() | ||||
| @@ -1150,7 +1147,7 @@ class RepositoryTest { | ||||
|         coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns | ||||
|             StatusAndData(success = false, data = generateTestApiItem()) | ||||
|  | ||||
|         initializeRepository(MutableStateFlow(false)) | ||||
|         initializeRepository(false) | ||||
|         prepareSearch() | ||||
|         runBlocking { | ||||
|             repository.tryToCacheItemsAndGetNewOnes() | ||||
|   | ||||
| @@ -0,0 +1,4 @@ | ||||
| **v125020581** | ||||
|  | ||||
| - fix: url can be empty ? | ||||
| - Changelog for v125020471 | ||||
							
								
								
									
										12
									
								
								fastlane/metadata/android/en-US/changelogs/v125030681.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								fastlane/metadata/android/en-US/changelogs/v125030681.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| **v125030681** | ||||
|  | ||||
| - chore: do not send reports on simulators. | ||||
| - Merge pull request 'chore: do not send reports on simulators.' (#188) from chore-acra-simulator into master | ||||
| - chore: do not send reports on simulators. | ||||
| - Merge pull request 'fix: Url validation was not failing login. Added tests.' (#186) from fix-invalid-url into master | ||||
| - Merge pull request 'chore: crowding ci integration.' (#187) from chore-crowdin-ci into master | ||||
| - chore: we don't need to check if the url is valid in upsert screen. | ||||
| - fix: Url validation was not failing login. Added tests. | ||||
| - chore: crowding ci integration. | ||||
| - Show a confirmation dialog before deleting sources (#185) | ||||
| - Changelog for v125020581 | ||||
| @@ -4,7 +4,6 @@ object SqlDelight { | ||||
|     const val runtime = "app.cash.sqldelight:runtime:2.0.2" | ||||
|     const val android = "app.cash.sqldelight:android-driver:2.0.2" | ||||
|     const val native = "app.cash.sqldelight:native-driver:2.0.2" | ||||
|  | ||||
| } | ||||
|  | ||||
| plugins { | ||||
| @@ -41,13 +40,13 @@ kotlin { | ||||
|  | ||||
|                 implementation("org.jsoup:jsoup:1.15.4") | ||||
|  | ||||
|                 //Dependency Injection | ||||
|                 // Dependency Injection | ||||
|                 implementation("org.kodein.di:kodein-di:7.14.0") | ||||
|  | ||||
|                 //Settings | ||||
|                 // Settings | ||||
|                 implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC") | ||||
|  | ||||
|                 //Logging | ||||
|                 // Logging | ||||
|                 implementation("io.github.aakira:napier:2.6.1") | ||||
|  | ||||
|                 // Sql | ||||
| @@ -55,6 +54,10 @@ kotlin { | ||||
|  | ||||
|                 // Sql | ||||
|                 implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") | ||||
|  | ||||
|                 // Connectivity | ||||
|                 implementation("dev.jordond.connectivity:connectivity-core:1.2.0") | ||||
|                 implementation("dev.jordond.connectivity:connectivity-device:1.2.0") | ||||
|             } | ||||
|         } | ||||
|         val commonTest by getting { | ||||
|   | ||||
| @@ -13,6 +13,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel | ||||
| import bou.amine.apps.readerforselfossv2.model.StatusAndData | ||||
| import bou.amine.apps.readerforselfossv2.rest.SelfossApi | ||||
| import bou.amine.apps.readerforselfossv2.service.AppSettingsService | ||||
| import bou.amine.apps.readerforselfossv2.service.ConnectivityService | ||||
| import bou.amine.apps.readerforselfossv2.utils.ItemType | ||||
| import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded | ||||
| import bou.amine.apps.readerforselfossv2.utils.toEntity | ||||
| @@ -30,11 +31,10 @@ private const val MAX_ITEMS_NUMBER = 200 | ||||
| class Repository( | ||||
|     private val api: SelfossApi, | ||||
|     private val appSettingsService: AppSettingsService, | ||||
|     val isConnectionAvailable: MutableStateFlow<Boolean>, | ||||
|     private val connectivityService: ConnectivityService, | ||||
|     private val db: ReaderForSelfossDB, | ||||
| ) { | ||||
|     var items = ArrayList<SelfossModel.Item>() | ||||
|     var connectionMonitored = false | ||||
|  | ||||
|     var baseUrl = appSettingsService.getBaseUrl() | ||||
|  | ||||
| @@ -63,7 +63,7 @@ class Repository( | ||||
|  | ||||
|     suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { | ||||
|         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             fetchedItems = | ||||
|                 api.getItems( | ||||
|                     displayedItems.type, | ||||
| @@ -102,7 +102,7 @@ class Repository( | ||||
|  | ||||
|     suspend fun getOlderItems(): ArrayList<SelfossModel.Item> { | ||||
|         var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             val offset = items.size | ||||
|             fetchedItems = | ||||
|                 api.getItems( | ||||
| @@ -122,7 +122,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     private suspend fun getMaxItemsForBackground(itemType: ItemType): List<SelfossModel.Item> { | ||||
|         return if (isNetworkAvailable()) { | ||||
|         return if (connectivityService.isNetworkAvailable()) { | ||||
|             val items = | ||||
|                 api.getItems( | ||||
|                     itemType.type, | ||||
| @@ -146,7 +146,7 @@ class Repository( | ||||
|     @Suppress("detekt:ForbiddenComment") | ||||
|     suspend fun reloadBadges(): Boolean { | ||||
|         var success = false | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             val response = api.stats() | ||||
|             if (response.success && response.data != null) { | ||||
|                 _badgeUnread.value = response.data.unread ?: 0 | ||||
| @@ -168,7 +168,7 @@ class Repository( | ||||
|     suspend fun getTags(): List<SelfossModel.Tag> { | ||||
|         val isDatabaseEnabled = | ||||
|             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||
|         return if (isNetworkAvailable() && !fetchedTags) { | ||||
|         return if (connectivityService.isNetworkAvailable() && !fetchedTags) { | ||||
|             val apiTags = api.tags() | ||||
|             if (apiTags.success && apiTags.data != null && isDatabaseEnabled) { | ||||
|                 resetDBTagsWithData(apiTags.data) | ||||
| @@ -185,7 +185,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     suspend fun getSpouts(): Map<String, SelfossModel.Spout> = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             val spouts = api.spouts() | ||||
|             if (spouts.success && spouts.data != null) { | ||||
|                 spouts.data | ||||
| @@ -201,7 +201,7 @@ class Repository( | ||||
|         val isDatabaseEnabled = | ||||
|             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||
|         val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true | ||||
|         if (shouldFetch && isNetworkAvailable()) { | ||||
|         if (shouldFetch && connectivityService.isNetworkAvailable()) { | ||||
|             if (appSettingsService.getPublicAccess()) { | ||||
|                 val apiSources = api.sourcesStats() | ||||
|                 if (apiSources.success && apiSources.data != null) { | ||||
| @@ -223,7 +223,7 @@ class Repository( | ||||
|         val isDatabaseEnabled = | ||||
|             appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() | ||||
|         val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true | ||||
|         if (shouldFetch && isNetworkAvailable()) { | ||||
|         if (shouldFetch && connectivityService.isNetworkAvailable()) { | ||||
|             val apiSources = api.sourcesDetailed() | ||||
|             if (apiSources.success && apiSources.data != null) { | ||||
|                 fetchedSources = true | ||||
| @@ -248,7 +248,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     private suspend fun markAsReadById(id: Int): Boolean = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             api.markAsRead(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), read = true) | ||||
| @@ -265,7 +265,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     private suspend fun unmarkAsReadById(id: Int): Boolean = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             api.unmarkAsRead(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), unread = true) | ||||
| @@ -282,7 +282,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     private suspend fun starrById(id: Int): Boolean = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             api.starr(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), starred = true) | ||||
| @@ -299,7 +299,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     private suspend fun unstarrById(id: Int): Boolean = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             api.unstarr(id.toString()).isSuccess | ||||
|         } else { | ||||
|             insertDBAction(id.toString(), starred = true) | ||||
| @@ -309,7 +309,8 @@ class Repository( | ||||
|     suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean { | ||||
|         var success = false | ||||
|  | ||||
|         if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess) { | ||||
|         if (connectivityService.isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() }).isSuccess | ||||
|         ) { | ||||
|             success = true | ||||
|             for (item in items) { | ||||
|                 markAsReadLocally(item) | ||||
| @@ -369,7 +370,7 @@ class Repository( | ||||
|         tags: String, | ||||
|     ): Boolean { | ||||
|         var response = false | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             response = api | ||||
|                 .createSourceForVersion( | ||||
|                     title, | ||||
| @@ -390,7 +391,7 @@ class Repository( | ||||
|         tags: String, | ||||
|     ): Boolean { | ||||
|         var response = false | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             response = api.updateSourceForVersion(id, title, url, spout, tags).isSuccess == true | ||||
|         } | ||||
|  | ||||
| @@ -402,13 +403,13 @@ class Repository( | ||||
|         title: String, | ||||
|     ): Boolean { | ||||
|         var success = false | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             val response = api.deleteSource(id) | ||||
|             success = response.isSuccess | ||||
|         } | ||||
|  | ||||
|         // We filter on success or if the network isn't available | ||||
|         if (success || !isNetworkAvailable()) { | ||||
|         if (success || !connectivityService.isNetworkAvailable()) { | ||||
|             items = ArrayList(items.filter { it.sourcetitle != title }) | ||||
|             setReaderItems(items) | ||||
|             db.itemsQueries.deleteItemsWhereSource(title) | ||||
| @@ -418,7 +419,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     suspend fun updateRemote(): Boolean = | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             api.update().data.equals("finished") | ||||
|         } else { | ||||
|             false | ||||
| @@ -426,7 +427,7 @@ class Repository( | ||||
|  | ||||
|     suspend fun login(): Boolean { | ||||
|         var result = false | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             try { | ||||
|                 val response = api.login() | ||||
|                 result = response.isSuccess == true | ||||
| @@ -439,7 +440,7 @@ class Repository( | ||||
|  | ||||
|     suspend fun checkIfFetchFails(): Boolean { | ||||
|         var fetchFailed = true | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             try { | ||||
|                 // Trying to fetch one item, and check someone is trying to use the app with | ||||
|                 // a random rss feed, that would throw a NoTransformationFoundException | ||||
| @@ -453,7 +454,7 @@ class Repository( | ||||
|     } | ||||
|  | ||||
|     suspend fun logout() { | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             try { | ||||
|                 val response = api.logout() | ||||
|                 if (!response.isSuccess) { | ||||
| @@ -481,7 +482,7 @@ class Repository( | ||||
|     suspend fun updateApiInformation() { | ||||
|         val apiMajorVersion = appSettingsService.getApiVersion() | ||||
|  | ||||
|         if (isNetworkAvailable()) { | ||||
|         if (connectivityService.isNetworkAvailable()) { | ||||
|             val fetchedInformation = api.apiInformation() | ||||
|             if (fetchedInformation.success && fetchedInformation.data != null) { | ||||
|                 if (fetchedInformation.data.getApiMajorVersion() != apiMajorVersion) { | ||||
| @@ -500,8 +501,6 @@ class Repository( | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride | ||||
|  | ||||
|     private fun getDBActions(): List<ACTION> = db.actionsQueries.actions().executeAsList() | ||||
|  | ||||
|     private fun deleteDBAction(action: ACTION) = db.actionsQueries.deleteAction(action.id) | ||||
|   | ||||
| @@ -0,0 +1,46 @@ | ||||
| package bou.amine.apps.readerforselfossv2.service | ||||
|  | ||||
| import dev.jordond.connectivity.Connectivity | ||||
| import kotlinx.coroutines.CoroutineScope | ||||
| import kotlinx.coroutines.Dispatchers | ||||
| import kotlinx.coroutines.flow.MutableSharedFlow | ||||
| import kotlinx.coroutines.flow.asSharedFlow | ||||
| import kotlinx.coroutines.launch | ||||
|  | ||||
| class ConnectivityService { | ||||
|     private val _networkAvailableProvider = MutableSharedFlow<Boolean>() | ||||
|     val networkAvailableProvider = _networkAvailableProvider.asSharedFlow() | ||||
|     private var currentStatus = true | ||||
|     private lateinit var connectivity: Connectivity | ||||
|  | ||||
|     fun start() { | ||||
|         connectivity = Connectivity() | ||||
|         connectivity.start() | ||||
|         CoroutineScope(Dispatchers.Main).launch { | ||||
|             connectivity.statusUpdates.collect { status -> | ||||
|                 when (status) { | ||||
|                     is Connectivity.Status.Connected -> { | ||||
|                         if (!currentStatus) { | ||||
|                             currentStatus = true | ||||
|                             _networkAvailableProvider.emit(true) | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     is Connectivity.Status.Disconnected -> { | ||||
|                         if (currentStatus) { | ||||
|                             currentStatus = false | ||||
|                             _networkAvailableProvider.emit(false) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fun isNetworkAvailable(): Boolean = currentStatus | ||||
|  | ||||
|     fun stop() { | ||||
|         currentStatus = true | ||||
|         connectivity.stop() | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user