Compare commits

...

7 Commits

Author SHA1 Message Date
a5932f2772 ci: Instrumentation tests coverage in ci.
Some checks failed
Check PR code / EspressoReports (pull_request) Has been cancelled
2025-03-16 17:59:40 +01:00
02d503e03a chore: comment robolectric tests for now. 2025-03-16 15:44:02 +01:00
24b9320d6d fix: Fixed source deletion test. 2025-03-16 14:27:30 +01:00
ceba58e98f Merge pull request 'Fix alignment changes resetting reader article position' (#190) from davidoskky/ReaderForSelfoss-multiplatform:alignment into master
All checks were successful
Check master code / build (push) Successful in 8m27s
Reviewed-on: #190
2025-03-16 13:14:44 +00:00
c3ee07dd85 Refactor star icon handling
All checks were successful
Check PR code / translations (pull_request) Successful in 1m4s
Check PR code / Lint (pull_request) Successful in 1m25s
Check PR code / build (pull_request) Successful in 11m52s
Extracted all favorite handling to two functions. Makes it a little bit more readable.
2025-03-12 16:07:49 +01:00
93d99192b3 Don't restart activity changing alignment
When changing alignment in the reader we were restarting the reader activity to reload. Doing this led to reloading the article which was initially opened every time you changed alignment. Now, when changing the alignment we retain all existing fragments but command all of them to update their alignment setting.
2025-03-12 16:07:49 +01:00
359dec2ca0 Changelog for v125030711 2025-03-12 11:39:57 +00:00
11 changed files with 255 additions and 183 deletions

View File

@ -0,0 +1,72 @@
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 "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: AVD cache
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-29
- name: create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
arch: x86_64
profile: Nexus 6
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."
- name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 29
arch: x86_64
profile: Nexus 6
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./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

View File

@ -3,89 +3,91 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
- chore-crowdin-ci
jobs: jobs:
Lint: EspressoReports:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: uses: ./.gitea/workflows/common_coverage.yml
- name: Check out repository code # Lint:
uses: actions/checkout@v4 # runs-on: ubuntu-latest
- uses: actions/setup-java@v4 # steps:
with: # - name: Check out repository code
distribution: 'temurin' # uses: actions/checkout@v4
java-version: '17' # - uses: actions/setup-java@v4
cache: gradle # with:
- name: Install klint # distribution: 'temurin'
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/ # java-version: '17'
- name: Install detekt # cache: gradle
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: Install klint
- name: Linting... # run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.5.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' # - name: Install detekt
- name: Detecting... # 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
run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt' # - name: Linting...
translations: # run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
runs-on: ubuntu-latest # - name: Detecting...
steps: # run: ./detekt-cli-1.23.7/bin/detekt-cli -c detekt.yml --excludes '**/shared/build/**/*.kt'
- name: Check out repository code # translations:
uses: actions/checkout@v4 # runs-on: ubuntu-latest
with: # steps:
fetch-depth: 0 # - name: Check out repository code
- name: "Check translations changes" # uses: actions/checkout@v4
id: check-translations-changes # with:
uses: tj-actions/changed-files@v45 # fetch-depth: 0
with: # - name: "Check translations changes"
files: | # id: check-translations-changes
androidApp/src/main/res/values/strings.xml # uses: tj-actions/changed-files@v45
- name: upload translation sources # with:
if: steps.check-api-changes.outputs.any_modified == 'true' # files: |
uses: crowdin/github-action@v2 # androidApp/src/main/res/values/strings.xml
with: # - name: upload translation sources
config: './.gitea/workflows/assets/crowdin.yml' # if: steps.check-api-changes.outputs.any_modified == 'true'
upload_sources: true # uses: crowdin/github-action@v2
upload_translations: false # with:
download_translations: false # config: './.gitea/workflows/assets/crowdin.yml'
create_pull_request: false # upload_sources: true
push_translations: false # upload_translations: false
env: # download_translations: false
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} # create_pull_request: false
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} # push_translations: false
- name: wait # env:
if: steps.check-api-changes.outputs.any_modified == 'true' # CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
run: sleep 10s # CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
- name: download translations # - name: wait
if: steps.check-api-changes.outputs.any_modified == 'true' # if: steps.check-api-changes.outputs.any_modified == 'true'
uses: crowdin/github-action@v2 # run: sleep 10s
with: # - name: download translations
config: './.gitea/workflows/assets/crowdin.yml' # if: steps.check-api-changes.outputs.any_modified == 'true'
upload_sources: false # uses: crowdin/github-action@v2
upload_translations: false # with:
download_translations: true # config: './.gitea/workflows/assets/crowdin.yml'
create_pull_request: false # upload_sources: false
push_translations: false # upload_translations: false
env: # download_translations: true
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} # create_pull_request: false
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} # push_translations: false
- name: Check for uncommitted changes # env:
if: steps.check-api-changes.outputs.any_modified == 'true' # CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
id: check-changes # CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
uses: mskri/check-uncommitted-changes-action@v1.0.1 # - name: Check for uncommitted changes
- name: Commit Changes # if: steps.check-api-changes.outputs.any_modified == 'true'
if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != '' # id: check-changes
run: | # uses: mskri/check-uncommitted-changes-action@v1.0.1
git config --global user.email aminecmi+giteadrone@pm.me # - name: Commit Changes
git config --global user.name giteadrone # if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
git add ./androidApp/src/main/res/* # run: |
git commit -m "translation: translation files" # git config --global user.email aminecmi+giteadrone@pm.me
- name: Push changes # git config --global user.name giteadrone
if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != '' # git add ./androidApp/src/main/res/*
uses: appleboy/git-push-action@v1.0.0 # git commit -m "translation: translation files"
with: # - name: Push changes
author_name: giteadrone # if: steps.check-api-changes.outputs.any_modified == 'true' && steps.check-changes.outputs.changes != ''
author_email: aminecmi+giteadrone@pm.me # uses: appleboy/git-push-action@v1.0.0
remote: ${{ secrets.REMOTE_URL }} # with:
ssh_key: ${{ secrets.PRIVATE_KEY }} # author_name: giteadrone
branch: ${{ github.head_ref || github.ref_name }} # author_email: aminecmi+giteadrone@pm.me
build: # remote: ${{ secrets.REMOTE_URL }}
needs: Lint # ssh_key: ${{ secrets.PRIVATE_KEY }}
uses: ./.gitea/workflows/common_build.yml # branch: ${{ github.head_ref || github.ref_name }}
# build:
# needs: Lint
# uses: ./.gitea/workflows/common_build.yml

View File

@ -1,3 +1,14 @@
**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 **v125030681
- chore: do not send reports on simulators. - chore: do not send reports on simulators.

View File

@ -96,6 +96,7 @@ android {
// tests // tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments["clearPackageData"] = "true" testInstrumentationRunnerArguments["clearPackageData"] = "true"
testInstrumentationRunnerArguments["useTestStorageService"] = "true"
} }
packaging { packaging {
resources { resources {
@ -109,6 +110,8 @@ android {
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
} }
getByName("debug") { getByName("debug") {
isTestCoverageEnabled = true
enableAndroidTestCoverage = true
} }
} }
flavorDimensions.add("build") flavorDimensions.add("build")
@ -197,14 +200,15 @@ dependencies {
testImplementation("io.mockk:mockk:1.13.14") testImplementation("io.mockk:mockk:1.13.14")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
androidTestImplementation("androidx.test:runner:1.6.2") androidTestImplementation("androidx.test:runner:1.7.0-alpha01")
androidTestImplementation("androidx.test:rules:1.6.1") androidTestImplementation("androidx.test:rules:1.7.0-alpha01")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
implementation("androidx.test.espresso:espresso-idling-resource:3.6.1") implementation("androidx.test.espresso:espresso-idling-resource:3.6.1")
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.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("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-http:$acraVersion")
implementation("ch.acra:acra-toast:$acraVersion") implementation("ch.acra:acra-toast:$acraVersion")

View File

@ -71,6 +71,8 @@ class SourcesActivityTest {
fun deleteTheCreatedSource() { fun deleteTheCreatedSource() {
onView(withText(sourceName)).check(matches(isDisplayed())) onView(withText(sourceName)).check(matches(isDisplayed()))
onView(withId(R.id.deleteBtn)).perform(click()) 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()) onView(withText(sourceName)).check(doesNotExist())
} }

View File

@ -37,22 +37,6 @@ class ReaderActivity :
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService 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") @Suppress("detekt:SwallowedException")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -73,14 +57,21 @@ class ReaderActivity :
finish() finish()
} }
try { readItem()
readItem(allItems[currentItem])
} catch (e: IndexOutOfBoundsException) {
finish()
}
binding.pager.adapter = ScreenSlidePagerAdapter(this) binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(currentItem, false) 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() { override fun onResume() {
@ -89,14 +80,20 @@ class ReaderActivity :
binding.indicator.setViewPager(binding.pager) binding.indicator.setViewPager(binding.pager)
} }
private fun readItem(item: SelfossModel.Item) { private fun readItem() {
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) { val item = allItems.getOrNull(currentItem)
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess() && item != null) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item) 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) { override fun onSaveInstanceState(oldInstanceState: Bundle) {
super.onSaveInstanceState(oldInstanceState) super.onSaveInstanceState(oldInstanceState)
oldInstanceState.clear() oldInstanceState.clear()
@ -141,8 +138,7 @@ class ReaderActivity :
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater menuInflater.inflate(R.menu.reader_menu, menu)
inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu toolbarMenu = menu
alignmentMenu() alignmentMenu()
@ -150,87 +146,50 @@ class ReaderActivity :
if (appSettingsService.getPublicAccess()) { if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.star) menu.removeItem(R.id.star)
} else { } else {
if (allItems.isNotEmpty() && allItems[currentItem].starred) { updateStarIcon()
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])
}
}
},
)
} }
return true return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { 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) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> onBackPressedDispatcher.onBackPressed()
onBackPressedDispatcher.onBackPressed() R.id.star -> toggleFavorite()
return true R.id.align_left -> switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
} R.id.align_justify -> switchAlignmentSetting(AppSettingsService.JUSTIFY)
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()
}
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
} }
private fun switchAlignmentSetting(allignment: Int) { private fun toggleFavorite() {
appSettingsService.changeAllignment(allignment) val item = allItems.getOrNull(currentItem) ?: return
alignmentMenu()
val starred = item.starred
CoroutineScope(Dispatchers.IO).launch {
if (starred) {
repository.unstarr(item)
} else {
repository.starr(item)
}
}
item.toggleStar()
updateStarIcon()
} }
private fun refreshFragment() { private fun switchAlignmentSetting(alignment: Int) {
finish() appSettingsService.changeAllignment(alignment)
overridePendingTransition(0, 0) alignmentMenu()
startActivity(intent)
overridePendingTransition(0, 0) val fragmentManager = supportFragmentManager
val fragments = fragmentManager.fragments
for (fragment in fragments) {
if (fragment is ArticleFragment) {
fragment.refreshAlignment()
}
}
} }
} }

View File

@ -263,13 +263,15 @@ class ArticleFragment :
) )
} }
private fun refreshAlignment() { fun refreshAlignment() {
textAlignment = textAlignment =
when (appSettingsService.getActiveAllignment()) { when (appSettingsService.getActiveAllignment()) {
1 -> "justify" 1 -> "justify"
2 -> "left" 2 -> "left"
else -> "justify" else -> "justify"
} }
htmlToWebview()
} }
@Suppress("detekt:SwallowedException") @Suppress("detekt:SwallowedException")

View File

@ -1,3 +1,5 @@
@file:Suppress("ktlint")
/*
package bou.amine.apps.readerforselfossv2.android.tests.robolectric package bou.amine.apps.readerforselfossv2.android.tests.robolectric
import android.view.Menu import android.view.Menu
@ -25,3 +27,4 @@ fun Menu.assertVisible(
val item = this.findItem(id) val item = this.findItem(id)
assertTrue(item.isVisible) assertTrue(item.isVisible)
} }
*/

View File

@ -1,3 +1,5 @@
@file:Suppress("ktlint")
/*
package bou.amine.apps.readerforselfossv2.android.tests.robolectric package bou.amine.apps.readerforselfossv2.android.tests.robolectric
import android.widget.Button import android.widget.Button
@ -57,7 +59,8 @@ class LoginActivityTest {
} }
} }
/* @Test */
/* @Test
fun connect() { fun connect() {
Robolectric.buildActivity(LoginActivity::class.java).use { controller -> Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
controller.setup() // Moves the Activity to the RESUMED state controller.setup() // Moves the Activity to the RESUMED state
@ -72,4 +75,7 @@ class LoginActivityTest {
assertEquals(expectedIntent.component, actual.component) assertEquals(expectedIntent.component, actual.component)
} }
}*/ }*/
/*
} }
*/

View File

@ -1,3 +1,5 @@
@file:Suppress("ktlint")
/*
package bou.amine.apps.readerforselfossv2.android.tests.robolectric package bou.amine.apps.readerforselfossv2.android.tests.robolectric
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
@ -8,3 +10,4 @@ class RobotElectriqueRunner(
) : RobolectricTestRunner(testClass) { ) : RobolectricTestRunner(testClass) {
override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build() override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build()
} }
*/

View File

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