Compare commits

..

No commits in common. "master" and "v122113181" have entirely different histories.

131 changed files with 3524 additions and 4884 deletions

View File

@ -3,38 +3,27 @@ type: docker
name: test name: test
steps: steps:
- name: Lint - name: AnylyseBuildTest
failure: ignore
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Install linters..."
- curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
- curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
- echo "---------------------------------------------------------"
- echo "Linting..."
- ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build' || true
- echo "---------------------------------------------------------"
- echo "Detecting..."
- ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
- echo "---------------------------------------------------------"
command_timeout: 1m
- name: BuildAndTest
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: commands:
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Configure gradle..." - echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Configure java..." - echo "Analysing..."
- . ~/.bash_profile - ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
- jenv global 17.0
- java --version
- date
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Building and testing..." - echo "Building..."
- ./gradlew build - ./gradlew build
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
SONAR_LOGIN:
from_secret: sonarScannerLogin
trigger: trigger:
event: event:
- push - push
@ -46,36 +35,23 @@ type: docker
name: Publish name: Publish
steps: steps:
- name: createTagAndChangelog - name: createTag
image: ubuntu:latest image: ubuntu:latest
commands: commands:
- apt-get update && apt-get install -y git - apt-get update && apt-get install -y git
- git fetch --tags -p
- PREV=$(git describe --tags --abbrev=0)
- ./build.sh --publish --from-ci - ./build.sh --publish --from-ci
- VER=$(git describe --tags --abbrev=0) - git remote add pushing https://$GITEA_USR:$GITEA_PASS@gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform.git
- CHANGELOG=$(git log $PREV..HEAD --pretty="- %s") - git push pushing --tags
- echo "**$VER**\n\n$CHANGELOG\n\n--------------------------------------------------------------------\n\n$(cat CHANGELOG.md)" > CHANGELOG.md
- git add CHANGELOG.md
- git commit -m "Changelog for $VER [CI SKIP]"
environment: environment:
TZ: Europe/Paris GITEA_USR:
from_secret: giteaUsr
- name: git-push GITEA_PASS:
image: appleboy/drone-git-push from_secret: giteaPass
settings:
branch: master
remote:
from_secret: remoteUrl
followtags: true
ssh_key:
from_secret: privateKey
skip_verify: true
- name: scpFiles - name: scpFiles
image: appleboy/drone-scp image: appleboy/drone-scp
settings: settings:
host: amine-bouabdallaoui.fr host: amine-louveau.fr
username: ubuntu username: ubuntu
key: key:
from_secret: privateKey from_secret: privateKey
@ -86,13 +62,16 @@ steps:
- name: deploy - name: deploy
image: appleboy/drone-ssh image: appleboy/drone-ssh
settings: settings:
host: amine-bouabdallaoui.fr host: amine-louveau.fr
user: ubuntu user: ubuntu
key: key:
from_secret: privateKey from_secret: privateKey
command_timeout: 2m command_timeout: 2m
script: script:
- cd /home/ubuntu && sudo rm -rf /var/www/amine/version.txt && sudo chown www-data:www-data ./version.txt && sudo mv version.txt /var/www/amine/ - cd /home/ubuntu
- sudo rm -rf /var/www/amine/version.txt
- sudo chown www-data:www-data ./version.txt
- sudo mv version.txt /var/www/amine/
trigger: trigger:
event: event:
@ -109,18 +88,15 @@ steps:
- name: build - name: build
image: mingc/android-build-box:latest image: mingc/android-build-box:latest
commands: commands:
- echo "---------------------------------------------------------"
- echo "Fetch tags..."
- git fetch --tags
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Configure gradle..." - echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties - mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\npushCache=false\nsystemProp.org.gradle.internal.http.connectionTimeout=180000\nsystemProp.org.gradle.internal.http.socketTimeout=180000" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Generate APK" - echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease - ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Get Key" - echo "Get Key"
- wget https://amine-bouabdallaoui.fr/key - wget https://amine-louveau.fr/key
- echo "---------------------------------------------------------" - echo "---------------------------------------------------------"
- echo "Zipalign" - echo "Zipalign"
- $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk - $ANDROID_HOME/build-tools/31.0.0/zipalign -f -v 4 androidApp/build/outputs/apk/githubConfig/release/androidApp-githubConfig-release-unsigned.apk androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
@ -131,7 +107,6 @@ steps:
- echo "Verify" - echo "Verify"
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk - $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
environment: environment:
TZ: Europe/Paris
YOUR_KEYSTORE_PASSWORD: YOUR_KEYSTORE_PASSWORD:
from_secret: keyPass from_secret: keyPass
YOUR_KEY_ALIAS: YOUR_KEY_ALIAS:
@ -142,29 +117,8 @@ steps:
settings: settings:
api_key: api_key:
from_secret: giteaAPI from_secret: giteaAPI
base_url: https://gitea.amine-bouabdallaoui.fr base_url: https://gitea.amine-louveau.fr
files: signed.apk files: signed.apk
- name: notify
image: drillster/drone-email
failure: ignore
settings:
host:
from_secret: smtpHOST
port:
from_secret: smtpPORT
username:
from_secret: smtpUSERNAME
password:
from_secret: smtpPASSWORD
from:
from_secret: smtpFROM
subject: Mapping file
recipients:
from_secret: smtpTO
recipients_only: true
skip_verify: true
attachment: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
trigger: trigger:
event: event:
- tag - tag

View File

