Compare commits
	
		
			13 Commits
		
	
	
		
			v125030681
			...
			a7e49dfa20
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a7e49dfa20 | |||
| 02d503e03a | |||
| 24b9320d6d | |||
| ceba58e98f | |||
| c3ee07dd85 | |||
| 93d99192b3 | |||
|  | 359dec2ca0 | ||
| 62354ec70a | |||
| 18a17251ac | |||
| 5e91724ee2 | |||
| 212d259a33 | |||
| 3bf60f1146 | |||
|  | ef13e300f0 | 
| @@ -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 | ||||
|       # 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 | ||||
|   | ||||
							
								
								
									
										43
									
								
								.gitea/workflows/common_coverage.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								.gitea/workflows/common_coverage.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| name: Build | ||||
| on: | ||||
|   workflow_call: | ||||
|  | ||||
| jobs: | ||||
|   BuildAndTestAndCoverage: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out repository code | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - name: Fetch tags | ||||
|         run: git fetch --tags -p | ||||
|       - uses: actions/setup-java@v4 | ||||
|         with: | ||||
|           distribution: 'temurin' | ||||
|           java-version: '17' | ||||
|           cache: gradle | ||||
|       - uses: gradle/actions/setup-gradle@v3 | ||||
|       - uses: android-actions/setup-android@v3 | ||||
|       - name: Configure gradle... | ||||
|         run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties | ||||
|       - 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 | ||||
|         run: | | ||||
|           ./gradlew androidApp:connectedAndroidTest | ||||
|       - uses: actions/upload-artifact@v3 | ||||
|         with: | ||||
|           name: coverage-espresso | ||||
|           path: build/reports/coverage/androidTest/githubConfig/debug/connected | ||||
|           retention-days: 1 | ||||
|           overwrite: true | ||||
|           include-hidden-files: true | ||||
|       - name: Clean | ||||
|         if: always() | ||||
|         run: | | ||||
|           docker compose -f .gitea/workflows/assets/docker-compose.yml stop | ||||
| @@ -3,9 +3,13 @@ on: | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - master | ||||
|       - chore-crowdin-ci | ||||
|  | ||||
| jobs: | ||||
|   EspressoReports: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Espresso Coverage | ||||
|         run: ./gradlew createDebugCoverageReport | ||||
|   Lint: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
| @@ -29,7 +33,16 @@ jobs: | ||||
|     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' | ||||
| @@ -42,8 +55,10 @@ jobs: | ||||
|           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' | ||||
| @@ -56,17 +71,18 @@ jobs: | ||||
|           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-changes.outputs.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-changes.outputs.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 | ||||
|   | ||||
							
								
								
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,29 @@ | ||||
| **v125030711 | ||||
|  | ||||
| - Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master | ||||
| - chore: check changes for translations and android. | ||||
| - fix: initial status loading issues. | ||||
| - Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master | ||||
| - chore: new connectivity dep. Closes #84. | ||||
| - Changelog for v125030681 | ||||
|  | ||||
| -------------------------------------------------------------------- | ||||
|  | ||||
| **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 ? | ||||
|   | ||||
| @@ -12,28 +12,38 @@ plugins { | ||||
|     id("app.cash.sqldelight") version "2.0.2" | ||||
| } | ||||
|  | ||||
| fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { | ||||
|     val result: String = ByteArrayOutputStream().use { outputStream -> | ||||
|         project.exec { | ||||
|             commandLine = cmd.split(" ") | ||||
|             standardOutput = outputStream | ||||
|             isIgnoreExitValue = ignore | ||||
| fun Project.execWithOutput( | ||||
|     cmd: String, | ||||
|     ignore: Boolean = false, | ||||
| ): String { | ||||
|     val result: String = | ||||
|         ByteArrayOutputStream().use { outputStream -> | ||||
|             project.exec { | ||||
|                 commandLine = cmd.split(" ") | ||||
|                 standardOutput = outputStream | ||||
|                 isIgnoreExitValue = ignore | ||||
|             } | ||||
|             outputStream.toString() | ||||
|         } | ||||
|         outputStream.toString() | ||||
|     } | ||||
|     return result | ||||
| } | ||||
|  | ||||
| fun gitVersion(): String { | ||||
|     val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) | ||||
|     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() | ||||
|     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() | ||||
| } | ||||
|  | ||||
| fun versionCodeFromGit(): Int { | ||||
| @@ -86,6 +96,7 @@ android { | ||||
|         // tests | ||||
|         testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" | ||||
|         testInstrumentationRunnerArguments["clearPackageData"] = "true" | ||||
|         testInstrumentationRunnerArguments["useTestStorageService"] = "true" | ||||
|     } | ||||
|     packaging { | ||||
|         resources { | ||||
| @@ -99,6 +110,8 @@ android { | ||||
|             proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") | ||||
|         } | ||||
|         getByName("debug") { | ||||
|             isTestCoverageEnabled = true | ||||
|             enableAndroidTestCoverage = true | ||||
|         } | ||||
|     } | ||||
|     flavorDimensions.add("build") | ||||
| @@ -116,7 +129,6 @@ android { | ||||
|             isIncludeAndroidResources = true | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
| @@ -141,7 +153,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,43 +174,41 @@ 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") | ||||
|     implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") | ||||
|     androidTestImplementation("androidx.test:runner:1.6.2") | ||||
|     androidTestImplementation("androidx.test:rules:1.6.1") | ||||
|     androidTestImplementation("androidx.test:runner:1.7.0-alpha01") | ||||
|     androidTestImplementation("androidx.test:rules:1.7.0-alpha01") | ||||
|     androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") | ||||
|     implementation("androidx.test.espresso:espresso-idling-resource:3.6.1") | ||||
|     androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1") | ||||
|     androidTestUtil("androidx.test:orchestrator:1.5.1") | ||||
|     androidTestUtil("androidx.test:orchestrator:1.6.0-alpha02") | ||||
|     androidTestUtil("androidx.test.services:test-services:1.6.0-alpha02") | ||||
|     testImplementation("org.robolectric:robolectric:4.14.1") | ||||
|     testImplementation("androidx.test:core-ktx:1.6.1") | ||||
|     testImplementation("androidx.test:core-ktx:1.7.0-alpha01") | ||||
|  | ||||
|     implementation("ch.acra:acra-http:$acraVersion") | ||||
|     implementation("ch.acra:acra-toast:$acraVersion") | ||||
| @@ -210,11 +220,12 @@ tasks.withType<Test> { | ||||
|     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 | ||||
|         ) | ||||
|         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 | ||||
|     } | ||||
| } | ||||
| @@ -227,4 +238,4 @@ aboutLibraries { | ||||
|     strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL | ||||
|     duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE | ||||
|     duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -71,6 +71,8 @@ class SourcesActivityTest { | ||||
|     fun deleteTheCreatedSource() { | ||||
|         onView(withText(sourceName)).check(matches(isDisplayed())) | ||||
|         onView(withId(R.id.deleteBtn)).perform(click()) | ||||
|         onView(withText(R.string.confirm_delete_title)).check(matches(isDisplayed())) | ||||
|         onView(withId(android.R.id.button1)).perform(click()) | ||||
|         onView(withText(sourceName)).check(doesNotExist()) | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -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() | ||||
| @@ -189,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) | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -37,22 +37,6 @@ class ReaderActivity : | ||||
|     private val repository: Repository by instance() | ||||
|     private val appSettingsService: AppSettingsService by instance() | ||||
|  | ||||
|     private fun showMenuItem(willAddToFavorite: Boolean) { | ||||
|         if (willAddToFavorite) { | ||||
|             toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE) | ||||
|         } else { | ||||
|             toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun canFavorite() { | ||||
|         showMenuItem(true) | ||||
|     } | ||||
|  | ||||
|     private fun canRemoveFromFavorite() { | ||||
|         showMenuItem(false) | ||||
|     } | ||||
|  | ||||
|     @Suppress("detekt:SwallowedException") | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
| @@ -73,14 +57,21 @@ class ReaderActivity : | ||||
|             finish() | ||||
|         } | ||||
|  | ||||
|         try { | ||||
|             readItem(allItems[currentItem]) | ||||
|         } catch (e: IndexOutOfBoundsException) { | ||||
|             finish() | ||||
|         } | ||||
|         readItem() | ||||
|  | ||||
|         binding.pager.adapter = ScreenSlidePagerAdapter(this) | ||||
|         binding.pager.setCurrentItem(currentItem, false) | ||||
|  | ||||
|         binding.pager.registerOnPageChangeCallback( | ||||
|             object : ViewPager2.OnPageChangeCallback() { | ||||
|                 override fun onPageSelected(position: Int) { | ||||
|                     super.onPageSelected(position) | ||||
|                     currentItem = position | ||||
|                     updateStarIcon() | ||||
|                     readItem() | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     override fun onResume() { | ||||
| @@ -89,14 +80,20 @@ class ReaderActivity : | ||||
|         binding.indicator.setViewPager(binding.pager) | ||||
|     } | ||||
|  | ||||
|     private fun readItem(item: SelfossModel.Item) { | ||||
|         if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) { | ||||
|     private fun readItem() { | ||||
|         val item = allItems.getOrNull(currentItem) | ||||
|         if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess() && item != null) { | ||||
|             CoroutineScope(Dispatchers.IO).launch { | ||||
|                 repository.markAsRead(item) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun updateStarIcon() { | ||||
|         val isStarred = allItems.getOrNull(currentItem)?.starred ?: false | ||||
|         toolbarMenu.findItem(R.id.star)?.icon?.setTint(if (isStarred) Color.RED else Color.WHITE) | ||||
|     } | ||||
|  | ||||
|     override fun onSaveInstanceState(oldInstanceState: Bundle) { | ||||
|         super.onSaveInstanceState(oldInstanceState) | ||||
|         oldInstanceState.clear() | ||||
| @@ -141,8 +138,7 @@ class ReaderActivity : | ||||
|     } | ||||
|  | ||||
|     override fun onCreateOptionsMenu(menu: Menu): Boolean { | ||||
|         val inflater = menuInflater | ||||
|         inflater.inflate(R.menu.reader_menu, menu) | ||||
|         menuInflater.inflate(R.menu.reader_menu, menu) | ||||
|         toolbarMenu = menu | ||||
|  | ||||
|         alignmentMenu() | ||||
| @@ -150,87 +146,50 @@ class ReaderActivity : | ||||
|         if (appSettingsService.getPublicAccess()) { | ||||
|             menu.removeItem(R.id.star) | ||||
|         } else { | ||||
|             if (allItems.isNotEmpty() && allItems[currentItem].starred) { | ||||
|                 canRemoveFromFavorite() | ||||
|             } else { | ||||
|                 canFavorite() | ||||
|             } | ||||
|  | ||||
|             binding.pager.registerOnPageChangeCallback( | ||||
|                 object : ViewPager2.OnPageChangeCallback() { | ||||
|                     override fun onPageSelected(position: Int) { | ||||
|                         super.onPageSelected(position) | ||||
|  | ||||
|                         if (!allItems.isNullOrEmpty() && allItems.size >= position) { | ||||
|                             if (allItems[position].starred) { | ||||
|                                 canRemoveFromFavorite() | ||||
|                             } else { | ||||
|                                 canFavorite() | ||||
|                             } | ||||
|                             readItem(allItems[position]) | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|             ) | ||||
|             updateStarIcon() | ||||
|         } | ||||
|  | ||||
|         return true | ||||
|     } | ||||
|  | ||||
|     override fun onOptionsItemSelected(item: MenuItem): Boolean { | ||||
|         fun afterSave() { | ||||
|             allItems[binding.pager.currentItem] = | ||||
|                 allItems[binding.pager.currentItem].toggleStar() | ||||
|             canRemoveFromFavorite() | ||||
|         } | ||||
|  | ||||
|         fun afterUnsave() { | ||||
|             allItems[binding.pager.currentItem] = allItems[binding.pager.currentItem].toggleStar() | ||||
|             canFavorite() | ||||
|         } | ||||
|  | ||||
|         when (item.itemId) { | ||||
|             android.R.id.home -> { | ||||
|                 onBackPressedDispatcher.onBackPressed() | ||||
|                 return true | ||||
|             } | ||||
|  | ||||
|             R.id.star -> { | ||||
|                 if (allItems[binding.pager.currentItem].starred) { | ||||
|                     CoroutineScope(Dispatchers.IO).launch { | ||||
|                         repository.unstarr(allItems[binding.pager.currentItem]) | ||||
|                     } | ||||
|                     afterUnsave() | ||||
|                 } else { | ||||
|                     CoroutineScope(Dispatchers.IO).launch { | ||||
|                         repository.starr(allItems[binding.pager.currentItem]) | ||||
|                     } | ||||
|                     afterSave() | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             R.id.align_left -> { | ||||
|                 switchAlignmentSetting(AppSettingsService.ALIGN_LEFT) | ||||
|                 refreshFragment() | ||||
|             } | ||||
|  | ||||
|             R.id.align_justify -> { | ||||
|                 switchAlignmentSetting(AppSettingsService.JUSTIFY) | ||||
|                 refreshFragment() | ||||
|             } | ||||
|             android.R.id.home -> onBackPressedDispatcher.onBackPressed() | ||||
|             R.id.star -> toggleFavorite() | ||||
|             R.id.align_left -> switchAlignmentSetting(AppSettingsService.ALIGN_LEFT) | ||||
|             R.id.align_justify -> switchAlignmentSetting(AppSettingsService.JUSTIFY) | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item) | ||||
|     } | ||||
|  | ||||
|     private fun switchAlignmentSetting(allignment: Int) { | ||||
|         appSettingsService.changeAllignment(allignment) | ||||
|         alignmentMenu() | ||||
|     private fun toggleFavorite() { | ||||
|         val item = allItems.getOrNull(currentItem) ?: return | ||||
|  | ||||
|         val starred = item.starred | ||||
|  | ||||
|         CoroutineScope(Dispatchers.IO).launch { | ||||
|             if (starred) { | ||||
|                 repository.unstarr(item) | ||||
|             } else { | ||||
|                 repository.starr(item) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         item.toggleStar() | ||||
|         updateStarIcon() | ||||
|     } | ||||
|  | ||||
|     private fun refreshFragment() { | ||||
|         finish() | ||||
|         overridePendingTransition(0, 0) | ||||
|         startActivity(intent) | ||||
|         overridePendingTransition(0, 0) | ||||
|     private fun switchAlignmentSetting(alignment: Int) { | ||||
|         appSettingsService.changeAllignment(alignment) | ||||
|         alignmentMenu() | ||||
|  | ||||
|         val fragmentManager = supportFragmentManager | ||||
|         val fragments = fragmentManager.fragments | ||||
|  | ||||
|         for (fragment in fragments) { | ||||
|             if (fragment is ArticleFragment) { | ||||
|                 fragment.refreshAlignment() | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -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 { | ||||
| @@ -261,13 +263,15 @@ class ArticleFragment : | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     private fun refreshAlignment() { | ||||
|     fun refreshAlignment() { | ||||
|         textAlignment = | ||||
|             when (appSettingsService.getActiveAllignment()) { | ||||
|                 1 -> "justify" | ||||
|                 2 -> "left" | ||||
|                 else -> "justify" | ||||
|             } | ||||
|  | ||||
|         htmlToWebview() | ||||
|     } | ||||
|  | ||||
|     @Suppress("detekt:SwallowedException") | ||||
|   | ||||
| @@ -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 | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,3 +1,5 @@ | ||||
| @file:Suppress("ktlint") | ||||
| /* | ||||
| package bou.amine.apps.readerforselfossv2.android.tests.robolectric | ||||
|  | ||||
| import android.view.Menu | ||||
| @@ -25,3 +27,4 @@ fun Menu.assertVisible( | ||||
|     val item = this.findItem(id) | ||||
|     assertTrue(item.isVisible) | ||||
| } | ||||
| */ | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| @file:Suppress("ktlint") | ||||
| /* | ||||
| package bou.amine.apps.readerforselfossv2.android.tests.robolectric | ||||
|  | ||||
| import android.widget.Button | ||||
| @@ -57,7 +59,8 @@ class LoginActivityTest { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* @Test | ||||
|  */ | ||||
| /* @Test | ||||
|      fun connect() { | ||||
|          Robolectric.buildActivity(LoginActivity::class.java).use { controller -> | ||||
|              controller.setup() // Moves the Activity to the RESUMED state | ||||
| @@ -72,4 +75,7 @@ class LoginActivityTest { | ||||
|              assertEquals(expectedIntent.component, actual.component) | ||||
|          } | ||||
|      }*/ | ||||
| /* | ||||
|  | ||||
| } | ||||
| */ | ||||
|   | ||||
| @@ -1,3 +1,5 @@ | ||||
| @file:Suppress("ktlint") | ||||
| /* | ||||
| package bou.amine.apps.readerforselfossv2.android.tests.robolectric | ||||
|  | ||||
| import org.robolectric.RobolectricTestRunner | ||||
| @@ -8,3 +10,4 @@ class RobotElectriqueRunner( | ||||
| ) : RobolectricTestRunner(testClass) { | ||||
|     override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build() | ||||
| } | ||||
| */ | ||||
|   | ||||
| @@ -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() | ||||
|   | ||||
							
								
								
									
										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 | ||||
| @@ -0,0 +1,8 @@ | ||||
| **v125030711** | ||||
|  | ||||
| - Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master | ||||
| - chore: check changes for translations and android. | ||||
| - fix: initial status loading issues. | ||||
| - Merge pull request 'chore: new connectivity dep. Closes #84.' (#189) from connectivity into master | ||||
| - chore: new connectivity dep. Closes #84. | ||||
| - Changelog for v125030681 | ||||
| @@ -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 { | ||||
| @@ -114,4 +117,4 @@ sqldelight { | ||||
|             packageName.set("bou.amine.apps.readerforselfossv2.dao") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
| @@ -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