Compare commits

..

14 Commits

Author SHA1 Message Date
e4b6119f65 fix: Bottom bar loading issues.
All checks were successful
PR / translations (pull_request) Successful in 31s
PR / PR (pull_request) Successful in 41s
PR / build (pull_request) Successful in 17m53s
PR test / integrationTests (pull_request) Successful in 40m46s
Master / build (push) Successful in 12m51s
Realease / build (push) Successful in 4m6s
Realease / createTagAndChangelog (push) Successful in 41s
Realease / release (push) Successful in 7m6s
2025-04-30 21:05:50 +02:00
ea70c64873 ci: make file changes check from master. (#199)
All checks were successful
Master / build (push) Successful in 25s
## Types of changes

- [ ] I have read the **CONTRIBUTING** document.
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [ ] This is **NOT** translation related.

This closes issue #XXX

This is implements feature #YYY

This finishes chore #ZZZ

Reviewed-on: #199
Co-authored-by: Amine <amine.bouabdallaoui@pm.me>
Co-committed-by: Amine <amine.bouabdallaoui@pm.me>
2025-04-15 20:03:24 +00:00
f4a1805474 Changelog for v125040991 2025-04-09 06:48:50 +00:00
a602f7adf2 fix: Connectivity toast message was causing issues.
All checks were successful
PR / translations (pull_request) Successful in 1m47s
PR / PR (pull_request) Successful in 2m10s
PR / build (pull_request) Successful in 17m42s
PR test / integrationTests (pull_request) Successful in 43m23s
Master / build (push) Successful in 10m45s
Realease / build (push) Successful in 4m37s
Realease / createTagAndChangelog (push) Successful in 44s
Realease / release (push) Successful in 7m4s
2025-04-08 20:31:29 +02:00
cf6ef51edf Changelog for v125030901 2025-03-31 18:44:08 +00:00
0b7f832573 Merge pull request 'fix-reload' (#195) from fix-reload into master
All checks were successful
Master / build (push) Successful in 11m29s
Realease / build (push) Successful in 5m52s
Realease / createTagAndChangelog (push) Successful in 45s
Realease / release (push) Successful in 7m23s
Reviewed-on: #195
2025-03-31 18:25:33 +00:00
22c966bf16 fix: Infinite scroll needs loading stats.
All checks were successful
PR / translations (pull_request) Successful in 31s
PR / PR (pull_request) Successful in 41s
PR / build (pull_request) Successful in 16m2s
PR test / integrationTests (pull_request) Successful in 43m44s
2025-03-30 21:57:36 +02:00
bdf2bb8b31 fix: do not reload items on resume. 2025-03-30 21:57:36 +02:00
ceae91206d Merge pull request 'tests' (#193) from tests into master
All checks were successful
Master / build (push) Successful in 12m5s
Reviewed-on: #193
2025-03-30 19:50:50 +00:00
11c0e744dc ci: Instrumentation tests coverage in ci.
All checks were successful
PR / PR (pull_request) Successful in 1m1s
PR / translations (pull_request) Successful in 1m0s
PR / build (pull_request) Successful in 21m57s
PR test / integrationTests (pull_request) Successful in 50m51s
2025-03-30 20:23:15 +02:00
7374e95b0e ci: Instrumentation tests coverage in ci.
Some checks failed
Check PR code / translations (pull_request) Successful in 1m5s
Check PR code / Lint (pull_request) Successful in 1m8s
Check PR code / build (pull_request) Successful in 18m22s
Check PR code / RunIntegrationTests (pull_request) Has been cancelled
2025-03-27 18:44:44 +01:00
8a7743a6fb ci: Instrumentation tests coverage in ci.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 41m45s
2025-03-26 19:05:48 +01:00
1b2e9edc8c chore: better handling of coroutine dispatchers.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 30m26s
2025-03-25 12:42:43 +01:00
7c65a63315 ci: Instrumentation tests coverage in ci.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 34m46s
2025-03-24 23:09:51 +01:00
34 changed files with 560 additions and 392 deletions

View File

@ -1,65 +0,0 @@
name: Coverage
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 "ignoreGitVersion=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: Set env url
run: |
export SELFOSS_URL=172.17.0.1:8888
# https://github.com/ReactiveCircus/android-emulator-runner/issues/385
- name: Kill crashpad_handler processes
if: always()
run: |
pkill -SIGTERM crashpad_handler || true
sleep 5
pkill -SIGKILL crashpad_handler || true
- name: Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
script: |
./gradlew androidApp:connectedAndroidTest
killall -INT crashpad_handler || true
- uses: actions/upload-artifact@v3
if: failure()
with:
name: failure-espresso
path: build/reports/androidTests/connected/screenshots
retention-days: 2
overwrite: true
include-hidden-files: true
- 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

View File

@ -6,16 +6,18 @@ jobs:
BuildAndTestAndCoverage: BuildAndTestAndCoverage:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - uses: actions/checkout@v4
uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: "Check android app changes" - name: "Check android app changes"
id: check-android-changes id: check-android-changes
uses: tj-actions/changed-files@v45 uses: tj-actions/changed-files@v46
with: with:
files: | files: |
androidApp/src/** androidApp/src/**
shared/src/commonMain/**
shared/src/androidMain/**
shared/src/commonTest/**
- name: Fetch tags - name: Fetch tags
if: steps.check-android-changes.outputs.any_modified == 'true' if: steps.check-android-changes.outputs.any_modified == 'true'
run: git fetch --tags -p run: git fetch --tags -p
@ -24,7 +26,6 @@ jobs:
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
cache: gradle
- uses: gradle/actions/setup-gradle@v3 - uses: gradle/actions/setup-gradle@v3
if: steps.check-android-changes.outputs.any_modified == 'true' if: steps.check-android-changes.outputs.any_modified == 'true'
- uses: android-actions/setup-android@v3 - uses: android-actions/setup-android@v3
@ -34,14 +35,7 @@ jobs:
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
- name: Build and test - name: Build and test
if: steps.check-android-changes.outputs.any_modified == 'true' 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 run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest
# 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 - name: coverage
if: steps.check-android-changes.outputs.any_modified == 'true' if: steps.check-android-changes.outputs.any_modified == 'true'
run: | run: |
@ -54,8 +48,3 @@ jobs:
retention-days: 1 retention-days: 1
overwrite: true overwrite: true
include-hidden-files: true include-hidden-files: true
# TESTS ARE RUN LOCALLY
# - name: Clean
# if: always()
# run: |
# docker compose -f .gitea/workflows/assets/docker-compose.yml stop

View File

@ -1,4 +1,4 @@
name: Create tag name: Realease
on: on:
push: push:
branches: branches:
@ -7,7 +7,7 @@ on:
jobs: jobs:
build: build:
uses: ./.gitea/workflows/common_build.yml uses: ./.gitea/workflows/on_called_build.yml
createTagAndChangelog: createTagAndChangelog:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build needs: build
@ -86,7 +86,6 @@ jobs:
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
cache: gradle
- name: Setup Android SDK - name: Setup Android SDK
uses: android-actions/setup-android@v3 uses: android-actions/setup-android@v3
- name: Configure gradle... - name: Configure gradle...

View File

@ -1,153 +1,90 @@
name: Check PR code name: PR
on: on:
pull_request: pull_request:
branches: branches:
- master - master
jobs: jobs:
BuildAndTestAndCoverage: PR:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- name: Install klint
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
- name: Install detekt
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
- name: Linting...
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 runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Fetch tags - name: "Check translations changes"
run: git fetch --tags -p id: check-translations-changes
- uses: actions/setup-java@v4 uses: tj-actions/changed-files@v46
with: with:
distribution: 'temurin' base_sha: ${{ github.event.pull_request.base.sha }}
java-version: '17' files: |
- uses: gradle/actions/setup-gradle@v3 androidApp/src/main/res/values/strings.xml
- uses: android-actions/setup-android@v3 - name: upload translation sources
- name: Configure gradle... if: steps.check-translations-changes.outputs.any_modified == 'true'
run: mkdir -p ~/.gradle && echo "ignoreGitVersion=true" >> ~/.gradle/gradle.properties uses: crowdin/github-action@v2
- uses: KengoTODA/actions-setup-docker-compose@v1
with: with:
version: "2.23.3" config: './.gitea/workflows/assets/crowdin.yml'
- name: run selfoss 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-translations-changes.outputs.any_modified == 'true'
run: sleep 10s
- name: download translations
if: steps.check-translations-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-translations-changes.outputs.any_modified == 'true'
id: check-changes
uses: mskri/check-uncommitted-changes-action@v1.0.1
- name: Commit Changes
if: steps.check-translations-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
run: | run: |
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d git config --global user.email aminecmi+giteadrone@pm.me
- name: Set env url git config --global user.name giteadrone
run: | git add ./androidApp/src/main/res/*
export SELFOSS_URL=172.17.0.1:8888 git commit -m "translation: translation files"
# https://github.com/ReactiveCircus/android-emulator-runner/issues/385 - name: Push changes
- name: Kill crashpad_handler processes if: steps.check-translations-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
if: always() uses: appleboy/git-push-action@v1.0.0
run: |
pkill -SIGTERM crashpad_handler || true
sleep 5
pkill -SIGKILL crashpad_handler || true
- name: Tests
uses: reactivecircus/android-emulator-runner@v2
with: with:
api-level: 29 author_name: giteadrone
script: | author_email: aminecmi+giteadrone@pm.me
./gradlew androidApp:clearScreenshotsTask || true remote: ${{ secrets.REMOTE_URL }}
./gradlew androidApp:createScreenshotDirectory ssh_key: ${{ secrets.PRIVATE_KEY }}
./gradlew JacocoDebugCodeCoverage || true branch: ${{ github.head_ref || github.ref_name }}
./gradlew androidApp:fetchScreenshots build:
adb logcat '*:I' -d -v long > ./androidApp/build/reports/androidTests/connected/screenshots/logs.txt needs: Lint
- uses: actions/upload-artifact@v3 uses: ./.gitea/workflows/on_called_build.yml
if: always()
with:
name: failure-espresso
path: androidApp/build/reports/androidTests/connected/screenshots
retention-days: 2
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3
with:
name: coverage-espresso
path: androidApp/build/reports/jacoco/JacocoDebugCodeCoverage
retention-days: 1
overwrite: true
include-hidden-files: true
- name: Clean
if: always()
run: |
docker compose -f .gitea/workflows/assets/docker-compose.yml stop
# Lint:
# runs-on: ubuntu-latest
# steps:
# - name: Check out repository code
# uses: actions/checkout@v4
# - uses: actions/setup-java@v4
# with:
# distribution: 'temurin'
# java-version: '17'
# cache: gradle
# - name: Install klint
# run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
# - name: Install detekt
# run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.7/detekt-cli-1.23.7.zip && unzip detekt-cli-1.23.7.zip
# - name: Linting...
# 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

View File

@ -0,0 +1,67 @@
name: PR test
on:
pull_request:
branches:
- master
jobs:
integrationTests:
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: 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
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- 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
- name: Change url until I find a better way to do it
run: |
sed -i "s/const val DEFAULT_URL = \"http:\/\/10\.0\.2\.2\:8888\"/const val DEFAULT_URL = \"http:\/\/172\.17\.0\.1\:8888\"/g" ./androidApp/src/androidTest/kotlin/bou/amine/apps/readerforselfossv2/android/CommonTests.kt
- name: Tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
profile: pixel_2
script: |
./gradlew androidApp:clearScreenshotsTask || true
./gradlew androidApp:createScreenshotDirectory
adb logcat -G 16M
./gradlew JacocoDebugCodeCoverage || (./gradlew androidApp:fetchScreenshots && adb logcat 'InputReader:S' 'chatty:S' 'audio_hw_generic:S' 'LogApiCalls:D' '*:I' -d > ./androidApp/build/reports/androidTests/connected/screenshots/logs.txt)
- uses: actions/upload-artifact@v3
with:
name: screenshot-espresso
path: androidApp/build/reports/androidTests/connected/screenshots
retention-days: 2
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3
with:
path: androidApp/build/reports/androidTests/connected/debug/flavors/githubConfig
retention-days: 1
overwrite: true
include-hidden-files: true
- uses: actions/upload-artifact@v3
with:
name: coverage-espresso
path: androidApp/build/reports/jacoco/JacocoDebugCodeCoverage
retention-days: 1
overwrite: true
include-hidden-files: true
- name: Clean
if: always()
run: |
docker compose -f .gitea/workflows/assets/docker-compose.yml stop

View File

@ -1,4 +1,4 @@
name: Check master code name: Master
on: on:
push: push:
branches: branches:
@ -6,4 +6,4 @@ on:
jobs: jobs:
build: build:
uses: ./.gitea/workflows/common_build.yml uses: ./.gitea/workflows/on_called_build.yml

View File

@ -1,3 +1,30 @@
**v125040991
- fix: Connectivity toast message was causing issues.
- Changelog for v125030901
--------------------------------------------------------------------
**v125030901
- Merge pull request 'fix-reload' (#195) from fix-reload into master
- fix: Infinite scroll needs loading stats.
- fix: do not reload items on resume.
- Merge pull request 'tests' (#193) from tests into master
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- chore: better handling of coroutine dispatchers.
- ci: Instrumentation tests coverage in ci.
- chore: comment robolectric tests for now.
- fix: Fixed source deletion test.
- Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
- Refactor star icon handling
- Don't restart activity changing alignment
- Changelog for v125030711
--------------------------------------------------------------------
**v125030711 **v125030711
- Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master - Merge pull request 'fix: initial status loading issues.' (#192) from connectivity into master

View File

@ -272,6 +272,12 @@ tasks.withType<Test> {
) )
showStandardStreams = true showStandardStreams = true
} }
if (this.name == "connectedAndroidTest") {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}
} }
aboutLibraries { aboutLibraries {
@ -311,12 +317,3 @@ tasks.register<Exec>("fetchScreenshots") {
reportsDirectory.mkdirs() reportsDirectory.mkdirs()
} }
} }
tasks.withType(Test::class) {
if (this.name == "connectedAndroidTest") {
configure<JacocoTaskExtension> {
isIncludeNoLocationClasses = true
excludes = listOf("jdk.internal.*")
}
}
}

View File

@ -77,7 +77,6 @@ class `1-LoginActivityTest` : WithANRException() {
@Test @Test
fun `4-connectError`() { fun `4-connectError`() {
performLogin("http://10.0.2.2:8889") 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))) onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
} }
@ -94,5 +93,6 @@ class `1-LoginActivityTest` : WithANRException() {
performLogin() performLogin()
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed())) onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
onView(withText("OK")).perform(click()) onView(withText("OK")).perform(click())
checkHomeLoadingDone()
} }
} }

View File

@ -1,6 +1,7 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
@ -14,7 +15,9 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -29,6 +32,14 @@ class `2-HomeActivityTest` : WithANRException() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(HomeActivity::class.java) val activityRule = ActivityScenarioRule(HomeActivity::class.java)
@Before
fun registerIdlingResource() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
checkHomeLoadingDone()
}
@Test @Test
fun testMenu() { fun testMenu() {
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check( onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(

View File

@ -2,14 +2,17 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.isSelected import androidx.test.espresso.matcher.ViewMatchers.isSelected
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.Before import org.junit.Before
@ -31,6 +34,9 @@ class `3-SettingsActivityTest` : WithANRException() {
activityRule.scenario.onActivity { activity -> activityRule.scenario.onActivity { activity ->
context = activity.window.context context = activity.window.context
} }
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
openMenu() openMenu()
onView(withText(R.string.title_activity_settings)).perform(click()) onView(withText(R.string.title_activity_settings)).perform(click())
} }
@ -91,6 +97,7 @@ class `3-SettingsActivityTest` : WithANRException() {
@Test @Test
fun testAbout() { fun testAbout() {
onView(withText(R.string.action_about)).perform(click()) onView(withText(R.string.action_about)).perform(click())
onView(isRoot()).perform(waitUntilShown("ACRA", 30000))
onView(withText("ACRA")).check(matches(isDisplayed())) onView(withText("ACRA")).check(matches(isDisplayed()))
} }
} }

View File

@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.android
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText import androidx.test.espresso.action.ViewActions.replaceText
@ -19,6 +20,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.Before import org.junit.Before
@ -38,6 +40,9 @@ class `4-SettingsActivityGeneralTest` : WithANRException() {
@Before @Before
fun init() { fun init() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
openActionBarOverflowOrOptionsMenu( openActionBarOverflowOrOptionsMenu(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
) )

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
@ -13,6 +14,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.After import org.junit.After
@ -35,6 +37,9 @@ class `5-SettingsActivityReaderTest` : WithANRException() {
activityRule.scenario.onActivity { activity -> activityRule.scenario.onActivity { activity ->
context = activity.window.context context = activity.window.context
} }
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
onView(withText(R.string.pref_header_viewer)).perform(click()) onView(withText(R.string.pref_header_viewer)).perform(click())
} }

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android
import android.content.Context import android.content.Context
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
@ -15,6 +16,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.After import org.junit.After
@ -37,6 +39,9 @@ class `6-SettingsActivityOfflineTest` : WithANRException() {
activityRule.scenario.onActivity { activity -> activityRule.scenario.onActivity { activity ->
context = activity.window.context context = activity.window.context
} }
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
onView(withText(R.string.pref_header_offline)).perform(click()) onView(withText(R.string.pref_header_offline)).perform(click())
} }

View File

@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.android
import androidx.test.espresso.AmbiguousViewMatcherException import androidx.test.espresso.AmbiguousViewMatcherException
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.action.ViewActions import androidx.test.espresso.action.ViewActions
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.swipeDown import androidx.test.espresso.action.ViewActions.swipeDown
@ -14,6 +15,7 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Rule import org.junit.Rule
@ -32,6 +34,9 @@ class `7-SourcesActivityTest` : WithANRException() {
@Before @Before
fun init() { fun init() {
IdlingRegistry
.getInstance()
.register(CountingIdlingResourceSingleton.countingIdlingResource)
sourceName = UUID.randomUUID().toString().substring(0, 15) sourceName = UUID.randomUUID().toString().substring(0, 15)
goToSources() goToSources()

View File

@ -15,6 +15,7 @@ import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.base.DefaultFailureHandler import androidx.test.espresso.base.DefaultFailureHandler
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.isChecked import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
@ -24,6 +25,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiSelector import androidx.test.uiautomator.UiSelector
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.hamcrest.Matchers.hasToString import org.hamcrest.Matchers.hasToString
import org.junit.BeforeClass import org.junit.BeforeClass
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
@ -32,13 +34,14 @@ import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.util.Locale import java.util.Locale
val defaultUrl = (System.getenv("SELFOSS_URL") ?: "").ifEmpty { "http://10.0.2.2:8888" } // For now, do not move this as it is modified by the integration tests
const val DEFAULT_URL = "http://10.0.2.2:8888"
fun performLogin(someUrl: String? = null) { fun performLogin(someUrl: String? = null) {
Log.i("AUTOMATION", "The url used will be ${if (!someUrl.isNullOrEmpty()) someUrl else defaultUrl}") Log.i("AUTOMATION", "The url used will be ${if (!someUrl.isNullOrEmpty()) someUrl else DEFAULT_URL}")
onView(withId(R.id.urlView)).perform(click()).perform( onView(withId(R.id.urlView)).perform(click()).perform(
typeTextIntoFocusedView( typeTextIntoFocusedView(
if (!someUrl.isNullOrEmpty()) someUrl else defaultUrl, if (!someUrl.isNullOrEmpty()) someUrl else DEFAULT_URL,
), ),
) )
onView(withId(R.id.signInButton)).perform(click()) onView(withId(R.id.signInButton)).perform(click())
@ -138,6 +141,10 @@ fun testAddSourceWithUrl(
onView(withText(sourceName)).check(matches(isDisplayed())) onView(withText(sourceName)).check(matches(isDisplayed()))
} }
fun checkHomeLoadingDone() {
onView(withId(R.id.swipeRefreshLayout)).inRoot(not(isDialog())).perform(waitForRecyclerViewToStopLoading(300000))
}
@Suppress("detekt:UtilityClassWithPublicConstructor") @Suppress("detekt:UtilityClassWithPublicConstructor")
open class WithANRException { open class WithANRException {
companion object { companion object {
@ -153,7 +160,7 @@ open class WithANRException {
"default root matcher, it may be picking a root that never takes focus. " + "default root matcher, it may be picking a root that never takes focus. " +
"Root:", "Root:",
) )
private val otherException = "System Ul isn't responding" private const val OTHER_EXCEPTION = "System Ul isn't responding"
private fun handleAnrDialogue() { private fun handleAnrDialogue() {
val device = UiDevice.getInstance(getInstrumentation()) val device = UiDevice.getInstance(getInstrumentation())
@ -168,7 +175,7 @@ open class WithANRException {
Espresso.setFailureHandler { error, viewMatcher -> Espresso.setFailureHandler { error, viewMatcher ->
takeScreenshot() takeScreenshot()
if (error.message!!.contains(otherException)) { if (error.message!!.contains(OTHER_EXCEPTION)) {
handleAnrDialogue() handleAnrDialogue()
} else if (error.message!!.contains(rootViewWithoutFocusExceptionMsg) && } else if (error.message!!.contains(rootViewWithoutFocusExceptionMsg) &&
anrCount < 20 anrCount < 20
@ -185,6 +192,7 @@ open class WithANRException {
} }
} }
@Suppress("detekt:NestedBlockDepth")
fun takeScreenshot() { fun takeScreenshot() {
try { try {
val bitmap = getInstrumentation().uiAutomation.takeScreenshot() val bitmap = getInstrumentation().uiAutomation.takeScreenshot()

View File

@ -8,22 +8,32 @@ import android.widget.RelativeLayout
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.view.isVisible
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.PerformException
import androidx.test.espresso.Root import androidx.test.espresso.Root
import androidx.test.espresso.UiController
import androidx.test.espresso.ViewAction
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
import androidx.test.espresso.matcher.ViewMatchers.hasSibling import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.isRoot
import androidx.test.espresso.matcher.ViewMatchers.withChild import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withClassName import androidx.test.espresso.matcher.ViewMatchers.withClassName
import androidx.test.espresso.matcher.ViewMatchers.withId import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withParent import androidx.test.espresso.matcher.ViewMatchers.withParent
import androidx.test.espresso.matcher.ViewMatchers.withResourceName import androidx.test.espresso.matcher.ViewMatchers.withResourceName
import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.espresso.util.HumanReadables
import androidx.test.espresso.util.TreeIterables
import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.any
import org.hamcrest.Description import org.hamcrest.Description
import org.hamcrest.Matcher import org.hamcrest.Matcher
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.hamcrest.TypeSafeMatcher import org.hamcrest.TypeSafeMatcher
import java.util.concurrent.TimeoutException
fun withError( fun withError(
@StringRes id: Int, @StringRes id: Int,
@ -44,6 +54,86 @@ fun withError(
} }
} }
fun waitUntilShown(
viewText: String,
millis: Long,
): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String = "wait for $millis millis, for a specific view with text <$viewText> to be visible."
override fun perform(
uiController: UiController,
view: View,
) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + millis
val viewMatcher = withText(viewText)
do {
for (child in TreeIterables.breadthFirstViewTraversal(view)) {
if (viewMatcher.matches(child) && child.isShown) {
return
}
}
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime)
// timeout happens
throw PerformException
.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
}
}
fun waitForRecyclerViewToStopLoading(millis: Long): ViewAction {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = any(View::class.java)
override fun getDescription(): String = "wait for $millis millis for the recyclerview to stop loading."
override fun perform(
uiController: UiController,
view: View?,
) {
uiController.loopMainThreadUntilIdle()
val startTime = System.currentTimeMillis()
val endTime = startTime + millis
do {
// either the empty view is displayed
for (child in TreeIterables.breadthFirstViewTraversal(view)) {
// found view with required ID
if (withId(R.id.emptyText).matches(child) && child.isVisible) {
return
}
}
// or the refresh layout is refreshing
if (view is SwipeRefreshLayout && !view.isRefreshing) {
return
}
uiController.loopMainThreadForAtLeast(100)
} while (System.currentTimeMillis() < endTime)
// timeout happens
throw PerformException
.Builder()
.withActionDescription(this.description)
.withViewDescription(HumanReadables.describe(view))
.withCause(TimeoutException())
.build()
}
}
}
fun isPopupWindow(): Matcher<Root> = isPlatformPopup() fun isPopupWindow(): Matcher<Root> = isPlatformPopup()
fun withDrawable( fun withDrawable(

View File

@ -104,7 +104,7 @@ class HomeActivity :
if (appSettingsService.isItemCachingEnabled()) { if (appSettingsService.isItemCachingEnabled()) {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -120,12 +120,9 @@ class HomeActivity :
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false repository.offlineOverride = false
lastFetchDone = false lastFetchDone = false
CountingIdlingResourceSingleton.increment() items.clear()
CoroutineScope(Dispatchers.Main).launch { getElementsAccordingToTab()
getElementsAccordingToTab() binding.swipeRefreshLayout.isRefreshing = false
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
}
} }
val swipeDirs = val swipeDirs =
@ -289,7 +286,7 @@ class HomeActivity :
handleRecurringTask() handleRecurringTask()
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.handleDBActions() repository.handleDBActions()
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -390,6 +387,7 @@ class HomeActivity :
lastFetchDone = false lastFetchDone = false
elementsShown = ItemType.fromInt(position + 1) elementsShown = ItemType.fromInt(position + 1)
items = ArrayList()
getElementsAccordingToTab() getElementsAccordingToTab()
binding.recyclerView.scrollToPosition(0) binding.recyclerView.scrollToPosition(0)
@ -462,19 +460,28 @@ class HomeActivity :
appendResults: Boolean, appendResults: Boolean,
itemType: ItemType, itemType: ItemType,
) { ) {
CountingIdlingResourceSingleton.increment() @Suppress("detekt:ComplexCondition")
CoroutineScope(Dispatchers.Main).launch { if ((appendResults && items.size > 0) || (!appendResults && items.size == 0)) {
CountingIdlingResourceSingleton.increment()
binding.swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
repository.displayedItems = itemType CoroutineScope(Dispatchers.IO).launch {
items = repository.displayedItems = itemType
if (appendResults) { items =
repository.getOlderItems() if (appendResults) {
} else { repository.getOlderItems()
repository.getNewerItems() } else {
repository.getNewerItems()
}
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
CountingIdlingResourceSingleton.decrement()
} }
binding.swipeRefreshLayout.isRefreshing = false CountingIdlingResourceSingleton.decrement()
}
} else {
handleListResult() handleListResult()
CountingIdlingResourceSingleton.decrement()
} }
} }
@ -534,7 +541,10 @@ class HomeActivity :
} }
private fun reloadBadges() { private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) { if (appSettingsService.isInfiniteLoadingEnabled() ||
appSettingsService.isDisplayUnreadCountEnabled() ||
appSettingsService.isDisplayAllCountEnabled()
) {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.reloadBadges() repository.reloadBadges()
@ -613,22 +623,26 @@ class HomeActivity :
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
val updatedRemote = repository.updateRemote() val updatedRemote = repository.updateRemote()
if (updatedRemote) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (updatedRemote) {
this@HomeActivity, Toast
R.string.refresh_success_response, .makeText(
Toast.LENGTH_LONG, this@HomeActivity,
).show() R.string.refresh_success_response,
} else { Toast.LENGTH_LONG,
Toast ).show()
.makeText( } else {
this@HomeActivity, Toast
R.string.refresh_failer_message, .makeText(
Toast.LENGTH_SHORT, this@HomeActivity,
).show() R.string.refresh_failer_message,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -639,30 +653,33 @@ class HomeActivity :
R.id.readAll -> { R.id.readAll -> {
if (elementsShown == ItemType.UNREAD) { if (elementsShown == ItemType.UNREAD) {
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
binding.swipeRefreshLayout.isRefreshing = true
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { binding.swipeRefreshLayout.isRefreshing = true
CoroutineScope(Dispatchers.IO).launch {
val success = repository.markAllAsRead(items) val success = repository.markAllAsRead(items)
if (success) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (success) {
this@HomeActivity, Toast
R.string.all_posts_read, .makeText(
Toast.LENGTH_SHORT, this@HomeActivity,
).show() R.string.all_posts_read,
tabNewBadge.removeBadge() Toast.LENGTH_SHORT,
).show()
tabNewBadge.removeBadge()
getElementsAccordingToTab() getElementsAccordingToTab()
} else { } else {
Toast Toast
.makeText( .makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_not_read, R.string.all_posts_not_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
).show() ).show()
}
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
} }
handleListResult()
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
} }

View File

@ -108,7 +108,7 @@ class LoginActivity :
private fun goToMain() { private fun goToMain() {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.updateApiInformation() repository.updateApiInformation()
ACRA.errorReporter.putCustomData( ACRA.errorReporter.putCustomData(
"SELFOSS_API_VERSION", "SELFOSS_API_VERSION",
@ -127,8 +127,12 @@ class LoginActivity :
binding.urlView.error = getString(R.string.wrong_infos) binding.urlView.error = getString(R.string.wrong_infos)
binding.loginView.error = getString(R.string.wrong_infos) binding.loginView.error = getString(R.string.wrong_infos)
binding.passwordView.error = getString(R.string.wrong_infos) binding.passwordView.error = getString(R.string.wrong_infos)
binding.urlView.requestFocus()
showProgress(false)
} }
@Suppress("detekt:LongMethod")
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
binding.urlView.error = null binding.urlView.error = null
@ -160,34 +164,41 @@ class LoginActivity :
repository.refreshLoginInformation(url, login, password) repository.refreshLoginInformation(url, login, password)
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
repository.updateApiInformation() repository.updateApiInformation()
val result = repository.login()
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
}
} catch (e: Exception) { } catch (e: Exception) {
if (e.message?.startsWith("No transformation found") == true) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (e.message?.startsWith("No transformation found") == true) {
applicationContext, Toast
R.string.application_selfoss_only, .makeText(
Toast.LENGTH_LONG, applicationContext,
).show() R.string.application_selfoss_only,
preferenceError() Toast.LENGTH_LONG,
showProgress(false) ).show()
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
} }
} finally {
CountingIdlingResourceSingleton.decrement()
} }
val result = repository.login()
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
showProgress(false)
CountingIdlingResourceSingleton.decrement()
} }
} }
@ -300,6 +311,7 @@ class LoginActivity :
.withAboutSpecial2Description(AppSettingsService.BUG_URL) .withAboutSpecial2Description(AppSettingsService.BUG_URL)
.withAboutSpecial1("Project Page") .withAboutSpecial1("Project Page")
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL) .withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
.withShowLoadingProgress(false)
.start(this) .start(this)
true true
} }

View File

@ -73,7 +73,7 @@ class MyApp :
), ),
) )
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
connectivityService.networkAvailableProvider.collect { networkAvailable -> connectivityService.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = val toastMessage =
if (networkAvailable) { if (networkAvailable) {
@ -82,13 +82,14 @@ class MyApp :
} else { } else {
R.string.network_connectivity_lost R.string.network_connectivity_lost
} }
launch(Dispatchers.Main) {
Toast Toast
.makeText( .makeText(
applicationContext, applicationContext,
toastMessage, toastMessage,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
).show() ).show()
}
} }
} }
} }

View File

@ -27,7 +27,7 @@ class ReaderActivity :
DIAware { DIAware {
private var currentItem: Int = 0 private var currentItem: Int = 0
private lateinit var toolbarMenu: Menu private var toolbarMenu: Menu? = null
private lateinit var binding: ActivityReaderBinding private lateinit var binding: ActivityReaderBinding
@ -90,8 +90,10 @@ class ReaderActivity :
} }
private fun updateStarIcon() { private fun updateStarIcon() {
val isStarred = allItems.getOrNull(currentItem)?.starred ?: false if (toolbarMenu != null) {
toolbarMenu.findItem(R.id.star)?.icon?.setTint(if (isStarred) Color.RED else Color.WHITE) 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) { override fun onSaveInstanceState(oldInstanceState: Bundle) {
@ -133,8 +135,10 @@ class ReaderActivity :
private fun alignmentMenu() { private fun alignmentMenu() {
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify if (toolbarMenu != null) {
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify toolbarMenu!!.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu!!.findItem(R.id.align_justify).isVisible = showJustify
}
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -50,6 +50,7 @@ class SourcesActivity :
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
CountingIdlingResourceSingleton.increment()
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
var items: ArrayList<SelfossModel.SourceDetail> var items: ArrayList<SelfossModel.SourceDetail>
@ -57,25 +58,28 @@ class SourcesActivity :
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
CountingIdlingResourceSingleton.increment() CoroutineScope(Dispatchers.IO).launch {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSourcesDetails() val response = repository.getSourcesDetails()
if (response.isNotEmpty()) { CountingIdlingResourceSingleton.increment()
items = response launch(Dispatchers.Main) {
val mAdapter = if (response.isNotEmpty()) {
SourcesListAdapter( items = response
this@SourcesActivity, val mAdapter =
items, SourcesListAdapter(
) this@SourcesActivity,
binding.recyclerView.adapter = mAdapter items,
mAdapter.notifyDataSetChanged() )
} else { binding.recyclerView.adapter = mAdapter
Toast mAdapter.notifyDataSetChanged()
.makeText( } else {
this@SourcesActivity, Toast
R.string.cant_get_sources, .makeText(
Toast.LENGTH_SHORT, this@SourcesActivity,
).show() R.string.cant_get_sources,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }

View File

@ -9,6 +9,7 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
@ -108,36 +109,42 @@ class UpsertSourceActivity :
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE
} }
CoroutineScope(Dispatchers.Main).launch { CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
try { try {
val items = repository.getSpouts() val items = repository.getSpouts()
if (items.isNotEmpty()) { CountingIdlingResourceSingleton.increment()
val itemsStrings = items.map { it.value.name } launch(Dispatchers.Main) {
for ((key, value) in items) { if (items.isNotEmpty()) {
spoutsKV[value.name] = key val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@UpsertSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings,
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else {
handleSpoutFailure()
} }
CountingIdlingResourceSingleton.decrement()
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@UpsertSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings,
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else {
handleSpoutFailure()
} }
} catch (e: NetworkUnavailableException) { } catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = true) handleSpoutFailure(networkIssue = true)
} }
CountingIdlingResourceSingleton.decrement()
} }
} }
@ -160,7 +167,8 @@ class UpsertSourceActivity :
} }
else -> { else -> {
CoroutineScope(Dispatchers.Main).launch { CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
val successfullyAddedSource = val successfullyAddedSource =
if (existingSource != null) { if (existingSource != null) {
repository.updateSource( repository.updateSource(
@ -178,16 +186,21 @@ class UpsertSourceActivity :
binding.tags.text.toString(), binding.tags.text.toString(),
) )
} }
if (successfullyAddedSource) { CountingIdlingResourceSingleton.increment()
finish() launch(Dispatchers.Main) {
} else { if (successfullyAddedSource) {
Toast finish()
.makeText( } else {
this@UpsertSourceActivity, Toast
R.string.cant_create_source, .makeText(
Toast.LENGTH_SHORT, this@UpsertSourceActivity,
).show() R.string.cant_create_source,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement()
} }
} }
} }

View File

@ -30,7 +30,7 @@ import org.kodein.di.instance
class ItemCardAdapter( class ItemCardAdapter(
override val app: Activity, override val app: Activity,
override val items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() { ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
override lateinit var binding: CardItemBinding override lateinit var binding: CardItemBinding

View File

@ -21,7 +21,7 @@ import org.kodein.di.instance
class ItemListAdapter( class ItemListAdapter(
override val app: Activity, override val app: Activity,
override val items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
) : ItemsAdapter<ItemListAdapter.ViewHolder>() { ) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
override lateinit var binding: ListItemBinding override lateinit var binding: ListItemBinding

View File

@ -21,7 +21,7 @@ import org.kodein.di.DIAware
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> :
RecyclerView.Adapter<VH>(), RecyclerView.Adapter<VH>(),
DIAware { DIAware {
abstract val items: ArrayList<SelfossModel.Item> abstract var items: ArrayList<SelfossModel.Item>
abstract val repository: Repository abstract val repository: Repository
abstract val binding: ViewBinding abstract val binding: ViewBinding
abstract val appSettingsService: AppSettingsService abstract val appSettingsService: AppSettingsService
@ -31,8 +31,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> :
protected val c: Context get() = app.baseContext protected val c: Context get() = app.baseContext
fun updateAllItems(items: ArrayList<SelfossModel.Item>) { fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
this.items.clear() this.items = items
this.items.addAll(items)
updateHomeItems(items) updateHomeItems(items)
notifyDataSetChanged() notifyDataSetChanged()
} }

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity import bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity
import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
@ -104,8 +105,10 @@ class SourcesListAdapter(
source: SelfossModel.SourceDetail, source: SelfossModel.SourceDetail,
position: Int, position: Int,
) { ) {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(source.id, source.title) val successfullyDeletedSource = repository.deleteSource(source.id, source.title)
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
if (successfullyDeletedSource) { if (successfullyDeletedSource) {
items.removeAt(position) items.removeAt(position)
@ -119,7 +122,9 @@ class SourcesListAdapter(
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
).show() ).show()
} }
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement()
} }
} }
} }

View File

@ -14,6 +14,7 @@ import android.view.ViewGroup
import bou.amine.apps.readerforselfossv2.android.HomeActivity import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget import bou.amine.apps.readerforselfossv2.android.utils.glide.imageIntoViewTarget
import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext import bou.amine.apps.readerforselfossv2.android.utils.maybeIfContext
@ -59,12 +60,14 @@ class FilterSheetFragment :
) )
try { try {
CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
handleTagChips() handleTagChips()
handleSourceChips() handleSourceChips()
binding.progressBar2.visibility = GONE binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE binding.filterView.visibility = VISIBLE
CountingIdlingResourceSingleton.decrement()
} }
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
dismiss() dismiss()

View File

@ -124,6 +124,7 @@ class SettingsActivity :
LibsBuilder() LibsBuilder()
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.withShowLoadingProgress(false)
.start(it) .start(it)
} }
true true

View File

@ -0,0 +1,17 @@
**v125030901**
- Merge pull request 'fix-reload' (#195) from fix-reload into master
- fix: Infinite scroll needs loading stats.
- fix: do not reload items on resume.
- Merge pull request 'tests' (#193) from tests into master
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- ci: Instrumentation tests coverage in ci.
- chore: better handling of coroutine dispatchers.
- ci: Instrumentation tests coverage in ci.
- chore: comment robolectric tests for now.
- fix: Fixed source deletion test.
- Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
- Refactor star icon handling
- Don't restart activity changing alignment
- Changelog for v125030711

View File

@ -0,0 +1,4 @@
**v125040991**
- fix: Connectivity toast message was causing issues.
- Changelog for v125030901

View File

@ -334,7 +334,7 @@ class Repository(
_badgeUnread.value -= 1 _badgeUnread.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -345,7 +345,7 @@ class Repository(
_badgeUnread.value += 1 _badgeUnread.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -356,7 +356,7 @@ class Repository(
_badgeStarred.value += 1 _badgeStarred.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -367,7 +367,7 @@ class Repository(
_badgeStarred.value -= 1 _badgeStarred.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }

View File

@ -32,6 +32,7 @@ import io.ktor.utils.io.charsets.Charsets
import io.ktor.utils.io.core.toByteArray import io.ktor.utils.io.core.toByteArray
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -82,7 +83,7 @@ class SelfossApi(
} }
modifyRequest { modifyRequest {
Napier.i("Will modify", tag = "HttpSend") Napier.i("Will modify", tag = "HttpSend")
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
Napier.i("Will login", tag = "HttpSend") Napier.i("Will login", tag = "HttpSend")
login() login()
Napier.i("Did login", tag = "HttpSend") Napier.i("Did login", tag = "HttpSend")

View File

@ -16,7 +16,7 @@ class ConnectivityService {
fun start() { fun start() {
connectivity = Connectivity() connectivity = Connectivity()
connectivity.start() connectivity.start()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
connectivity.statusUpdates.collect { status -> connectivity.statusUpdates.collect { status ->
when (status) { when (status) {
is Connectivity.Status.Connected -> { is Connectivity.Status.Connected -> {