@ -10,7 +10,7 @@ Please read the guidelines before contributing, and follow them (or try to) when
There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users. There are many ways to contribute to this project, you could [translate the app](https://crowdin.com/project/readerforselfoss), report bugs, request missing features, suggest enhancements and changes to existing ones. You also can improve the README with useful tips that could help the other users.
You can fork the repository, and [help me solve some issues](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues) You can fork the repository, and [help me solve some issues](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues?q=is%3Aissue+is%3Aopen+label%3A%22Up+For+Grabs%22) or [develop new things](https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/issues)
### What I can't help you with. ### What I can't help you with.

View File

@ -1,284 +1,3 @@
**v124041081**
- chore: comment.
- fix: Last time fixing the parsing date hack before moving it to os version.
- Changelog for v124030731 [CI SKIP]
--------------------------------------------------------------------
**v124030731**
- fix: Basic auth and password can have non whitspace characters. Fixes 142.
- Changelog for v124020451 [CI SKIP]
--------------------------------------------------------------------
**v124020451**
- fix: Fixed handling of position in card adapter.
- Changelog for v124010301 [CI SKIP]
--------------------------------------------------------------------
**v124010301**
- fix: This may fix the oom errors.
- Changelog for v124010191 [CI SKIP]
--------------------------------------------------------------------
**v124010191**
- fix: moving listeners.
- chore: removed a useless log.
- Changelog for v124010032 [CI SKIP]
--------------------------------------------------------------------
**v124010032**
- fix: Another date format thing.
- Changelog for v124010031 [CI SKIP]
--------------------------------------------------------------------
**v124010031**
- fix: Checking if selfoss instance.
- fix: handle three characters lenght hexcode colors.
- Changelog for v123113311 [CI SKIP]
--------------------------------------------------------------------
**v123113311**
- chore: Source tracker url in the menu.
- fix: Handle kodein proguard rules.
- Changelog for v123102961 [CI SKIP]
--------------------------------------------------------------------
**v123102961**
- chore: domain changes.
- Changelog for v123102852 [CI SKIP]
--------------------------------------------------------------------
**v123102852**
- chore: lint cleaning.
- Changelog for v123102841 [CI SKIP]
--------------------------------------------------------------------
**v123102841**
- chore: cleaning ci steps and upgrading dependencies.
- feat: Self signed ssl support.
- Changelog for v123061811 [CI SKIP]
--------------------------------------------------------------------
**v123061811**
- feat: Added confirmation dialog for disconnect item menu.
- Changelog for v123061651 [CI SKIP]
--------------------------------------------------------------------
**v123061651**
- i18n: Translation update.
- i18n: Translation update.
- i18n: Translation update.
- fix: avoid trying to open invalid image urls.
- Changelog for v123051471 [CI SKIP]
--------------------------------------------------------------------
**v123051471**
- fix: images could be null.
- fix: Check if color is not empty before parsing it.
- chore: Removed unused log.
- Changelog for v123051331 [CI SKIP]
--------------------------------------------------------------------
**v123051331**
- fix: illegal input.
- Changelog for v123051321 [CI SKIP]
--------------------------------------------------------------------
**v123051321**
- debug: Debug null context.
- Changelog for v123051301 [CI SKIP]
--------------------------------------------------------------------
**v123051301**
- feat: Basic auth from url. Fixes #142 (#143)
- debug: Debug index out of bound exception.
- Changelog for v123051211 [CI SKIP]
--------------------------------------------------------------------
**v123051211**
- fix: Sometimes url isn't even defined.
- Changelog for v123041021 [CI SKIP]
--------------------------------------------------------------------
**v123041021**
- fix: 'Enable Core Library Desugaring to support older Android versions' (#138) from davidoskky/ReaderForSelfoss-multiplatform:desugaring into master
- Enable Core Library Desugaring to support older Android versions
- Changelog for v123030851 [CI SKIP]
--------------------------------------------------------------------
**v123030851**
- chore: replace textDrawable library (#136)
- refactor: Remove slow login check. Closes #135.
- ci: send the mapping file after a release.
- Changelog for v123030751 [CI SKIP]
--------------------------------------------------------------------
**v123030751**
- debug: added a lot to pinpoint the url issue.
- feat: Use /sources/stats in the home (#133)
- Changelog for v123030681 [CI SKIP]
--------------------------------------------------------------------
**v123030681**
- fix: Unread and starred can be null.
- Fixed version number issue.
- Changelog for v123030621 [CI SKIP]
--------------------------------------------------------------------
**v123030621**
- fix: url required issue.
- fix: Canvas reused issue.
- Changelog for v123020572 [CI SKIP]
--------------------------------------------------------------------
**v123020572**
- fix: requirecontext issues ?
- debug: activity not found exception.
- Changelog for v123020571 [CI SKIP]
--------------------------------------------------------------------
**v123020571**
- chore: remove errors logging.
- fix: quickfix for url param not provided for some sources.
- Update 'CHANGELOG.md'
- Changelog for v123020523 [CI SKIP]
--------------------------------------------------------------------
**v123020523**
- fix: Git changelog.
--------------------------------------------------------------------
**v123020491**
- fix: Fixed acra bug reporting.
--------------------------------------------------------------------
**v123010301**
- Chore: acra config.
--------------------------------------------------------------------
**v123010281**
- improvement: Improve right to left support (#130) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
--------------------------------------------------------------------
**v123010261**
- feat: Handle public instances (#126) Co-authored-by: davidoskky <davidoskky@hidden.hidden> Co-committed-by: davidoskky <davidoskky@hidden.hidden>
- ci: Pull request should trigger ci.
- fix: Complete the disconnection before redirecting to the login screen
- Complete the disconnection before redirecting to the login screen
--------------------------------------------------------------------
**v123010241**
- Merge pull request 'feat: swipe down to close images' (#122) from davidoskky/ReaderForSelfoss-multiplatform:swipe_down into master
- Remove unnecessary definition
- Remove unused import
- Adjust the image closing animation
- Add a dark hue to the underlying article when swiping to close images
- Rename activity style to avoid interferences
- Adapt the style of the image activity to the rest of the application
- Resolve issues when swiping down to close images
- Close the image fragment only if the image has been dragged down
- Animate swipe down to close images
- Swipe down to close images
--------------------------------------------------------------------
**v123010041**
- Merge pull request 'scroll-tag-filters' (#124) from scroll-tag-filters into master
- fix: added POST_NOTIFICATIONS to fix notifications issues.
- fix: scrollable filter sheet.
- enhancement: Ellipsize chips text.
- Cleaning.
--------------------------------------------------------------------
**v122123641**
- feat: Disable the failing source in the filter sheet.
- feat: Display the source error in the sources list.
--------------------------------------------------------------------
**v122123631**
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- build: Added back maven repos (see https://gitlab.com/fdroid/fdroiddata/-/commit/1fb9d60dc58511abc2bb4eb321977922a0682c8b#note_1223925153)
- debug: trying to resolve `Canvas: trying to use a recycled bitmap`.
- fix: NPE may be caused by the binding or the title that was null.
- chore: Skip drone pipeline on changelog push.
--------------------------------------------------------------------
**v122123621**
- fix: Automatic CHANGELOG generation.
- Merge pull request 'Sources Upsert' (#119) from sources-edit into master
- Source update screen.
- Sources menu.
- chore: Automatic CHANGELOG generation.
--------------------------------------------------------------------
# V2/Multiplatform rewrite # V2/Multiplatform rewrite
**v1** **v1**

View File

@ -1,4 +1,4 @@
# ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-bouabdallaoui.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform) # ReaderForSelfoss-multiplatform [![Build Status](https://build.amine-louveau.fr/api/badges/Louvorg/ReaderForSelfoss-multiplatform/status.svg)](https://build.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss) [![Crowdin](https://d322cqt584bo4o.cloudfront.net/readerforselfoss/localized.svg)](https://crowdin.com/project/readerforselfoss)
@ -22,15 +22,15 @@ If you are a user, you can still create new issues. I'll fix them when I can.
1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/). 1. **You'll have to have a Selfoss instance running.** You'll find everything you need to install it [here](https://selfoss.aditu.de/).
2. Check the [Contribution guide](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md). 2. Check the [Contribution guide](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md).
3. Build the project by following [these steps](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide) 3. Build the project by following [these steps](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/.github/CONTRIBUTING.md#build-the-project) (you should have read them after the contribution guide)
## Useful links ## Useful links
- [Check what changed](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md) - [Check what changed](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/src/branch/master/CHANGELOG.md)
- [See what I'm doing](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1) - [See what I'm doing](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/projects/1)
- [Create an issue, or request a new feature](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderforSelfoss-multiplatform/issues) - [Create an issue, or request a new feature](https://gitea.amine-louveau.fr/Louvorg/ReaderforSelfoss-multiplatform/issues)
- [Help translation the app](https://crowdin.com/project/readerforselfoss) - [Help translation the app](https://crowdin.com/project/readerforselfoss)
## Contributors (V1) (Alphabetical order) ❤️ ## Contributors (V1) (Alphabetical order) ❤️

View File

@ -1,22 +1,20 @@
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
val ignoreGitVersion: String by project val ignoreGitVersion: String by project
val acraVersion = "5.9.7"
plugins { plugins {
id("com.android.application") id("com.android.application")
kotlin("android") kotlin("android")
kotlin("kapt") kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin") id("com.mikepenz.aboutlibraries.plugin")
id("org.jetbrains.kotlinx.kover")
} }
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
val result: String = ByteArrayOutputStream().use { outputStream -> var result: String = ByteArrayOutputStream().use { outputStream ->
project.exec { project.exec {
commandLine = cmd.split(" ") commandLine = cmd.split(" ")
standardOutput = outputStream standardOutput = outputStream
isIgnoreExitValue = ignore isIgnoreExitValue = ignore ?: false
} }
outputStream.toString() outputStream.toString()
} }
@ -24,10 +22,11 @@ fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
} }
fun gitVersion(): String { fun gitVersion(): String {
var process = ""
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true) val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
val process = if (maybeTagOfCurrentCommit.isEmpty()) { process = if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.") 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") execWithOutput("git -C ../ for-each-ref refs/tags --sort=-authordate --format='%(refname:short)' --count=1")
} else { } else {
println("Tag found on current commit") println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD") execWithOutput("git -C ../ describe --contains HEAD")
@ -55,24 +54,24 @@ fun versionNameFromGit(): String {
android { android {
compileOptions { compileOptions {
isCoreLibraryDesugaringEnabled = true
// Flag to enable support for the new language APIs // Flag to enable support for the new language APIs
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_11
} }
// For Kotlin projects // For Kotlin projects
kotlinOptions { kotlinOptions {
jvmTarget = "17" jvmTarget = "11"
} }
compileSdk = 34 compileSdk = 33
buildToolsVersion = "31.0.0"
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
} }
defaultConfig { defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android" applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 25 minSdk = 21
targetSdk = 34 targetSdk = 33
versionCode = versionCodeFromGit() versionCode = versionCodeFromGit()
versionName = versionNameFromGit() versionName = versionNameFromGit()
@ -85,7 +84,7 @@ android {
// tests // tests
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
packaging { packagingOptions {
resources { resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}" excludes += "/META-INF/{AL2.0,LGPL2.1}"
} }
@ -111,28 +110,27 @@ android {
} }
dependencies { dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
implementation(project(":shared")) implementation(project(":shared"))
implementation("com.google.android.material:material:1.9.0") implementation("com.google.android.material:material:1.5.0")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.4.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1") implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference-ktx:1.1.1")
implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs"))) implementation(fileTree(mapOf("include" to listOf("*.jar"), "dir" to "libs")))
// Android Support // Android Support
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.4.1")
implementation("com.google.android.material:material:1.9.0") implementation("com.google.android.material:material:1.5.0")
implementation("androidx.recyclerview:recyclerview:1.3.1") implementation("androidx.recyclerview:recyclerview:1.3.0-alpha01")
implementation("androidx.legacy:legacy-support-v4:1.0.0") implementation("androidx.legacy:legacy-support-v4:1.0.0")
implementation("androidx.vectordrawable:vectordrawable:1.2.0-beta01") implementation("androidx.vectordrawable:vectordrawable:1.2.0-alpha02")
implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.annotation:annotation:1.7.0") implementation("androidx.annotation:annotation:1.3.0")
implementation("androidx.work:work-runtime-ktx:2.8.1") implementation("androidx.work:work-runtime-ktx:2.7.1")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jsoup:jsoup:1.15.4") implementation("org.jsoup:jsoup:1.14.3")
//multidex //multidex
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
@ -143,17 +141,21 @@ dependencies {
// Material-ish things // Material-ish things
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0") implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
implementation("com.amulyakhare:com.amulyakhare.textdrawable:1.0.1")
// glide // glide
kapt("com.github.bumptech.glide:compiler:4.15.0") kapt("com.github.bumptech.glide:compiler:4.11.0")
implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0") implementation("com.github.bumptech.glide:okhttp3-integration:4.1.1")
// Drawer
implementation("com.mikepenz:materialdrawer:8.4.5")
// Themes // Themes
implementation("com.github.rubensousa:floatingtoolbar:1.5.1") implementation("com.github.rubensousa:floatingtoolbar:1.5.1")
// Pager // Pager
implementation("me.relex:circleindicator:2.1.6") implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0-beta02") implementation("androidx.viewpager2:viewpager2:1.1.0-beta01")
//Dependency Injection //Dependency Injection
implementation("org.kodein.di:kodein-di:7.14.0") implementation("org.kodein.di:kodein-di:7.14.0")
@ -169,7 +171,7 @@ dependencies {
//PhotoView //PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0") implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.core:core-ktx:1.8.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
@ -182,11 +184,11 @@ dependencies {
//test //test
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0") testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
implementation("ch.acra:acra-http:$acraVersion") // Matomo
implementation("ch.acra:acra-toast:$acraVersion") implementation("com.github.matomo-org:matomo-sdk-android:4.1.4")
} }
tasks.withType<Test> { tasks.withType<Test> {

View File

@ -55,7 +55,6 @@
# maybe remove later ? # maybe remove later ?
-keep class * extends androidx.fragment.app.Fragment -keep class * extends androidx.fragment.app.Fragment
-dontwarn org.slf4j.impl.StaticLoggerBinder
# Keep `Companion` object fields of serializable classes. # Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects. # This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
@ -86,12 +85,3 @@
-dontwarn io.mockk.** -dontwarn io.mockk.**
-keep class io.mockk.** { *; } -keep class io.mockk.** { *; }
# Kodein
-keep, allowobfuscation, allowoptimization class org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.TypeReference
-keep, allowobfuscation, allowoptimization class * extends org.kodein.type.JVMAbstractTypeToken$Companion$WrappingTest

View File

@ -2,7 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -53,7 +52,7 @@
android:value=".HomeActivity" /> android:value=".HomeActivity" />
</activity> </activity>
<activity <activity
android:name=".UpsertSourceActivity" android:name=".AddSourceActivity"
android:parentActivityName=".SourcesActivity" android:parentActivityName=".SourcesActivity"
android:exported="true"> android:exported="true">
<meta-data <meta-data
@ -70,8 +69,7 @@
android:name=".ReaderActivity"> android:name=".ReaderActivity">
</activity> </activity>
<activity <activity
android:name=".ImageActivity" android:name=".ImageActivity">
android:theme="@style/Theme.AppCompat.ImageActivity">
</activity> </activity>
<meta-data android:name="android.webkit.WebView.MetricsOptOut" <meta-data android:name="android.webkit.WebView.MetricsOptOut"
@ -81,5 +79,8 @@
android:value="true" /> android:value="true" />
<meta-data android:name="android.max_aspect" android:value="2.1" /> <meta-data android:name="android.max_aspect" android:value="2.1" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application> </application>
</manifest> </manifest>

View File

@ -1,9 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import org.acra.ACRA
import org.acra.ktx.sendSilentlyWithAcra
fun Throwable.sendSilentlyWithAcraWithName(name: String) {
ACRA.errorReporter.putCustomData("error_source", name)
this.sendSilentlyWithAcra()
}

View File

@ -0,0 +1,172 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class AddSourceActivity : AppCompatActivity(), DIAware {
private var mSpoutsValue: String? = null
private lateinit var binding: ActivityAddSourceBinding
override val di by closestDI()
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAddSourceBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
maybeGetDetailsFromIntentSharing(intent, binding.sourceUri, binding.nameInput)
binding.saveBtn.setOnClickListener {
handleSaveSource(
binding.tags,
binding.nameInput.text.toString(),
binding.sourceUri.text.toString()
)
}
}
override fun onResume() {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner(binding.spoutsSpinner, binding.progress, binding.formContainer)
}
}
private fun handleSpoutsSpinner(
spoutsSpinner: Spinner,
mProgress: ProgressBar,
formContainer: ConstraintLayout
) {
val spoutsKV = HashMap<String, String>()
spoutsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>, view: View?, i: Int, l: Long) {
if (view != null) {
val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName]
}
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
mSpoutsValue = null
}
}
fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText(
this@AddSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT
).show()
mProgress.visibility = View.GONE
}
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
mProgress.visibility = View.GONE
formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@AddSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spoutsSpinner.adapter = spinnerArrayAdapter
} else {
handleSpoutFailure()
}
} catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = true)
}
}
}
private fun maybeGetDetailsFromIntentSharing(
intent: Intent,
sourceUri: EditText,
nameInput: EditText
) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
}
}
private fun mustLoginToAddSource() {
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
finish()
}
private fun handleSaveSource(tags: EditText, title: String, url: String) {
val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
when {
sourceDetailsUnavailable -> {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource = repository.createSource(
title,
url,
mSpoutsValue!!,
tags.text.toString(),
"",
)
if (successfullyAddedSource) {
finish()
} else {
Toast.makeText(
this@AddSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}

View File

@ -1,17 +1,23 @@
package bou.amine.apps.readerforselfossv2.android package bou.amine.apps.readerforselfossv2.android
import android.content.Intent import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import androidx.work.Constraints import androidx.work.Constraints
@ -23,7 +29,6 @@ import bou.amine.apps.readerforselfossv2.android.adapters.ItemListAdapter
import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter import bou.amine.apps.readerforselfossv2.android.adapters.ItemsAdapter
import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker import bou.amine.apps.readerforselfossv2.android.background.LoadingWorker
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivityHomeBinding
import bou.amine.apps.readerforselfossv2.android.fragments.FilterSheetFragment
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow
import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge
@ -31,20 +36,45 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import bou.amine.apps.readerforselfossv2.utils.longHash
import com.ashokvarma.bottomnavigation.BottomNavigationBar import com.ashokvarma.bottomnavigation.BottomNavigationBar
import com.ashokvarma.bottomnavigation.BottomNavigationItem import com.ashokvarma.bottomnavigation.BottomNavigationItem
import com.ashokvarma.bottomnavigation.TextBadgeItem import com.ashokvarma.bottomnavigation.TextBadgeItem
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.mikepenz.aboutlibraries.LibsBuilder
import com.mikepenz.materialdrawer.holder.BadgeStyle
import com.mikepenz.materialdrawer.holder.ColorHolder
import com.mikepenz.materialdrawer.holder.StringHolder
import com.mikepenz.materialdrawer.model.DividerDrawerItem
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.*
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import com.mikepenz.materialdrawer.util.addStickyFooterItem
import com.mikepenz.materialdrawer.util.updateBadge
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import java.security.MessageDigest import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.DimensionQueue
import org.matomo.sdk.extra.TrackHelper
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware { class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
private val DRAWER_ID_TAGS = 100101L
private val DRAWER_ID_HIDDEN_TAGS = 101100L
private val DRAWER_ID_SOURCES = 100110L
private val DRAWER_ID_FILTERS = 100111L
private var items: ArrayList<SelfossModel.Item> = ArrayList() private var items: ArrayList<SelfossModel.Item> = ArrayList()
private var elementsShown: ItemType = ItemType.UNREAD private var elementsShown: ItemType = ItemType.UNREAD
@ -62,20 +92,25 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private var fromTabShortcut: Boolean = false private var fromTabShortcut: Boolean = false
private val settingsLauncher = private lateinit var tagsBadge: Map<Long, Int>
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
private val settingsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
appSettingsService.refreshUserSettings() appSettingsService.refreshUserSettings()
} }
override val di by closestDI() override val di by closestDI()
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 val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityHomeBinding.inflate(layoutInflater) binding = ActivityHomeBinding.inflate(layoutInflater)
val view = binding.root val view = binding.root
TrackHelper.track().screen("/home").with(tracker)
fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1
repository.offlineOverride = intent.getBooleanExtra("startOffline", false) repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
@ -86,11 +121,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
setContentView(view) setContentView(view)
setSupportActionBar(binding.toolBar) setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setHomeButtonEnabled(true)
val mDrawerToggle = ActionBarDrawerToggle(this, binding.drawerContainer, binding.toolBar, R.string.material_drawer_open, R.string.material_drawer_close)
binding.drawerContainer.addDrawerListener(mDrawerToggle)
mDrawerToggle.syncState()
handleBottomBar() handleBottomBar()
initDrawer()
handleSwipeRefreshLayout() handleSwipeRefreshLayout()
if (appSettingsService.isItemCachingEnabled()) { if (appSettingsService.isItemCachingEnabled()) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
@ -102,52 +144,43 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.swipeRefreshLayout.setColorSchemeResources( binding.swipeRefreshLayout.setColorSchemeResources(
R.color.refresh_progress_1, R.color.refresh_progress_1,
R.color.refresh_progress_2, R.color.refresh_progress_2,
R.color.refresh_progress_3, R.color.refresh_progress_3
) )
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false repository.offlineOverride = false
lastFetchDone = false lastFetchDone = false
handleDrawerItems()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
getElementsAccordingToTab() getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
} }
} }
val swipeDirs =
if (appSettingsService.getPublicAccess()) {
0
} else {
ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val simpleItemTouchCallback = val simpleItemTouchCallback =
object : ItemTouchHelper.SimpleCallback( object : ItemTouchHelper.SimpleCallback(
0, 0,
swipeDirs, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
) { ) {
override fun getSwipeDirs( override fun getSwipeDirs(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder
): Int = ): Int =
if (elementsShown == ItemType.STARRED) { if (elementsShown == ItemType.STARRED) {
0 0
} else { } else {
super.getSwipeDirs( super.getSwipeDirs(
recyclerView, recyclerView,
viewHolder, viewHolder
) )
} }
override fun onMove( override fun onMove(
recyclerView: RecyclerView, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder, viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder
): Boolean = false ): Boolean = false
override fun onSwiped( override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
viewHolder: RecyclerView.ViewHolder,
swipeDir: Int,
) {
val position = viewHolder.bindingAdapterPosition val position = viewHolder.bindingAdapterPosition
val i = items.elementAtOrNull(position) val i = items.elementAtOrNull(position)
@ -156,6 +189,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position) adapter.handleItemAtIndex(position)
val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) {
(it.key to (it.value - 1))
} else {
(it.key to it.value)
}
}.toMap()
reloadTagsBadges()
// Just load everythin // Just load everythin
if (items.size <= 0) { if (items.size <= 0) {
getElementsAccordingToTab() getElementsAccordingToTab()
@ -164,7 +207,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
"Found null when swiping at positon $position.", "Found null when swiping at positon $position.",
Toast.LENGTH_LONG, Toast.LENGTH_LONG
).show() ).show()
} }
} }
@ -173,10 +216,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView) ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
} }
private fun updateBottomBarBadgeCount( private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
badge: TextBadgeItem,
count: Int,
) {
if (count > 0) { if (count > 0) {
badge badge
.setText(count.toString()) .setText(count.toString())
@ -187,16 +227,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleBottomBar() { private fun handleBottomBar() {
tabNewBadge =
TextBadgeItem() tabNewBadge = TextBadgeItem()
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .setHideOnSelect(false).hide(false)
tabArchiveBadge = tabArchiveBadge = TextBadgeItem()
TextBadgeItem()
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .setHideOnSelect(false).hide(false)
tabStarredBadge = tabStarredBadge = TextBadgeItem()
TextBadgeItem()
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .setHideOnSelect(false).hide(false)
@ -225,19 +263,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
val tabNew = val tabNew =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp, R.drawable.ic_tab_fiber_new_black_24dp,
getString(R.string.tab_new), getString(R.string.tab_new)
) )
.setBadgeItem(tabNewBadge) .setBadgeItem(tabNewBadge)
val tabArchive = val tabArchive =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_archive_black_24dp, R.drawable.ic_tab_archive_black_24dp,
getString(R.string.tab_read), getString(R.string.tab_read)
) )
.setBadgeItem(tabArchiveBadge) .setBadgeItem(tabArchiveBadge)
val tabStarred = val tabStarred =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_favorite_black_24dp, R.drawable.ic_tab_favorite_black_24dp,
getString(R.string.tab_favs), getString(R.string.tab_favs)
).setActiveColorResource(R.color.pink) ).setActiveColorResource(R.color.pink)
.setBadgeItem(tabStarredBadge) .setBadgeItem(tabStarredBadge)
@ -257,6 +295,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
handleDrawerItems()
reloadLayoutManager() reloadLayoutManager()
if (appSettingsService.isInfiniteLoadingEnabled()) { if (appSettingsService.isInfiniteLoadingEnabled()) {
@ -267,8 +307,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
handleBottomBarActions() handleBottomBarActions()
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
handleRecurringTask() handleRecurringTask()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@ -278,21 +316,245 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getElementsAccordingToTab() getElementsAccordingToTab()
} }
private fun handleGDPRDialog(GDPRShown: Boolean) { private fun initDrawer() {
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
messageDigest.update(appSettingsService.getBaseUrl().toByteArray()) override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
if (!GDPRShown) { Glide.with(this@HomeActivity)
val alertDialog = AlertDialog.Builder(this).create() .asBitmap()
alertDialog.setTitle(getString(R.string.gdpr_dialog_title)) .load(uri)
alertDialog.setMessage(getString(R.string.gdpr_dialog_message)) .apply(RequestOptions()
alertDialog.setButton( .placeholder(R.mipmap.ic_launcher)
AlertDialog.BUTTON_NEUTRAL, .fallback(R.mipmap.ic_launcher)
"OK", .fitCenter())
) { dialog, _ -> .into(imageView)
appSettingsService.settings.putBoolean("GDPR_shown", true)
dialog.dismiss()
} }
alertDialog.show()
override fun cancel(imageView: ImageView) {
Glide.with(this@HomeActivity).clear(imageView)
}
})
val drawerListener = object : DrawerLayout.DrawerListener {
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
// We do nothing
}
override fun onDrawerOpened(drawerView: View) {
binding.bottomBar.hide()
}
override fun onDrawerClosed(drawerView: View) {
binding.bottomBar.show()
}
override fun onDrawerStateChanged(newState: Int) {
// We do nothing
}
}
binding.drawerContainer.addDrawerListener(drawerListener)
// Sticky items
addStickyPrimaryItem(R.string.drawer_report_bug, R.drawable.ic_bug_report_black_24dp) { _, _, _ ->
val browserIntent =
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
false
}
addStickyPrimaryItem(R.string.title_activity_settings, R.drawable.ic_settings_black_24dp) { _, _, _ ->
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
false
}
}
private fun addStickyPrimaryItem(name: Int, icon: Int, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)?) {
binding.mainDrawer.addStickyFooterItem(
PrimaryDrawerItem().apply {
nameRes = name
iconRes = icon
isIconTinted = true
onDrawerItemClickListener = clickListener
})
}
private fun handleDrawerItems() {
tagsBadge = emptyMap()
binding.mainDrawer.itemAdapter.add(
PrimaryDrawerItem().apply {
nameRes = R.string.drawer_loading
isSelectable = false
}
)
CoroutineScope(Dispatchers.IO).launch {
val tags = repository.getTags()
val sources = repository.getSources()
runOnUiThread {
handleDrawerData(tags, sources)
}
}
}
private fun handleDrawerData(tags: List<SelfossModel.Tag>, sources: List<SelfossModel.Source>) {
binding.mainDrawer.itemAdapter.clear()
// Filters title with clear action
secondaryItem(withDivider = false, R.string.drawer_item_filters, DRAWER_ID_FILTERS, R.string.drawer_action_clear) { _,_,_ ->
repository.sourceFilter = null
repository.tagFilter = null
binding.mainDrawer.setSelectionAtPosition(-1)
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
// Hidden tags
if (tags.isNotEmpty() && appSettingsService.getHiddenTags().isNotEmpty()) {
secondaryItem(
withDivider = true,
R.string.drawer_item_hidden_tags,
DRAWER_ID_HIDDEN_TAGS
)
handleHiddenTags(tags)
}
// Tags
secondaryItem(withDivider = true, R.string.drawer_item_tags, DRAWER_ID_TAGS)
if (tags.isEmpty()) {
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem()
.apply { nameRes = R.string.drawer_error_loading_tags; isSelectable = false }
)
} else {
handleTags(tags)
}
// Sources
secondaryItem(withDivider = true, R.string.drawer_item_sources, DRAWER_ID_SOURCES, R.string.drawer_action_edit) { v, _, _ ->
startActivity(Intent(v!!.context, SourcesActivity::class.java))
false
}
if (sources.isEmpty()) {
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem().apply {
nameRes = R.string.drawer_error_loading_sources
isSelectable = false
}
)
} else {
handleSources(sources)
}
// About action
binding.mainDrawer.itemAdapter.add(
DividerDrawerItem(),
PrimaryDrawerItem().apply {
nameRes = R.string.action_about
isSelectable = false
iconRes = R.drawable.ic_info_outline_white_24dp
isIconTinted = true
onDrawerItemClickListener = { _,_,_ ->
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(this@HomeActivity)
false
}
}
)
}
private fun secondaryItem(withDivider: Boolean, name: Int, id: Long, badgeId: Int? = null, clickListener: ((v: View?, item: IDrawerItem<*>, position: Int) -> Boolean)? = null) {
if (withDivider) {
binding.mainDrawer.itemAdapter.add(DividerDrawerItem())
}
binding.mainDrawer.itemAdapter.add(
SecondaryDrawerItem().apply {
nameRes = name
identifier = id
isSelectable = false
if (badgeId != null) {
badgeRes = badgeId
}
onDrawerItemClickListener = clickListener
}
)
}
private fun createDrawerItem(it: SelfossModel.Tag) {
val gd = GradientDrawable()
val gdColor = try {
Color.parseColor(it.color)
} catch (e: IllegalArgumentException) {
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
val drawerItem = PrimaryDrawerItem()
.apply {
nameText = it.tag.getHtmlDecoded()
identifier = it.tag.longHash()
iconDrawable = gd
badgeStyle = BadgeStyle().apply {
textColor = ColorHolder.fromColor(Color.WHITE)
color = ColorHolder.fromColor(resources.getColor(R.color.colorAccent))
}
onDrawerItemClickListener = { _, _, _ ->
repository.tagFilter = it
repository.sourceFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
}
if (it.unread > 0) {
drawerItem.badgeText = it.unread.toString()
}
binding.mainDrawer.itemAdapter.add(drawerItem)
}
private fun handleTags(tags: List<SelfossModel.Tag>) {
val filteredTags = tags
.filterNot { appSettingsService.getHiddenTags().contains(it.tag) }
.sortedBy { it.tag }
createTagItems(filteredTags)
}
private fun handleHiddenTags(tags: List<SelfossModel.Tag>) {
val filteredHiddenTags: List<SelfossModel.Tag> =
tags.filter { appSettingsService.getHiddenTags().contains(it.tag) }
createTagItems(filteredHiddenTags)
}
private fun createTagItems(tags: List<SelfossModel.Tag>) {
tagsBadge = tags.associate {
createDrawerItem(it)
(it.tag.longHash() to it.unread)
}
}
private fun handleSources(sources: List<SelfossModel.Source>) {
for (source in sources) {
val item = PrimaryDrawerItem().apply {
nameText = source.title.getHtmlDecoded()
identifier = source.id.toLong()
iconUrl = source.getIcon(repository.baseUrl)
onDrawerItemClickListener = { _,_,_ ->
repository.sourceFilter = source
repository.tagFilter = null
getElementsAccordingToTab()
fetchOnEmptyList()
false
}
}
binding.mainDrawer.itemAdapter.add(item)
} }
} }
@ -304,19 +566,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
when (currentManager) { when (currentManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = GridLayoutManager(
GridLayoutManager(
this, this,
calculateNoOfColumns(), calculateNoOfColumns()
) )
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} }
is GridLayoutManager -> is GridLayoutManager ->
if (appSettingsService.isCardViewEnabled()) { if (appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = StaggeredGridLayoutManager(
StaggeredGridLayoutManager(
calculateNoOfColumns(), calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL, StaggeredGridLayoutManager.VERTICAL
) )
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
@ -325,17 +585,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
else -> else ->
if (currentManager == null) { if (currentManager == null) {
if (!appSettingsService.isCardViewEnabled()) { if (!appSettingsService.isCardViewEnabled()) {
layoutManager = layoutManager = GridLayoutManager(
GridLayoutManager(
this, this,
calculateNoOfColumns(), calculateNoOfColumns()
) )
binding.recyclerView.layoutManager = layoutManager binding.recyclerView.layoutManager = layoutManager
} else { } else {
layoutManager = layoutManager = StaggeredGridLayoutManager(
StaggeredGridLayoutManager(
calculateNoOfColumns(), calculateNoOfColumns(),
StaggeredGridLayoutManager.VERTICAL, StaggeredGridLayoutManager.VERTICAL
) )
layoutManager.gapStrategy = layoutManager.gapStrategy =
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
@ -346,11 +604,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
private fun handleBottomBarActions() { private fun handleBottomBarActions() {
binding.bottomBar.setTabSelectedListener( binding.bottomBar.setTabSelectedListener(object : BottomNavigationBar.OnTabSelectedListener {
object : BottomNavigationBar.OnTabSelectedListener {
override fun onTabUnselected(position: Int) = Unit override fun onTabUnselected(position: Int) = Unit
override fun onTabReselected(position: Int) { override fun onTabReselected(position: Int) {
when (val layoutManager = binding.recyclerView.adapter) { when (val layoutManager = binding.recyclerView.adapter) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) { if (layoutManager.findFirstCompletelyVisibleItemPositions(null)[0] == 0) {
@ -378,24 +636,19 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
fetchOnEmptyList() fetchOnEmptyList()
} }
}, })
)
} }
fun fetchOnEmptyList() { private fun fetchOnEmptyList() {
binding.recyclerView.doOnNextLayout { binding.recyclerView.doOnNextLayout {
// TODO: do if last element (or is empty ?)
getElementsAccordingToTab(true) getElementsAccordingToTab(true)
} }
} }
private fun handleInfiniteScroll() { private fun handleInfiniteScroll() {
recyclerViewScrollListener = recyclerViewScrollListener = object : RecyclerView.OnScrollListener() {
object : RecyclerView.OnScrollListener() { override fun onScrolled(localRecycler: RecyclerView, dx: Int, dy: Int) {
override fun onScrolled(
localRecycler: RecyclerView,
dx: Int,
dy: Int,
) {
if (dy > 0) { if (dy > 0) {
val lastVisibleItem = getLastVisibleItem() val lastVisibleItem = getLastVisibleItem()
@ -410,11 +663,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.recyclerView.addOnScrollListener(recyclerViewScrollListener) binding.recyclerView.addOnScrollListener(recyclerViewScrollListener)
} }
private fun getLastVisibleItem(): Int { private fun getLastVisibleItem() : Int {
return when (val manager = binding.recyclerView.layoutManager) { return when (val manager = binding.recyclerView.layoutManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager -> manager.findLastCompletelyVisibleItemPositions(
manager.findLastCompletelyVisibleItemPositions( null
null,
).last() ).last()
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition() is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
else -> 0 else -> 0
@ -428,9 +680,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.emptyText.visibility = View.GONE binding.emptyText.visibility = View.GONE
} }
fun getElementsAccordingToTab(appendResults: Boolean = false) { private fun getElementsAccordingToTab(
offset = appendResults: Boolean = false
if (appendResults && items.size > 0) { ) {
offset = if (appendResults && items.size > 0) {
items.size - 1 items.size - 1
} else { } else {
0 0
@ -440,15 +693,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
getItems(appendResults, elementsShown) getItems(appendResults, elementsShown)
} }
private fun getItems( private fun getItems(appendResults: Boolean, itemType: ItemType) {
appendResults: Boolean,
itemType: ItemType,
) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
binding.swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
repository.displayedItems = itemType repository.displayedItems = itemType
items = items = if (appendResults) {
if (appendResults) {
repository.getOlderItems() repository.getOlderItems()
} else { } else {
repository.getNewerItems() repository.getNewerItems()
@ -461,8 +710,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun handleListResult(appendResults: Boolean = false) { private fun handleListResult(appendResults: Boolean = false) {
if (appendResults) { if (appendResults) {
val oldManager = binding.recyclerView.layoutManager val oldManager = binding.recyclerView.layoutManager
firstVisible = firstVisible = when (oldManager) {
when (oldManager) {
is StaggeredGridLayoutManager -> is StaggeredGridLayoutManager ->
oldManager.findFirstCompletelyVisibleItemPositions(null).last() oldManager.findFirstCompletelyVisibleItemPositions(null).last()
is GridLayoutManager -> is GridLayoutManager ->
@ -492,8 +740,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
binding.recyclerView.addItemDecoration( binding.recyclerView.addItemDecoration(
DividerItemDecoration( DividerItemDecoration(
this@HomeActivity, this@HomeActivity,
DividerItemDecoration.VERTICAL, DividerItemDecoration.VERTICAL
), )
) )
} }
binding.recyclerView.adapter = recyclerAdapter binding.recyclerView.adapter = recyclerAdapter
@ -513,6 +761,13 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
} }
} }
private fun reloadTagsBadges() {
tagsBadge.forEach {
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
}
binding.mainDrawer.resetDrawerContent()
}
private fun calculateNoOfColumns(): Int { private fun calculateNoOfColumns(): Int {
val displayMetrics = resources.displayMetrics val displayMetrics = resources.displayMetrics
val dpWidth = displayMetrics.widthPixels / displayMetrics.density val dpWidth = displayMetrics.widthPixels / displayMetrics.density
@ -538,10 +793,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater val inflater = menuInflater
inflater.inflate(R.menu.home_menu, menu) inflater.inflate(R.menu.home_menu, menu)
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.readAll)
menu.removeItem(R.id.action_sources)
}
val searchItem = menu.findItem(R.id.action_search) val searchItem = menu.findItem(R.id.action_search)
val searchView = searchItem.getActionView() as SearchView val searchView = searchItem.getActionView() as SearchView
@ -550,11 +801,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true return true
} }
private fun needsConfirmation( private fun needsConfirmation(titleRes: Int, messageRes: Int, doFn: () -> Unit) {
titleRes: Int,
messageRes: Int,
doFn: () -> Unit,
) {
AlertDialog.Builder(this@HomeActivity) AlertDialog.Builder(this@HomeActivity)
.setMessage(messageRes) .setMessage(messageRes)
.setTitle(titleRes) .setTitle(titleRes)
@ -566,33 +813,24 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.issue_tracker -> {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
return true
}
R.id.action_filter -> {
val filterSheetFragment = FilterSheetFragment()
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
return true
}
R.id.refresh -> { R.id.refresh -> {
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()
// TODO: Use Dispatchers.IO
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val updatedRemote = repository.updateRemote() val updatedRemote = repository.updateRemote()
if (updatedRemote) { if (updatedRemote) {
// TODO: Send toast messages from the repository
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_success_response, R.string.refresh_success_response, Toast.LENGTH_LONG
Toast.LENGTH_LONG,
) )
.show() .show()
} else { } else {
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.refresh_failer_message, R.string.refresh_failer_message,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
@ -610,16 +848,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
Toast.makeText( Toast.makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_read, R.string.all_posts_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
tabNewBadge.removeBadge() tabNewBadge.removeBadge()
handleDrawerItems()
getElementsAccordingToTab() getElementsAccordingToTab()
} else { } else {
Toast.makeText( Toast.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()
} }
handleListResult() handleListResult()
@ -630,22 +870,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
return true return true
} }
R.id.action_disconnect -> { R.id.action_disconnect -> {
needsConfirmation(R.string.confirm_disconnect_title, R.string.confirm_disconnect_description) { appSettingsService.clearAll()
runBlocking {
repository.logout()
}
val intent = Intent(this, LoginActivity::class.java) val intent = Intent(this, LoginActivity::class.java)
this.startActivity(intent) this.startActivity(intent)
finish() this@HomeActivity.finish()
}
return true
}
R.id.action_settings -> {
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
return true
}
R.id.action_sources -> {
startActivity(Intent(this, SourcesActivity::class.java))
return true return true
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
@ -666,8 +894,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun handleRecurringTask() { private fun handleRecurringTask() {
if (appSettingsService.isPeriodicRefreshEnabled()) { if (appSettingsService.isPeriodicRefreshEnabled()) {
val myConstraints = val myConstraints = Constraints.Builder()
Constraints.Builder()
.setRequiresBatteryNotLow(true) .setRequiresBatteryNotLow(true)
.setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled()) .setRequiresCharging(appSettingsService.isRefreshWhenChargingOnlyEnabled())
.setRequiresStorageNotLow(true) .setRequiresStorageNotLow(true)
@ -679,9 +906,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.addTag("selfoss-loading") .addTag("selfoss-loading")
.build() .build()
WorkManager.getInstance( WorkManager.getInstance(baseContext).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
baseContext,
).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
} }
} }
} }

View File

@ -3,7 +3,6 @@ package bou.amine.apps.readerforselfossv2.android
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
@ -11,8 +10,8 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityImageBindin
import bou.amine.apps.readerforselfossv2.android.fragments.ImageFragment import bou.amine.apps.readerforselfossv2.android.fragments.ImageFragment
class ImageActivity : AppCompatActivity() { class ImageActivity : AppCompatActivity() {
private lateinit var allImages: ArrayList<String> private lateinit var allImages : ArrayList<String>
private var position: Int = 0 private var position : Int = 0
private lateinit var binding: ActivityImageBinding private lateinit var binding: ActivityImageBinding
@ -24,6 +23,7 @@ class ImageActivity : AppCompatActivity() {
setContentView(view) setContentView(view)
setSupportActionBar(binding.toolBar) setSupportActionBar(binding.toolBar)
supportActionBar?.setDisplayShowTitleEnabled(false)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String> allImages = intent.getStringArrayListExtra("allImages") as ArrayList<String>
@ -31,52 +31,12 @@ class ImageActivity : AppCompatActivity() {
binding.pager.adapter = ScreenSlidePagerAdapter(this) binding.pager.adapter = ScreenSlidePagerAdapter(this)
binding.pager.setCurrentItem(position, false) binding.pager.setCurrentItem(position, false)
val transitionListener =
object : MotionLayout.TransitionListener {
override fun onTransitionStarted(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
) {
// Nothing
}
override fun onTransitionChange(
motionLayout: MotionLayout?,
startId: Int,
endId: Int,
progress: Float,
) {
// Nothing
}
override fun onTransitionCompleted(
motionLayout: MotionLayout?,
currentId: Int,
) {
if (motionLayout?.currentState == binding.root.endState) {
onBackPressedDispatcher.onBackPressed()
overridePendingTransition(0, 0)
}
}
override fun onTransitionTrigger(
motionLayout: MotionLayout?,
triggerId: Int,
positive: Boolean,
progress: Float,
) {
// Nothing
}
}
binding.root.setTransitionListener(transitionListener)
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressedDispatcher.onBackPressed() onBackPressed()
return true return true
} }
} }
@ -85,6 +45,7 @@ class ImageActivity : AppCompatActivity() {
} }
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) { private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allImages.size override fun getItemCount(): Int = allImages.size
override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position]) override fun createFragment(position: Int): Fragment = ImageFragment.newInstance(allImages[position])

View File

@ -4,7 +4,6 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.TextUtils import android.text.TextUtils
import android.view.Menu import android.view.Menu
@ -12,7 +11,6 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
@ -24,24 +22,36 @@ import com.mikepenz.aboutlibraries.LibsBuilder
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.acra.ACRA
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.DimensionQueue
import org.matomo.sdk.extra.DownloadTracker
import org.matomo.sdk.extra.TrackHelper
import java.security.MessageDigest
class LoginActivity : AppCompatActivity(), DIAware { class LoginActivity : AppCompatActivity(), DIAware {
private var inValidCount: Int = 0 private var inValidCount: Int = 0
private var isWithLogin = false private var isWithLogin = false
private lateinit var binding: ActivityLoginBinding private lateinit var binding: ActivityLoginBinding
override val di by closestDI() override val di by closestDI()
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 val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
TrackHelper.track().download().identifier(DownloadTracker.Extra.ApkChecksum(applicationContext))
.with(tracker)
TrackHelper.track().screen("/login").with(tracker)
handleTheme() handleTheme()
binding = ActivityLoginBinding.inflate(layoutInflater) binding = ActivityLoginBinding.inflate(layoutInflater)
@ -54,7 +64,6 @@ class LoginActivity : AppCompatActivity(), DIAware {
handleBaseUrlFail() handleBaseUrlFail()
if (appSettingsService.getBaseUrl().isNotEmpty()) { if (appSettingsService.getBaseUrl().isNotEmpty()) {
showProgress(true)
goToMain() goToMain()
} }
@ -67,6 +76,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
} }
private fun handleActions() { private fun handleActions() {
binding.passwordView.setOnEditorActionListener( binding.passwordView.setOnEditorActionListener(
TextView.OnEditorActionListener { _, id, _ -> TextView.OnEditorActionListener { _, id, _ ->
if (id == R.id.loginView || id == EditorInfo.IME_NULL) { if (id == R.id.loginView || id == EditorInfo.IME_NULL) {
@ -74,7 +84,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
return@OnEditorActionListener true return@OnEditorActionListener true
} }
false false
}, }
) )
binding.signInButton.setOnClickListener { attemptLogin() } binding.signInButton.setOnClickListener { attemptLogin() }
@ -95,7 +105,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
alertDialog.setMessage(getString(R.string.base_url_error)) alertDialog.setMessage(getString(R.string.base_url_error))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK"
) { dialog, _ -> dialog.dismiss() } ) { dialog, _ -> dialog.dismiss() }
alertDialog.show() alertDialog.show()
} }
@ -103,8 +113,16 @@ class LoginActivity : AppCompatActivity(), DIAware {
private fun goToMain() { private fun goToMain() {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
repository.updateApiInformation() repository.updateApiVersion()
ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256")
messageDigest.update(appSettingsService.getBaseUrl().toByteArray())
tracker.userId = String(messageDigest.digest())
val mDimensionQueue = DimensionQueue(tracker)
mDimensionQueue.add(1, appSettingsService.getApiVersion().toString())
tracker.isOptOut = !appSettingsService.isAnalyticsEnabled()
} }
val intent = Intent(this, HomeActivity::class.java) val intent = Intent(this, HomeActivity::class.java)
startActivity(intent) startActivity(intent)
@ -120,6 +138,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
} }
private fun attemptLogin() { private fun attemptLogin() {
// Reset errors. // Reset errors.
binding.urlView.error = null binding.urlView.error = null
binding.loginView.error = null binding.loginView.error = null
@ -130,72 +149,13 @@ class LoginActivity : AppCompatActivity(), DIAware {
val login = binding.loginView.text.toString().trim() val login = binding.loginView.text.toString().trim()
val password = binding.passwordView.text.toString().trim() val password = binding.passwordView.text.toString().trim()
failInvalidUrl(url)
failLoginDetails(password, login)
showProgress(true)
appSettingsService.updateSelfSigned(binding.selfSigned.isChecked)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.Main).launch {
try {
repository.updateApiInformation()
} catch (e: Exception) {
if (e.message?.startsWith("No transformation found") == true) {
Toast.makeText(
applicationContext,
R.string.application_selfoss_only,
Toast.LENGTH_LONG,
).show()
preferenceError()
showProgress(false)
}
}
val result = repository.login()
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
showProgress(false)
}
}
private fun failLoginDetails(
password: String,
login: String,
) {
var lastFocusedView: View? = null
var cancel = false var cancel = false
if (isWithLogin) { var focusView: View? = null
if (TextUtils.isEmpty(password)) {
binding.passwordView.error = getString(R.string.error_invalid_password)
lastFocusedView = binding.passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
binding.loginView.error = getString(R.string.error_field_required)
lastFocusedView = binding.loginView
cancel = true
}
}
maybeCancelAndFocusView(cancel, lastFocusedView)
}
private fun failInvalidUrl(url: String) {
val focusView = binding.urlView
var cancel = false
if (url.isBaseUrlInvalid()) { if (url.isBaseUrlInvalid()) {
cancel = true
binding.urlView.error = getString(R.string.login_url_problem) binding.urlView.error = getString(R.string.login_url_problem)
focusView = binding.urlView
cancel = true
inValidCount++ inValidCount++
if (inValidCount == 3) { if (inValidCount == 3) {
val alertDialog = AlertDialog.Builder(this).create() val alertDialog = AlertDialog.Builder(this).create()
@ -203,21 +163,45 @@ class LoginActivity : AppCompatActivity(), DIAware {
alertDialog.setMessage(getString(R.string.text_wrong_url)) alertDialog.setMessage(getString(R.string.text_wrong_url))
alertDialog.setButton( alertDialog.setButton(
AlertDialog.BUTTON_NEUTRAL, AlertDialog.BUTTON_NEUTRAL,
"OK", "OK"
) { dialog, _ -> dialog.dismiss() } ) { dialog, _ -> dialog.dismiss() }
alertDialog.show() alertDialog.show()
inValidCount = 0 inValidCount = 0
} }
} }
maybeCancelAndFocusView(cancel, focusView)
if (isWithLogin) {
if (TextUtils.isEmpty(password)) {
binding.passwordView.error = getString(R.string.error_invalid_password)
focusView = binding.passwordView
cancel = true
}
if (TextUtils.isEmpty(login)) {
binding.loginView.error = getString(R.string.error_field_required)
focusView = binding.loginView
cancel = true
}
} }
private fun maybeCancelAndFocusView(
cancel: Boolean,
focusView: View?,
) {
if (cancel) { if (cancel) {
focusView?.requestFocus() focusView?.requestFocus()
} else {
showProgress(true)
repository.refreshLoginInformation(url, login, password)
CoroutineScope(Dispatchers.IO).launch {
val result = repository.login()
if (result) {
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
preferenceError()
}
}
}
showProgress(false)
} }
} }
@ -229,13 +213,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 0F else 1F, if (show) 0F else 1F
).setListener( ).setListener(object : AnimatorListenerAdapter() {
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE binding.loginForm.visibility = if (show) View.GONE else View.VISIBLE
} }
}, }
) )
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
@ -243,13 +226,12 @@ class LoginActivity : AppCompatActivity(), DIAware {
.animate() .animate()
.setDuration(shortAnimTime.toLong()) .setDuration(shortAnimTime.toLong())
.alpha( .alpha(
if (show) 1F else 0F, if (show) 1F else 0F
).setListener( ).setListener(object : AnimatorListenerAdapter() {
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) { override fun onAnimationEnd(animation: Animator) {
binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE binding.loginProgress.visibility = if (show) View.VISIBLE else View.GONE
} }
}, }
) )
} }
@ -260,17 +242,10 @@ class LoginActivity : AppCompatActivity(), DIAware {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.issue_tracker -> {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
startActivity(browserIntent)
return true
}
R.id.about -> { R.id.about -> {
LibsBuilder() LibsBuilder()
.withAboutIconShown(true) .withAboutIconShown(true)
.withAboutVersionShown(true) .withAboutVersionShown(true)
.withAboutSpecial2("Bug reports").withAboutSpecial2Description(AppSettingsService.trackerUrl)
.withAboutSpecial1("Project Page").withAboutSpecial1Description(AppSettingsService.sourceUrl)
.start(this) .start(this)
true true
} }

View File

@ -8,6 +8,7 @@ import bou.amine.apps.readerforselfossv2.android.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)

View File

@ -3,7 +3,10 @@ package bou.amine.apps.readerforselfossv2.android
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.ImageView
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@ -15,45 +18,40 @@ import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.github.ln_12.library.ConnectivityStatus import com.github.ln_12.library.ConnectivityStatus
import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader
import com.mikepenz.materialdrawer.util.DrawerImageLoader
import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.httpSender
import org.acra.config.toast
import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.HttpSender
import org.kodein.di.* import org.kodein.di.*
import org.matomo.sdk.Matomo
import org.matomo.sdk.Tracker
import org.matomo.sdk.TrackerBuilder
class MyApp : MultiDexApplication(), DIAware { class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy { override val di by DI.lazy {
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess()) }
import(networkModule) import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) } bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) } bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<Repository>() with bind<Repository>() with singleton { Repository(instance(), instance(), isConnectionAvailable, instance()) }
singleton {
Repository(
instance(),
instance(),
isConnectionAvailable,
instance(),
)
}
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) } bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) } bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
bind<Tracker>() with singleton { TrackerBuilder.createDefault("https://matomo.amine-louveau.fr/matomo.php", if (BuildConfig.DEBUG) 4 else 5).build(
Matomo.getInstance(applicationContext)) }
} }
private val repository: Repository by instance() private val repository: Repository by instance()
private val viewModel: AppViewModel by instance() private val viewModel: AppViewModel by instance()
private val connectivityStatus: ConnectivityStatus by instance() private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance() private val driverFactory: DriverFactory by instance()
private val tracker: Tracker by instance()
// TODO: handle with the "previous" way // TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true) private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
@ -62,22 +60,17 @@ class MyApp : MultiDexApplication(), DIAware {
super.onCreate() super.onCreate()
Napier.base(DebugAntilog()) Napier.base(DebugAntilog())
if (!ACRA.isACRASenderServiceProcess()) { initDrawerImageLoader()
tryToHandleBug() tryToHandleBug()
handleNotificationChannels() handleNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver( ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository))
AppLifeCycleObserver(
connectivityStatus,
repository,
),
)
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable -> viewModel.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = val toastMessage = if (networkAvailable) {
if (networkAvailable) {
repository.handleDBActions() repository.handleDBActions()
R.string.network_connectivity_retrieved R.string.network_connectivity_retrieved
} else { } else {
@ -87,59 +80,12 @@ class MyApp : MultiDexApplication(), DIAware {
Toast.makeText( Toast.makeText(
applicationContext, applicationContext,
toastMessage, toastMessage,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
} }
repository.migrate(driverFactory)
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
initAcra {
reportFormat = StringFormat.JSON
reportContent =
listOf(
ReportField.REPORT_ID,
ReportField.INSTALLATION_ID,
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.BUILD,
ReportField.ANDROID_VERSION,
ReportField.BRAND,
ReportField.PHONE_MODEL,
ReportField.AVAILABLE_MEM_SIZE,
ReportField.TOTAL_MEM_SIZE,
ReportField.STACK_TRACE,
ReportField.APPLICATION_LOG,
ReportField.LOGCAT,
ReportField.INITIAL_CONFIGURATION,
ReportField.CRASH_CONFIGURATION,
ReportField.IS_SILENT,
ReportField.USER_APP_START_DATE,
ReportField.USER_COMMENT,
ReportField.USER_CRASH_DATE,
ReportField.USER_EMAIL,
ReportField.CUSTOM_DATA,
)
toast {
// required
text = getString(R.string.crash_toast_text)
length = Toast.LENGTH_SHORT
}
httpSender {
uri =
"https://bugs.amine-bouabdallaoui.fr/report" // best guess, you may need to adjust this
basicAuthLogin = "qMEscjj89Gwt6cPR"
basicAuthPassword = "Yo58QFlGzFaWlBzP"
httpMethod = HttpSender.Method.POST
}
}
}
private fun handleNotificationChannels() { private fun handleNotificationChannels() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
@ -150,38 +96,47 @@ class MyApp : MultiDexApplication(), DIAware {
val newItemsChannelname = getString(R.string.new_items_channel_sync) val newItemsChannelname = getString(R.string.new_items_channel_sync)
val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT val newItemsChannelimportance = NotificationManager.IMPORTANCE_DEFAULT
val newItemsChannelmChannel = val newItemsChannelmChannel = NotificationChannel(AppSettingsService.newItemsChannelId, newItemsChannelname, newItemsChannelimportance)
NotificationChannel(
AppSettingsService.newItemsChannelId,
newItemsChannelname,
newItemsChannelimportance,
)
notificationManager.createNotificationChannel(mChannel) notificationManager.createNotificationChannel(mChannel)
notificationManager.createNotificationChannel(newItemsChannelmChannel) notificationManager.createNotificationChannel(newItemsChannelmChannel)
} }
} }
private fun initDrawerImageLoader() {
DrawerImageLoader.init(object : AbstractDrawerImageLoader() {
override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable, tag: String?) {
Glide.with(imageView.context)
.load(uri.toString())
.apply(RequestOptions.fitCenterTransform().placeholder(placeholder))
.into(imageView)
}
override fun cancel(imageView: ImageView) {
Glide.with(imageView.context).clear(imageView)
}
override fun placeholder(ctx: Context, tag: String?): Drawable {
return baseContext.resources.getDrawable(R.mipmap.ic_launcher)
}
})
}
private fun tryToHandleBug() { private fun tryToHandleBug() {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler() val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, e -> Thread.setDefaultUncaughtExceptionHandler { thread, e ->
if (e is NoClassDefFoundError && if (e is NoClassDefFoundError && e.stackTrace.asList().any {
e.stackTrace.asList().any {
it.toString().contains("android.view.ViewDebug") it.toString().contains("android.view.ViewDebug")
} }) {
) {
// Nothing
} else { } else {
oldHandler.uncaughtException(thread, e) oldHandler.uncaughtException(thread, e)
} }
} }
} }
class AppLifeCycleObserver( class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver {
val connectivityStatus: ConnectivityStatus,
val repository: Repository,
) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) { override fun onResume(owner: LifecycleOwner) {
super.onResume(owner) super.onResume(owner)
repository.connectionMonitored = true repository.connectionMonitored = true

View File

@ -23,14 +23,13 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class ReaderActivity : AppCompatActivity(), DIAware { class ReaderActivity : AppCompatActivity(), DIAware {
private var currentItem: Int = 0 private var currentItem: Int = 0
private lateinit var toolbarMenu: Menu private lateinit var toolbarMenu: Menu
private lateinit var binding: ActivityReaderBinding private lateinit var binding: ActivityReaderBinding
private var allItems: ArrayList<SelfossModel.Item> = ArrayList()
override val di by closestDI() override val di by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance() private val appSettingsService: AppSettingsService by instance()
@ -62,19 +61,13 @@ class ReaderActivity : AppCompatActivity(), DIAware {
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
if (allItems.isEmpty()) {
finish()
}
currentItem = intent.getIntExtra("currentItem", 0) currentItem = intent.getIntExtra("currentItem", 0)
allItems = repository.getReaderItems()
if (allItems.isEmpty() || currentItem > allItems.size) {
finish()
}
try {
readItem(allItems[currentItem]) 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)
@ -87,7 +80,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
} }
private fun readItem(item: SelfossModel.Item) { private fun readItem(item: SelfossModel.Item) {
if (appSettingsService.isMarkOnScrollEnabled() && !appSettingsService.getPublicAccess()) { if (appSettingsService.isMarkOnScrollEnabled()) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item) repository.markAsRead(item)
} }
@ -101,15 +94,15 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) :
FragmentStateAdapter(fa) { FragmentStateAdapter(fa) {
override fun getItemCount(): Int = allItems.size override fun getItemCount(): Int = allItems.size
override fun createFragment(position: Int): Fragment = ArticleFragment.newInstance(allItems[position]) override fun createFragment(position: Int): Fragment =
ArticleFragment.newInstance(allItems[position])
} }
override fun onKeyDown( override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
keyCode: Int,
event: KeyEvent?,
): Boolean {
return when (keyCode) { return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN -> {
val currentFragment = val currentFragment =
@ -140,19 +133,16 @@ class ReaderActivity : AppCompatActivity(), DIAware {
inflater.inflate(R.menu.reader_menu, menu) inflater.inflate(R.menu.reader_menu, menu)
toolbarMenu = menu toolbarMenu = menu
alignmentMenu()
if (appSettingsService.getPublicAccess()) {
menu.removeItem(R.id.star)
} else {
if (allItems.isNotEmpty() && allItems[currentItem].starred) { if (allItems.isNotEmpty() && allItems[currentItem].starred) {
canRemoveFromFavorite() canRemoveFromFavorite()
} else { } else {
canFavorite() canFavorite()
} }
alignmentMenu()
binding.pager.registerOnPageChangeCallback( binding.pager.registerOnPageChangeCallback(
object : ViewPager2.OnPageChangeCallback() { object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
@ -163,9 +153,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
} }
readItem(allItems[position]) readItem(allItems[position])
} }
},
)
} }
)
return true return true
} }
@ -184,18 +173,20 @@ class ReaderActivity : AppCompatActivity(), DIAware {
when (item.itemId) { when (item.itemId) {
android.R.id.home -> { android.R.id.home -> {
onBackPressedDispatcher.onBackPressed() onBackPressed()
return true return true
} }
R.id.star -> { R.id.star -> {
if (allItems[binding.pager.currentItem].starred) { if (allItems[binding.pager.currentItem].starred) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(allItems[binding.pager.currentItem]) repository.unstarr(allItems[binding.pager.currentItem])
// TODO: Handle failure
} }
afterUnsave() afterUnsave()
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.starr(allItems[binding.pager.currentItem]) repository.starr(allItems[binding.pager.currentItem])
// TODO: Handle failure
} }
afterSave() afterSave()
} }
@ -223,4 +214,8 @@ class ReaderActivity : AppCompatActivity(), DIAware {
startActivity(intent) startActivity(intent)
overridePendingTransition(0, 0) overridePendingTransition(0, 0)
} }
companion object {
var allItems: ArrayList<SelfossModel.Item> = ArrayList()
}
} }

View File

@ -18,10 +18,11 @@ import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
class SourcesActivity : AppCompatActivity(), DIAware { class SourcesActivity : AppCompatActivity(), DIAware {
private lateinit var binding: ActivitySourcesBinding private lateinit var binding: ActivitySourcesBinding
override val di by closestDI() override val di by closestDI()
private val repository: Repository by instance() private val repository : Repository by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivitySourcesBinding.inflate(layoutInflater) binding = ActivitySourcesBinding.inflate(layoutInflater)
@ -48,19 +49,17 @@ class SourcesActivity : AppCompatActivity(), DIAware {
super.onResume() super.onResume()
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
var items: ArrayList<SelfossModel.SourceDetail> var items: ArrayList<SelfossModel.Source>
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSourcesDetails() val response = repository.getSources()
if (response.isNotEmpty()) { if (response.isNotEmpty()) {
items = response items = response
val mAdapter = val mAdapter = SourcesListAdapter(
SourcesListAdapter( this@SourcesActivity, items
this@SourcesActivity,
items,
) )
binding.recyclerView.adapter = mAdapter binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged() mAdapter.notifyDataSetChanged()
@ -68,13 +67,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
Toast.makeText( Toast.makeText(
this@SourcesActivity, this@SourcesActivity,
R.string.cant_get_sources, R.string.cant_get_sources,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT
).show() ).show()
} }
} }
binding.fab.setOnClickListener { binding.fab.setOnClickListener {
startActivity(Intent(this@SourcesActivity, UpsertSourceActivity::class.java)) startActivity(Intent(this@SourcesActivity, AddSourceActivity::class.java))
} }
} }
} }

View File

@ -1,210 +0,0 @@
package bou.amine.apps.readerforselfossv2.android
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DIAware
import org.kodein.di.android.closestDI
import org.kodein.di.instance
class UpsertSourceActivity : AppCompatActivity(), DIAware {
private var existingSource: SelfossModel.SourceDetail? = null
private var mSpoutsValue: String? = null
private lateinit var binding: ActivityUpsertSourceBinding
override val di by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUpsertSourceBinding.inflate(layoutInflater)
val view = binding.root
existingSource = repository.getSelectedSource()
if (existingSource != null) {
binding.formContainer.visibility = View.GONE
binding.progress.visibility = View.VISIBLE
}
val title = if (existingSource == null) R.string.add_source else R.string.update_source
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(title)
}
}
setContentView(view)
setSupportActionBar(binding.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.title = resources.getString(title)
maybeGetDetailsFromIntentSharing(intent)
binding.saveBtn.setOnClickListener {
handleSaveSource()
}
}
private fun initFields(items: Map<String, SelfossModel.Spout>) {
binding.nameInput.setText(existingSource!!.title)
binding.tags.setText(existingSource!!.tags?.joinToString(", "))
binding.sourceUri.setText(existingSource!!.params?.url)
binding.spoutsSpinner.setSelection(items.keys.indexOf(existingSource!!.spout))
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
}
override fun onResume() {
super.onResume()
val baseUrl = appSettingsService.getBaseUrl()
if (baseUrl.isEmpty() || baseUrl.isBaseUrlInvalid()) {
mustLoginToAddSource()
} else {
handleSpoutsSpinner()
}
}
private fun handleSpoutsSpinner() {
val spoutsKV = HashMap<String, String>()
binding.spoutsSpinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
adapterView: AdapterView<*>,
view: View?,
i: Int,
l: Long,
) {
if (view != null) {
val spoutName = (view as TextView).text.toString()
mSpoutsValue = spoutsKV[spoutName]
}
}
override fun onNothingSelected(adapterView: AdapterView<*>) {
mSpoutsValue = null
}
}
fun handleSpoutFailure(networkIssue: Boolean = false) {
Toast.makeText(
this@UpsertSourceActivity,
if (networkIssue) R.string.cant_get_spouts_no_network else R.string.cant_get_spouts,
Toast.LENGTH_SHORT,
).show()
binding.progress.visibility = View.GONE
}
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items.isNotEmpty()) {
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()
}
} catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = true)
}
}
}
private fun maybeGetDetailsFromIntentSharing(intent: Intent) {
if (Intent.ACTION_SEND == intent.action && "text/plain" == intent.type) {
binding.sourceUri.setText(intent.getStringExtra(Intent.EXTRA_TEXT))
binding.nameInput.setText(intent.getStringExtra(Intent.EXTRA_TITLE))
}
}
private fun mustLoginToAddSource() {
Toast.makeText(this, getString(R.string.addStringNoUrl), Toast.LENGTH_SHORT).show()
val i = Intent(this, LoginActivity::class.java)
startActivity(i)
finish()
}
private fun handleSaveSource() {
val url = binding.sourceUri.text.toString()
val sourceDetailsUnavailable =
title.isEmpty() || url.isEmpty() || mSpoutsValue == null || mSpoutsValue!!.isEmpty()
when {
sourceDetailsUnavailable -> {
Toast.makeText(this, R.string.form_not_complete, Toast.LENGTH_SHORT).show()
}
else -> {
CoroutineScope(Dispatchers.Main).launch {
val successfullyAddedSource =
if (existingSource != null) {
repository.updateSource(
existingSource!!.id,
binding.nameInput.text.toString(),
url,
mSpoutsValue!!,
binding.tags.text.toString(),
)
} else {
repository.createSource(
binding.nameInput.text.toString(),
url,
mSpoutsValue!!,
binding.tags.text.toString(),
)
}
if (successfullyAddedSource) {
finish()
} else {
Toast.makeText(
this@UpsertSourceActivity,
R.string.cant_create_source,
Toast.LENGTH_SHORT,
).show()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
repository.unsetSelectedSource()
}
}

View File

@ -9,10 +9,10 @@ import android.widget.ImageView.ScaleType
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.android.utils.shareLink import bou.amine.apps.readerforselfossv2.android.utils.shareLink
@ -22,6 +22,8 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon import bou.amine.apps.readerforselfossv2.utils.getIcon
import bou.amine.apps.readerforselfossv2.utils.getThumbnail import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -33,10 +35,10 @@ import org.kodein.di.instance
class ItemCardAdapter( class ItemCardAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() { ) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
private lateinit var binding: CardItemBinding
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val imageMaxHeight: Int = private val imageMaxHeight: Int =
c.resources.getDimension(R.dimen.card_image_max_height).toInt() c.resources.getDimension(R.dimen.card_image_max_height).toInt()
@ -44,79 +46,23 @@ class ItemCardAdapter(
override val repository: Repository by instance() override val repository: Repository by instance()
override val appSettingsService: AppSettingsService by instance() override val appSettingsService: AppSettingsService by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup, val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
viewType: Int,
): ViewHolder {
binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding) return ViewHolder(binding)
} }
private fun handleClickListeners(position: Int) { override fun onBindViewHolder(holder: ViewHolder, position: Int) {
binding.favButton.setOnClickListener {
val item = items[position]
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
binding.favButton.isSelected = true
}
}
binding.shareBtn.setOnClickListener {
val item = items[position]
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
}
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[position])
}
}
private fun handleLinkOpening(position: Int) {
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
position,
items[position].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app,
)
}
}
override fun onBindViewHolder(
holder: ViewHolder,
position: Int,
) {
with(holder) { with(holder) {
val itm = items[holder.bindingAdapterPosition] val itm = items[position]
handleClickListeners(holder.bindingAdapterPosition)
handleLinkOpening(holder.bindingAdapterPosition)
binding.favButton.isSelected = itm.starred binding.favButton.isSelected = itm.starred
if (appSettingsService.getPublicAccess()) {
binding.favButton.visibility = View.GONE
}
binding.title.text = itm.title.getHtmlDecoded() binding.title.text = itm.title.getHtmlDecoded()
binding.title.setOnTouchListener(LinkOnTouchListener()) binding.title.setOnTouchListener(LinkOnTouchListener())
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = try { binding.sourceTitleAndDate.text = itm.sourceAndDateText()
itm.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemCardAdapter parse date")
itm.sourceAuthorOnly()
}
if (!appSettingsService.isFullHeightCardsEnabled()) { if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight binding.itemImage.maxHeight = imageMaxHeight
@ -133,9 +79,16 @@ class ItemCardAdapter(
} }
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.sourceImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded()) val color = generator.getColor(itm.title.getHtmlDecoded())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.sourceImage.setImageDrawable(drawable)
} else { } else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage) c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.sourceImage)
} }
} }
} }
@ -144,5 +97,49 @@ class ItemCardAdapter(
return items.size return items.size
} }
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleClickListeners()
handleLinkOpening()
}
private fun handleClickListeners() {
binding.favButton.setOnClickListener {
val item = items[bindingAdapterPosition]
if (item.starred) {
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
binding.favButton.isSelected = true
}
}
binding.shareBtn.setOnClickListener {
val item = items[bindingAdapterPosition]
c.shareLink(item.getLinkDecoded(), item.title.getHtmlDecoded())
}
binding.browserBtn.setOnClickListener {
c.openInBrowserAsNewTask(items[bindingAdapterPosition])
}
}
private fun handleLinkOpening() {
binding.root.setOnClickListener {
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app
)
}
}
}
} }

View File

@ -7,9 +7,10 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.ListItemBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
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
@ -17,6 +18,8 @@ import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon import bou.amine.apps.readerforselfossv2.utils.getIcon
import bou.amine.apps.readerforselfossv2.utils.getThumbnail import bou.amine.apps.readerforselfossv2.utils.getThumbnail
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance import org.kodein.di.instance
@ -24,39 +27,23 @@ import org.kodein.di.instance
class ItemListAdapter( class ItemListAdapter(
override val app: Activity, override val app: Activity,
override var items: ArrayList<SelfossModel.Item>, override var items: ArrayList<SelfossModel.Item>,
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit, override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
) : ItemsAdapter<ItemListAdapter.ViewHolder>() { ) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
private lateinit var binding: ListItemBinding private val generator: ColorGenerator = ColorGenerator.MATERIAL
private val c: Context = app.baseContext private val c: Context = app.baseContext
override val di: DI by closestDI(app) override val di: DI by closestDI(app)
override val repository: Repository by instance() override val repository : Repository by instance()
override val appSettingsService: AppSettingsService by instance() override val appSettingsService : AppSettingsService by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup, val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
viewType: Int,
): ViewHolder {
binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding) return ViewHolder(binding)
} }
override fun onBindViewHolder( override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder: ViewHolder,
position: Int,
) {
with(holder) { with(holder) {
val itm = items[holder.bindingAdapterPosition] val itm = items[position]
binding.root.setOnClickListener {
repository.setReaderItems(items)
c.openItemUrl(
holder.bindingAdapterPosition,
items[holder.bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app,
)
}
binding.title.text = itm.title.getHtmlDecoded() binding.title.text = itm.title.getHtmlDecoded()
@ -64,26 +51,47 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent)) binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = try { binding.sourceTitleAndDate.text = itm.sourceAndDateText()
itm.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("ItemListAdapter parse date")
itm.sourceAuthorOnly()
}
if (itm.getThumbnail(repository.baseUrl).isEmpty()) { if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.sourcetitle.getHtmlDecoded()) val color = generator.getColor(itm.title.getHtmlDecoded())
val drawable =
TextDrawable
.builder()
.round()
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.itemImage.setImageDrawable(drawable)
} else { } else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage) c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
} }
} else { } else {
c.circularDrawable(itm.getThumbnail(repository.baseUrl), binding.itemImage) c.bitmapCenterCrop(itm.getThumbnail(repository.baseUrl), binding.itemImage)
} }
} }
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) inner class ViewHolder(val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root) {
init {
handleLinkOpening()
}
private fun handleLinkOpening() {
binding.root.setOnClickListener {
c.openItemUrl(
items,
bindingAdapterPosition,
items[bindingAdapterPosition].getLinkDecoded(),
appSettingsService.isArticleViewerEnabled(),
app
)
}
}
}
} }

View File

@ -28,20 +28,18 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
updateItems(this.items) updateItems(this.items)
} }
private fun unmarkSnackbar( private fun unmarkSnackbar(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item, val s = Snackbar
position: Int,
) {
val s =
Snackbar
.make( .make(
app.findViewById(R.id.coordLayout), app.findViewById(R.id.coordLayout),
R.string.marked_as_read, R.string.marked_as_read,
Snackbar.LENGTH_LONG, Snackbar.LENGTH_LONG
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
CoroutineScope(Dispatchers.IO).launch {
unreadItemAtIndex(item, position, false) unreadItemAtIndex(item, position, false)
} }
}
val view = s.view val view = s.view
val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text)
@ -49,16 +47,12 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
s.show() s.show()
} }
private fun markSnackbar( private fun markSnackbar(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item, val s = Snackbar
position: Int,
) {
val s =
Snackbar
.make( .make(
app.findViewById(R.id.coordLayout), app.findViewById(R.id.coordLayout),
R.string.marked_as_unread, R.string.marked_as_unread,
Snackbar.LENGTH_LONG, Snackbar.LENGTH_LONG
) )
.setAction(R.string.undo_string) { .setAction(R.string.undo_string) {
readItemAtIndex(item, position, false) readItemAtIndex(item, position, false)
@ -78,18 +72,13 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
private fun readItemAtIndex( private fun readItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
item: SelfossModel.Item,
position: Int,
showSnackbar: Boolean = true,
) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(item) repository.markAsRead(item)
} }
if (repository.displayedItems == ItemType.UNREAD) { if (repository.displayedItems == ItemType.UNREAD) {
items.remove(item) items.remove(item)
notifyItemRemoved(position) notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount)
updateItems(items) updateItems(items)
} else { } else {
notifyItemChanged(position) notifyItemChanged(position)
@ -99,13 +88,10 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
private fun unreadItemAtIndex( private fun unreadItemAtIndex(item: SelfossModel.Item, position: Int, showSnackbar: Boolean = true) {
item: SelfossModel.Item,
position: Int,
showSnackbar: Boolean = true,
) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(item) repository.unmarkAsRead(item)
} }
notifyItemChanged(position) notifyItemChanged(position)
if (showSnackbar) { if (showSnackbar) {
@ -113,13 +99,11 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
} }
} }
fun addItemAtIndex( fun addItemAtIndex(item: SelfossModel.Item, position: Int) {
item: SelfossModel.Item,
position: Int,
) {
items.add(position, item) items.add(position, item)
notifyItemInserted(position) notifyItemInserted(position)
updateItems(items) updateItems(items)
} }
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) { fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
@ -127,5 +111,6 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
items.addAll(newItems) items.addAll(newItems)
notifyItemRangeInserted(oldSize, newItems.size) notifyItemRangeInserted(oldSize, newItems.size)
updateItems(items) updateItems(items)
} }
} }

View File

@ -2,22 +2,22 @@ package bou.amine.apps.readerforselfossv2.android.adapters
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView 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.databinding.SourceListItemBinding import bou.amine.apps.readerforselfossv2.android.databinding.SourceListItemBinding
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.amulyakhare.textdrawable.TextDrawable
import com.amulyakhare.textdrawable.util.ColorGenerator
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -28,66 +28,34 @@ import org.kodein.di.instance
class SourcesListAdapter( class SourcesListAdapter(
private val app: Activity, private val app: Activity,
private val items: ArrayList<SelfossModel.SourceDetail>, private val items: ArrayList<SelfossModel.Source>
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware { ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL
private lateinit var binding: SourceListItemBinding private lateinit var binding: SourceListItemBinding
override val di: DI by closestDI(app) override val di: DI by closestDI(app)
private val repository: Repository by instance() private val repository : Repository by instance()
override fun onCreateViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
parent: ViewGroup,
viewType: Int,
): ViewHolder {
binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) binding = SourceListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ViewHolder(binding.root) return ViewHolder(binding.root)
} }
override fun onBindViewHolder( override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder: ViewHolder,
position: Int,
) {
val itm = items[position] val itm = items[position]
val deleteBtn: Button = holder.mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id, title) = items[position]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id, title)
if (successfullyDeletedSource) {
items.removeAt(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT,
).show()
}
}
}
holder.mView.setOnClickListener {
val source = items[position]
repository.setSelectedSource(source)
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
}
if (itm.getIcon(repository.baseUrl).isEmpty()) { if (itm.getIcon(repository.baseUrl).isEmpty()) {
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded()) val color = generator.getColor(itm.title.getHtmlDecoded())
} else {
c.circularDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
}
if (!itm.error.isNullOrBlank()) { val drawable =
binding.errorText.visibility = View.VISIBLE TextDrawable
binding.errorText.text = itm.error .builder()
.round()
.build(itm.title.getHtmlDecoded().toTextDrawableString(), color)
binding.itemImage.setImageDrawable(drawable)
} else { } else {
binding.errorText.visibility = View.GONE c.circularBitmapDrawable(itm.getIcon(repository.baseUrl), binding.itemImage)
} }
binding.sourceTitle.text = itm.title.getHtmlDecoded() binding.sourceTitle.text = itm.title.getHtmlDecoded()
@ -99,5 +67,33 @@ class SourcesListAdapter(
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
init {
handleClickListeners()
}
private fun handleClickListeners() {
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) {
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else {
Toast.makeText(
app,
R.string.can_delete_source,
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
} }

View File

@ -26,15 +26,15 @@ import org.kodein.di.instance
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
class LoadingWorker(val context: Context, params: WorkerParameters) : class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(context, params), DIAware {
Worker(context, params),
DIAware {
override val di by lazy { (applicationContext as MyApp).di }
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
override fun doWork(): Result { override val di by lazy { (applicationContext as MyApp).di }
private val repository : Repository by instance()
private val appSettingsService : AppSettingsService by instance()
override fun doWork(): Result {
if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) { if (appSettingsService.isPeriodicRefreshEnabled() && isNetworkAccessible(context)) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val notificationManager = val notificationManager =
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@ -62,41 +62,38 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
} }
} }
return Result.success() return Result.success()
} }
private fun handleNewItemsNotification( private fun handleNewItemsNotification(
newItems: List<SelfossModel.Item>?, newItems: List<SelfossModel.Item>?,
notificationManager: NotificationManager, notificationManager: NotificationManager
) { ) {
// TODO: Check if this coroutine is actually required
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
val apiItems = newItems.orEmpty() val apiItems = newItems.orEmpty()
val newSize = apiItems.filter { it.unread }.size val newSize = apiItems.filter { it.unread }.size
if (newSize > 0) { if (newSize > 0) {
val intent =
Intent(context, MainActivity::class.java).apply { val intent = Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
} }
val pflags = val pflags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_IMMUTABLE PendingIntent.FLAG_IMMUTABLE
} else { } else {
0 0
} }
val pendingIntent: PendingIntent = val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, pflags)
PendingIntent.getActivity(context, 0, intent, pflags)
val newItemsNotification = val newItemsNotification =
NotificationCompat.Builder( NotificationCompat.Builder(applicationContext, AppSettingsService.newItemsChannelId)
applicationContext,
AppSettingsService.newItemsChannelId,
)
.setContentTitle(context.getString(R.string.new_items_notification_title)) .setContentTitle(context.getString(R.string.new_items_notification_title))
.setContentText( .setContentText(
context.getString( context.getString(
R.string.new_items_notification_text, R.string.new_items_notification_text,
newSize, newSize
), )
) )
.setPriority(PRIORITY_DEFAULT) .setPriority(PRIORITY_DEFAULT)
.setChannelId(AppSettingsService.newItemsChannelId) .setChannelId(AppSettingsService.newItemsChannelId)

View File

@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.android.fragments package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.content.res.TypedArray import android.content.res.TypedArray
@ -17,6 +16,7 @@ import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.content.res.ResourcesCompat
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.ImageActivity
@ -25,12 +25,9 @@ import bou.amine.apps.readerforselfossv2.android.databinding.FragmentArticleBind
import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem import bou.amine.apps.readerforselfossv2.android.model.ParecelableItem
import bou.amine.apps.readerforselfossv2.android.model.toModel import bou.amine.apps.readerforselfossv2.android.model.toModel
import bou.amine.apps.readerforselfossv2.android.model.toParcelable import bou.amine.apps.readerforselfossv2.android.model.toParcelable
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream
import bou.amine.apps.readerforselfossv2.android.utils.isUrlValid
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.shareLink import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.MercuryModel
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.MercuryApi import bou.amine.apps.readerforselfossv2.rest.MercuryApi
@ -56,7 +53,6 @@ import java.net.URL
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
private const val IMAGE_JPG = "image/jpg"
class ArticleFragment : Fragment(), DIAware { class ArticleFragment : Fragment(), DIAware {
private var fontSize: Int = 16 private var fontSize: Int = 16
@ -66,12 +62,13 @@ class ArticleFragment : Fragment(), DIAware {
private lateinit var contentSource: String private lateinit var contentSource: String
private lateinit var contentImage: String private lateinit var contentImage: String
private lateinit var contentTitle: String private lateinit var contentTitle: String
private lateinit var allImages: ArrayList<String> private lateinit var allImages : ArrayList<String>
private lateinit var fab: FloatingActionButton private lateinit var fab: FloatingActionButton
private lateinit var textAlignment: String private lateinit var textAlignment: String
private lateinit var binding: FragmentArticleBinding private var _binding: FragmentArticleBinding? = null
private val binding get() = _binding!!
override val di: DI by closestDI() override val di : DI by closestDI()
private val repository: Repository by instance() private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance() private val appSettingsService: AppSettingsService by instance()
@ -80,7 +77,8 @@ class ArticleFragment : Fragment(), DIAware {
private var font = "" private var font = ""
private var staticBar = false private var staticBar = false
private val mercuryApi: MercuryApi by instance() private val mercuryApi : MercuryApi by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -93,27 +91,32 @@ class ArticleFragment : Fragment(), DIAware {
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?
): View { ): View {
try { try {
binding = FragmentArticleBinding.inflate(inflater, container, false) _binding = FragmentArticleBinding.inflate(inflater, container, false)
url = item.getLinkDecoded() url = item.getLinkDecoded()
contentText = item.content contentText = item.content
contentTitle = item.title.getHtmlDecoded() contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl) contentImage = item.getThumbnail(repository.baseUrl)
contentSource = try { contentSource = item.sourceAndDateText()
item.sourceAuthorAndDate()
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("Article Fragment parse date")
item.sourceAuthorOnly()
}
allImages = item.getImages() allImages = item.getImages()
fontSize = appSettingsService.getFontSize() fontSize = appSettingsService.getFontSize()
staticBar = appSettingsService.isStaticBarEnabled() staticBar = appSettingsService.isStaticBarEnabled()
font = appSettingsService.getFont() font = appSettingsService.getFont()
if (font.isNotEmpty()) {
resId = requireContext().resources.getIdentifier(font, "font", requireContext().packageName)
typeface = try {
ResourcesCompat.getFont(requireContext(), resId)!!
} catch (e: java.lang.Exception) {
// Just to be sure
null
}
}
refreshAlignment() refreshAlignment()
fab = binding.fab fab = binding.fab
@ -122,7 +125,48 @@ class ArticleFragment : Fragment(), DIAware {
fab.rippleColor = resources.getColor(R.color.colorAccentDark) fab.rippleColor = resources.getColor(R.color.colorAccentDark)
val floatingToolbar: FloatingToolbar = handleFloatingToolbar() val floatingToolbar: FloatingToolbar = binding.floatingToolbar
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action -> if (context != null) {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG
).show()
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.unmarkAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = true
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG
).show()
}
}
else -> Unit
}
}
override fun onItemLongClick(item: MenuItem?) {
}
}
)
if (staticBar) { if (staticBar) {
fab.hide() fab.hide()
@ -134,47 +178,8 @@ class ArticleFragment : Fragment(), DIAware {
binding.source.typeface = typeface binding.source.typeface = typeface
} }
handleContent()
binding.nestedScrollView.setOnScrollChangeListener(
NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (scrollY > oldScrollY) {
floatingToolbar.hide()
fab.hide()
} else {
if (staticBar) {
floatingToolbar.show()
} else {
if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
}
}
},
)
} catch (e: InflateException) {
e.sendSilentlyWithAcraWithName("webview not available")
if (context != null) {
AlertDialog.Builder(requireContext())
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(
android.R.string.ok,
) { _, _ ->
appSettingsService.disableArticleViewer()
requireActivity().finish()
}
.create()
.show()
}
}
return binding.root
}
private fun handleContent() {
if (contentText.isEmptyOrNullOrNullString()) { if (contentText.isEmptyOrNullOrNullString()) {
if (repository.isNetworkAvailable()) {
getContentFromMercury() getContentFromMercury()
}
} else { } else {
binding.titleView.text = contentTitle binding.titleView.text = contentTitle
if (typeface != null) { if (typeface != null) {
@ -195,62 +200,45 @@ class ArticleFragment : Fragment(), DIAware {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} }
}
private fun handleFloatingToolbar(): FloatingToolbar { binding.nestedScrollView.setOnScrollChangeListener(
val floatingToolbar: FloatingToolbar = binding.floatingToolbar NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
if (appSettingsService.getPublicAccess()) { if (scrollY > oldScrollY) {
floatingToolbar.setMenu(R.menu.reader_toolbar_no_read) floatingToolbar.hide()
} fab.hide()
floatingToolbar.attachFab(fab)
floatingToolbar.background = ColorDrawable(resources.getColor(R.color.colorAccent))
floatingToolbar.setClickListener(
object : FloatingToolbar.ItemClickListener {
override fun onItemClick(item: MenuItem) {
when (item.itemId) {
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
R.id.unread_action ->
if (context != null) {
if (this@ArticleFragment.item.unread) {
CoroutineScope(Dispatchers.IO).launch {
repository.markAsRead(this@ArticleFragment.item)
}
this@ArticleFragment.item.unread = false
Toast.makeText(
context,
R.string.marked_as_read,
Toast.LENGTH_LONG,
).show()
} else { } else {
CoroutineScope(Dispatchers.IO).launch { if (staticBar) {
repository.unmarkAsRead(this@ArticleFragment.item) floatingToolbar.show()
} } else {
this@ArticleFragment.item.unread = true if (floatingToolbar.isShowing) floatingToolbar.hide() else fab.show()
Toast.makeText(
context,
R.string.marked_as_unread,
Toast.LENGTH_LONG,
).show()
} }
} }
else -> Unit
} }
)
} catch (e: InflateException) {
AlertDialog.Builder(requireContext())
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
.setPositiveButton(android.R.string.ok
) { _, _ ->
appSettingsService.disableArticleViewer()
requireActivity().finish()
}
.create()
.show()
} }
override fun onItemLongClick(item: MenuItem?) { return binding.root
// We do nothing
} }
},
) override fun onDestroyView() {
return floatingToolbar super.onDestroyView()
_binding = null
} }
private fun refreshAlignment() { private fun refreshAlignment() {
textAlignment = textAlignment = when (appSettingsService.getActiveAllignment()) {
when (appSettingsService.getActiveAllignment()) {
1 -> "justify" 1 -> "justify"
2 -> "left" 2 -> "left"
else -> "justify" else -> "justify"
@ -258,136 +246,90 @@ class ArticleFragment : Fragment(), DIAware {
} }
private fun getContentFromMercury() { private fun getContentFromMercury() {
if (repository.isNetworkAvailable()) {
binding.progressBar.visibility = View.VISIBLE binding.progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
try {
val response = mercuryApi.query(url) val response = mercuryApi.query(url)
if (response.success && response.data != null) { if (response.success) {
handleMercuryData(response.data!!) try {
} else { if (response.data != null && response.data!!.content != null && !response.data!!.content.isNullOrEmpty()) {
openInBrowserAfterFailing() try {
} binding.titleView.text = response.data!!.title
} catch (e: Exception) {
openInBrowserAfterFailing()
}
}
}
private fun handleMercuryData(data: MercuryModel.ParsedContent) {
if (data.error == true || data.failed == true) {
openInBrowserAfterFailing()
} else {
binding.titleView.text = data.title.orEmpty()
if (typeface != null) { if (typeface != null) {
binding.titleView.typeface = typeface binding.titleView.typeface = typeface
} }
URL(data.url) try {
url = data.url!! // Note: Mercury may return relative urls... If it does the url val will not be changed.
URL(response.data!!.url)
url = response.data!!.url
} catch (e: MalformedURLException) {
// Mercury returned a relative url. We do nothing.
}
} catch (e: Exception) {
}
contentText = data.content.orEmpty() try {
contentText = response.data!!.content.orEmpty()
htmlToWebview() htmlToWebview()
} catch (e: Exception) {
handleLeadImage(data?.lead_image_url)
binding.nestedScrollView.scrollTo(0, 0)
binding.progressBar.visibility = View.GONE
}
} }
private fun handleLeadImage(lead_image_url: String?) { try {
if (!lead_image_url.isNullOrEmpty() && context != null) { if (response.data!!.lead_image_url != null && !response.data!!.lead_image_url.isNullOrEmpty() && context != null) {
binding.imageView.visibility = View.VISIBLE binding.imageView.visibility = View.VISIBLE
try {
Glide Glide
.with(requireContext()) .with(requireContext())
.asBitmap() .asBitmap()
.load( .load(
lead_image_url, response.data!!.lead_image_url.orEmpty()
) )
.apply(RequestOptions.fitCenterTransform()) .apply(RequestOptions.fitCenterTransform())
.into(binding.imageView) .into(binding.imageView)
} catch (e: Exception) {
}
} else { } else {
binding.imageView.visibility = View.GONE binding.imageView.visibility = View.GONE
} }
} catch (e: Exception) {
if (context != null) {
}
} }
private fun handleImageLoading() {
binding.webcontent.webViewClient =
object : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(
view: WebView?,
url: String,
): Boolean {
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
try { try {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) binding.nestedScrollView.scrollTo(0, 0)
} catch (e: ActivityNotFoundException) {
e.sendSilentlyWithAcraWithName("activityNotFound > $url") binding.progressBar.visibility = View.GONE
} catch (e: Exception) {
if (context != null) {
}
} }
true
} else { } else {
false
}
}
@Deprecated("Deprecated in Java")
override fun shouldInterceptRequest(
view: WebView,
url: String,
): WebResourceResponse? {
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US)
.contains(".jpeg")
) {
try { try {
val image = openInBrowserAfterFailing()
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get() } catch (e: Exception) {
return WebResourceResponse( if (context != null) {
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.JPEG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.PNG),
)
} catch (e: ExecutionException) {
// Do nothing
}
} else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image =
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse(
IMAGE_JPG,
"UTF-8",
getBitmapInputStream(image, Bitmap.CompressFormat.WEBP),
)
} catch (e: ExecutionException) {
// Do nothing
} }
} }
}
return super.shouldInterceptRequest(view, url) } catch (e: Exception) {
if (context != null) {
}
}
} else {
openInBrowserAfterFailing()
}
} }
} }
} }
private fun htmlToWebview() { private fun htmlToWebview() {
if (context != null) {
val attrs: IntArray = intArrayOf(android.R.attr.fontFamily) val attrs: IntArray = intArrayOf(android.R.attr.fontFamily)
val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs) val a: TypedArray = requireContext().obtainStyledAttributes(resId, attrs)
binding.webcontent.settings.standardFontFamily = a.getString(0) binding.webcontent.settings.standardFontFamily = a.getString(0)
binding.webcontent.visibility = View.VISIBLE binding.webcontent.visibility = View.VISIBLE
@ -401,19 +343,48 @@ class ArticleFragment : Fragment(), DIAware {
binding.webcontent.settings.loadWithOverviewMode = true binding.webcontent.settings.loadWithOverviewMode = true
binding.webcontent.settings.javaScriptEnabled = false binding.webcontent.settings.javaScriptEnabled = false
handleImageLoading() binding.webcontent.webViewClient = object : WebViewClient() {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url : String): Boolean {
if (binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
}
return true
}
val gestureDetector = @Deprecated("Deprecated in Java")
GestureDetector( override fun shouldInterceptRequest(view: WebView?, url: String): WebResourceResponse? {
activity, val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
object : GestureDetector.SimpleOnGestureListener() { if (url.lowercase(Locale.US).contains(".jpg") || url.lowercase(Locale.US).contains(".jpeg")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.JPEG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".png")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.PNG))
}catch ( e : ExecutionException) {}
}
else if (url.lowercase(Locale.US).contains(".webp")) {
try {
val image = Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
return WebResourceResponse("image/jpg", "UTF-8", getBitmapInputStream(image, Bitmap.CompressFormat.WEBP))
}catch ( e : ExecutionException) {}
}
return super.shouldInterceptRequest(view, url)
}
}
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick() return performClick()
} }
}, })
)
binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event) } binding.webcontent.setOnTouchListener { _, event -> gestureDetector.onTouchEvent(event)}
binding.webcontent.settings.layoutAlgorithm = binding.webcontent.settings.layoutAlgorithm =
WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING WebSettings.LayoutAlgorithm.TEXT_AUTOSIZING
@ -424,25 +395,17 @@ class ArticleFragment : Fragment(), DIAware {
val itemUrl = URL(url) val itemUrl = URL(url)
baseUrl = itemUrl.protocol + "://" + itemUrl.host baseUrl = itemUrl.protocol + "://" + itemUrl.host
} catch (e: MalformedURLException) { } catch (e: MalformedURLException) {
e.sendSilentlyWithAcraWithName("htmlToWebview > $url")
} }
val fontName = val fontName = when (font) {
when (font) {
getString(R.string.open_sans_font_id) -> "Open Sans" getString(R.string.open_sans_font_id) -> "Open Sans"
getString(R.string.roboto_font_id) -> "Roboto" getString(R.string.roboto_font_id) -> "Roboto"
getString(R.string.source_code_pro_font_id) -> "Source Code Pro" getString(R.string.source_code_pro_font_id) -> "Source Code Pro"
else -> "" else -> ""
} }
val fontLinkAndStyle = val fontLinkAndStyle = if (font.isNotEmpty()) {
if (font.isNotEmpty()) { """<link href="https://fonts.googleapis.com/css?family=${fontName.replace(" ", "+")}" rel="stylesheet">
"""<link href="https://fonts.googleapis.com/css?family=${
fontName.replace(
" ",
"+",
)
}" rel="stylesheet">
|<style> |<style>
| * { | * {
| font-family: '$fontName'; | font-family: '$fontName';
@ -466,12 +429,7 @@ class ArticleFragment : Fragment(), DIAware {
| max-width: 100%; | max-width: 100%;
| } | }
| a { | a {
| color: ${ | color: ${String.format("#%06X", 0xFFFFFF and resources.getColor(R.color.colorAccent))} !important;
String.format(
"#%06X",
0xFFFFFF and resources.getColor(R.color.colorAccent),
)
} !important;
| } | }
| *:not(a) { | *:not(a) {
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)}; | color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
@ -482,26 +440,11 @@ class ArticleFragment : Fragment(), DIAware {
| word-break: break-word; | word-break: break-word;
| overflow:hidden; | overflow:hidden;
| line-height: 1.5em; | line-height: 1.5em;
| background-color: ${ | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
String.format(
"#%06X",
0xFFFFFF and colorSurface.data,
)
};
| } | }
| body, html { | body, html {
| background-color: ${ | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
String.format( | border-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)} !important;
"#%06X",
0xFFFFFF and colorSurface.data,
)
} !important;
| border-color: ${
String.format(
"#%06X",
0xFFFFFF and colorSurface.data,
)
} !important;
| padding: 0 !important; | padding: 0 !important;
| margin: 0 !important; | margin: 0 !important;
| } | }
@ -511,50 +454,41 @@ class ArticleFragment : Fragment(), DIAware {
| pre, code { | pre, code {
| white-space: pre-wrap; | white-space: pre-wrap;
| width:100%; | width:100%;
| background-color: ${ | background-color: ${String.format("#%06X", 0xFFFFFF and colorSurface.data)};
String.format(
"#%06X",
0xFFFFFF and colorSurface.data,
)
};
| } | }
| </style> | </style>
| $fontLinkAndStyle | $fontLinkAndStyle
|</head> |</head>
|<body> |<body>
| $contentText | $contentText
|</body> |</body>""".trimMargin(),
""".trimMargin(),
"text/html", "text/html",
"utf-8", "utf-8",
null, null
) )
} }
}
fun scrollDown() { fun scrollDown() {
val height = binding.nestedScrollView.measuredHeight val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, height / 2) binding.nestedScrollView.smoothScrollBy(0, height/2)
} }
fun scrollUp() { fun scrollUp() {
val height = binding.nestedScrollView.measuredHeight val height = binding.nestedScrollView.measuredHeight
binding.nestedScrollView.smoothScrollBy(0, -height / 2) binding.nestedScrollView.smoothScrollBy(0, -height/2)
} }
private fun openInBrowserAfterFailing() { private fun openInBrowserAfterFailing() {
binding.progressBar.visibility = View.GONE binding.progressBar.visibility = View.GONE
if (context != null) { requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
requireContext().openInBrowserAsNewTask(this@ArticleFragment.item)
} else {
Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context")
}
} }
companion object { companion object {
private const val ARG_ITEMS = "items" private const val ARG_ITEMS = "items"
fun newInstance(item: SelfossModel.Item): ArticleFragment { fun newInstance(
item: SelfossModel.Item
): ArticleFragment {
val fragment = ArticleFragment() val fragment = ArticleFragment()
val args = Bundle() val args = Bundle()
args.putParcelable(ARG_ITEMS, item.toParcelable()) args.putParcelable(ARG_ITEMS, item.toParcelable())
@ -564,12 +498,10 @@ class ArticleFragment : Fragment(), DIAware {
} }
fun performClick(): Boolean { fun performClick(): Boolean {
if (allImages != null && ( if (binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||
binding.webcontent.hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE || binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
binding.webcontent.hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
) val position : Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
) {
val position: Int = allImages.indexOf(binding.webcontent.hitTestResult.extra)
val intent = Intent(activity, ImageActivity::class.java) val intent = Intent(activity, ImageActivity::class.java)
intent.putExtra("allImages", allImages) intent.putExtra("allImages", allImages)
@ -579,4 +511,6 @@ class ArticleFragment : Fragment(), DIAware {
} }
return false return false
} }
} }

View File

@ -1,195 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.fragments
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import bou.amine.apps.readerforselfossv2.android.HomeActivity
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.getColorHexCode
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getIcon
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.ViewTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.chip.Chip
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.android.x.closestDI
import org.kodein.di.instance
class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
private lateinit var binding: FilterFragmentBinding
override val di: DI by closestDI()
private val repository: Repository by instance()
private var selectedChip: Chip? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding =
FilterFragmentBinding.inflate(
inflater,
container,
false,
)
val context: Context? = context
if (context == null) {
dismiss()
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
} else {
CoroutineScope(Dispatchers.Main).launch {
handleTagChips(context)
handleSourceChips(context)
binding.progressBar2.visibility = GONE
binding.filterView.visibility = VISIBLE
}
}
binding.floatingActionButton2.setOnClickListener {
(activity as HomeActivity).getElementsAccordingToTab()
(activity as HomeActivity).fetchOnEmptyList()
dismiss()
}
return binding.root
}
private suspend fun handleSourceChips(context: Context) {
val sourceGroup = binding.sourcesGroup
repository.getSourcesDetailsOrStats().forEachIndexed { _, source ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
Glide.with(context)
.load(source.getIcon(repository.baseUrl))
.into(
object : ViewTarget<Chip?, Drawable?>(c) {
override fun onResourceReady(
resource: Drawable,
transition: Transition<in Drawable?>?,
) {
try {
c.chipIcon = resource
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("sources > onResourceReady")
}
}
},
)
c.text = source.title.getHtmlDecoded()
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setSourceFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setSourceFilter(source)
repository.setTagFilter(null)
}
if (repository.sourceFilter.value?.equals(source) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
c.isEnabled = source.error.isNullOrBlank()
if (!source.error.isNullOrBlank() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
c.tooltipText = source.error
}
sourceGroup.addView(c)
}
}
private suspend fun handleTagChips(context: Context) {
val tagGroup = binding.tagsGroup
val tags = repository.getTags()
tags.forEachIndexed { _, tag ->
val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END
c.text = tag.tag
if (tag.color.isNotEmpty()) {
try {
val gd = GradientDrawable()
val gdColor =
try {
Color.parseColor(tag.getColorHexCode())
} catch (e: IllegalArgumentException) {
e.sendSilentlyWithAcraWithName("color issue " + tag.color + " / " + tag.getColorHexCode())
resources.getColor(R.color.colorPrimary)
}
gd.setColor(gdColor)
gd.shape = GradientDrawable.RECTANGLE
gd.setSize(30, 30)
gd.cornerRadius = 30F
c.chipIcon = gd
} catch (e: Exception) {
e.sendSilentlyWithAcraWithName("tags > GradientDrawable")
}
}
c.setOnCloseIconClickListener {
(it as Chip).isCloseIconVisible = false
selectedChip = null
repository.setTagFilter(null)
}
c.setOnClickListener {
if (selectedChip != null) {
selectedChip!!.isCloseIconVisible = false
}
(it as Chip).isCloseIconVisible = true
selectedChip = it
repository.setTagFilter(tag)
repository.setSourceFilter(null)
}
if (repository.tagFilter.value?.equals(tag) == true) {
c.isCloseIconVisible = true
selectedChip = c
}
tagGroup.addView(c)
}
}
companion object {
const val TAG = "FilterModalBottomSheet"
}
}

View File

@ -11,7 +11,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
class ImageFragment : Fragment() { class ImageFragment : Fragment() {
private lateinit var imageUrl: String
private lateinit var imageUrl : String
private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL) private val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL)
private var _binding: FragmentImageBinding? = null private var _binding: FragmentImageBinding? = null
private val binding get() = _binding private val binding get() = _binding
@ -22,16 +23,12 @@ class ImageFragment : Fragment() {
imageUrl = requireArguments().getString("imageUrl")!! imageUrl = requireArguments().getString("imageUrl")!!
} }
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = FragmentImageBinding.inflate(inflater, container, false) _binding = FragmentImageBinding.inflate(inflater, container, false)
val view = binding?.root val view = binding?.root
binding!!.photoView.visibility = View.VISIBLE binding!!.photoView.visibility = View.VISIBLE
Glide.with(requireActivity()) Glide.with(activity)
.asBitmap() .asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(imageUrl) .load(imageUrl)
@ -48,7 +45,9 @@ class ImageFragment : Fragment() {
companion object { companion object {
private const val ARG_IMAGE = "imageUrl" private const val ARG_IMAGE = "imageUrl"
fun newInstance(imageUrl: String): ImageFragment { fun newInstance(
imageUrl : String
): ImageFragment {
val fragment = ImageFragment() val fragment = ImageFragment()
val args = Bundle() val args = Bundle()
args.putString(ARG_IMAGE, imageUrl) args.putString(ARG_IMAGE, imageUrl)

View File

@ -2,28 +2,27 @@ package bou.amine.apps.readerforselfossv2.android.model
import android.content.Context import android.content.Context
import android.webkit.URLUtil import android.webkit.URLUtil
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.utils.getImages import bou.amine.apps.readerforselfossv2.utils.getImages
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
fun SelfossModel.Item.preloadImages(context: Context): Boolean { fun SelfossModel.Item.preloadImages(context: Context) : Boolean {
val imageUrls = this.getImages() val imageUrls = this.getImages()
val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000) val glideOptions = RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.ALL).timeout(10000)
try { try {
for (url in imageUrls) { for (url in imageUrls) {
if (URLUtil.isValidUrl(url)) { if ( URLUtil.isValidUrl(url)) {
Glide.with(context).asBitmap() Glide.with(context).asBitmap()
.apply(glideOptions) .apply(glideOptions)
.load(url).submit() .load(url).submit()
} }
} }
} catch (e: Error) { } catch (e : Error) {
e.sendSilentlyWithAcraWithName("preloadImages")
return false return false
} }
@ -36,7 +35,7 @@ fun String.toTextDrawableString(): String {
try { try {
textDrawable.append(s[0]) textDrawable.append(s[0])
} catch (e: StringIndexOutOfBoundsException) { } catch (e: StringIndexOutOfBoundsException) {
e.sendSilentlyWithAcraWithName("toTextDrawableString") // We do nothing
} }
} }
return textDrawable.toString() return textDrawable.toString()

View File

@ -4,7 +4,7 @@ import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun SelfossModel.Item.toParcelable(): ParecelableItem = fun SelfossModel.Item.toParcelable() : ParecelableItem =
ParecelableItem( ParecelableItem(
this.id, this.id,
this.datetime, this.datetime,
@ -16,11 +16,9 @@ fun SelfossModel.Item.toParcelable(): ParecelableItem =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.joinToString(","), this.tags.joinToString(",")
this.author,
) )
fun ParecelableItem.toModel() : SelfossModel.Item =
fun ParecelableItem.toModel(): SelfossModel.Item =
SelfossModel.Item( SelfossModel.Item(
this.id, this.id,
this.datetime, this.datetime,
@ -32,10 +30,8 @@ fun ParecelableItem.toModel(): SelfossModel.Item =
this.icon, this.icon,
this.link, this.link,
this.sourcetitle, this.sourcetitle,
this.tags.split(","), this.tags.split(",")
this.author,
) )
data class ParecelableItem( data class ParecelableItem(
val id: Int, val id: Int,
val datetime: String, val datetime: String,
@ -47,15 +43,13 @@ data class ParecelableItem(
val icon: String?, val icon: String?,
val link: String, val link: String,
val sourcetitle: String, val sourcetitle: String,
val tags: String, val tags: String
val author: String?,
) : Parcelable { ) : Parcelable {
companion object { companion object {
@JvmField @JvmField
val CREATOR: Parcelable.Creator<ParecelableItem> = val CREATOR: Parcelable.Creator<ParecelableItem> = object : Parcelable.Creator<ParecelableItem> {
object : Parcelable.Creator<ParecelableItem> {
override fun createFromParcel(source: Parcel): ParecelableItem = ParecelableItem(source) override fun createFromParcel(source: Parcel): ParecelableItem = ParecelableItem(source)
override fun newArray(size: Int): Array<ParecelableItem?> = arrayOfNulls(size) override fun newArray(size: Int): Array<ParecelableItem?> = arrayOfNulls(size)
} }
} }
@ -71,16 +65,12 @@ data class ParecelableItem(
icon = source.readString(), icon = source.readString(),
link = source.readString().orEmpty(), link = source.readString().orEmpty(),
sourcetitle = source.readString().orEmpty(), sourcetitle = source.readString().orEmpty(),
tags = source.readString().orEmpty(), tags = source.readString().orEmpty()
author = source.readString().orEmpty(),
) )
override fun describeContents() = 0 override fun describeContents() = 0
override fun writeToParcel( override fun writeToParcel(dest: Parcel, flags: Int) {
dest: Parcel,
flags: Int,
) {
dest.writeInt(id) dest.writeInt(id)
dest.writeString(datetime) dest.writeString(datetime)
dest.writeString(title) dest.writeString(title)
@ -92,6 +82,5 @@ data class ParecelableItem(
dest.writeString(link) dest.writeString(link)
dest.writeString(sourcetitle) dest.writeString(sourcetitle)
dest.writeString(tags) dest.writeString(tags)
dest.writeString(author)
} }
} }

View File

@ -16,24 +16,27 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySettingsBinding
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
import bou.amine.apps.readerforselfossv2.service.AppSettingsService import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.mikepenz.aboutlibraries.LibsBuilder
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import org.kodein.di.instance
import org.matomo.sdk.Tracker
import org.matomo.sdk.extra.TrackHelper
private const val TITLE_TAG = "settingsActivityTitle" private const val TITLE_TAG = "settingsActivityTitle"
class SettingsActivity : class SettingsActivity : AppCompatActivity(),
AppCompatActivity(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback, DIAware {
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
DIAware {
override val di by closestDI() override val di by closestDI()
private val tracker : Tracker by instance()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val binding = ActivitySettingsBinding.inflate(layoutInflater) val binding = ActivitySettingsBinding.inflate(layoutInflater)
TrackHelper.track().screen("/settings").with(tracker)
setContentView(binding.root) setContentView(binding.root)
if (savedInstanceState == null) { if (savedInstanceState == null) {
supportFragmentManager supportFragmentManager
@ -74,14 +77,13 @@ class SettingsActivity :
override fun onPreferenceStartFragment( override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat, caller: PreferenceFragmentCompat,
pref: Preference, pref: Preference
): Boolean { ): Boolean {
// Instantiate the new Fragment // Instantiate the new Fragment
val args = pref.extras val args = pref.extras
val fragment = val fragment = supportFragmentManager.fragmentFactory.instantiate(
supportFragmentManager.fragmentFactory.instantiate(
classLoader, classLoader,
pref.fragment.toString(), pref.fragment
).apply { ).apply {
arguments = args arguments = args
setTargetFragment(caller, 0) setTargetFragment(caller, 0)
@ -97,131 +99,75 @@ class SettingsActivity :
} }
class MainPreferenceFragment : PreferenceFragmentCompat() { class MainPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_main, rootKey) setPreferencesFromResource(R.xml.pref_main, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true
}
preferenceManager.findPreference<Preference>("action_about")?.onPreferenceClickListener =
Preference.OnPreferenceClickListener { _ ->
context?.let {
LibsBuilder()
.withAboutIconShown(true)
.withAboutVersionShown(true)
.start(it)
}
true
}
} }
} }
class GeneralPreferenceFragment : PreferenceFragmentCompat() { class GeneralPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_general, rootKey) setPreferencesFromResource(R.xml.pref_general, rootKey)
val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number") val editTextPreference = preferenceManager.findPreference<EditTextPreference>("prefer_api_items_number")
editTextPreference?.setOnBindEditTextListener { editText -> editTextPreference?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.filters = editText.filters = arrayOf(
arrayOf(
InputFilter { source, _, _, dest, _, _ -> InputFilter { source, _, _, dest, _, _ ->
try { try {
val input: Int = (dest.toString() + source.toString()).toInt() val input: Int = (dest.toString() + source.toString()).toInt()
if (input in 1..200) return@InputFilter null if (input in 1..200) return@InputFilter null
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcraWithName("GeneralPreferenceFragment")
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
} }
"" ""
}, }
) )
} }
} }
} }
class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() { class ArticleViewerPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_viewer, rootKey) setPreferencesFromResource(R.xml.pref_viewer, rootKey)
val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size") val fontSize = preferenceManager.findPreference<EditTextPreference>("reader_font_size")
fontSize?.setOnBindEditTextListener { editText -> fontSize?.setOnBindEditTextListener { editText ->
editText.inputType = InputType.TYPE_CLASS_NUMBER editText.inputType = InputType.TYPE_CLASS_NUMBER
editText.addTextChangedListener { editText.addTextChangedListener { object : TextWatcher {
object : TextWatcher { override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun beforeTextChanged( override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
charSequence: CharSequence,
i: Int,
i1: Int,
i2: Int,
) {
// We do nothing
}
override fun onTextChanged(
charSequence: CharSequence,
i: Int,
i1: Int,
i2: Int,
) {
// We do nothing
}
override fun afterTextChanged(editable: Editable) { override fun afterTextChanged(editable: Editable) {
try { try {
editText.textSize = editable.toString().toInt().toFloat() editText.textSize = editable.toString().toInt().toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
e.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > afterTextChanged")
} }
} }
} } }
} editText.filters = arrayOf(
editText.filters =
arrayOf(
InputFilter { source, _, _, dest, _, _ -> InputFilter { source, _, _, dest, _, _ ->
try { try {
val input = (dest.toString() + source.toString()).toInt() val input = (dest.toString() + source.toString()).toInt()
if (input > 0) return@InputFilter null if (input > 0) return@InputFilter null
} catch (nfe: NumberFormatException) { } catch (nfe: NumberFormatException) {
nfe.sendSilentlyWithAcraWithName("ArticleViewerPreferenceFragment > filters")
} }
"" ""
}, }
) )
} }
} }
} }
class OfflinePreferenceFragment : PreferenceFragmentCompat() { class OfflinePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_offline, rootKey) setPreferencesFromResource(R.xml.pref_offline, rootKey)
} }
} }
class ThemePreferenceFragment : PreferenceFragmentCompat() { class ThemePreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_theme, rootKey) setPreferencesFromResource(R.xml.pref_theme, rootKey)
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯ AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
true true
} }
@ -234,26 +180,20 @@ class SettingsActivity :
startActivity(browserIntent) startActivity(browserIntent)
} }
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_links, rootKey) setPreferencesFromResource(R.xml.pref_links, rootKey)
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener {
openUrl(Uri.parse(AppSettingsService.trackerUrl)) openUrl(Uri.parse(AppSettingsService.trackerUrl))
true true
} }
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener {
openUrl(Uri.parse(AppSettingsService.sourceUrl)) openUrl(Uri.parse(AppSettingsService.sourceUrl))
false false
} }
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
Preference.OnPreferenceClickListener {
openUrl(Uri.parse(AppSettingsService.translationUrl)) openUrl(Uri.parse(AppSettingsService.translationUrl))
false false
} }
@ -261,10 +201,7 @@ class SettingsActivity :
} }
class ExperimentalPreferenceFragment : PreferenceFragmentCompat() { class ExperimentalPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences( override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
savedInstanceState: Bundle?,
rootKey: String?,
) {
setPreferencesFromResource(R.xml.pref_experimental, rootKey) setPreferencesFromResource(R.xml.pref_experimental, rootKey)
} }
} }

View File

@ -5,10 +5,7 @@ import android.content.Intent
import bou.amine.apps.readerforselfossv2.android.R import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
fun Context.shareLink( fun Context.shareLink(itemUrl: String, itemTitle: String) {
itemUrl: String,
itemTitle: String,
) {
val sendIntent = Intent() val sendIntent = Intent()
sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
sendIntent.action = Intent.ACTION_SEND sendIntent.action = Intent.ACTION_SEND
@ -18,7 +15,7 @@ fun Context.shareLink(
startActivity( startActivity(
Intent.createChooser( Intent.createChooser(
sendIntent, sendIntent,
getString(R.string.share), getString(R.string.share)
).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
) )
} }

View File

@ -1,65 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.utils
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import com.google.android.material.imageview.ShapeableImageView
import kotlin.math.abs
class CircleImageView
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : RelativeLayout(context, attrs, defStyleAttr) {
val view: View
val imageView: ShapeableImageView
val textView: TextView
private val colorScheme =
listOf(
-0x1a8c8d,
-0xf9d6e,
-0x459738,
-0x6a8a33,
-0x867935,
-0x9b4a0a,
-0xb03c09,
-0xb22f1f,
-0xb24954,
-0x7e387c,
-0x512a7f,
-0x759b,
-0x2b1ea9,
-0x2ab1,
-0x48b3,
-0x5e7781,
-0x6f5b52,
)
init {
view = LayoutInflater.from(context).inflate(R.layout.circle_image_view, this, true)
imageView = view.findViewById(R.id.circleImage)
textView = view.findViewById(R.id.circleText)
}
fun setBackgroundAndText(text: String) {
val circleDrawable = GradientDrawable()
val color = colorFromIdentifier(text)
circleDrawable.setColor(color)
imageView.setImageDrawable(circleDrawable)
textView.text = text.toTextDrawableString()
}
private fun colorFromIdentifier(key: String): Int {
return colorScheme[abs(key.hashCode()) % colorScheme.size]
}
}

View File

@ -1,9 +1,13 @@
package bou.amine.apps.readerforselfossv2.android.utils package bou.amine.apps.readerforselfossv2.android.utils
import android.app.Activity import android.app.Activity
import android.app.PendingIntent
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Build
import android.text.Spannable import android.text.Spannable
import android.text.style.ClickableSpan import android.text.style.ClickableSpan
import android.util.Patterns import android.util.Patterns
@ -18,19 +22,22 @@ import bou.amine.apps.readerforselfossv2.utils.toStringUriWithHttp
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
fun Context.openItemUrl( fun Context.openItemUrl(
allItems: ArrayList<SelfossModel.Item>,
currentItem: Int, currentItem: Int,
linkDecoded: String, linkDecoded: String,
articleViewer: Boolean, articleViewer: Boolean,
app: Activity, app: Activity
) { ) {
if (!linkDecoded.isUrlValid()) { if (!linkDecoded.isUrlValid()) {
Toast.makeText( Toast.makeText(
this, this,
this.getString(R.string.cant_open_invalid_url), this.getString(R.string.cant_open_invalid_url),
Toast.LENGTH_LONG, Toast.LENGTH_LONG
).show() ).show()
} else { } else {
if (articleViewer) { if (articleViewer) {
ReaderActivity.allItems = allItems
val intent = Intent(this, ReaderActivity::class.java) val intent = Intent(this, ReaderActivity::class.java)
intent.putExtra("currentItem", currentItem) intent.putExtra("currentItem", currentItem)
app.startActivity(intent) app.startActivity(intent)
@ -43,7 +50,8 @@ fun Context.openItemUrl(
} }
} }
fun String.isUrlValid(): Boolean = this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches() fun String.isUrlValid(): Boolean =
this.toHttpUrlOrNull() != null && Patterns.WEB_URL.matcher(this).matches()
fun String.isBaseUrlInvalid(): Boolean { fun String.isBaseUrlInvalid(): Boolean {
val baseUrl = this.toHttpUrlOrNull() val baseUrl = this.toHttpUrlOrNull()
@ -64,10 +72,7 @@ fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
} }
class LinkOnTouchListener : View.OnTouchListener { class LinkOnTouchListener : View.OnTouchListener {
override fun onTouch( override fun onTouch(v: View?, event: MotionEvent?): Boolean {
v: View?,
event: MotionEvent?,
): Boolean {
var ret = false var ret = false
val widget: TextView = v as TextView val widget: TextView = v as TextView
val text: CharSequence = widget.text val text: CharSequence = widget.text

View File

@ -8,4 +8,5 @@ fun TextBadgeItem.removeBadge(): TextBadgeItem {
return this return this
} }
fun TextBadgeItem.maybeShow(): TextBadgeItem = if (this.isHidden) this.show() else this fun TextBadgeItem.maybeShow(): TextBadgeItem =
if (this.isHidden) this.show() else this

View File

@ -0,0 +1,14 @@
/* From https://github.com/mikepenz/MaterialDrawer/blob/develop/app/src/main/java/com/mikepenz/materialdrawer/app/drawerItems/CustomBaseViewHolder.java */
package bou.amine.apps.readerforselfossv2.android.utils.drawer
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
open class CustomBaseViewHolder(var view: View) : RecyclerView.ViewHolder(view) {
var icon: ImageView = view.findViewById(R.id.material_drawer_icon)
var name: TextView = view.findViewById(R.id.material_drawer_name)
var description: TextView = view.findViewById(R.id.material_drawer_description)
}

View File

@ -3,37 +3,38 @@ package bou.amine.apps.readerforselfossv2.android.utils.glide
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.widget.ImageView import android.widget.ImageView
import bou.amine.apps.readerforselfossv2.android.utils.CircleImageView import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.BitmapImageViewTarget
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
fun Context.bitmapCenterCrop( fun Context.bitmapCenterCrop(url: String, iv: ImageView) =
url: String, Glide.with(this)
iv: ImageView,
) = Glide.with(this)
.asBitmap() .asBitmap()
.load(url) .load(url)
.apply(RequestOptions.centerCropTransform()) .apply(RequestOptions.centerCropTransform())
.into(iv) .into(iv)
fun Context.circularDrawable( fun Context.circularBitmapDrawable(url: String, iv: ImageView) =
url: String,
view: CircleImageView,
) {
view.textView.text = ""
Glide.with(this) Glide.with(this)
.asBitmap()
.load(url) .load(url)
.into(view.imageView) .apply(RequestOptions.centerCropTransform())
} .into(object : BitmapImageViewTarget(iv) {
override fun setResource(resource: Bitmap?) {
val circularBitmapDrawable = RoundedBitmapDrawableFactory.create(
resources,
resource
)
circularBitmapDrawable.isCircular = true
iv.setImageDrawable(circularBitmapDrawable)
}
})
fun getBitmapInputStream( fun getBitmapInputStream(bitmap:Bitmap,compressFormat: Bitmap.CompressFormat): InputStream {
bitmap: Bitmap,
compressFormat: Bitmap.CompressFormat,
): InputStream {
val byteArrayOutputStream = ByteArrayOutputStream() val byteArrayOutputStream = ByteArrayOutputStream()
bitmap.compress(compressFormat, 80, byteArrayOutputStream) bitmap.compress(compressFormat, 80, byteArrayOutputStream)
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray() val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()

View File

@ -19,8 +19,7 @@ class AppViewModel(private val repository: Repository) : ViewModel() {
if (isConnected && !wasConnected && repository.connectionMonitored) { if (isConnected && !wasConnected && repository.connectionMonitored) {
_networkAvailableProvider.emit(true) _networkAvailableProvider.emit(true)
wasConnected = true wasConnected = true
} else if (!isConnected && wasConnected && repository.connectionMonitored) } else if (!isConnected && wasConnected && repository.connectionMonitored){
{
_networkAvailableProvider.emit(false) _networkAvailableProvider.emit(false)
wasConnected = false wasConnected = false
} }

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="100%p" android:toXDelta="0"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromXDelta="0" android:toXDelta="-100%p"
android:duration="@android:integer/config_mediumAnimTime"/>
</set>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
</vector>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Open Sans"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Roboto"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Source Code Pro&amp;weight=500"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View File

@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="bou.amine.apps.readerforselfossv2.android.UpsertSourceActivity"> tools:context="bou.amine.apps.readerforselfossv2.android.AddSourceActivity">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -17,83 +17,116 @@
<androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle" <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" /> android:layout_height="?attr/actionBarSize"
/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/formContainer" android:paddingBottom="@dimen/activity_vertical_margin"
android:layout_width="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="16dp" android:layout_width="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/formContainer"
android:visibility="gone" android:visibility="gone"
app:layout_constraintHorizontal_bias="1.0" app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintVertical_bias="0.0">
app:layout_constraintTop_toTopOf="parent">
<TextView
<EditText android:text="@string/add_source"
android:id="@+id/nameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_name"
android:inputType="text"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/sourceUri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_url"
android:inputType="textUri"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/nameInput" />
<EditText
android:id="@+id/tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
android:autofillHints="false"
android:hint="@string/add_source_hint_tags"
android:inputType="text"
android:textColorHint="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceUri" />
<Spinner
android:id="@+id/spoutsSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tags" />
<Button
android:id="@+id/saveBtn"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/textView2"
android:textAppearance="@style/TextAppearance.AppCompat.Large"
android:textAlignment="center"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:elevation="5dp" app:layout_constraintTop_toTopOf="parent"
android:text="@string/add_source_save" android:layout_marginEnd="16dp"
android:textColor="?android:textColorPrimary" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:gravity="center_horizontal" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/nameInput"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@+id/textView2"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner" /> android:inputType="text"
android:hint="@string/add_source_hint_name"
android:textColorHint="?android:textColorPrimary"
android:autofillHints="false" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textUri"
android:ems="10"
android:id="@+id/sourceUri"
android:hint="@string/add_source_hint_url"
android:textColorHint="?android:textColorPrimary"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/nameInput"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:autofillHints="false" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:id="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/sourceUri"
android:hint="@string/add_source_hint_tags"
android:textColorHint="?android:textColorPrimary"
android:inputType="text"
android:autofillHints="false" />
<Spinner
android:layout_width="match_parent"
android:id="@+id/spoutsSpinner"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/tags"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_height="40dp"/>
<Button
android:text="@string/add_source_save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/saveBtn"
android:elevation="5dp"
android:textColor="?android:textColorPrimary"
android:layout_marginEnd="16dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
app:layout_constraintLeft_toLeftOf="parent"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@+id/spoutsSpinner"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
@ -102,6 +135,8 @@
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.drawerlayout.widget.DrawerLayout
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerContainer" android:id="@+id/drawerContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:context="bou.amine.apps.readerforselfossv2.android.HomeActivity"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordLayout" android:id="@+id/coordLayout"
@ -27,12 +28,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
android:id="@+id/toolBar" android:id="@+id/toolBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme"
/> />
@ -46,19 +45,19 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/windowBackground" android:orientation="vertical"
android:orientation="vertical"> android:background="?android:attr/windowBackground">
<TextView <TextView
android:id="@+id/emptyText" android:id="@+id/emptyText"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:paddingTop="100dp" android:paddingTop="100dp"
android:text="@string/nothing_here" android:text="@string/nothing_here"
android:textAlignment="center" android:textAlignment="center"
android:textAppearance="@style/TextAppearance.AppCompat.Headline" android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:background="@android:color/transparent"
android:visibility="gone" /> android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -70,7 +69,7 @@
android:paddingBottom="60dp" android:paddingBottom="60dp"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/list_item" /> tools:listitem="@layout/list_item"/>
</LinearLayout> </LinearLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -78,7 +77,6 @@
</LinearLayout> </LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.ashokvarma.bottomnavigation.BottomNavigationBar <com.ashokvarma.bottomnavigation.BottomNavigationBar
android:id="@+id/bottomBar" android:id="@+id/bottomBar"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -87,4 +85,11 @@
app:bnbActiveColor="@color/colorAccent" app:bnbActiveColor="@color/colorAccent"
app:bnbBackgroundColor="?attr/bottomBarBackground" /> app:bnbBackgroundColor="?attr/bottomBarBackground" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.mikepenz.materialdrawer.widget.MaterialDrawerSliderView
android:id="@+id/mainDrawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true" />
</androidx.drawerlayout.widget.DrawerLayout> </androidx.drawerlayout.widget.DrawerLayout>

View File

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent">
app:layoutDescription="@xml/image_close_scene">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout" android:id="@+id/appBarLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.appcompat.widget.Toolbar android:theme="@style/ToolBarStyle" <androidx.appcompat.widget.Toolbar
android:id="@+id/toolBar" android:id="@+id/toolBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
@ -20,21 +21,13 @@
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout">
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager" android:id="@+id/pager"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent"
</androidx.core.widget.NestedScrollView> app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.motion.widget.MotionLayout>

View File

@ -1,30 +1,31 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:orientation="vertical" android:orientation="vertical"
tools:context="bou.amine.apps.readerforselfossv2.android.LoginActivity"> tools:context="bou.amine.apps.readerforselfossv2.android.LoginActivity">
<com.google.android.material.appbar.AppBarLayout <com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar app:popupTheme="?attr/toolbarPopupTheme" android:theme="@style/ToolBarStyle"
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:theme="@style/ToolBarStyle"
app:popupTheme="?attr/toolbarPopupTheme" /> />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin"> android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<!-- Login progress --> <!-- Login progress -->
<ProgressBar <ProgressBar
android:id="@+id/loginProgress" android:id="@+id/loginProgress"
@ -32,10 +33,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:visibility="gone" /> android:visibility="gone"/>
<ScrollView
android:id="@+id/loginForm"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout <LinearLayout
android:id="@+id/loginForm"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@ -48,22 +53,14 @@
android:imeOptions="actionUnspecified" android:imeOptions="actionUnspecified"
android:importantForAutofill="no" android:importantForAutofill="no"
android:inputType="textUri" android:inputType="textUri"
android:maxLines="1" android:maxLines="1" />
android:minHeight="48dp" />
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/selfSigned"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/disable_ssl"
android:textAlignment="viewStart" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/withLogin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/withLoginSwitch" android:text="@string/withLoginSwitch"
android:textAlignment="viewStart" /> android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/withLogin"
android:layout_weight="1"/>
<EditText <EditText
android:id="@+id/loginView" android:id="@+id/loginView"
@ -73,7 +70,6 @@
android:hint="@string/prompt_login" android:hint="@string/prompt_login"
android:inputType="text" android:inputType="text"
android:maxLines="1" android:maxLines="1"
android:minHeight="48dp"
android:visibility="gone" /> android:visibility="gone" />
<EditText <EditText
@ -84,7 +80,6 @@
android:hint="@string/prompt_password" android:hint="@string/prompt_password"
android:inputType="textPassword" android:inputType="textPassword"
android:maxLines="1" android:maxLines="1"
android:minHeight="48dp"
android:visibility="gone" /> android:visibility="gone" />
<Button <Button
@ -98,6 +93,7 @@
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
</ScrollView>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -24,8 +24,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior">
tools:listitem="@layout/source_list_item">
</androidx.recyclerview.widget.RecyclerView> </androidx.recyclerview.widget.RecyclerView>
<com.google.android.material.floatingactionbutton.FloatingActionButton <com.google.android.material.floatingactionbutton.FloatingActionButton

View File

@ -1,14 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto" xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/card" android:id="@+id/card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_marginLeft="8dp"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginRight="8dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="8dp"
app:layout_constraintHorizontal_bias="0.62"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
card_view:cardElevation="2dp" card_view:cardElevation="2dp"
card_view:cardUseCompatPadding="true" card_view:cardUseCompatPadding="true"
@ -24,8 +28,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:cropToPadding="true" android:cropToPadding="true"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash" app:srcCompat="@drawable/background_splash"
card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" /> card_view:layout_constraintBottom_toTopOf="@+id/constraintLayout" />
@ -35,17 +39,18 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/itemImage"> app:layout_constraintTop_toBottomOf="@+id/itemImage">
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView <ImageView
android:id="@+id/sourceImage" android:id="@+id/sourceImage"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/background_splash" /> app:srcCompat="@drawable/background_splash" />
@ -53,58 +58,70 @@
android:id="@+id/title" android:id="@+id/title"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="8dp" android:layout_marginEnd="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginStart="8dp"
android:gravity="start"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="?android:textColorPrimary"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent" android:textColor="?android:textColorPrimary"
app:layout_constraintStart_toEndOf="@+id/sourceImage" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toRightOf="@+id/sourceImage"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/sourceImage" app:layout_constraintTop_toTopOf="@+id/sourceImage"
tools:text="Titre" /> tools:text="Titre" />
<TextView <TextView
android:id="@+id/sourceTitleAndDate" android:id="@+id/sourceTitleAndDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAlignment="viewStart"
android:textColor="?android:textColorPrimary"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Google Actualité Il y a 5h" />
<LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginEnd="8dp" android:gravity="start"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintLeft_toLeftOf="@+id/title"
app:layout_constraintTop_toBottomOf="@+id/title"
tools:text="Google Actualité Il y a 5h" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate"> app:layout_constraintTop_toBottomOf="@+id/sourceTitleAndDate">
<ImageButton <ImageButton
android:id="@+id/browserBtn" android:id="@+id/favButton"
android:layout_width="35dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:contentDescription="@string/reader_action_open"
android:elevation="5dp" android:elevation="5dp"
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_open_in_browser_black_24dp" app:srcCompat="@drawable/ic_menu_heart_60dp"
app:tint="?android:attr/textColorPrimary" /> app:tint="@color/ic_menu_heart_color" />
<ImageButton <ImageButton
android:id="@+id/shareBtn" android:id="@+id/shareBtn"
android:layout_width="35dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginStart="16dp" android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/favButton"
android:layout_toStartOf="@+id/favButton"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:contentDescription="@string/share"
android:elevation="5dp" android:elevation="5dp"
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
@ -112,21 +129,23 @@
app:tint="?android:attr/textColorPrimary" /> app:tint="?android:attr/textColorPrimary" />
<ImageButton <ImageButton
android:id="@+id/favButton" android:id="@+id/browserBtn"
android:layout_width="35dp" android:layout_width="35dp"
android:layout_height="35dp" android:layout_height="35dp"
android:layout_marginStart="16dp" android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_toLeftOf="@+id/shareBtn"
android:layout_toStartOf="@+id/shareBtn"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:contentDescription="@string/add_to_favs_reader"
android:elevation="5dp" android:elevation="5dp"
android:padding="4dp" android:padding="4dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_menu_heart_60dp" app:srcCompat="@drawable/ic_open_in_browser_black_24dp"
app:tint="@color/ic_menu_heart_color" /> app:tint="?android:attr/textColorPrimary" />
</RelativeLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/circleImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/circleImageView"
app:srcCompat="@drawable/background_splash" />
<TextView
android:id="@+id/circleText"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="none"
android:gravity="center"
android:singleLine="true"
android:textColor="@color/white"
android:textIsSelectable="false"
android:textSize="20sp"
android:typeface="normal" />
</RelativeLayout>

View File

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar2"
style="?android:attr/progressBarStyle"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="gone" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/filterTagsTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:text="@string/filter_item_tags"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/filterSourcesTitle"
style="@style/MaterialAlertDialog.MaterialComponents.Title.Text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="@string/filter_item_sources"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tagsGroup" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/tagsGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filterTagsTitle"
app:singleSelection="true">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.chip.ChipGroup
android:id="@+id/sourcesGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/filterSourcesTitle">
</com.google.android.material.chip.ChipGroup>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:clickable="true"
android:contentDescription="@string/menu_home_search"
android:focusable="true"
app:backgroundTint="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:rippleColor="@color/colorAccentDark"
app:srcCompat="@drawable/ic_menu_search_white_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,4 +1,5 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -21,22 +22,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="200dp" android:layout_height="200dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginRight="16dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView <TextView
android:id="@+id/source" android:id="@+id/source"
@ -47,23 +36,40 @@
android:layout_marginRight="16dp" android:layout_marginRight="16dp"
android:textColor="?android:textColorSecondary" android:textColor="?android:textColorSecondary"
android:textSize="12sp" android:textSize="12sp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/titleView" /> app:layout_constraintTop_toBottomOf="@+id/titleView" />
<TextView
android:id="@+id/titleView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textStyle="bold"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<WebView <WebView
android:id="@+id/webcontent" android:id="@+id/webcontent"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="24dp"
android:layout_marginRight="16dp"
android:background="?attr/webviewBackground"
android:paddingBottom="48dp"
android:textColorLink="?attr/colorAccent" android:textColorLink="?attr/colorAccent"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginLeft="16dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginRight="16dp"
android:layout_marginTop="24dp"
android:paddingBottom="48dp"
android:background="?attr/webviewBackground"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/source" app:layout_constraintTop_toBottomOf="@+id/source"
tools:visibility="visible" /> tools:visibility="visible" />
@ -74,10 +80,10 @@
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="start|bottom|end"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintLeft_toLeftOf="parent"
android:layout_gravity="end|bottom|right">
<com.github.rubensousa.floatingtoolbar.FloatingToolbar <com.github.rubensousa.floatingtoolbar.FloatingToolbar
android:id="@+id/floatingToolbar" android:id="@+id/floatingToolbar"
@ -90,11 +96,12 @@
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end|bottom" android:layout_gravity="end|bottom|right"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:paddingTop="@dimen/activity_vertical_margin" android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:paddingBottom="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:src="@drawable/ic_add_white_24dp" android:src="@drawable/ic_add_white_24dp"
app:backgroundTint="?attr/colorAccent" app:backgroundTint="?attr/colorAccent"
app:fabSize="mini" app:fabSize="mini"
@ -105,11 +112,11 @@
android:id="@+id/progressBar" android:id="@+id/progressBar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:alpha="0.8" android:visibility="gone"
android:animateLayoutChanges="true" android:animateLayoutChanges="true"
android:alpha="0.8"
android:background="@color/black" android:background="@color/black"
android:clickable="false" android:clickable="false">
android:visibility="gone">
<ProgressBar <ProgressBar
style="?android:attr/progressBarStyleLarge" style="?android:attr/progressBarStyleLarge"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
@ -13,4 +13,4 @@
android:background="@drawable/checkerboard" android:background="@drawable/checkerboard"
app:srcCompat="@android:drawable/screen_background_dark" /> app:srcCompat="@android:drawable/screen_background_dark" />
</androidx.constraintlayout.widget.ConstraintLayout> </RelativeLayout>

View File

@ -3,16 +3,17 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="88dp">
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView <ImageView
android:id="@+id/itemImage" android:id="@+id/itemImage"
android:layout_width="46dp" android:layout_width="46dp"
android:layout_height="46dp" android:layout_height="46dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent" android:layout_marginTop="21dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="8dp" />
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
@ -23,30 +24,39 @@
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="sans-serif" android:fontFamily="sans-serif"
android:gravity="start"
android:maxLines="3" android:maxLines="3"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="?android:textColorPrimary" android:textAllCaps="false"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage" app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Titre" /> tools:text="Titre"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
<TextView <TextView
android:id="@+id/sourceTitleAndDate" android:id="@+id/sourceTitleAndDate"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="66dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:gravity="start" android:gravity="start"
android:maxLines="1" android:maxLines="1"
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="?android:textColorPrimary"
android:textSize="14sp" android:textSize="14sp"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemImage" app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toBottomOf="@+id/itemImage" app:layout_constraintTop_toTopOf="parent"
tools:text="Google Actualité Il y a 5h" /> tools:text="Google Actualité Il y a 5h"
android:layout_marginLeft="8dp"
android:layout_marginRight="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,74 +3,48 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="48dp"
android:orientation="vertical"> android:orientation="vertical">
<Button <ImageView
android:id="@+id/deleteBtn"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="48dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/ic_remove_circle_outline_black_24dp"
android:backgroundTint="?android:textColorSecondary"
android:contentDescription="@string/remove_source"
android:elevation="4dp"
app:iconSize="34dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<bou.amine.apps.readerforselfossv2.android.utils.CircleImageView
android:id="@+id/itemImage" android:id="@+id/itemImage"
android:layout_width="36dp" android:layout_width="36dp"
android:layout_height="36dp" android:layout_height="36dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:importantForAccessibility="no" android:importantForAccessibility="no"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintVertical_bias="0.0" />
<TextView <TextView
android:id="@+id/sourceTitle" android:id="@+id/sourceTitle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="17dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:ellipsize="end" android:ellipsize="end"
android:gravity="start"
android:maxLines="1" android:maxLines="1"
android:textAlignment="viewStart" android:textAlignment="textStart"
android:textSize="13sp"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/errorText"
app:layout_constraintEnd_toStartOf="@+id/deleteBtn" app:layout_constraintEnd_toStartOf="@+id/deleteBtn"
app:layout_constraintStart_toEndOf="@+id/itemImage" app:layout_constraintStart_toEndOf="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Source title" /> tools:text="source title" />
<TextView <Button
android:id="@+id/errorText" android:id="@+id/deleteBtn"
android:layout_width="0dp" style="@style/Widget.AppCompat.Button.Borderless"
android:layout_height="wrap_content" android:layout_width="34dp"
android:layout_marginStart="10sp" android:layout_height="34dp"
android:layout_marginTop="8dp" android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" android:background="@drawable/ic_remove_circle_outline_black_24dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:backgroundTint="?android:textColorSecondary"
android:textColor="@color/red" android:elevation="4dp"
android:textStyle="italic" android:contentDescription="@string/remove_source"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/deleteBtn" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toBottomOf="@+id/itemImage"
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -8,40 +8,18 @@
app:showAsAction="ifRoom|collapseActionView" app:showAsAction="ifRoom|collapseActionView"
app:actionViewClass="androidx.appcompat.widget.SearchView" /> app:actionViewClass="androidx.appcompat.widget.SearchView" />
<item android:id="@+id/action_filter"
android:title="@string/menu_home_filter"
android:icon="@drawable/ic_baseline_filter_alt_24"
android:orderInCategory="1"
app:showAsAction="always" />
<item android:id="@+id/readAll" <item android:id="@+id/readAll"
android:icon="@drawable/ic_menu_done_all_white_24dp" android:icon="@drawable/ic_menu_done_all_white_24dp"
android:title="@string/readAll" android:title="@string/readAll"
android:orderInCategory="2" android:orderInCategory="1"
app:showAsAction="ifRoom"/> app:showAsAction="always"/>
<item android:id="@+id/action_sources"
android:title="@string/menu_home_sources"
android:orderInCategory="97"
app:showAsAction="never"/>
<item android:id="@+id/action_settings"
android:title="@string/title_activity_settings"
android:orderInCategory="98"
app:showAsAction="never"/>
<item <item
android:id="@+id/refresh" android:id="@+id/refresh"
app:showAsAction="never" android:icon="@drawable/ic_menu_refresh_white_24dp"
android:orderInCategory="101" android:orderInCategory="99"
android:title="@string/menu_home_refresh" /> android:title="@string/menu_home_refresh" />
<item
android:id="@+id/issue_tracker"
app:showAsAction="never"
android:orderInCategory="103"
android:title="@string/issue_tracker_link" />
<item android:id="@+id/action_disconnect" <item android:id="@+id/action_disconnect"
android:title="@string/action_disconnect" android:title="@string/action_disconnect"
android:orderInCategory="104" android:orderInCategory="104"

View File

@ -3,13 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/issue_tracker"
app:showAsAction="never"
android:orderInCategory="101"
android:title="@string/issue_tracker_link" />
<item android:id="@+id/about" <item android:id="@+id/about"
android:title="@string/action_about" android:title="@string/action_about"
android:orderInCategory="102" android:orderInCategory="102"

View File

@ -1,16 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/open_action"
android:icon="@drawable/ic_open_in_browser_white_24dp"
android:title="@string/reader_action_open"
app:showAsAction="ifRoom" />
<item
android:id="@+id/share_action"
android:icon="@drawable/ic_share_white_24dp"
android:title="@string/reader_action_share"
app:showAsAction="ifRoom" />
</menu>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Autenticació (si és necessària)"</string> <string name="withLoginSwitch">"Autenticació (si és necessària)"</string>
<string name="login_url_problem">"Pot ser que falti una \"/\" al final de l'url."</string> <string name="login_url_problem">"Pot ser que falti una \"/\" al final de l'url."</string>
<string name="prompt_login">"Nom d'usuari"</string> <string name="prompt_login">"Nom d'usuari"</string>
<string name="label_share">"Comparteix"</string>
<string name="readAll">"Llegeix-ho tot"</string> <string name="readAll">"Llegeix-ho tot"</string>
<string name="action_disconnect">"Desconnecta't"</string> <string name="action_disconnect">"Desconnecta't"</string>
<string name="title_activity_settings">"Configuració"</string> <string name="title_activity_settings">"Configuració"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">L\'alçada de les targetes s\'ajustarà al seu contingut</string> <string name="card_height_on">L\'alçada de les targetes s\'ajustarà al seu contingut</string>
<string name="card_height_off">L\'alçada de les targetes serà fixa</string> <string name="card_height_off">L\'alçada de les targetes serà fixa</string>
<string name="source_code">Codi font</string> <string name="source_code">Codi font</string>
<string name="filter_item_tags">Etiquetes</string> <string name="drawer_error_loading_tags">S\'ha produït un error en carregar les etiquetes</string>
<string name="filter_item_sources">Fonts</string> <string name="drawer_item_filters">Filtres</string>
<string name="drawer_action_clear">Esborra</string>
<string name="drawer_item_tags">Etiquetes</string>
<string name="drawer_item_sources">Fonts</string>
<string name="drawer_action_edit">Edita</string>
<string name="drawer_loading">S\'està carregant…</string>
<string name="menu_home_search">Cerca</string> <string name="menu_home_search">Cerca</string>
<string name="can_delete_source">No es pot suprimir la font</string> <string name="can_delete_source">No es pot suprimir la font</string>
<string name="base_url_error">S\'ha produït un error en comunicar-se amb la instància de Selfoss. Si el problema persisteix, posa\'t en contacte amb mi.</string> <string name="base_url_error">S\'ha produït un error en comunicar-se amb la instància de Selfoss. Si el problema persisteix, posa\'t en contacte amb mi.</string>
<string name="pref_header_theme">Temes</string> <string name="pref_header_theme">Temes</string>
<string name="default_theme">Predeterminat</string>
<string name="default_dark_theme">Predeterminat/Fosc</string>
<string name="pref_selfoss_category">API de Selfoss</string> <string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'elements carregats</string> <string name="pref_api_items_number_title">Nombre d\'elements carregats</string>
<string name="pref_hidden_tags">Etiquetes ocultes</string>
<string name="pref_general_infinite_loading_title">Carrega articles en desplaçar</string> <string name="pref_general_infinite_loading_title">Carrega articles en desplaçar</string>
<string name="translation">Traducció</string> <string name="translation">Traducció</string>
<string name="cant_open_invalid_url">L\'element URL no és vàlid. Estic intentant solucionar aquest problema perquè l\'aplicació no falli.</string> <string name="cant_open_invalid_url">L\'element URL no és vàlid. Estic intentant solucionar aquest problema perquè l\'aplicació no falli.</string>
<string name="drawer_report_bug">Informa d\'un error</string>
<string name="items_number_should_be_number">El nombre d\'elements ha de ser enter.</string> <string name="items_number_should_be_number">El nombre d\'elements ha de ser enter.</string>
<string name="reader_action_more">Més informació</string>
<string name="reader_action_open">Obre al navegador</string> <string name="reader_action_open">Obre al navegador</string>
<string name="reader_action_share">Comparteix</string> <string name="reader_action_share">Comparteix</string>
<string name="pref_switch_actions_pager_scroll_on">Es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string> <string name="pref_switch_actions_pager_scroll_on">Es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Aquesta acció marcarà els elements com a llegits.</string> <string name="markall_dialog_message">Aquesta acció marcarà els elements com a llegits.</string>
<string name="pref_switch_actions_pager_scroll">Marca com a llegit en lliscar el dit</string> <string name="pref_switch_actions_pager_scroll">Marca com a llegit en lliscar el dit</string>
<string name="pref_switch_actions_pager_scroll_off">No es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string> <string name="pref_switch_actions_pager_scroll_off">No es marcaran els articles com a llegits en lliscar el dit d\'un article a l\'altre.</string>
<string name="drawer_item_hidden_tags">Etiquetes ocultes</string>
<string name="unmark">Marca com no llegit</string> <string name="unmark">Marca com no llegit</string>
<string name="pref_header_offline">Sense connexió i memòria clau</string> <string name="pref_header_offline">Sense connexió i memòria clau</string>
<string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string> <string name="pref_switch_items_caching_off">Els articles no es guardaran a la memòria del dispositiu i l\'aplicació no es podrà utilitzar sense connexió.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string> <string name="pref_switch_periodic_refresh_on">Els articles se sincronitzaran periòdicament</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de sincronització ( >= 15 minuts)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de sincronització ( >= 15 minuts)]]></string>
<string name="pref_switch_refresh_when_charging">Sincronitza només quan el telèfon s\'està carregant</string> <string name="pref_switch_refresh_when_charging">Sincronitza només quan el telèfon s\'està carregant</string>
<string name="loading_notification_title">S\'està carregant</string> <string name="loading_notification_title">S\'està carregant...</string>
<string name="loading_notification_text">Selfoss està sincronitzant els articles</string> <string name="loading_notification_text">Selfoss està sincronitzant els articles</string>
<string name="notification_channel_sync">Notificació de sincronització</string> <string name="notification_channel_sync">Notificació de sincronització</string>
<string name="new_items_channel_sync">Notificació d\'elements nous</string> <string name="new_items_channel_sync">Notificació d\'elements nous</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Anmeldung erforderlich?"</string> <string name="withLoginSwitch">"Anmeldung erforderlich?"</string>
<string name="login_url_problem">"Ups. Du musst eventuell ein \"/\" am Ende der URL anhängen."</string> <string name="login_url_problem">"Ups. Du musst eventuell ein \"/\" am Ende der URL anhängen."</string>
<string name="prompt_login">"Benutzername"</string> <string name="prompt_login">"Benutzername"</string>
<string name="label_share">"Teilen"</string>
<string name="readAll">"Alle gelesen"</string> <string name="readAll">"Alle gelesen"</string>
<string name="action_disconnect">"Verbindung trennen"</string> <string name="action_disconnect">"Verbindung trennen"</string>
<string name="title_activity_settings">"Einstellungen"</string> <string name="title_activity_settings">"Einstellungen"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Kartenhöhe passt sich Inhalt an</string> <string name="card_height_on">Kartenhöhe passt sich Inhalt an</string>
<string name="card_height_off">Kartenhöhe ist fix</string> <string name="card_height_off">Kartenhöhe ist fix</string>
<string name="source_code">Quellcode</string> <string name="source_code">Quellcode</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Fehler beim Laden der Tags…</string>
<string name="filter_item_sources">Quellen</string> <string name="drawer_item_filters">Filter</string>
<string name="drawer_action_clear">leeren</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Quellen</string>
<string name="drawer_action_edit">bearbeiten</string>
<string name="drawer_loading">Lade…</string>
<string name="menu_home_search">Suche</string> <string name="menu_home_search">Suche</string>
<string name="can_delete_source">Can\'t delete the source…</string> <string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">Beim Versuch deine Selfoss-Instanz zu erreichen ist ein Fehler aufgetreten. Solltet dieser Fehler bestehen bleiben, trete bitte mit mir in Kontakt.</string> <string name="base_url_error">Beim Versuch deine Selfoss-Instanz zu erreichen ist ein Fehler aufgetreten. Solltet dieser Fehler bestehen bleiben, trete bitte mit mir in Kontakt.</string>
<string name="pref_header_theme">Designs</string> <string name="pref_header_theme">Designs</string>
<string name="default_theme">Standard</string>
<string name="default_dark_theme">Standard (Dunkel)</string>
<string name="pref_selfoss_category">selfoss API</string> <string name="pref_selfoss_category">selfoss API</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Übersetzung</string> <string name="translation">Übersetzung</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Melde einen Fehler</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Im Browser öffnen</string> <string name="reader_action_open">Im Browser öffnen</string>
<string name="reader_action_share">Teilen</string> <string name="reader_action_share">Teilen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Dies wird alle Elemente als gelesen markieren.</string> <string name="markall_dialog_message">Dies wird alle Elemente als gelesen markieren.</string>
<string name="pref_switch_actions_pager_scroll">Beim Wischen als gelesen markieren</string> <string name="pref_switch_actions_pager_scroll">Beim Wischen als gelesen markieren</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Eintrag als ungelesen markieren</string> <string name="unmark">Eintrag als ungelesen markieren</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string> <string name="pref_switch_periodic_refresh_on">Die Artikel werden regelmäßig synchronisiert</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Nur aktualisieren, wenn das Telefon aufgeladen wird</string> <string name="pref_switch_refresh_when_charging">Nur aktualisieren, wenn das Telefon aufgeladen wird</string>
<string name="loading_notification_title">Lädt</string> <string name="loading_notification_title">Lädt...</string>
<string name="loading_notification_text">Selfoss synchronisiert Ihre Artikel</string> <string name="loading_notification_text">Selfoss synchronisiert Ihre Artikel</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Inicio de sesión requerido ?"</string> <string name="withLoginSwitch">"Inicio de sesión requerido ?"</string>
<string name="login_url_problem">"Oops. Puede que necesite añadir un \"/\" al final de la url."</string> <string name="login_url_problem">"Oops. Puede que necesite añadir un \"/\" al final de la url."</string>
<string name="prompt_login">"Nombre de usuario"</string> <string name="prompt_login">"Nombre de usuario"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Leer todo"</string> <string name="readAll">"Leer todo"</string>
<string name="action_disconnect">"Desconectar"</string> <string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Configuración"</string> <string name="title_activity_settings">"Configuración"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Altura de tarjetas se ajustará a su contenido</string> <string name="card_height_on">Altura de tarjetas se ajustará a su contenido</string>
<string name="card_height_off">Se fijará la altura de la tarjeta</string> <string name="card_height_off">Se fijará la altura de la tarjeta</string>
<string name="source_code">Código fuente</string> <string name="source_code">Código fuente</string>
<string name="filter_item_tags">Etiquetas</string> <string name="drawer_error_loading_tags">Error al cargar etiquetas…</string>
<string name="filter_item_sources">Fuentes</string> <string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpiar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fuentes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Cargando…</string>
<string name="menu_home_search">Buscar</string> <string name="menu_home_search">Buscar</string>
<string name="can_delete_source">No se puede eliminar la fuente…</string> <string name="can_delete_source">No se puede eliminar la fuente…</string>
<string name="base_url_error">Hubo un problema al intentar comunicarse con su instancia de Selfoss. Si el problema persiste, póngase en contacto conmigo.</string> <string name="base_url_error">Hubo un problema al intentar comunicarse con su instancia de Selfoss. Si el problema persiste, póngase en contacto conmigo.</string>
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Oscuro</string>
<string name="pref_selfoss_category">Api de Selfoss</string> <string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de artículos cargados</string> <string name="pref_api_items_number_title">Número de artículos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar más artículos en desplazamiento</string> <string name="pref_general_infinite_loading_title">Cargar más artículos en desplazamiento</string>
<string name="translation">Traducción</string> <string name="translation">Traducción</string>
<string name="cant_open_invalid_url">La url del elemento no es válida. Estoy buscando resolver este problema para que la aplicación no colapse.</string> <string name="cant_open_invalid_url">La url del elemento no es válida. Estoy buscando resolver este problema para que la aplicación no colapse.</string>
<string name="drawer_report_bug">Reportar un error</string>
<string name="items_number_should_be_number">El número de artículos debe ser un número entero.</string> <string name="items_number_should_be_number">El número de artículos debe ser un número entero.</string>
<string name="reader_action_more">Leer más</string>
<string name="reader_action_open">Abrir en el navegador</string> <string name="reader_action_open">Abrir en el navegador</string>
<string name="reader_action_share">Compartir</string> <string name="reader_action_share">Compartir</string>
<string name="pref_switch_actions_pager_scroll_on">Marcar artículos como leidos al desplazarse entre ellos.</string> <string name="pref_switch_actions_pager_scroll_on">Marcar artículos como leidos al desplazarse entre ellos.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Esto marcará todos los artículos como leídos.</string> <string name="markall_dialog_message">Esto marcará todos los artículos como leídos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artículos como leídos al deslizar con el dedo hacia los lados</string> <string name="pref_switch_actions_pager_scroll">Marcar artículos como leídos al deslizar con el dedo hacia los lados</string>
<string name="pref_switch_actions_pager_scroll_off">No marcar artículos como leídos al deslizar con el dedo hacia los lados.</string> <string name="pref_switch_actions_pager_scroll_off">No marcar artículos como leídos al deslizar con el dedo hacia los lados.</string>
<string name="drawer_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como no leído</string> <string name="unmark">Marcar artículo como no leído</string>
<string name="pref_header_offline">Sin conexión y caché</string> <string name="pref_header_offline">Sin conexión y caché</string>
<string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string> <string name="pref_switch_items_caching_off">Los artículos no se guardarán en la memoria del dispositivo y la aplicación no se podrá utilizar sin conexión.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string> <string name="pref_switch_periodic_refresh_on">Los artículos se sincronizarán periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Sólo refrescar cuando el teléfono está cargando</string> <string name="pref_switch_refresh_when_charging">Sólo refrescar cuando el teléfono está cargando</string>
<string name="loading_notification_title">Cargando</string> <string name="loading_notification_title">Cargando...</string>
<string name="loading_notification_text">Selfoss está sincronizando tus artículos</string> <string name="loading_notification_text">Selfoss está sincronizando tus artículos</string>
<string name="notification_channel_sync">Notificación de sincronización</string> <string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de elementos nuevos</string> <string name="new_items_channel_sync">Notificación de elementos nuevos</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Login required ?"</string> <string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> <string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string> <string name="prompt_login">"Username"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string> <string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string> <string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"Settings"</string> <string name="title_activity_settings">"Settings"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="filter_item_sources">Sources</string> <string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string> <string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string> <string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Avec login ?"</string> <string name="withLoginSwitch">"Avec login ?"</string>
<string name="login_url_problem">"Petit souci. Il manque peut-être un / à la fin ?"</string> <string name="login_url_problem">"Petit souci. Il manque peut-être un / à la fin ?"</string>
<string name="prompt_login">"Utilisateur"</string> <string name="prompt_login">"Utilisateur"</string>
<string name="label_share">"Partager"</string>
<string name="readAll">"Tout lire"</string> <string name="readAll">"Tout lire"</string>
<string name="action_disconnect">"Déconnecter"</string> <string name="action_disconnect">"Déconnecter"</string>
<string name="title_activity_settings">"Paramètres"</string> <string name="title_activity_settings">"Paramètres"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">La taille de la carte s\'adaptera au contenu</string> <string name="card_height_on">La taille de la carte s\'adaptera au contenu</string>
<string name="card_height_off">La taille de la carte sera fixe</string> <string name="card_height_off">La taille de la carte sera fixe</string>
<string name="source_code">Code source</string> <string name="source_code">Code source</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Erreur lors du chargement des tags…</string>
<string name="filter_item_sources">Sources</string> <string name="drawer_item_filters">Filtres</string>
<string name="drawer_action_clear">raz</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">éditer</string>
<string name="drawer_loading">Chargement …</string>
<string name="menu_home_search">Rechercher</string> <string name="menu_home_search">Rechercher</string>
<string name="can_delete_source">Impossible de supprimer la source…</string> <string name="can_delete_source">Impossible de supprimer la source…</string>
<string name="base_url_error">Il y a eu un souci lors de la communication avec votre instance Selfoss. Si le problèmes persiste, contactez-moi pour trouver une solution.</string> <string name="base_url_error">Il y a eu un souci lors de la communication avec votre instance Selfoss. Si le problèmes persiste, contactez-moi pour trouver une solution.</string>
<string name="pref_header_theme">Thèmes</string> <string name="pref_header_theme">Thèmes</string>
<string name="default_theme">Par défaut</string>
<string name="default_dark_theme">Par défaut/Foncé</string>
<string name="pref_selfoss_category">Api Selfoss</string> <string name="pref_selfoss_category">Api Selfoss</string>
<string name="pref_api_items_number_title">Nombre d\'articles chargés</string> <string name="pref_api_items_number_title">Nombre d\'articles chargés</string>
<string name="pref_hidden_tags">Tags Cachés</string>
<string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string> <string name="pref_general_infinite_loading_title">Charger plus d\'articles au scroll</string>
<string name="translation">Traduction</string> <string name="translation">Traduction</string>
<string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string> <string name="cant_open_invalid_url">Lurl de lélément nest pas valide. En attendant la résolution du problème, le lien ne s\'ouvrira pas.</string>
<string name="drawer_report_bug">Signaler un bug</string>
<string name="items_number_should_be_number">Le nombre d\'articles doit être un entier.</string> <string name="items_number_should_be_number">Le nombre d\'articles doit être un entier.</string>
<string name="reader_action_more">Lire plus</string>
<string name="reader_action_open">Ouvrir</string> <string name="reader_action_open">Ouvrir</string>
<string name="reader_action_share">Partager</string> <string name="reader_action_share">Partager</string>
<string name="pref_switch_actions_pager_scroll_on">Marquer les articles comme lus à la navigation dans le lecteur d\'article.</string> <string name="pref_switch_actions_pager_scroll_on">Marquer les articles comme lus à la navigation dans le lecteur d\'article.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Marquer tous les éléments comme lus ?</string> <string name="markall_dialog_message">Marquer tous les éléments comme lus ?</string>
<string name="pref_switch_actions_pager_scroll">Marquer comme lu à la navigation.</string> <string name="pref_switch_actions_pager_scroll">Marquer comme lu à la navigation.</string>
<string name="pref_switch_actions_pager_scroll_off">Ne pas marquer les articles comme lus à la navigation.</string> <string name="pref_switch_actions_pager_scroll_off">Ne pas marquer les articles comme lus à la navigation.</string>
<string name="drawer_item_hidden_tags">Tags Cachés</string>
<string name="unmark">Marquer l\'article comme non lu</string> <string name="unmark">Marquer l\'article comme non lu</string>
<string name="pref_header_offline">Hors ligne et cache</string> <string name="pref_header_offline">Hors ligne et cache</string>
<string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string> <string name="pref_switch_items_caching_off">Les articles ne seront pas enregistrés et l\'application ne sera pas utilisable hors ligne.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string> <string name="pref_switch_periodic_refresh_on">Articles seront périodiquement synchronisées</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Synchroniser uniquement lorsque le téléphone est en charge</string> <string name="pref_switch_refresh_when_charging">Synchroniser uniquement lorsque le téléphone est en charge</string>
<string name="loading_notification_title">Chargement </string> <string name="loading_notification_title">Chargement ...</string>
<string name="loading_notification_text">Selfoss synchronise vos articles</string> <string name="loading_notification_text">Selfoss synchronise vos articles</string>
<string name="notification_channel_sync">Notification de synchronisation</string> <string name="notification_channel_sync">Notification de synchronisation</string>
<string name="new_items_channel_sync">Notification de nouveaux articles</string> <string name="new_items_channel_sync">Notification de nouveaux articles</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Thème sombre</string> <string name="mode_dark">Thème sombre</string>
<string name="mode_system">Utiliser les paramètres système</string> <string name="mode_system">Utiliser les paramètres système</string>
<string name="mode_light">Thème clair</string> <string name="mode_light">Thème clair</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"É preciso iniciar sesión?"</string> <string name="withLoginSwitch">"É preciso iniciar sesión?"</string>
<string name="login_url_problem">"Ups! Pode que precises engadir un \"/\" o final da URL."</string> <string name="login_url_problem">"Ups! Pode que precises engadir un \"/\" o final da URL."</string>
<string name="prompt_login">"Nome de usuario"</string> <string name="prompt_login">"Nome de usuario"</string>
<string name="label_share">"Compartir"</string>
<string name="readAll">"Ler todos"</string> <string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string> <string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Axustes"</string> <string name="title_activity_settings">"Axustes"</string>
@ -33,8 +34,8 @@
<string name="addStringNoUrl">"Accede pra engadir fontes."</string> <string name="addStringNoUrl">"Accede pra engadir fontes."</string>
<string name="cant_get_sources">"Non se pode obter a lista de fontes."</string> <string name="cant_get_sources">"Non se pode obter a lista de fontes."</string>
<string name="cant_create_source">"Non se pode crear unha fonte."</string> <string name="cant_create_source">"Non se pode crear unha fonte."</string>
<string name="cant_get_spouts_no_network">"Non se pode obter a lista de spouts por mor dun erro de rede."</string> <string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
<string name="cant_get_spouts">"Non se pode obter a lista de spoits. Pode que haxa algún problema coa api."</string> <string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
<string name="form_not_complete">"O formulario non está completo"</string> <string name="form_not_complete">"O formulario non está completo"</string>
<string name="pref_header_links">"Ligazóns"</string> <string name="pref_header_links">"Ligazóns"</string>
<string name="issue_tracker_link">"Rastrexador de Incidencias"</string> <string name="issue_tracker_link">"Rastrexador de Incidencias"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">A altura das tarxetas axustarase ao seu contido</string> <string name="card_height_on">A altura das tarxetas axustarase ao seu contido</string>
<string name="card_height_off">A altura das tarxetas será fixa</string> <string name="card_height_off">A altura das tarxetas será fixa</string>
<string name="source_code">Código fonte</string> <string name="source_code">Código fonte</string>
<string name="filter_item_tags">Etiquetas</string> <string name="drawer_error_loading_tags">Produciuse un erro ao cargar as etiquetas…</string>
<string name="filter_item_sources">Fontes</string> <string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Cargando…</string>
<string name="menu_home_search">Procurar</string> <string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Non se puido eliminar a fonte…</string> <string name="can_delete_source">Non se puido eliminar a fonte…</string>
<string name="base_url_error">Houno unha incidencia ao tratar de comunicarse coa túa instancia de Selfoss. Se o problema persiste, prégolle que se poña en contacto conmigo.</string> <string name="base_url_error">Houno unha incidencia ao tratar de comunicarse coa túa instancia de Selfoss. Se o problema persiste, prégolle que se poña en contacto conmigo.</string>
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="default_theme">Predeterminado</string>
<string name="default_dark_theme">Predeterminado/Escuro</string>
<string name="pref_selfoss_category">API de Selfoss</string> <string name="pref_selfoss_category">API de Selfoss</string>
<string name="pref_api_items_number_title">Número de elementos cargados</string> <string name="pref_api_items_number_title">Número de elementos cargados</string>
<string name="pref_hidden_tags">Etiquetas ocultas</string>
<string name="pref_general_infinite_loading_title">Cargar máis artigos ao desprazarse</string> <string name="pref_general_infinite_loading_title">Cargar máis artigos ao desprazarse</string>
<string name="translation">Traducción</string> <string name="translation">Traducción</string>
<string name="cant_open_invalid_url">A URL do elemento non é válida. Estou tratando de solucionar isto pra que a aplicación non falle.</string> <string name="cant_open_invalid_url">A URL do elemento non é válida. Estou tratando de solucionar isto pra que a aplicación non falle.</string>
<string name="drawer_report_bug">Informar dun erro</string>
<string name="items_number_should_be_number">O número de elementos debería ser un enteiro.</string> <string name="items_number_should_be_number">O número de elementos debería ser un enteiro.</string>
<string name="reader_action_more">Ler máis</string>
<string name="reader_action_open">Abrir no navegador</string> <string name="reader_action_open">Abrir no navegador</string>
<string name="reader_action_share">Compartir</string> <string name="reader_action_share">Compartir</string>
<string name="pref_switch_actions_pager_scroll_on">Marcar artigos como lidos cando se desliza o dedo dun a outro.</string> <string name="pref_switch_actions_pager_scroll_on">Marcar artigos como lidos cando se desliza o dedo dun a outro.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Isto marcara todos os elementos como lidos.</string> <string name="markall_dialog_message">Isto marcara todos os elementos como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar artigos como lidos ao deslizar co dedo cara os lados</string> <string name="pref_switch_actions_pager_scroll">Marcar artigos como lidos ao deslizar co dedo cara os lados</string>
<string name="pref_switch_actions_pager_scroll_off">Non marcar artigos como lidos ao deslizar co dedo cara os lados.</string> <string name="pref_switch_actions_pager_scroll_off">Non marcar artigos como lidos ao deslizar co dedo cara os lados.</string>
<string name="drawer_item_hidden_tags">Etiquetas ocultas</string>
<string name="unmark">Marcar artículo como non lido</string> <string name="unmark">Marcar artículo como non lido</string>
<string name="pref_header_offline">Sen conexión e caché</string> <string name="pref_header_offline">Sen conexión e caché</string>
<string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string> <string name="pref_switch_items_caching_off">Os artigos non se gardaran na memoria do dispositivo e non se poderá utilizar a aplicación sen conexión.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string> <string name="pref_switch_periodic_refresh_on">Os artigos sincronizaranse periódicamente</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalo de sincronización (>= 15 minutos)]]></string>
<string name="pref_switch_refresh_when_charging">Só refrescar cando o teléfono se está a cargar</string> <string name="pref_switch_refresh_when_charging">Só refrescar cando o teléfono se está a cargar</string>
<string name="loading_notification_title">Cargando</string> <string name="loading_notification_title">Cargando...</string>
<string name="loading_notification_text">Selfoss está sincronizando os teus ar tigos</string> <string name="loading_notification_text">Selfoss está sincronizando os teus ar tigos</string>
<string name="notification_channel_sync">Notificación de sincronización</string> <string name="notification_channel_sync">Notificación de sincronización</string>
<string name="new_items_channel_sync">Notificación de actualizacións</string> <string name="new_items_channel_sync">Notificación de actualizacións</string>
@ -116,19 +128,10 @@
<string name="reader_static_bar_on">A barra inferior mostrarase sempre</string> <string name="reader_static_bar_on">A barra inferior mostrarase sempre</string>
<string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string> <string name="reader_static_bar_off">A barra inferior pode mostrarse a través do botón flotante</string>
<string name="remove_source">Eliminar fonte</string> <string name="remove_source">Eliminar fonte</string>
<string name="pref_theme_title">Modo Claro/Escuro</string> <string name="pref_theme_title">Light/Dark mode</string>
<string name="mode_dark">Modo escuro</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Seguir axustes do sistema</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Modo claro</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[O envío de informes de erros está habilitado. Pode deshabilitarse dende a páxina de axustes. Ten en conta que os informes de erros son esenciais para o desenvolvemento da aplicación.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">Ocurriu un erro. Enviando os detalles o desenvolvedor.</string>
<string name="pref_switch_disable_acra">"Deshabilitar o reporte automático de erros. "</string>
<string name="menu_home_filter">Filtros</string>
<string name="application_selfoss_only">Esta aplicación só funciona cunha instancia de Selfoss, e con ningún outro filtro RSS.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Harus masuk?"</string> <string name="withLoginSwitch">"Harus masuk?"</string>
<string name="login_url_problem">"Ups. Anda mungkin harus menambahkan \"/\" di akhir url."</string> <string name="login_url_problem">"Ups. Anda mungkin harus menambahkan \"/\" di akhir url."</string>
<string name="prompt_login">"Nama pengguna"</string> <string name="prompt_login">"Nama pengguna"</string>
<string name="label_share">"Bagikan"</string>
<string name="readAll">"Baca semua"</string> <string name="readAll">"Baca semua"</string>
<string name="action_disconnect">"Putuskan sambungan"</string> <string name="action_disconnect">"Putuskan sambungan"</string>
<string name="title_activity_settings">"Pengaturan"</string> <string name="title_activity_settings">"Pengaturan"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Tinggi kartu akan disesuaikan dengan konten</string> <string name="card_height_on">Tinggi kartu akan disesuaikan dengan konten</string>
<string name="card_height_off">Ukuran kartu akan tetap</string> <string name="card_height_off">Ukuran kartu akan tetap</string>
<string name="source_code">Kode sumber</string> <string name="source_code">Kode sumber</string>
<string name="filter_item_tags">Tag</string> <string name="drawer_error_loading_tags">Kesalahan saat memuat tag…</string>
<string name="filter_item_sources">Sumber</string> <string name="drawer_item_filters">Filter</string>
<string name="drawer_action_clear">kosongkan</string>
<string name="drawer_item_tags">Tag</string>
<string name="drawer_item_sources">Sumber</string>
<string name="drawer_action_edit">suntung</string>
<string name="drawer_loading">Memuat …</string>
<string name="menu_home_search">Cari</string> <string name="menu_home_search">Cari</string>
<string name="can_delete_source">Tidak dapat menghapus sumber…</string> <string name="can_delete_source">Tidak dapat menghapus sumber…</string>
<string name="base_url_error">Ada masalah saat berkomunikasi dengan Selfoss Anda. Jika masalah berlanjut, tolong hubungi saya.</string> <string name="base_url_error">Ada masalah saat berkomunikasi dengan Selfoss Anda. Jika masalah berlanjut, tolong hubungi saya.</string>
<string name="pref_header_theme">Tema</string> <string name="pref_header_theme">Tema</string>
<string name="default_theme">Bawaan</string>
<string name="default_dark_theme">Bawaan/Gelap</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Item nomor dimuat</string> <string name="pref_api_items_number_title">Item nomor dimuat</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Muat lebih banyak artikel saat membalik halaman</string> <string name="pref_general_infinite_loading_title">Muat lebih banyak artikel saat membalik halaman</string>
<string name="translation">Terjemahan</string> <string name="translation">Terjemahan</string>
<string name="cant_open_invalid_url">Alamat tautan proyek tidak valid. Saya mencoba memecahkan masalah ini untuk menghindari aplikasi berhenti.</string> <string name="cant_open_invalid_url">Alamat tautan proyek tidak valid. Saya mencoba memecahkan masalah ini untuk menghindari aplikasi berhenti.</string>
<string name="drawer_report_bug">Laporkan bug</string>
<string name="items_number_should_be_number">Jumlah item harus berupa bilangan bulat.</string> <string name="items_number_should_be_number">Jumlah item harus berupa bilangan bulat.</string>
<string name="reader_action_more">Baca lebih lanjut</string>
<string name="reader_action_open">Buka di peramban</string> <string name="reader_action_open">Buka di peramban</string>
<string name="reader_action_share">Bagikan</string> <string name="reader_action_share">Bagikan</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -90,14 +102,14 @@
<string name="pref_switch_items_caching">Save items for offline use</string> <string name="pref_switch_items_caching">Save items for offline use</string>
<string name="pref_switch_update_sources">Check for new sources and tags</string> <string name="pref_switch_update_sources">Check for new sources and tags</string>
<string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string> <string name="pref_switch_update_sources_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
<string name="network_connectivity_lost">"Koneksi jaringan hilang"</string> <string name="network_connectivity_lost">"Network connection lost"</string>
<string name="network_connectivity_retrieved">"Network connection is now available"</string> <string name="network_connectivity_retrieved">"Network connection is now available"</string>
<string name="pref_switch_periodic_refresh">Sync articles</string> <string name="pref_switch_periodic_refresh">Sync articles</string>
<string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string> <string name="pref_switch_periodic_refresh_off">Articles will not be synced in the background</string>
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"È richiesto l'accesso?"</string> <string name="withLoginSwitch">"È richiesto l'accesso?"</string>
<string name="login_url_problem">"Oops. Potrebbe essere necessario aggiungere un \"/\" alla fine dell'url."</string> <string name="login_url_problem">"Oops. Potrebbe essere necessario aggiungere un \"/\" alla fine dell'url."</string>
<string name="prompt_login">"Nome utente"</string> <string name="prompt_login">"Nome utente"</string>
<string name="label_share">"Condividi"</string>
<string name="readAll">"Segna tutte come lette"</string> <string name="readAll">"Segna tutte come lette"</string>
<string name="action_disconnect">"Scollegati"</string> <string name="action_disconnect">"Scollegati"</string>
<string name="title_activity_settings">"Impostazioni"</string> <string name="title_activity_settings">"Impostazioni"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Codice sorgente</string> <string name="source_code">Codice sorgente</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Errore nel caricamento dei tag…</string>
<string name="filter_item_sources">Fonti</string> <string name="drawer_item_filters">Filtri</string>
<string name="drawer_action_clear">cancella</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Fonti</string>
<string name="drawer_action_edit">modifica</string>
<string name="drawer_loading">Caricamento…</string>
<string name="menu_home_search">Cerca</string> <string name="menu_home_search">Cerca</string>
<string name="can_delete_source">Non è possibile eliminare la fonte…</string> <string name="can_delete_source">Non è possibile eliminare la fonte…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Temi</string> <string name="pref_header_theme">Temi</string>
<string name="default_theme">Predefinito</string>
<string name="default_dark_theme">Predefinito (Scuro)</string>
<string name="pref_selfoss_category">Api di Selfoss</string> <string name="pref_selfoss_category">Api di Selfoss</string>
<string name="pref_api_items_number_title">Numero di elementi caricati</string> <string name="pref_api_items_number_title">Numero di elementi caricati</string>
<string name="pref_hidden_tags">Tag nascosti</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Traduzioni</string> <string name="translation">Traduzioni</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Segnala un bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Segna come non letto</string> <string name="unmark">Segna come non letto</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"로그인이 필요합니까?"</string> <string name="withLoginSwitch">"로그인이 필요합니까?"</string>
<string name="login_url_problem">"죄송합니다. Url의 끝에 \"/\"를 추가할 필요가 있습니다."</string> <string name="login_url_problem">"죄송합니다. Url의 끝에 \"/\"를 추가할 필요가 있습니다."</string>
<string name="prompt_login">"사용자 이름"</string> <string name="prompt_login">"사용자 이름"</string>
<string name="label_share">"공유"</string>
<string name="readAll">"모두 읽기"</string> <string name="readAll">"모두 읽기"</string>
<string name="action_disconnect">"연결 해제"</string> <string name="action_disconnect">"연결 해제"</string>
<string name="title_activity_settings">"설정"</string> <string name="title_activity_settings">"설정"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="filter_item_sources">Sources</string> <string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string> <string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string> <string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pref_switch_enable_analytics">Enable analytics</string>
</resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Authenticatie vereist?"</string> <string name="withLoginSwitch">"Authenticatie vereist?"</string>
<string name="login_url_problem">"Oeps, ben je soms de \"/\" vergeten aan het eind?"</string> <string name="login_url_problem">"Oeps, ben je soms de \"/\" vergeten aan het eind?"</string>
<string name="prompt_login">"Gebruikersnaam"</string> <string name="prompt_login">"Gebruikersnaam"</string>
<string name="label_share">"Delen"</string>
<string name="readAll">"Alles lezen"</string> <string name="readAll">"Alles lezen"</string>
<string name="action_disconnect">"Verbinding verbreken"</string> <string name="action_disconnect">"Verbinding verbreken"</string>
<string name="title_activity_settings">"Instellingen"</string> <string name="title_activity_settings">"Instellingen"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Hoogte aanpassen aan de hand van kaartinhoud</string> <string name="card_height_on">Hoogte aanpassen aan de hand van kaartinhoud</string>
<string name="card_height_off">Vaste hoogte</string> <string name="card_height_off">Vaste hoogte</string>
<string name="source_code">Broncode</string> <string name="source_code">Broncode</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Fout bij het laden van tags…</string>
<string name="filter_item_sources">Bronnen</string> <string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">wissen</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Bronnen</string>
<string name="drawer_action_edit">bewerken</string>
<string name="drawer_loading">Bezig met laden …</string>
<string name="menu_home_search">Zoeken</string> <string name="menu_home_search">Zoeken</string>
<string name="can_delete_source">Kan de bron niet verwijderen…</string> <string name="can_delete_source">Kan de bron niet verwijderen…</string>
<string name="base_url_error">Er was een probleem bij het communiceren met uw Selfoss Instance. Als het probleem blijft, neem dan contact met mij op.</string> <string name="base_url_error">Er was een probleem bij het communiceren met uw Selfoss Instance. Als het probleem blijft, neem dan contact met mij op.</string>
<string name="pref_header_theme">Thema \'s</string> <string name="pref_header_theme">Thema \'s</string>
<string name="default_theme">Standaard</string>
<string name="default_dark_theme">Standaard/Donker</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Geladen items nummer</string> <string name="pref_api_items_number_title">Geladen items nummer</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Laad meer artikelen door te bladeren</string> <string name="pref_general_infinite_loading_title">Laad meer artikelen door te bladeren</string>
<string name="translation">Vertaling</string> <string name="translation">Vertaling</string>
<string name="cant_open_invalid_url">De URL is ongeldig. Ik probeer dit probleem op te lossen, zodat de toepassing niet wordt afgesloten.</string> <string name="cant_open_invalid_url">De URL is ongeldig. Ik probeer dit probleem op te lossen, zodat de toepassing niet wordt afgesloten.</string>
<string name="drawer_report_bug">Een fout melden</string>
<string name="items_number_should_be_number">Het aantal items moet een geheel getal zijn.</string> <string name="items_number_should_be_number">Het aantal items moet een geheel getal zijn.</string>
<string name="reader_action_more">Lees meer</string>
<string name="reader_action_open">Openen in browser</string> <string name="reader_action_open">Openen in browser</string>
<string name="reader_action_share">Delen</string> <string name="reader_action_share">Delen</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"É necessário o login ?"</string> <string name="withLoginSwitch">"É necessário o login ?"</string>
<string name="login_url_problem">"Oops. Talvez você precise adicionar uma \"/\" no final da url."</string> <string name="login_url_problem">"Oops. Talvez você precise adicionar uma \"/\" no final da url."</string>
<string name="prompt_login">"Usuário"</string> <string name="prompt_login">"Usuário"</string>
<string name="label_share">"Compartilhar"</string>
<string name="readAll">"Ler todos"</string> <string name="readAll">"Ler todos"</string>
<string name="action_disconnect">"Desconectar"</string> <string name="action_disconnect">"Desconectar"</string>
<string name="title_activity_settings">"Configurações"</string> <string name="title_activity_settings">"Configurações"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Cards com altura ajustáveis de acordo com o conteúdo</string> <string name="card_height_on">Cards com altura ajustáveis de acordo com o conteúdo</string>
<string name="card_height_off">Cards com altura de tamanho fixo</string> <string name="card_height_off">Cards com altura de tamanho fixo</string>
<string name="source_code">Código fonte</string> <string name="source_code">Código fonte</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Erro ao carregar as tags…</string>
<string name="filter_item_sources">Fontes</string> <string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">Carregando …</string>
<string name="menu_home_search">Procurar</string> <string name="menu_home_search">Procurar</string>
<string name="can_delete_source">Não foi possível apagar a fonte…</string> <string name="can_delete_source">Não foi possível apagar a fonte…</string>
<string name="base_url_error">Houve um problema ao tentar se comunicar com o seu Selfoss. Se o problema persistir, entre em contato comigo.</string> <string name="base_url_error">Houve um problema ao tentar se comunicar com o seu Selfoss. Se o problema persistir, entre em contato comigo.</string>
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="default_theme">Padrão</string>
<string name="default_dark_theme">Padrão/Escuro</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Quantidade de itens carregados</string> <string name="pref_api_items_number_title">Quantidade de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string> <string name="pref_general_infinite_loading_title">Carregar mais artigos ao realizar o scroll</string>
<string name="translation">Traduções</string> <string name="translation">Traduções</string>
<string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string> <string name="cant_open_invalid_url">A url está inválida. Estou tentando resolver esse problema para que o aplicativo não encerre.</string>
<string name="drawer_report_bug">Reportar erro</string>
<string name="items_number_should_be_number">O número dos itens deve ser um número inteiro.</string> <string name="items_number_should_be_number">O número dos itens deve ser um número inteiro.</string>
<string name="reader_action_more">Leia mais</string>
<string name="reader_action_open">Abrir no navegador</string> <string name="reader_action_open">Abrir no navegador</string>
<string name="reader_action_share">Compartilhar</string> <string name="reader_action_share">Compartilhar</string>
<string name="pref_switch_actions_pager_scroll_on">Se esta configuração estiver ativada, os artigos serão marcados como lidos ao deslizar para a esquerda e para a direita no leitor do artigo.</string> <string name="pref_switch_actions_pager_scroll_on">Se esta configuração estiver ativada, os artigos serão marcados como lidos ao deslizar para a esquerda e para a direita no leitor do artigo.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">Isso marcará todos os itens como lidos.</string> <string name="markall_dialog_message">Isso marcará todos os itens como lidos.</string>
<string name="pref_switch_actions_pager_scroll">Marcar Como Lida ao Abrir</string> <string name="pref_switch_actions_pager_scroll">Marcar Como Lida ao Abrir</string>
<string name="pref_switch_actions_pager_scroll_off">Não marca artigos como lido quando abrir.</string> <string name="pref_switch_actions_pager_scroll_off">Não marca artigos como lido quando abrir.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"É necessário fazer login?"</string> <string name="withLoginSwitch">"É necessário fazer login?"</string>
<string name="login_url_problem">"Uups. Você pode precisar adicionar uma \"/\" no final da url."</string> <string name="login_url_problem">"Uups. Você pode precisar adicionar uma \"/\" no final da url."</string>
<string name="prompt_login">"Nome do usuário"</string> <string name="prompt_login">"Nome do usuário"</string>
<string name="label_share">"Compartilhar"</string>
<string name="readAll">"Ler tudo"</string> <string name="readAll">"Ler tudo"</string>
<string name="action_disconnect">"Desligar"</string> <string name="action_disconnect">"Desligar"</string>
<string name="title_activity_settings">"Configurações"</string> <string name="title_activity_settings">"Configurações"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Altura de cartas irá ajustar ao seu conteúdo</string> <string name="card_height_on">Altura de cartas irá ajustar ao seu conteúdo</string>
<string name="card_height_off">Altura do cartão será corrigida</string> <string name="card_height_off">Altura do cartão será corrigida</string>
<string name="source_code">Código fonte</string> <string name="source_code">Código fonte</string>
<string name="filter_item_tags">Etiquetas</string> <string name="drawer_error_loading_tags">Erro ao carregar etiquetas…</string>
<string name="filter_item_sources">Fontes</string> <string name="drawer_item_filters">Filtros</string>
<string name="drawer_action_clear">limpar</string>
<string name="drawer_item_tags">Etiquetas</string>
<string name="drawer_item_sources">Fontes</string>
<string name="drawer_action_edit">editar</string>
<string name="drawer_loading">A carregar…</string>
<string name="menu_home_search">Buscar</string> <string name="menu_home_search">Buscar</string>
<string name="can_delete_source">Não é possível excluir a fonte…</string> <string name="can_delete_source">Não é possível excluir a fonte…</string>
<string name="base_url_error">Houve um problema ao tentar se comunicar com sua instância de Selfoss. Se o problema persistir, por favor entre em contato comigo.</string> <string name="base_url_error">Houve um problema ao tentar se comunicar com sua instância de Selfoss. Se o problema persistir, por favor entre em contato comigo.</string>
<string name="pref_header_theme">Temas</string> <string name="pref_header_theme">Temas</string>
<string name="default_theme">Predefinição</string>
<string name="default_dark_theme">Padrão/escuro</string>
<string name="pref_selfoss_category">Api de Selfoss</string> <string name="pref_selfoss_category">Api de Selfoss</string>
<string name="pref_api_items_number_title">Número de itens carregados</string> <string name="pref_api_items_number_title">Número de itens carregados</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Carregar mais artigos no pergaminho</string> <string name="pref_general_infinite_loading_title">Carregar mais artigos no pergaminho</string>
<string name="translation">Tradução</string> <string name="translation">Tradução</string>
<string name="cant_open_invalid_url">A url do item é inválido. Eu estou olhando para resolver esta questão, para que o app não vai falhar.</string> <string name="cant_open_invalid_url">A url do item é inválido. Eu estou olhando para resolver esta questão, para que o app não vai falhar.</string>
<string name="drawer_report_bug">Reportar falha</string>
<string name="items_number_should_be_number">O número de itens deve ser um número inteiro.</string> <string name="items_number_should_be_number">O número de itens deve ser um número inteiro.</string>
<string name="reader_action_more">Ler mais</string>
<string name="reader_action_open">Abrir no browser</string> <string name="reader_action_open">Abrir no browser</string>
<string name="reader_action_share">Compartilhar</string> <string name="reader_action_share">Compartilhar</string>
<string name="pref_switch_actions_pager_scroll_on">Artigos de marca como lida quando passar entre artigos.</string> <string name="pref_switch_actions_pager_scroll_on">Artigos de marca como lida quando passar entre artigos.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Login required ?"</string> <string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> <string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"පරිශීලක නාමය"</string> <string name="prompt_login">"පරිශීලක නාමය"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string> <string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string> <string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"සැකසුම්"</string> <string name="title_activity_settings">"සැකසුම්"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="filter_item_sources">Sources</string> <string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string> <string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string> <string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"Kullanıcı Girişi Gerekli?"</string> <string name="withLoginSwitch">"Kullanıcı Girişi Gerekli?"</string>
<string name="login_url_problem">"Oops. Url'nin sonuna \"/\" eklemek gerekebilir."</string> <string name="login_url_problem">"Oops. Url'nin sonuna \"/\" eklemek gerekebilir."</string>
<string name="prompt_login">"Kullanıcı adı"</string> <string name="prompt_login">"Kullanıcı adı"</string>
<string name="label_share">"Paylaş"</string>
<string name="readAll">"Tümünü oku"</string> <string name="readAll">"Tümünü oku"</string>
<string name="action_disconnect">"Bağlantıyı kes"</string> <string name="action_disconnect">"Bağlantıyı kes"</string>
<string name="title_activity_settings">"Ayarlar"</string> <string name="title_activity_settings">"Ayarlar"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">Kartların yüksekliği içeriğine göre ayarlanır</string> <string name="card_height_on">Kartların yüksekliği içeriğine göre ayarlanır</string>
<string name="card_height_off">Kart yüksekliği sabit olacak</string> <string name="card_height_off">Kart yüksekliği sabit olacak</string>
<string name="source_code">Kaynak kodu</string> <string name="source_code">Kaynak kodu</string>
<string name="filter_item_tags">Etiketler</string> <string name="drawer_error_loading_tags">Etiketler yükleme hatası</string>
<string name="filter_item_sources">Kaynaklar</string> <string name="drawer_item_filters">Filtreler</string>
<string name="drawer_action_clear">temizle</string>
<string name="drawer_item_tags">Etiketler</string>
<string name="drawer_item_sources">Kaynaklar</string>
<string name="drawer_action_edit">düzenle</string>
<string name="drawer_loading">Yükleniyor…</string>
<string name="menu_home_search">Ara</string> <string name="menu_home_search">Ara</string>
<string name="can_delete_source">Kaynak silinemiyor…</string> <string name="can_delete_source">Kaynak silinemiyor…</string>
<string name="base_url_error">Selfoss Örneğinizle iletişim kurmaya çalışırken bir sorun oluştu. Sorun devam ederse, lütfen benimle iletişime geçin.</string> <string name="base_url_error">Selfoss Örneğinizle iletişim kurmaya çalışırken bir sorun oluştu. Sorun devam ederse, lütfen benimle iletişime geçin.</string>
<string name="pref_header_theme">Temalar</string> <string name="pref_header_theme">Temalar</string>
<string name="default_theme">Varsayılan</string>
<string name="default_dark_theme">Varsayılan/koyu</string>
<string name="pref_selfoss_category">Selfoss Uygulaması</string> <string name="pref_selfoss_category">Selfoss Uygulaması</string>
<string name="pref_api_items_number_title">Yüklenen öğe numarası</string> <string name="pref_api_items_number_title">Yüklenen öğe numarası</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Kaydırma üzerine daha fazla makale yükleyin</string> <string name="pref_general_infinite_loading_title">Kaydırma üzerine daha fazla makale yükleyin</string>
<string name="translation">Çeviri</string> <string name="translation">Çeviri</string>
<string name="cant_open_invalid_url">Öğe url geçersiz. Uygulama çökmeyeceği için bu sorunu çözmeye çalışıyorum.</string> <string name="cant_open_invalid_url">Öğe url geçersiz. Uygulama çökmeyeceği için bu sorunu çözmeye çalışıyorum.</string>
<string name="drawer_report_bug">Hata bildir</string>
<string name="items_number_should_be_number">Öğe sayısı bir tamsayı olmalıdır.</string> <string name="items_number_should_be_number">Öğe sayısı bir tamsayı olmalıdır.</string>
<string name="reader_action_more">Daha fazlasını görüntüle</string>
<string name="reader_action_open">Tarayıcıda aç</string> <string name="reader_action_open">Tarayıcıda aç</string>
<string name="reader_action_share">Paylaş</string> <string name="reader_action_share">Paylaş</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"需要登录?"</string> <string name="withLoginSwitch">"需要登录?"</string>
<string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string> <string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string>
<string name="prompt_login">"用户名"</string> <string name="prompt_login">"用户名"</string>
<string name="label_share">"分享"</string>
<string name="readAll">"全部阅读"</string> <string name="readAll">"全部阅读"</string>
<string name="action_disconnect">"断开连接"</string> <string name="action_disconnect">"断开连接"</string>
<string name="title_activity_settings">"设置"</string> <string name="title_activity_settings">"设置"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">卡片高度将根据内容调整</string> <string name="card_height_on">卡片高度将根据内容调整</string>
<string name="card_height_off">卡片高度将被固定</string> <string name="card_height_off">卡片高度将被固定</string>
<string name="source_code">源代码</string> <string name="source_code">源代码</string>
<string name="filter_item_tags">标签</string> <string name="drawer_error_loading_tags">加载标记时出错..。</string>
<string name="filter_item_sources">来源</string> <string name="drawer_item_filters">搜索条件</string>
<string name="drawer_action_clear">清空</string>
<string name="drawer_item_tags">标签</string>
<string name="drawer_item_sources">来源</string>
<string name="drawer_action_edit">编辑</string>
<string name="drawer_loading">正在载入…</string>
<string name="menu_home_search">搜索</string> <string name="menu_home_search">搜索</string>
<string name="can_delete_source">无法删除数据源…</string> <string name="can_delete_source">无法删除数据源…</string>
<string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string> <string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string>
<string name="pref_header_theme">主题</string> <string name="pref_header_theme">主题</string>
<string name="default_theme">默认​​​​​</string>
<string name="default_dark_theme">默认值/暗</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string> <string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string> <string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">隐藏标签</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string> <string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string> <string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string> <string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
<string name="drawer_report_bug">报告错误</string>
<string name="items_number_should_be_number">项目数应为整数。</string> <string name="items_number_should_be_number">项目数应为整数。</string>
<string name="reader_action_more">阅读更多</string>
<string name="reader_action_open">在浏览器中打开</string> <string name="reader_action_open">在浏览器中打开</string>
<string name="reader_action_share">分享</string> <string name="reader_action_share">分享</string>
<string name="pref_switch_actions_pager_scroll_on">切换文章时将文章标记为已读。</string> <string name="pref_switch_actions_pager_scroll_on">切换文章时将文章标记为已读。</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">这将标记所有项目为已读。</string> <string name="markall_dialog_message">这将标记所有项目为已读。</string>
<string name="pref_switch_actions_pager_scroll">滑动时标为已读</string> <string name="pref_switch_actions_pager_scroll">滑动时标为已读</string>
<string name="pref_switch_actions_pager_scroll_off">滑动时不标记文章为已读</string> <string name="pref_switch_actions_pager_scroll_off">滑动时不标记文章为已读</string>
<string name="drawer_item_hidden_tags">隐藏标签</string>
<string name="unmark">标记条目为未读</string> <string name="unmark">标记条目为未读</string>
<string name="pref_header_offline">离线和缓存</string> <string name="pref_header_offline">离线和缓存</string>
<string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string> <string name="pref_switch_items_caching_off">文章不会被保存到设备内存,应用程序在离线时将无法阅读它们</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">将定期同步文章</string> <string name="pref_switch_periodic_refresh_on">将定期同步文章</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[同步间隔 (>= 15分钟)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[同步间隔 (>= 15分钟)]]></string>
<string name="pref_switch_refresh_when_charging">仅在手机充电时刷新</string> <string name="pref_switch_refresh_when_charging">仅在手机充电时刷新</string>
<string name="loading_notification_title">加载中</string> <string name="loading_notification_title">加载中...</string>
<string name="loading_notification_text">Selfoss 正在同步您的文章</string> <string name="loading_notification_text">Selfoss 正在同步您的文章</string>
<string name="notification_channel_sync">同步通知</string> <string name="notification_channel_sync">同步通知</string>
<string name="new_items_channel_sync">新条目通知</string> <string name="new_items_channel_sync">新条目通知</string>
@ -120,15 +132,6 @@
<string name="mode_dark">深色模式</string> <string name="mode_dark">深色模式</string>
<string name="mode_system">遵循系统设置</string> <string name="mode_system">遵循系统设置</string>
<string name="mode_light">浅色模式</string> <string name="mode_light">浅色模式</string>
<string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string>
<string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
<string name="menu_home_filter">筛选器</string>
<string name="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string>
<string name="menu_home_sources"></string>
<string name="update_source">更新源</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -10,6 +10,7 @@
<string name="withLoginSwitch">"需要登入?"</string> <string name="withLoginSwitch">"需要登入?"</string>
<string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string> <string name="login_url_problem">"哎呀。您可能需要在网址的末尾添加一个 \"/\"。"</string>
<string name="prompt_login">"使用者名稱"</string> <string name="prompt_login">"使用者名稱"</string>
<string name="label_share">"分享"</string>
<string name="readAll">"全部阅读"</string> <string name="readAll">"全部阅读"</string>
<string name="action_disconnect">"断开连接"</string> <string name="action_disconnect">"断开连接"</string>
<string name="title_activity_settings">"设置"</string> <string name="title_activity_settings">"设置"</string>
@ -61,18 +62,28 @@
<string name="card_height_on">卡片高度将根据内容调整</string> <string name="card_height_on">卡片高度将根据内容调整</string>
<string name="card_height_off">卡片高度将被固定</string> <string name="card_height_off">卡片高度将被固定</string>
<string name="source_code">源代码</string> <string name="source_code">源代码</string>
<string name="filter_item_tags">标签</string> <string name="drawer_error_loading_tags">加载标记时出错..。</string>
<string name="filter_item_sources">来源</string> <string name="drawer_item_filters">搜索条件</string>
<string name="drawer_action_clear">清空</string>
<string name="drawer_item_tags">标签</string>
<string name="drawer_item_sources">来源</string>
<string name="drawer_action_edit">编辑</string>
<string name="drawer_loading">正在载入…</string>
<string name="menu_home_search">搜索</string> <string name="menu_home_search">搜索</string>
<string name="can_delete_source">无法删除数据源…</string> <string name="can_delete_source">无法删除数据源…</string>
<string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string> <string name="base_url_error">与您的 Selfoss 通信时出现问题。如果问题一直存在,请与我联系。</string>
<string name="pref_header_theme">主题</string> <string name="pref_header_theme">主题</string>
<string name="default_theme">默认​​​​​</string>
<string name="default_dark_theme">默认值/暗</string>
<string name="pref_selfoss_category">塞尔福斯 Api</string> <string name="pref_selfoss_category">塞尔福斯 Api</string>
<string name="pref_api_items_number_title">已加载项目编号</string> <string name="pref_api_items_number_title">已加载项目编号</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">翻页时载入更多文章</string> <string name="pref_general_infinite_loading_title">翻页时载入更多文章</string>
<string name="translation">翻译</string> <string name="translation">翻译</string>
<string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string> <string name="cant_open_invalid_url">项目链接地址无效。我正在设法解决这个问题,以避免应用程序崩溃。</string>
<string name="drawer_report_bug">报告错误</string>
<string name="items_number_should_be_number">项目数应为整数。</string> <string name="items_number_should_be_number">项目数应为整数。</string>
<string name="reader_action_more">阅读更多</string>
<string name="reader_action_open">在浏览器中打开</string> <string name="reader_action_open">在浏览器中打开</string>
<string name="reader_action_share">分享</string> <string name="reader_action_share">分享</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -83,6 +94,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -97,7 +109,7 @@
<string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string> <string name="pref_switch_periodic_refresh_on">Articles will periodically be synced</string>
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string> <string name="pref_periodic_refresh_minutes_title"><![CDATA[Sync interval ( >= 15 minutes)]]></string>
<string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string> <string name="pref_switch_refresh_when_charging">Only refresh when phone is charging</string>
<string name="loading_notification_title">Loading </string> <string name="loading_notification_title">Loading ...</string>
<string name="loading_notification_text">Selfoss is syncing your articles</string> <string name="loading_notification_text">Selfoss is syncing your articles</string>
<string name="notification_channel_sync">Sync notification</string> <string name="notification_channel_sync">Sync notification</string>
<string name="new_items_channel_sync">New items notification</string> <string name="new_items_channel_sync">New items notification</string>
@ -120,15 +132,6 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
<string name="disable_ssl">Disable SSL</string>
</resources> </resources>

View File

@ -12,5 +12,4 @@
<color name="refresh_progress_2">@color/colorAccent</color> <color name="refresh_progress_2">@color/colorAccent</color>
<color name="refresh_progress_3">@color/pink</color> <color name="refresh_progress_3">@color/pink</color>
<color name="dark">#FF282828</color> <color name="dark">#FF282828</color>
<color name="transparent_dark_background">#33000000</color>
</resources> </resources>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>
MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=
</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>
MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK
</item>
</string-array>
</resources>

View File

@ -11,4 +11,22 @@
<item>2</item> <!--MODE_NIGHT_YES--> <item>2</item> <!--MODE_NIGHT_YES-->
<item>-1</item> <!--MODE_NIGHT_FOLLOW_SYSTEM--> <item>-1</item> <!--MODE_NIGHT_FOLLOW_SYSTEM-->
</string-array> </string-array>
<string-array name="Voice">
<item>Male</item>
<item>Female</item>
</string-array>
<string-array name="VoiceAlias">
<item>"usenglishmale"</item>
<item>"usenglishfemale"</item>
<item>"ukenglishmale"</item>
<item>"ukenglishfemale"</item>
<item>"eurfrenchmale"</item>
<item>"eurfrenchfemale"</item>
<item>"eurspanishmale"</item>
<item>"eurspanishfemale"</item>
<item>"euritalianmale"</item>
<item>"euritalianfemale"</item>
</string-array>
</resources> </resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/open_sans</item>
<item>@font/roboto</item>
<item>@font/source_code_pro_medium</item>
</array>
</resources>

View File

@ -6,10 +6,10 @@
<string name="error_invalid_password">"Password not long enough"</string> <string name="error_invalid_password">"Password not long enough"</string>
<string name="error_field_required">"Field required"</string> <string name="error_field_required">"Field required"</string>
<string name="prompt_url">"Url"</string> <string name="prompt_url">"Url"</string>
<string name="disable_ssl">"Disable SSL"</string>
<string name="withLoginSwitch">"Login required ?"</string> <string name="withLoginSwitch">"Login required ?"</string>
<string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string> <string name="login_url_problem">"Oops. You may need to add a \"/\" at the end of the url."</string>
<string name="prompt_login">"Username"</string> <string name="prompt_login">"Username"</string>
<string name="label_share">"Share"</string>
<string name="readAll">"Read all"</string> <string name="readAll">"Read all"</string>
<string name="action_disconnect">"Disconnect"</string> <string name="action_disconnect">"Disconnect"</string>
<string name="title_activity_settings">"Settings"</string> <string name="title_activity_settings">"Settings"</string>
@ -62,18 +62,29 @@
<string name="card_height_on">Cards height will adjust to its content</string> <string name="card_height_on">Cards height will adjust to its content</string>
<string name="card_height_off">Card height will be fixed</string> <string name="card_height_off">Card height will be fixed</string>
<string name="source_code">Source code</string> <string name="source_code">Source code</string>
<string name="filter_item_tags">Tags</string> <string name="drawer_error_loading_tags">Error loading tags…</string>
<string name="filter_item_sources">Sources</string> <string name="drawer_error_loading_sources">Error loading sources…</string>
<string name="drawer_item_filters">Filters</string>
<string name="drawer_action_clear">clear</string>
<string name="drawer_item_tags">Tags</string>
<string name="drawer_item_sources">Sources</string>
<string name="drawer_action_edit">edit</string>
<string name="drawer_loading">Loading …</string>
<string name="menu_home_search">Search</string> <string name="menu_home_search">Search</string>
<string name="can_delete_source">Can\'t delete the source…</string> <string name="can_delete_source">Can\'t delete the source…</string>
<string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string> <string name="base_url_error">There was an issue when trying to communicate with your Selfoss Instance. If the issue persists, please get in touch with me.</string>
<string name="pref_header_theme">Themes</string> <string name="pref_header_theme">Themes</string>
<string name="default_theme">Default</string>
<string name="default_dark_theme">Default/Dark</string>
<string name="pref_selfoss_category">Selfoss Api</string> <string name="pref_selfoss_category">Selfoss Api</string>
<string name="pref_api_items_number_title">Loaded items number</string> <string name="pref_api_items_number_title">Loaded items number</string>
<string name="pref_hidden_tags">Hidden Tags</string>
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string> <string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
<string name="translation">Translation</string> <string name="translation">Translation</string>
<string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string> <string name="cant_open_invalid_url">The item url is invalid. I\'m looking into solving this issue so the app won\'t crash.</string>
<string name="drawer_report_bug">Report a bug</string>
<string name="items_number_should_be_number">The items number should be an integer.</string> <string name="items_number_should_be_number">The items number should be an integer.</string>
<string name="reader_action_more">Read more</string>
<string name="reader_action_open">Open in browser</string> <string name="reader_action_open">Open in browser</string>
<string name="reader_action_share">Share</string> <string name="reader_action_share">Share</string>
<string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string> <string name="pref_switch_actions_pager_scroll_on">Mark articles as read when swiping between articles.</string>
@ -84,6 +95,7 @@
<string name="markall_dialog_message">This will mark all the items as read.</string> <string name="markall_dialog_message">This will mark all the items as read.</string>
<string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string> <string name="pref_switch_actions_pager_scroll">Mark as read on swipe</string>
<string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string> <string name="pref_switch_actions_pager_scroll_off">Don\'t mark articles as read when swiping.</string>
<string name="drawer_item_hidden_tags">Hidden Tags</string>
<string name="unmark">Mark item as unread</string> <string name="unmark">Mark item as unread</string>
<string name="pref_header_offline">Offline and cache</string> <string name="pref_header_offline">Offline and cache</string>
<string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string> <string name="pref_switch_items_caching_off">Articles won\'t be saved to the device memory, and the app won\'t be usable offline.</string>
@ -106,7 +118,7 @@
<string name="new_items_notification_text">%1$d new items loaded.</string> <string name="new_items_notification_text">%1$d new items loaded.</string>
<string name="pref_switch_notify_new_items">Notify on new items synced.</string> <string name="pref_switch_notify_new_items">Notify on new items synced.</string>
<string name="shortcut_offline">Offline</string> <string name="shortcut_offline">Offline</string>
<string name="pref_api_timeout">Api Timeout (seconds)</string> <string name="pref_api_timeout">Api Timeout</string>
<string name="pref_header_experimental">Experimental</string> <string name="pref_header_experimental">Experimental</string>
<string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string> <string name="webview_dialog_issue_message">Webview not available. Disabling the article viewer to avoid any future crashes. Will load articles inside of your browser from now on.</string>
<string name="webview_dialog_issue_title">Webview issue</string> <string name="webview_dialog_issue_title">Webview issue</string>
@ -124,14 +136,5 @@
<string name="mode_dark">Dark mode</string> <string name="mode_dark">Dark mode</string>
<string name="mode_system">Follow the system setting</string> <string name="mode_system">Follow the system setting</string>
<string name="mode_light">Light mode</string> <string name="mode_light">Light mode</string>
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string> <string name="pref_switch_enable_analytics">Enable analytics</string>
<string name="gdpr_dialog_message"><![CDATA[Crash reports sending is now enabled. It can be disabled from the settings page. Keep in mind that crash reports are essential for the app development.]]></string>
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
<string name="menu_home_filter">Filters</string>
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
<string name="menu_home_sources">Sources</string>
<string name="update_source">Update source</string>
<string name="confirm_disconnect_title">Disconnect ?</string>
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
</resources> </resources>

View File

@ -25,17 +25,7 @@
<item name="android:textColorPrimary">@color/white</item> <item name="android:textColorPrimary">@color/white</item>
<item name="android:textColorSecondary">@color/white</item> <item name="android:textColorSecondary">@color/white</item>
<item name="actionMenuTextColor">@color/white</item> <item name="actionMenuTextColor">@color/white</item>
<!--<item name="actionOverflowButtonStyle">@style/ActionButtonOverflowStyle</item>
<item name="drawerArrowStyle">@style/DrawerArrowStyle</item>-->
</style> </style>
<style name="Theme.AppCompat.ImageActivity" parent="NoBar">
<item name="android:windowBackground">@color/transparent_dark_background</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
<style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
</style>
</resources> </resources>

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:motion="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout">
<Transition
motion:constraintSetStart="@+id/start"
motion:constraintSetEnd="@+id/end"
motion:duration="500"
motion:motionInterpolator="linear">
<OnSwipe
motion:touchAnchorId="@+id/scrollView"
motion:touchAnchorSide="top"
motion:onTouchUp="autoCompleteToStart" />
</Transition>
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@id/scrollView"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintTop_toBottomOf="@+id/appBarLayout"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent">
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/scrollView"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginBottom="0dp"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent">
</Constraint>
</ConstraintSet>
</MotionScene>

View File

@ -1,6 +1,12 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:defaultValue="true"
app:iconSpaceReserved="false"
android:key="enable_analytics"
android:title="@string/pref_switch_enable_analytics" />
<EditTextPreference <EditTextPreference
android:inputType="number" android:inputType="number"
android:key="api_timeout" android:key="api_timeout"

View File

@ -14,6 +14,15 @@
android:title="@string/pref_api_items_number_title" android:title="@string/pref_api_items_number_title"
app:iconSpaceReserved="false"/> app:iconSpaceReserved="false"/>
<EditTextPreference
android:defaultValue=""
android:hint="@string/add_source_hint_tags"
android:key="hidden_tags"
android:selectAllOnFocus="true"
android:singleLine="true"
android:title="@string/pref_hidden_tags"
app:iconSpaceReserved="false"/>
<SwitchPreference <SwitchPreference
android:defaultValue="false" android:defaultValue="false"
android:key="infinite_loading" android:key="infinite_loading"

View File

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/title_activity_settings"> android:title="@string/title_activity_settings">
<Preference <Preference
@ -18,13 +17,9 @@
android:title="@string/pref_header_offline" android:title="@string/pref_header_offline"
android:icon="@drawable/ic_signal_wifi_off_black_24dp" /> android:icon="@drawable/ic_signal_wifi_off_black_24dp" />
<ListPreference <Preference
android:defaultValue="0" android:fragment="bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity$ThemePreferenceFragment"
android:entries="@array/ModeTitles"
android:entryValues="@array/ModeValues"
android:key="currentMode"
android:title="@string/pref_header_theme" android:title="@string/pref_header_theme"
app:useSimpleSummaryProvider="false"
android:icon="@drawable/ic_color_lens_black_24dp" /> android:icon="@drawable/ic_color_lens_black_24dp" />
<Preference <Preference
@ -37,18 +32,4 @@
android:title="@string/pref_header_experimental" android:title="@string/pref_header_experimental"
android:icon="@drawable/ic_widgets_black_24dp" /> android:icon="@drawable/ic_widgets_black_24dp" />
<SwitchPreference
android:defaultValue="false"
android:key="acra.disable"
android:title="@string/pref_switch_disable_acra"
android:icon="@drawable/ic_baseline_bug_report_24"/>
<Preference
android:key="action_about"
android:title="@string/action_about"
android:icon="@drawable/ic_info_outline_white_24dp" />
</PreferenceScreen> </PreferenceScreen>

View File

@ -8,59 +8,26 @@ import kotlinx.datetime.toInstant
import org.junit.Test import org.junit.Test
class DatesTest { class DatesTest {
private val newVersionDateVariant = "2022-12-24T17:00:08+00"
private val newVersionDate = "2013-04-07T13:43:00+01:00"
private val newVersionDate2 = "2013-04-07T13:43:00-01:00"
private val oldVersionDate = "2013-05-07 13:46:00"
private val oldVersionDateVariant = "2021-03-21 10:32:00.000000"
private val v3Date = "2013-04-07T13:43:00+01:00"
private val v4Date = "2013-04-07 13:43:00"
@Test @Test
fun new_version_date_should_be_parsed() { fun v3_date_should_be_parsed() {
val date = DateUtils.parseDate(newVersionDate) val date = DateUtils.parseDate(v3Date)
val expected = LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.of("UTC+1")) .toEpochMilliseconds()
assertEquals(date, expected)
}
@Test
fun v4_date_should_be_parsed() {
val date = DateUtils.parseDate(v4Date)
val expected = val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault()) LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds() .toEpochMilliseconds()
assertEquals(expected, date) assertEquals(date, expected)
}
@Test
fun new_version_date2_should_be_parsed() {
val date = DateUtils.parseDate(newVersionDate2)
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
} }
@Test
fun old_version_date_should_be_parsed() {
val date = DateUtils.parseDate(oldVersionDate)
val expected =
LocalDateTime(2013, 5, 7, 13, 46, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
@Test
fun old_version_variant_date_should_be_parsed() {
val date = DateUtils.parseDate(oldVersionDateVariant)
val expected =
LocalDateTime(2021, 3, 21, 10, 32, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
@Test
fun new_version_variant_date_should_be_parsed() {
val date = DateUtils.parseDate(newVersionDateVariant)
val expected =
LocalDateTime(2022, 12, 24, 17, 0, 8, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(expected, date)
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> { fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
return listOf( return listOf(
ITEM( ITEM(
@ -16,9 +17,8 @@ fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<I
icon = item.icon, icon = item.icon,
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags, tags = item.tags
author = item.author, )
),
) )
} }
@ -35,9 +35,8 @@ fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<S
icon = item.icon, icon = item.icon,
link = item.link, link = item.link,
sourcetitle = item.sourcetitle, sourcetitle = item.sourcetitle,
tags = item.tags.split(','), tags = item.tags.split(',')
author = item.author, )
),
) )
} }
@ -55,5 +54,4 @@ class FakeItemParameters {
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/" "https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società" var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing" var tags = "Chimica, Testing"
var author = "Someone important"
} }

View File

@ -1,24 +1,27 @@
buildscript { buildscript {
dependencies { dependencies {
// SqlDelight // SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.5") classpath("com.squareup.sqldelight:gradle-plugin:1.5.4")
} }
} }
plugins { plugins {
//trick: for the same plugin versions in all sub-modules //trick: for the same plugin versions in all sub-modules
id("com.android.application").version("8.1.2").apply(false) id("com.android.application").version("7.3.1").apply(false)
id("com.android.library").version("8.1.2").apply(false) id("com.android.library").version("7.3.1").apply(false)
id("org.jetbrains.kotlin.android").version("1.9.10").apply(false) kotlin("android").version("1.7.20").apply(false)
kotlin("multiplatform").version("1.9.10").apply(false) kotlin("multiplatform").version("1.7.20").apply(false)
id("org.sonarqube").version("3.4.0.2513").apply(false)
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false) id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
id("org.jetbrains.kotlinx.kover").version("0.6.1").apply(true)
} }
apply(plugin = "org.sonarqube")
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
jcenter()
maven { url = uri("https://www.jitpack.io") } maven { url = uri("https://www.jitpack.io") }
} }
} }
@ -27,7 +30,3 @@ allprojects {
tasks.register("clean", Delete::class) { tasks.register("clean", Delete::class) {
delete(rootProject.buildDir) delete(rootProject.buildDir)
} }
koverMerged {
enable()
}

View File

@ -68,9 +68,9 @@ redirect_from: "/ReaderforSelfoss-multiplatform/"
<div id="links"> <div id="links">
<a class="github-button" href="https://gitea.amine-bouabdallaoui.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a> <a class="github-button" href="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform" data-size="large" aria-label="Star aminecmi/readerforselfoss-multiplatform on GitHub">Star</a>
</div> </div>
<meta itemprop="url" content="https://gitea.amine-bouabdallaoui.fr/Louvorg/readerforselfoss-multiplatform"> <meta itemprop="url" content="https://gitea.amine-louveau.fr/Louvorg/readerforselfoss-multiplatform">
<meta itemprop="applicationCategory" content="News & Magazines"> <meta itemprop="applicationCategory" content="News & Magazines">
</div> </div>
</body> </body>

View File

@ -13,16 +13,24 @@
#Tue Mar 22 16:50:00 CET 2022 #Tue Mar 22 16:50:00 CET 2022
#Gradle #Gradle
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
#Kotlin #Kotlin
kotlin.code.style=official kotlin.code.style=official
#Android #Android
android.useAndroidX=true android.useAndroidX=true
kotlin.native.enableDependencyPropagation=false
#android.nonTransitiveRClass=true #android.nonTransitiveRClass=true
android.enableJetifier=true android.enableJetifier=true
android.nonTransitiveRClass=false
#MPP #MPP
kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.enableCInteropCommonization=true
kotlin.mpp.enableGranularSourceSetsMetadata=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.caching=true org.gradle.caching=true
ignoreGitVersion=false ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none kotlin.native.cacheKind.iosX64=none
pushCache=true

View File

@ -1,6 +1,6 @@
#Thu Jul 13 11:41:19 CEST 2023 #Wed Feb 09 17:05:19 CET 2022
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -1,3 +1,5 @@
val pushCache: String by settings
pluginManagement { pluginManagement {
repositories { repositories {
google() google()
@ -13,6 +15,17 @@ dependencyResolutionManagement {
} }
} }
buildCache {
remote<HttpBuildCache> {
url = uri("http://18.0.0.7:3071/cache/")
isAllowInsecureProtocol = true
isAllowUntrustedServer = true
isUseExpectContinue = true
isPush = (pushCache == "true")
}
}
rootProject.name = "ReaderForSelfossV2" rootProject.name = "ReaderForSelfossV2"
include(":androidApp") include(":androidApp")
include(":shared") include(":shared")

View File

@ -1,5 +1,3 @@
val ktorVersion = "2.3.2"
object SqlDelight { object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.4" const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "com.squareup.sqldelight:android-driver:1.5.4" const val android = "com.squareup.sqldelight:android-driver:1.5.4"
@ -11,12 +9,11 @@ plugins {
kotlin("multiplatform") kotlin("multiplatform")
id("com.android.library") id("com.android.library")
id("com.squareup.sqldelight") id("com.squareup.sqldelight")
kotlin("plugin.serialization") version "1.9.0" kotlin("plugin.serialization") version "1.4.10"
id("org.jetbrains.kotlinx.kover")
} }
kotlin { kotlin {
androidTarget() android()
listOf( listOf(
iosX64(), iosX64(),
@ -31,18 +28,16 @@ kotlin {
sourceSets { sourceSets {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
implementation("io.ktor:ktor-client-core:$ktorVersion") implementation("io.ktor:ktor-client-core:2.1.1")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") implementation("io.ktor:ktor-client-content-negotiation:2.1.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") implementation("io.ktor:ktor-serialization-kotlinx-json:2.1.1")
implementation("io.ktor:ktor-client-logging:$ktorVersion") implementation("io.ktor:ktor-client-logging:2.1.1")
implementation("io.ktor:ktor-client-auth:$ktorVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("io.ktor:ktor-client-cio:$ktorVersion") implementation("io.ktor:ktor-client-auth:2.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1") implementation("org.jsoup:jsoup:1.14.3")
implementation("org.jsoup:jsoup:1.15.4")
//Dependency Injection //Dependency Injection
implementation("org.kodein.di:kodein-di:7.14.0") implementation("org.kodein.di:kodein-di:7.12.0")
//Settings //Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC") implementation("com.russhwolf:multiplatform-settings-no-arg:1.0.0-RC")
@ -62,15 +57,14 @@ kotlin {
} }
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation("com.squareup.okhttp3:okhttp:4.11.0") implementation("io.ktor:ktor-client-okhttp:2.1.1")
implementation("io.ktor:ktor-client-okhttp:2.2.4")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Sql // Sql
implementation(SqlDelight.android) implementation(SqlDelight.android)
} }
} }
val androidUnitTest by getting { val androidTest by getting {
dependencies { dependencies {
implementation(kotlin("test-junit")) implementation(kotlin("test-junit"))
implementation("junit:junit:4.13.2") implementation("junit:junit:4.13.2")
@ -103,14 +97,15 @@ kotlin {
} }
android { android {
compileSdk = 34 compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig { defaultConfig {
minSdk = 25 minSdk = 21
targetSdk = 32
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_1_8
} }
namespace = "bou.amine.apps.readerforselfossv2" namespace = "bou.amine.apps.readerforselfossv2"
} }

View File

@ -1,23 +0,0 @@
package bou.amine.apps.readerforselfossv2.rest
import io.ktor.client.engine.cio.CIOEngineConfig
import java.security.cert.X509Certificate
import javax.net.ssl.X509TrustManager
class NaiveTrustManager : X509TrustManager {
override fun checkClientTrusted(
chain: Array<out X509Certificate>?,
authType: String?,
) {}
override fun checkServerTrusted(
chain: Array<out X509Certificate>?,
authType: String?,
) {}
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
}
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
config.https.trustManager = NaiveTrustManager()
}

View File

@ -1,45 +1,28 @@
package bou.amine.apps.readerforselfossv2.utils package bou.amine.apps.readerforselfossv2.utils
import android.text.format.DateUtils import android.text.format.DateUtils
import io.github.aakira.napier.Napier
import kotlinx.datetime.* import kotlinx.datetime.*
actual class DateUtils { actual class DateUtils {
actual companion object { actual companion object {
// Possible formats are
// yyyy-mm-dd hh:mm:ss format
private val oldVersionFormat = "\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}(.()\\d*)?".toRegex()
// yyyy-MM-dd'T'HH:mm:ss[.SSS]XXX (RFC3339)
private val newVersionFormat = "(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})[+-](\\d{2}(:\\d{2})?)?".toRegex()
// TODO: do not fix any more issues here. Move everything to plateform specific code.
actual fun parseDate(dateString: String): Long { actual fun parseDate(dateString: String): Long {
var isoDateString: String = return try {
try { Instant.parse(dateString).toEpochMilliseconds()
if (dateString.matches(oldVersionFormat)) {
dateString.replace(" ", "T")
} else if (dateString.matches(newVersionFormat)) {
newVersionFormat.find(dateString)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $dateString")
} else {
throw Exception("Unrecognized format for $dateString")
}
} catch (e: Exception) { } catch (e: Exception) {
throw Exception("parseDate failed for $dateString", e) LocalDateTime.parse(dateString.replace(" ", "T")).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
} }
actual fun parseRelativeDate(dateString: String): String { actual fun parseRelativeDate(dateString: String): String {
val date = parseDate(dateString) val date = parseDate(dateString)
return " " + return " " + DateUtils.getRelativeTimeSpanString(
DateUtils.getRelativeTimeSpanString(
date, date,
Clock.System.now().toEpochMilliseconds(), Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS, DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE, DateUtils.FORMAT_ABBREV_RELATIVE
) )
} }
} }

View File

@ -21,13 +21,13 @@ actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
actual fun SelfossModel.Item.getImages(): ArrayList<String> { actual fun SelfossModel.Item.getImages(): ArrayList<String> {
val allImages = ArrayList<String>() val allImages = ArrayList<String>()
for (image in Jsoup.parse(content).getElementsByTag("img")) { for ( image in Jsoup.parse(content).getElementsByTag("img")) {
val url = image.attr("src") val url = image.attr("src")
if (url.lowercase(Locale.US).contains(".jpg") || if (url.lowercase(Locale.US).contains(".jpg") ||
url.lowercase(Locale.US).contains(".jpeg") || url.lowercase(Locale.US).contains(".jpeg") ||
url.lowercase(Locale.US).contains(".png") || url.lowercase(Locale.US).contains(".png") ||
url.lowercase(Locale.US).contains(".webp") url.lowercase(Locale.US).contains(".webp"))
) { {
allImages.add(url) allImages.add(url)
} }
} }
@ -38,11 +38,7 @@ actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
return constructUrl(baseUrl, "favicons", icon) return constructUrl(baseUrl, "favicons", icon)
} }
actual fun constructUrl( actual fun constructUrl(baseUrl: String, path: String, file: String?): String {
baseUrl: String,
path: String,
file: String?,
): String {
return if (file == null || file == "null" || file.isEmpty()) { return if (file == null || file == "null" || file.isEmpty()) {
"" ""
} else { } else {

Some files were not shown because too many files have changed in this diff Show More