Compare commits
66 Commits
v123113311
...
6b5f6cbbe0
Author | SHA1 | Date | |
---|---|---|---|
6b5f6cbbe0 | |||
5035392aff | |||
54dbda76ab | |||
11c39ae87c | |||
6645902ec8 | |||
0a07a5dfad | |||
d88d38fd3b | |||
28fe38aa17 | |||
d524c30732 | |||
8c00aa65da | |||
ae81261cb1 | |||
03c567ee33 | |||
d23dd82fc2 | |||
2e7a168424 | |||
5bc2f614af | |||
934c112db5 | |||
ad7549a89f | |||
fb9ceecabd | |||
61b9fd30e0 | |||
806e56e20b | |||
cd8b7aaf9d | |||
c25ad7621e | |||
63da3b9fe7 | |||
1d99eeb633 | |||
162a350a8f | |||
27c1bba146 | |||
b7f3a9877a | |||
47f78754dc | |||
1bdfb143ac | |||
d81ced3964 | |||
fbafece1fa | |||
cbed8f07cb | |||
f54fcc3ba1 | |||
|
aad93ef722 | ||
9e83af0302 | |||
|
24b86e66b4 | ||
641c444061 | |||
0902c61544 | |||
|
6790152a0b | ||
|
46d1ba418e | ||
436373d0ad | |||
|
5b9b51c02d | ||
b81abe384a | |||
|
851f862dbe | ||
|
8d7e302af8 | ||
236e1cca90 | |||
3a33cb4510 | |||
0bf9ca9a49 | |||
|
61e0087894 | ||
|
1ec05d9913 | ||
|
859bd91bbb | ||
|
204b736c53 | ||
|
f24609c143 | ||
|
b94d7dc537 | ||
|
41910cc4cd | ||
|
db166ca9d4 | ||
|
db0d5a4a85 | ||
|
3bc0d7cf95 | ||
|
8f464d95fd | ||
|
5ccd6a3368 | ||
|
cdbded246e | ||
|
750c7758bd | ||
|
22f8b14ecd | ||
|
6e27d6d4e6 | ||
|
14ff4dbd05 | ||
|
390c2d0cf3 |
170
.drone.yml
170
.drone.yml
@@ -1,170 +0,0 @@
|
|||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: test
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Lint
|
|
||||||
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
|
|
||||||
commands:
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- 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
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Configure java..."
|
|
||||||
- . ~/.bash_profile
|
|
||||||
- jenv global 17.0
|
|
||||||
- java --version
|
|
||||||
- date
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Building and testing..."
|
|
||||||
- ./gradlew build
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: Publish
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: createTagAndChangelog
|
|
||||||
image: ubuntu:latest
|
|
||||||
commands:
|
|
||||||
- apt-get update && apt-get install -y git
|
|
||||||
- git fetch --tags -p
|
|
||||||
- PREV=$(git describe --tags --abbrev=0)
|
|
||||||
- ./build.sh --publish --from-ci
|
|
||||||
- VER=$(git describe --tags --abbrev=0)
|
|
||||||
- CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
|
|
||||||
- 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:
|
|
||||||
TZ: Europe/Paris
|
|
||||||
|
|
||||||
- name: git-push
|
|
||||||
image: appleboy/drone-git-push
|
|
||||||
settings:
|
|
||||||
branch: master
|
|
||||||
remote:
|
|
||||||
from_secret: remoteUrl
|
|
||||||
followtags: true
|
|
||||||
ssh_key:
|
|
||||||
from_secret: privateKey
|
|
||||||
skip_verify: true
|
|
||||||
|
|
||||||
- name: scpFiles
|
|
||||||
image: appleboy/drone-scp
|
|
||||||
settings:
|
|
||||||
host: amine-bouabdallaoui.fr
|
|
||||||
username: ubuntu
|
|
||||||
key:
|
|
||||||
from_secret: privateKey
|
|
||||||
port: 22
|
|
||||||
target: /home/ubuntu/
|
|
||||||
source: version.txt
|
|
||||||
|
|
||||||
- name: deploy
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
settings:
|
|
||||||
host: amine-bouabdallaoui.fr
|
|
||||||
user: ubuntu
|
|
||||||
key:
|
|
||||||
from_secret: privateKey
|
|
||||||
command_timeout: 2m
|
|
||||||
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/
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- promote
|
|
||||||
target:
|
|
||||||
- production
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
type: docker
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build
|
|
||||||
image: mingc/android-build-box:latest
|
|
||||||
commands:
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Fetch tags..."
|
|
||||||
- git fetch --tags
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- 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
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Generate APK"
|
|
||||||
- ./gradlew :androidApp:assembleGithubConfigRelease
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Get Key"
|
|
||||||
- wget https://amine-bouabdallaoui.fr/key
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- 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
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Sign"
|
|
||||||
- $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias $YOUR_KEY_ALIAS --ks-pass pass:$YOUR_KEYSTORE_PASSWORD --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
|
|
||||||
- echo "---------------------------------------------------------"
|
|
||||||
- echo "Verify"
|
|
||||||
- $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
|
|
||||||
environment:
|
|
||||||
TZ: Europe/Paris
|
|
||||||
YOUR_KEYSTORE_PASSWORD:
|
|
||||||
from_secret: keyPass
|
|
||||||
YOUR_KEY_ALIAS:
|
|
||||||
from_secret: keyAlias
|
|
||||||
|
|
||||||
- name: gitea_release
|
|
||||||
image: plugins/gitea-release
|
|
||||||
settings:
|
|
||||||
api_key:
|
|
||||||
from_secret: giteaAPI
|
|
||||||
base_url: https://gitea.amine-bouabdallaoui.fr
|
|
||||||
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:
|
|
||||||
event:
|
|
||||||
- tag
|
|
27
.editorconfig
Normal file
27
.editorconfig
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[{*.kt,*.kts}]
|
||||||
|
|
||||||
|
[*.{kt,kts}]
|
||||||
|
# Disable wildcard imports entirely
|
||||||
|
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||||
|
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
||||||
|
|
||||||
|
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
|
||||||
|
end_of_line = lf
|
||||||
|
ij_kotlin_allow_trailing_comma = true
|
||||||
|
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||||
|
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
|
||||||
|
ij_kotlin_packages_to_use_import_on_demand = unset
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
ktlint_chain_method_rule_force_multiline_when_chain_operator_count_greater_or_equal_than = unset
|
||||||
|
ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1
|
||||||
|
ktlint_code_style = ktlint_official
|
||||||
|
ktlint_function_signature_body_expression_wrapping = multiline
|
||||||
|
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
|
||||||
|
ktlint_ignore_back_ticked_identifier = false
|
||||||
|
max_line_length = 140
|
10
.gitea/workflows/assets/docker-compose.yml
Normal file
10
.gitea/workflows/assets/docker-compose.yml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
selfoss:
|
||||||
|
container_name: selfoss
|
||||||
|
image: rsprta/selfoss
|
||||||
|
network_mode: "host"
|
||||||
|
ports:
|
||||||
|
- "8888:8888"
|
||||||
|
|
||||||
|
|
45
.gitea/workflows/common_build.yml
Normal file
45
.gitea/workflows/common_build.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
name: Build
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
BuildAndTestAndCoverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Fetch tags
|
||||||
|
run: git fetch --tags -p
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: gradle
|
||||||
|
- uses: gradle/actions/setup-gradle@v3
|
||||||
|
- uses: android-actions/setup-android@v3
|
||||||
|
- name: Configure gradle...
|
||||||
|
run: mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true" >> ~/.gradle/gradle.properties
|
||||||
|
- name: Build and test
|
||||||
|
run: ./gradlew build -x testReleaseUnitTest -x testDebugUnitTest -x testGithubConfigReleaseUnitTest -x testGithubConfigDebugUnitTest # These tests will be done
|
||||||
|
- uses: KengoTODA/actions-setup-docker-compose@v1
|
||||||
|
with:
|
||||||
|
version: "2.23.3"
|
||||||
|
- name: run selfoss
|
||||||
|
run: |
|
||||||
|
docker compose -f .gitea/workflows/assets/docker-compose.yml up -d
|
||||||
|
- name: coverage
|
||||||
|
run: |
|
||||||
|
./gradlew :koverHtmlReport
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage
|
||||||
|
path: build/reports/kover/html
|
||||||
|
retention-days: 1
|
||||||
|
overwrite: true
|
||||||
|
include-hidden-files: true
|
||||||
|
- name: Clean
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose -f .gitea/workflows/assets/docker-compose.yml stop
|
127
.gitea/workflows/on_merge_on_release.yml
Normal file
127
.gitea/workflows/on_merge_on_release.yml
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
name: Create tag
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- release
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: ./.gitea/workflows/common_build.yml
|
||||||
|
createTagAndChangelog:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Config git
|
||||||
|
run: |
|
||||||
|
git config --global user.email aminecmi+giteadrone@pm.me
|
||||||
|
git config --global user.name giteadrone
|
||||||
|
- name: Creating the tag and generate changelog
|
||||||
|
run: |
|
||||||
|
git fetch --tags -p
|
||||||
|
PREV=$(git describe --tags --abbrev=0)
|
||||||
|
./build.sh --publish --from-ci
|
||||||
|
VER=$(git describe --tags --abbrev=0)
|
||||||
|
CHANGELOG=$(git log $PREV..HEAD --pretty="- %s")
|
||||||
|
echo "**$VER
|
||||||
|
|
||||||
|
$CHANGELOG
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
$(cat CHANGELOG.md)" > CHANGELOG.md
|
||||||
|
git add CHANGELOG.md
|
||||||
|
touch ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
|
||||||
|
echo "**$VER**
|
||||||
|
|
||||||
|
$CHANGELOG" > ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
|
||||||
|
git add ./fastlane/metadata/android/en\-US/changelogs/$VER.txt
|
||||||
|
git commit -m "Changelog for $VER"
|
||||||
|
- name: Push changes
|
||||||
|
uses: appleboy/git-push-action@v1.0.0
|
||||||
|
with:
|
||||||
|
author_name: giteadrone
|
||||||
|
author_email: aminecmi+giteadrone@pm.me
|
||||||
|
remote: ${{ secrets.REMOTE_URL }}
|
||||||
|
followtags: true
|
||||||
|
ssh_key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
tags: true
|
||||||
|
branch: release
|
||||||
|
- name: copy file via ssh password
|
||||||
|
uses: appleboy/scp-action@v0.1.7
|
||||||
|
with:
|
||||||
|
host: amine-bouabdallaoui.fr
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
source: "version.txt"
|
||||||
|
target: "/home/ubuntu/"
|
||||||
|
- name: deploy version file
|
||||||
|
uses: appleboy/ssh-action@v1.2.0
|
||||||
|
with:
|
||||||
|
host: amine-bouabdallaoui.fr
|
||||||
|
username: ubuntu
|
||||||
|
key: ${{ secrets.PRIVATE_KEY }}
|
||||||
|
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/
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: createTagAndChangelog
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Fetch tags
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
git fetch --tags -p
|
||||||
|
PREV=$(git describe --tags --abbrev=0)
|
||||||
|
echo $PREV
|
||||||
|
echo "VERSION=$PREV" >> $GITHUB_OUTPUT
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: gradle
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
- name: Configure gradle...
|
||||||
|
run: 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
|
||||||
|
- name: setup go
|
||||||
|
uses: https://github.com/actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: '>=1.20.1'
|
||||||
|
- name: Generate APK
|
||||||
|
run: ./gradlew :androidApp:assembleGithubConfigRelease
|
||||||
|
- name: Get Key
|
||||||
|
run: wget ${{ secrets.KEY_URL }}
|
||||||
|
- name: Zippalign
|
||||||
|
run: |
|
||||||
|
sdkmanager "build-tools;31.0.0"
|
||||||
|
ls $ANDROID_HOME/build-tools
|
||||||
|
$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
|
||||||
|
- name: Sigh
|
||||||
|
run: $ANDROID_HOME/build-tools/31.0.0/apksigner sign -v --out signed.apk --ks ./key --ks-key-alias ${{ secrets.KEY_ALIAS }} --ks-pass pass:${{ secrets.KEYSTORE_PASSWORD }} --v1-signing-enabled true --v2-signing-enabled true androidApp/build/outputs/apk/githubConfig/release/android-prod-released-ziped.apk
|
||||||
|
- name: Verify
|
||||||
|
run: $ANDROID_HOME/build-tools/31.0.0/apksigner verify signed.apk
|
||||||
|
- name: Release
|
||||||
|
uses: https://gitea.com/actions/gitea-release-action@main
|
||||||
|
with:
|
||||||
|
files: signed.apk
|
||||||
|
token: ${{ secrets.API_KEY }}
|
||||||
|
tag_name: ${{ steps.version.outputs.VERSION }}
|
||||||
|
name: ${{ steps.version.outputs.VERSION }}
|
||||||
|
- name: Send mail
|
||||||
|
uses: https://github.com/dawidd6/action-send-mail@v4
|
||||||
|
with:
|
||||||
|
connection_url: ${{ secrets.MAIL_CONNECTION }}
|
||||||
|
to: ${{ secrets.MAIL_TO }}
|
||||||
|
from: ${{ secrets.MAIL_FROM }}
|
||||||
|
subject: Mapping file
|
||||||
|
priority: high
|
||||||
|
convert_markdown: true
|
||||||
|
body: Nouveau fichier de mapping pour la version ${{ steps.version.outputs.VERSION }}
|
||||||
|
attachments: androidApp/build/outputs/mapping/githubConfigRelease/mapping.txt
|
28
.gitea/workflows/on_pr.yml
Normal file
28
.gitea/workflows/on_pr.yml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: Check PR code
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: gradle
|
||||||
|
- name: Install klint
|
||||||
|
run: curl -sSLO https://github.com/pinterest/ktlint/releases/download/1.0.0/ktlint && chmod a+x ktlint && mv ktlint /usr/local/bin/
|
||||||
|
- name: Install detekt
|
||||||
|
run: curl -sSLO https://github.com/detekt/detekt/releases/download/v1.23.1/detekt-cli-1.23.1.zip && unzip detekt-cli-1.23.1.zip
|
||||||
|
- name: Linting...
|
||||||
|
run: ktlint 'shared/**/*.kt' 'androidApp/**/*.kt' '!shared/build'
|
||||||
|
- name: Detecting...
|
||||||
|
run: ./detekt-cli-1.23.1/bin/detekt-cli --all-rules --excludes '**/shared/build/**/*.kt' || true
|
||||||
|
build:
|
||||||
|
needs: Lint
|
||||||
|
uses: ./.gitea/workflows/common_build.yml
|
9
.gitea/workflows/on_push.yml
Normal file
9
.gitea/workflows/on_push.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
name: Check master code
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
uses: ./.gitea/workflows/common_build.yml
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -320,4 +320,7 @@ fabric.properties
|
|||||||
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
|
# End of https://www.toptal.com/developers/gitignore/api/gradle,kotlin,androidstudio,android,xcode,swift
|
||||||
|
|
||||||
|
|
||||||
crowdin.properties
|
crowdin.properties
|
||||||
|
|
||||||
|
.kotlin/
|
||||||
|
build-cache/
|
132
CHANGELOG.md
132
CHANGELOG.md
@@ -1,3 +1,135 @@
|
|||||||
|
**v125010031
|
||||||
|
|
||||||
|
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
||||||
|
- chore: "faster" action.
|
||||||
|
- fastlane: icon change.
|
||||||
|
- chore: ignoring a pixel issue.
|
||||||
|
- test: fixed an ui test issue.
|
||||||
|
- fix: center the loading thing.
|
||||||
|
- test: items displaying.
|
||||||
|
- bump: sqldelight.
|
||||||
|
- bump: material, desugar jdk, jsoup, kodein, settings, napier, mock.
|
||||||
|
- bump: androix and coroutines.
|
||||||
|
- bump: ktor. Closes #67.
|
||||||
|
- Changelog for v124123651
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v124123651
|
||||||
|
|
||||||
|
- Merge pull request 'Bugfixes' (#171) from bugfixes into master
|
||||||
|
- config: crowdin
|
||||||
|
- chore: can links be empty ?
|
||||||
|
- fix: Context issues in article fragment.
|
||||||
|
- fix: Context issues in fragment sheet.
|
||||||
|
- fix: build.
|
||||||
|
- chore: compile issue fix.
|
||||||
|
- chore: filter some bugs.
|
||||||
|
- bugfix: catch users using something other than selfoss.
|
||||||
|
- bugfix: No browser, no link.
|
||||||
|
- translations
|
||||||
|
- chore: remove log.
|
||||||
|
- translation
|
||||||
|
- Changelog for v124123641
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v124123641
|
||||||
|
|
||||||
|
- Chore: no tests on build.
|
||||||
|
- Merge pull request 'testing' (#170) from testing into master
|
||||||
|
- fix: Displaying fixes. Fixes #155
|
||||||
|
- test: coverage
|
||||||
|
- chore: update and use multiplatform datetime
|
||||||
|
- Changelog for v124123421
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v124123421
|
||||||
|
|
||||||
|
- fix: Trying to fix the serialization issue.
|
||||||
|
- Changelog for v124113311
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v124113311
|
||||||
|
|
||||||
|
- chore: update versions. (#165)
|
||||||
|
- chore: fastlane changelog.
|
||||||
|
- chore: fastlane fixes.
|
||||||
|
- Changelog for v124113301
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**v124113301**
|
||||||
|
|
||||||
|
- chore: Gitea Action
|
||||||
|
- Merge pull request 'chore: Gitea Action' (#164) from runner into master
|
||||||
|
- chore: Gitea Action
|
||||||
|
- chore: Readme update.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------
|
||||||
|
|
||||||
|
**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**
|
**v123102961**
|
||||||
|
|
||||||
- chore: domain changes.
|
- chore: domain changes.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
# ReaderForSelfoss-multiplatform [](https://build.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform)
|
# ReaderForSelfoss-multiplatform [](https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/actions?workflow=on_push.yml&actor=0&status=0)
|
||||||
|
|
||||||
[](https://crowdin.com/project/readerforselfoss)
|
[](https://crowdin.com/project/readerforselfoss)
|
||||||
|
|
||||||
@@ -10,10 +10,6 @@ If you are a user, you can still create new issues. I'll fix them when I can.
|
|||||||
|
|
||||||
<a href="https://f-droid.org/packages/bou.amine.apps.readerforselfossv2.android"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
|
<a href="https://f-droid.org/packages/bou.amine.apps.readerforselfossv2.android"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
|
||||||
|
|
||||||
## Screen captures
|
|
||||||
|
|
||||||
<img src="res//fr-card.png?raw=true" alt="card view" width="400"/> <img src="res//fr-list.png?raw=true" alt="list view" width="400"/>
|
|
||||||
|
|
||||||
## Like my app ?
|
## Like my app ?
|
||||||
|
|
||||||
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
|
<a href="https://www.buymeacoffee.com/aminecmi" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/lato-orange.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
|
||||||
|
1
androidApp/.gitignore
vendored
1
androidApp/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
/build
|
/build
|
||||||
|
.kotlin/
|
@@ -1,7 +1,7 @@
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
val ignoreGitVersion: String by project
|
val ignoreGitVersion: String by project
|
||||||
val acraVersion = "5.9.7"
|
val acraVersion = "5.12.0"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
@@ -9,6 +9,7 @@ plugins {
|
|||||||
kotlin("kapt")
|
kotlin("kapt")
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
id("com.mikepenz.aboutlibraries.plugin")
|
||||||
id("org.jetbrains.kotlinx.kover")
|
id("org.jetbrains.kotlinx.kover")
|
||||||
|
id("app.cash.sqldelight") version "2.0.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
|
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
|
||||||
@@ -65,14 +66,14 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
compileSdk = 34
|
compileSdk = 35
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
}
|
}
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "bou.amine.apps.readerforselfossv2.android"
|
applicationId = "bou.amine.apps.readerforselfossv2.android"
|
||||||
minSdk = 25
|
minSdk = 25
|
||||||
targetSdk = 34
|
targetSdk = 34 // 35 when edge-to-edge is handled
|
||||||
versionCode = versionCodeFromGit()
|
versionCode = versionCodeFromGit()
|
||||||
versionName = versionNameFromGit()
|
versionName = versionNameFromGit()
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ android {
|
|||||||
|
|
||||||
// tests
|
// tests
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
testInstrumentationRunnerArguments["clearPackageData"] = "true"
|
||||||
}
|
}
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
@@ -107,32 +109,37 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace = "bou.amine.apps.readerforselfossv2.android"
|
namespace = "bou.amine.apps.readerforselfossv2.android"
|
||||||
|
testOptions {
|
||||||
|
animationsDisabled = true
|
||||||
|
execution = "ANDROIDX_TEST_ORCHESTRATOR"
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
|
|
||||||
implementation(project(":shared"))
|
implementation(project(":shared"))
|
||||||
implementation("com.google.android.material:material:1.9.0")
|
implementation("androidx.appcompat:appcompat:1.7.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
|
|
||||||
|
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.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("com.google.android.material:material:1.12.0")
|
||||||
implementation("com.google.android.material:material:1.9.0")
|
implementation("androidx.recyclerview:recyclerview:1.4.0-rc01")
|
||||||
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
|
||||||
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")
|
||||||
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.9.1")
|
||||||
implementation("androidx.work:work-runtime-ktx:2.8.1")
|
implementation("androidx.work:work-runtime-ktx:2.10.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
|
||||||
implementation("org.jsoup:jsoup:1.15.4")
|
implementation("org.jsoup:jsoup:1.18.3")
|
||||||
|
|
||||||
//multidex
|
//multidex
|
||||||
implementation("androidx.multidex:multidex:2.0.1")
|
implementation("androidx.multidex:multidex:2.0.1")
|
||||||
@@ -145,31 +152,31 @@ dependencies {
|
|||||||
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
|
implementation("com.ashokvarma.android:bottom-navigation-bar:2.2.0")
|
||||||
|
|
||||||
// glide
|
// glide
|
||||||
kapt("com.github.bumptech.glide:compiler:4.15.0")
|
kapt("com.github.bumptech.glide:compiler:4.16.0")
|
||||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.15.0")
|
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
||||||
|
|
||||||
// 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")
|
||||||
|
|
||||||
//Dependency Injection
|
//Dependency Injection
|
||||||
implementation("org.kodein.di:kodein-di:7.14.0")
|
implementation("org.kodein.di:kodein-di:7.23.1")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0")
|
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1")
|
||||||
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0")
|
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1")
|
||||||
|
|
||||||
//Settings
|
//Settings
|
||||||
implementation("com.russhwolf:multiplatform-settings-no-arg:0.9")
|
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0")
|
||||||
|
|
||||||
//Logging
|
//Logging
|
||||||
implementation("io.github.aakira:napier:2.6.1")
|
implementation("io.github.aakira:napier:2.7.1")
|
||||||
|
|
||||||
//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.15.0")
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
|
||||||
|
|
||||||
@@ -177,16 +184,25 @@ dependencies {
|
|||||||
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
|
||||||
|
|
||||||
// SQLDELIGHT
|
// SQLDELIGHT
|
||||||
implementation("com.squareup.sqldelight:android-driver:1.5.4")
|
implementation("app.cash.sqldelight:android-driver:2.0.2")
|
||||||
|
|
||||||
//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.13.14")
|
||||||
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
||||||
|
androidTestImplementation("androidx.test:runner:1.6.2")
|
||||||
|
androidTestImplementation("androidx.test:rules:1.6.1")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||||
|
implementation("androidx.test.espresso:espresso-idling-resource:3.6.1")
|
||||||
|
androidTestImplementation("androidx.test.ext:junit-ktx:1.2.1")
|
||||||
|
androidTestUtil("androidx.test:orchestrator:1.5.1")
|
||||||
|
testImplementation("org.robolectric:robolectric:4.14.1")
|
||||||
|
testImplementation("androidx.test:core-ktx:1.6.1")
|
||||||
|
|
||||||
implementation("ch.acra:acra-http:$acraVersion")
|
implementation("ch.acra:acra-http:$acraVersion")
|
||||||
implementation("ch.acra:acra-toast:$acraVersion")
|
implementation("ch.acra:acra-toast:$acraVersion")
|
||||||
|
implementation("com.google.auto.service:auto-service:1.1.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<Test> {
|
tasks.withType<Test> {
|
||||||
|
@@ -0,0 +1,119 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.ArrayRes
|
||||||
|
import androidx.test.espresso.Espresso.onData
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.replaceText
|
||||||
|
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.Matchers.hasToString
|
||||||
|
|
||||||
|
fun performLogin(someUrl: String? = null) {
|
||||||
|
onView(withId(R.id.urlView)).perform(click()).perform(
|
||||||
|
typeTextIntoFocusedView(
|
||||||
|
if (!someUrl.isNullOrEmpty()) someUrl else "http://10.0.2.2:8888",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginAndInitHome() {
|
||||||
|
performLogin()
|
||||||
|
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||||
|
onView(withText("OK")).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAndCancelSetting(
|
||||||
|
oldValue: String,
|
||||||
|
newValue: String,
|
||||||
|
openSettingItem: () -> Unit,
|
||||||
|
) {
|
||||||
|
openSettingItem()
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText(newValue))
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.button2),
|
||||||
|
).perform(click())
|
||||||
|
openSettingItem()
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).check(matches(withText(oldValue)))
|
||||||
|
onView(
|
||||||
|
withText(newValue),
|
||||||
|
).check(doesNotExist())
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.button2),
|
||||||
|
).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAndSaveSetting(
|
||||||
|
oldValue: String,
|
||||||
|
newValue: String,
|
||||||
|
openSettingItem: () -> Unit,
|
||||||
|
) {
|
||||||
|
openSettingItem()
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText(newValue))
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.button1),
|
||||||
|
).perform(click())
|
||||||
|
openSettingItem()
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).check(matches(withText(newValue)))
|
||||||
|
if (oldValue.isNotEmpty()) {
|
||||||
|
onView(
|
||||||
|
withText(oldValue),
|
||||||
|
).check(doesNotExist())
|
||||||
|
}
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.button2),
|
||||||
|
).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testPreferencesFromArray(
|
||||||
|
context: Context,
|
||||||
|
@ArrayRes arrayRes: Int,
|
||||||
|
openSettingItem: () -> Unit,
|
||||||
|
) {
|
||||||
|
openSettingItem()
|
||||||
|
context.resources.getStringArray(arrayRes).forEach { res ->
|
||||||
|
onView(withText(res)).check(matches(allOf(isDisplayed(), isNotChecked())))
|
||||||
|
onView(withText(res)).perform(click())
|
||||||
|
onView(withText(res)).check(doesNotExist())
|
||||||
|
openSettingItem()
|
||||||
|
onView(withText(res)).check(matches(allOf(isDisplayed(), isChecked())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAddSourceWithUrl(
|
||||||
|
url: String,
|
||||||
|
sourceName: String,
|
||||||
|
) {
|
||||||
|
onView(withId(R.id.fab))
|
||||||
|
.perform(click())
|
||||||
|
onView(withId(R.id.nameInput))
|
||||||
|
.perform(click()).perform(typeTextIntoFocusedView(sourceName))
|
||||||
|
onView(withId(R.id.sourceUri))
|
||||||
|
.perform(click())
|
||||||
|
.perform(typeTextIntoFocusedView(url))
|
||||||
|
onView(withId(R.id.tags))
|
||||||
|
.perform(click()).perform(typeTextIntoFocusedView("tag1,tag2,tag3"))
|
||||||
|
onView(withId(R.id.spoutsSpinner))
|
||||||
|
.perform(click())
|
||||||
|
onData(hasToString("RSS Feed")).perform(click())
|
||||||
|
onView(withId(R.id.saveBtn))
|
||||||
|
.perform(click())
|
||||||
|
onView(withText(sourceName)).check(matches(isDisplayed()))
|
||||||
|
}
|
@@ -0,0 +1,119 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
|
import androidx.test.espresso.Root
|
||||||
|
import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withChild
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withParent
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withResourceName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.Description
|
||||||
|
import org.hamcrest.Matcher
|
||||||
|
import org.hamcrest.Matchers
|
||||||
|
import org.hamcrest.TypeSafeMatcher
|
||||||
|
|
||||||
|
fun withError(
|
||||||
|
@StringRes id: Int,
|
||||||
|
): TypeSafeMatcher<View?> {
|
||||||
|
return object : TypeSafeMatcher<View?>() {
|
||||||
|
override fun matchesSafely(view: View?): Boolean {
|
||||||
|
if (view == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val context = view.context
|
||||||
|
if (view !is EditText) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (view.error == null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return view.error.toString() == context.getString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeTo(description: Description?) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isPopupWindow(): Matcher<Root> {
|
||||||
|
return isPlatformPopup()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withDrawable(
|
||||||
|
@DrawableRes id: Int,
|
||||||
|
) = object : TypeSafeMatcher<View>() {
|
||||||
|
override fun describeTo(description: Description) {
|
||||||
|
description.appendText("ImageView with drawable same as drawable with id $id")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun matchesSafely(view: View): Boolean {
|
||||||
|
val context = view.context
|
||||||
|
val expectedBitmap = context.getDrawable(id)!!.toBitmap()
|
||||||
|
try {
|
||||||
|
return view is ImageView && view.drawable.toBitmap().sameAs(expectedBitmap)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasBottombarItemText(
|
||||||
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? {
|
||||||
|
return allOf(
|
||||||
|
withResourceName("fixed_bottom_navigation_icon"),
|
||||||
|
withParent(
|
||||||
|
allOf(
|
||||||
|
withResourceName("fixed_bottom_navigation_icon_container"),
|
||||||
|
hasSibling(withText(id)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withSettingsCheckboxWidget(
|
||||||
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? {
|
||||||
|
return allOf(
|
||||||
|
withId(android.R.id.switch_widget),
|
||||||
|
withParent(
|
||||||
|
withSettingsCheckboxFrame(id),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withSettingsCheckboxFrame(
|
||||||
|
@StringRes id: Int,
|
||||||
|
): Matcher<View>? {
|
||||||
|
return allOf(
|
||||||
|
withId(android.R.id.widget_frame),
|
||||||
|
hasSibling(
|
||||||
|
allOf(
|
||||||
|
withClassName(Matchers.equalTo(RelativeLayout::class.java.name)),
|
||||||
|
withChild(
|
||||||
|
withText(id),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openMenu() {
|
||||||
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
ApplicationProvider.getApplicationContext<Context>(),
|
||||||
|
)
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isClickable
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isFocused
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isSelected
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.hamcrest.CoreMatchers.not
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class HomeActivityTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
loginAndInitHome()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMenu() {
|
||||||
|
onView(withId(R.id.action_search)).check(matches(isDisplayed())).check(
|
||||||
|
matches(
|
||||||
|
isClickable(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withId(R.id.action_filter)).check(matches(isDisplayed())).check(
|
||||||
|
matches(
|
||||||
|
isClickable(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
openMenu()
|
||||||
|
onView(withText(R.string.readAll)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.menu_home_sources)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.title_activity_settings)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.menu_home_refresh)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.issue_tracker_link)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.action_disconnect)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMenuActions() {
|
||||||
|
onView(withId(R.id.action_search)).perform(click())
|
||||||
|
onView(
|
||||||
|
withId(R.id.search_src_text),
|
||||||
|
).check(matches(isFocused()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
|
onView(withId(R.id.action_filter)).perform(click())
|
||||||
|
onView(
|
||||||
|
withText(R.string.filter_item_sources),
|
||||||
|
).check(matches(isDisplayed()))
|
||||||
|
onView(
|
||||||
|
withText(R.string.filter_item_tags),
|
||||||
|
).check(matches(isDisplayed()))
|
||||||
|
onView(
|
||||||
|
withId(R.id.floatingActionButton2),
|
||||||
|
).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
|
openMenu()
|
||||||
|
onView(withText(R.string.readAll)).perform(click())
|
||||||
|
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()
|
||||||
|
|
||||||
|
onView(withText(R.string.menu_home_sources)).perform(click())
|
||||||
|
onView(withId(R.id.fab)).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()
|
||||||
|
|
||||||
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
|
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()
|
||||||
|
|
||||||
|
onView(withText(R.string.menu_home_refresh)).perform(click())
|
||||||
|
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()
|
||||||
|
|
||||||
|
/*onView(withText(R.string.issue_tracker_link)).perform(click())
|
||||||
|
onView(withText(R.string.markall_dialog_message)).check(matches(isDisplayed()))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()*/
|
||||||
|
|
||||||
|
onView(withText(R.string.action_disconnect)).perform(click())
|
||||||
|
onView(withText(R.string.confirm_disconnect_title)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testEmptyView() {
|
||||||
|
onView(withId(R.id.emptyText)).check(matches(isDisplayed()))
|
||||||
|
onView(
|
||||||
|
hasBottombarItemText(R.string.tab_new),
|
||||||
|
).check(matches(isDisplayed())).check(matches(isSelected()))
|
||||||
|
onView(
|
||||||
|
hasBottombarItemText(R.string.tab_read),
|
||||||
|
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||||
|
onView(
|
||||||
|
hasBottombarItemText(R.string.tab_favs),
|
||||||
|
).check(matches(isDisplayed())).check(matches(not(isSelected())))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,82 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.IdlingRegistry
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isClickable
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class LoginActivityTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
private fun getActivity(): Activity? {
|
||||||
|
var activity: Activity? = null
|
||||||
|
activityRule.scenario.onActivity {
|
||||||
|
activity = it
|
||||||
|
}
|
||||||
|
return activity
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun registerIdlingResource() {
|
||||||
|
IdlingRegistry.getInstance()
|
||||||
|
.register(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun unregisterIdlingResource() {
|
||||||
|
IdlingRegistry.getInstance()
|
||||||
|
.unregister(CountingIdlingResourceSingleton.countingIdlingResource)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun viewIsInitialized() {
|
||||||
|
onView(withId(R.id.urlView)).check(matches(isDisplayed()))
|
||||||
|
onView(withId(R.id.selfSigned)).check(matches(isDisplayed())).check(matches(isNotChecked()))
|
||||||
|
.check(
|
||||||
|
matches(isClickable()),
|
||||||
|
)
|
||||||
|
onView(withId(R.id.withLogin)).check(matches(isDisplayed()))
|
||||||
|
.check(matches(isNotChecked())).check(
|
||||||
|
matches(isClickable()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun urlError() {
|
||||||
|
performLogin("10.0.2.2:8888")
|
||||||
|
onView(withId(R.id.urlView)).perform(click())
|
||||||
|
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiError() {
|
||||||
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
|
onView(withId(R.id.signInButton)).perform(click())
|
||||||
|
onView(withText(R.string.warning_wrong_url)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun connect() {
|
||||||
|
performLogin()
|
||||||
|
onView(withText(R.string.gdpr_dialog_title)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,179 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.replaceText
|
||||||
|
import androidx.test.espresso.action.ViewActions.swipeUp
|
||||||
|
import androidx.test.espresso.action.ViewActions.typeTextIntoFocusedView
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isFocused
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.CoreMatchers.not
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class SettingsActivityGeneralTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
loginAndInitHome()
|
||||||
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
|
onView(withText(R.string.pref_header_general)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGeneral() {
|
||||||
|
onView(withText(R.string.pref_api_items_number_title)).check(matches(isDisplayed()))
|
||||||
|
onView(
|
||||||
|
withSettingsCheckboxWidget(R.string.pref_general_infinite_loading_title),
|
||||||
|
).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.pref_general_category_links)).check(matches(isDisplayed()))
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
isChecked(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.pref_general_category_displaying)).check(matches(isDisplayed()))
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.card_height_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(
|
||||||
|
matches(
|
||||||
|
not(isEnabled()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.switch_unread_count_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
isChecked(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withId(R.id.settings)).perform(swipeUp())
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.display_all_counts_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGeneralActionsNumberItems() {
|
||||||
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
|
onView(withId(android.R.id.edit)).check(matches(isFocused()))
|
||||||
|
|
||||||
|
// Value check
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText("AVC"))
|
||||||
|
.check(matches(withText("")))
|
||||||
|
// TODO: should check message error. Not working for api level 30+
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText("-1"))
|
||||||
|
.check(matches(withText("")))
|
||||||
|
// TODO: should check message error. Not working for api level 30+
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText("300"))
|
||||||
|
.check(matches(withText("")))
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(typeTextIntoFocusedView("300"))
|
||||||
|
.check(matches(withText("30")))
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.edit),
|
||||||
|
).perform(replaceText("10"))
|
||||||
|
.check(matches(withText("10")))
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
|
||||||
|
// Value saving
|
||||||
|
changeAndCancelSetting("20", "10") {
|
||||||
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
|
}
|
||||||
|
changeAndSaveSetting("20", "10") {
|
||||||
|
onView(withText(R.string.pref_api_items_number_title)).perform(click())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGeneralActionsCheckboxes() {
|
||||||
|
// article viewer settings
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_article_viewer_title)).perform(click())
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.reader_static_bar_title)).check(
|
||||||
|
matches(
|
||||||
|
not(isEnabled()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(not(isEnabled())))
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_card_view_title)).perform(click())
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.card_height_title)).check(matches(isEnabled()))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,172 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isEnabled
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isNotEnabled
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.CoreMatchers.not
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class SettingsActivityOfflineTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
activityRule.scenario.onActivity { activity ->
|
||||||
|
context = activity.window.context
|
||||||
|
}
|
||||||
|
loginAndInitHome()
|
||||||
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
|
onView(withText(R.string.pref_header_offline)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOffline() {
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_items_caching)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
|
matches(
|
||||||
|
allOf(isNotEnabled(), isDisplayed()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
|
matches(
|
||||||
|
isNotEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_notify_new_items)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isChecked()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
|
matches(
|
||||||
|
isNotEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
isChecked(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOfflineActions() {
|
||||||
|
onView(withText(R.string.pref_switch_items_caching_off)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_switch_items_caching)).perform(click())
|
||||||
|
onView(withText(R.string.pref_switch_items_caching_on)).check(matches(isDisplayed()))
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_items_caching)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
|
matches(
|
||||||
|
isNotEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
|
matches(
|
||||||
|
isNotEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
|
matches(
|
||||||
|
isNotEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
onView(withText(R.string.pref_switch_periodic_refresh_off)).check(
|
||||||
|
matches(
|
||||||
|
isDisplayed(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_periodic_refresh)).perform(click())
|
||||||
|
onView(withText(R.string.pref_switch_periodic_refresh_on)).check(
|
||||||
|
matches(
|
||||||
|
isDisplayed(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_periodic_refresh_minutes_title)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).check(
|
||||||
|
matches(
|
||||||
|
isEnabled(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
changeAndCancelSetting("360", "123") {
|
||||||
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
|
||||||
|
}
|
||||||
|
changeAndSaveSetting("360", "123") {
|
||||||
|
onView(withText(R.string.pref_periodic_refresh_minutes_title)).perform(click())
|
||||||
|
}
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_refresh_when_charging)).perform(click())
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_notify_new_items)).perform(click())
|
||||||
|
onView(withSettingsCheckboxWidget(R.string.pref_switch_update_sources)).perform(click())
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,85 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isChecked
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.CoreMatchers.not
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class SettingsActivityReaderTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
activityRule.scenario.onActivity { activity ->
|
||||||
|
context = activity.window.context
|
||||||
|
}
|
||||||
|
loginAndInitHome()
|
||||||
|
openActionBarOverflowOrOptionsMenu(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
|
onView(withText(R.string.pref_header_viewer)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReader() {
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(
|
||||||
|
isChecked(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.pref_content_reader_font_size)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.settings_reader_font)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testReaderActions() {
|
||||||
|
onView(withText(R.string.pref_switch_actions_pager_scroll_off)).check(
|
||||||
|
matches(
|
||||||
|
isDisplayed(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withSettingsCheckboxFrame(R.string.pref_switch_actions_pager_scroll)).perform(click())
|
||||||
|
onView(withText(R.string.pref_switch_actions_pager_scroll_on)).check(
|
||||||
|
matches(
|
||||||
|
isDisplayed(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
||||||
|
changeAndCancelSetting("16", "10") {
|
||||||
|
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
||||||
|
}
|
||||||
|
changeAndSaveSetting("16", "10") {
|
||||||
|
onView(withText(R.string.pref_content_reader_font_size)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
testPreferencesFromArray(context, R.array.preloaded_fonts_values) {
|
||||||
|
onView(withText(R.string.settings_reader_font)).perform(click())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,92 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isSelected
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.hamcrest.CoreMatchers.allOf
|
||||||
|
import org.hamcrest.CoreMatchers.not
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class SettingsActivityTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
activityRule.scenario.onActivity { activity ->
|
||||||
|
context = activity.window.context
|
||||||
|
}
|
||||||
|
loginAndInitHome()
|
||||||
|
openMenu()
|
||||||
|
onView(withText(R.string.title_activity_settings)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAllSettings() {
|
||||||
|
onView(withText(R.string.pref_header_general)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_header_viewer)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_header_offline)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_header_theme)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_header_links)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.pref_switch_disable_acra)).check(
|
||||||
|
matches(
|
||||||
|
allOf(
|
||||||
|
isDisplayed(),
|
||||||
|
not(isSelected()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onView(withText(R.string.action_about)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testThemes() {
|
||||||
|
testPreferencesFromArray(context, R.array.ModeTitles) {
|
||||||
|
onView(withText(R.string.pref_header_theme)).perform(click())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExperimentail() {
|
||||||
|
onView(withText(R.string.pref_header_experimental)).perform(click())
|
||||||
|
changeAndCancelSetting("", "10") {
|
||||||
|
onView(withText(R.string.pref_api_timeout)).perform(click())
|
||||||
|
}
|
||||||
|
changeAndSaveSetting("", "10") {
|
||||||
|
onView(withText(R.string.pref_api_timeout)).perform(click())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBugReports() {
|
||||||
|
onView(withText(R.string.pref_switch_disable_acra)).perform(click())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLinks() {
|
||||||
|
onView(withText(R.string.pref_header_links)).perform(click())
|
||||||
|
onView(withText(R.string.issue_tracker_link)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.issue_tracker_summary)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.source_code)).check(matches(isDisplayed()))
|
||||||
|
onView(withText(R.string.translation)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAbout() {
|
||||||
|
onView(withText(R.string.action_about)).perform(click())
|
||||||
|
onView(withText("ACRA")).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,81 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
|
import androidx.test.espresso.AmbiguousViewMatcherException
|
||||||
|
import androidx.test.espresso.Espresso.onView
|
||||||
|
import androidx.test.espresso.action.ViewActions
|
||||||
|
import androidx.test.espresso.action.ViewActions.click
|
||||||
|
import androidx.test.espresso.action.ViewActions.swipeDown
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.doesNotExist
|
||||||
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
|
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import androidx.test.filters.LargeTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@LargeTest
|
||||||
|
class SourcesActivityTest {
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
lateinit var sourceName: String
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun init() {
|
||||||
|
sourceName = UUID.randomUUID().toString().substring(0, 15)
|
||||||
|
|
||||||
|
loginAndInitHome()
|
||||||
|
goToSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addSource() {
|
||||||
|
testAddSourceWithUrl(
|
||||||
|
"https://lorem-rss.herokuapp.com/feed?unit=year&interval=1&length=10",
|
||||||
|
sourceName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addSourceCheckContent() {
|
||||||
|
testAddSourceWithUrl("https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en", sourceName)
|
||||||
|
onView(isRoot()).perform(ViewActions.pressBack())
|
||||||
|
openMenu()
|
||||||
|
onView(withText(R.string.menu_home_refresh)).perform(click())
|
||||||
|
onView(withText(R.string.refresh_dialog_message)).check(matches(isDisplayed()))
|
||||||
|
onView(
|
||||||
|
withId(android.R.id.button1),
|
||||||
|
).perform(click())
|
||||||
|
Thread.sleep(10000)
|
||||||
|
onView(withId(R.id.swipeRefreshLayout)).perform(swipeDown())
|
||||||
|
Thread.sleep(2000)
|
||||||
|
try {
|
||||||
|
onView(withId(R.id.sourceTitleAndDate)).check(matches(isDisplayed()))
|
||||||
|
} catch (e: AmbiguousViewMatcherException) {
|
||||||
|
assert(true)
|
||||||
|
}
|
||||||
|
goToSources()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun deleteTheCreatedSource() {
|
||||||
|
onView(withText(sourceName)).check(matches(isDisplayed()))
|
||||||
|
onView(withId(R.id.deleteBtn)).perform(click())
|
||||||
|
onView(withText(sourceName)).check(doesNotExist())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun goToSources() {
|
||||||
|
openMenu()
|
||||||
|
onView(withText(R.string.menu_home_sources))
|
||||||
|
.perform(click())
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android
|
package bou.amine.apps.readerforselfossv2.android
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
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
|
||||||
@@ -13,7 +12,11 @@ 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.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.*
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.ItemTouchHelper
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
@@ -25,12 +28,14 @@ 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.fragments.FilterSheetFragment
|
||||||
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
import bou.amine.apps.readerforselfossv2.android.settings.SettingsActivity
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||||
import 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
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
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.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.ItemType
|
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||||
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
|
||||||
@@ -44,7 +49,10 @@ import org.kodein.di.instance
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAware {
|
class HomeActivity :
|
||||||
|
AppCompatActivity(),
|
||||||
|
SearchView.OnQueryTextListener,
|
||||||
|
DIAware {
|
||||||
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
|
||||||
@@ -58,7 +66,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
|
private lateinit var recyclerViewScrollListener: RecyclerView.OnScrollListener
|
||||||
private lateinit var binding: ActivityHomeBinding
|
private lateinit var binding: ActivityHomeBinding
|
||||||
|
|
||||||
private var recyclerAdapter: RecyclerView.Adapter<*>? = null
|
private var recyclerAdapter: ItemsAdapter<out RecyclerView.ViewHolder>? = null
|
||||||
|
|
||||||
private var fromTabShortcut: Boolean = false
|
private var fromTabShortcut: Boolean = false
|
||||||
|
|
||||||
@@ -80,7 +88,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
repository.offlineOverride = intent.getBooleanExtra("startOffline", false)
|
||||||
|
|
||||||
if (fromTabShortcut) {
|
if (fromTabShortcut) {
|
||||||
elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
elementsShown =
|
||||||
|
ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position))
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
@@ -92,8 +101,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
handleSwipeRefreshLayout()
|
handleSwipeRefreshLayout()
|
||||||
|
|
||||||
if (appSettingsService.isItemCachingEnabled()) {
|
if (appSettingsService.isItemCachingEnabled()) {
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
repository.tryToCacheItemsAndGetNewOnes()
|
repository.tryToCacheItemsAndGetNewOnes()
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,9 +118,11 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
binding.swipeRefreshLayout.setOnRefreshListener {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
repository.offlineOverride = false
|
repository.offlineOverride = false
|
||||||
lastFetchDone = false
|
lastFetchDone = false
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,11 +174,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
"Found null when swiping at positon $position.",
|
this@HomeActivity,
|
||||||
Toast.LENGTH_LONG,
|
"Found null when swiping at positon $position.",
|
||||||
).show()
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,15 +204,18 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
tabNewBadge =
|
tabNewBadge =
|
||||||
TextBadgeItem()
|
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)
|
||||||
|
|
||||||
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
if (appSettingsService.isDisplayUnreadCountEnabled()) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@@ -226,14 +243,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
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,
|
||||||
@@ -270,9 +285,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
handleGDPRDialog(appSettingsService.settings.getBoolean("GDPR_shown", false))
|
||||||
|
|
||||||
handleRecurringTask()
|
handleRecurringTask()
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
repository.handleDBActions()
|
repository.handleDBActions()
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
|
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
@@ -311,6 +327,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
)
|
)
|
||||||
binding.recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
is GridLayoutManager ->
|
is GridLayoutManager ->
|
||||||
if (appSettingsService.isCardViewEnabled()) {
|
if (appSettingsService.isCardViewEnabled()) {
|
||||||
layoutManager =
|
layoutManager =
|
||||||
@@ -322,6 +339,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
|
||||||
binding.recyclerView.layoutManager = layoutManager
|
binding.recyclerView.layoutManager = layoutManager
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
if (currentManager == null) {
|
if (currentManager == null) {
|
||||||
if (!appSettingsService.isCardViewEnabled()) {
|
if (!appSettingsService.isCardViewEnabled()) {
|
||||||
@@ -358,12 +376,14 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
} else {
|
} else {
|
||||||
layoutManager.scrollToPositionWithOffset(0, 0)
|
layoutManager.scrollToPositionWithOffset(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
is GridLayoutManager ->
|
is GridLayoutManager ->
|
||||||
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
|
if (layoutManager.findFirstCompletelyVisibleItemPosition() == 0) {
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
} else {
|
} else {
|
||||||
layoutManager.scrollToPositionWithOffset(0, 0)
|
layoutManager.scrollToPositionWithOffset(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,16 +430,17 @@ 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) {
|
when (val manager = binding.recyclerView.layoutManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
manager.findLastCompletelyVisibleItemPositions(
|
manager
|
||||||
null,
|
.findLastCompletelyVisibleItemPositions(
|
||||||
).last()
|
null,
|
||||||
|
).last()
|
||||||
|
|
||||||
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
|
is GridLayoutManager -> manager.findLastCompletelyVisibleItemPosition()
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun mayBeEmpty() =
|
private fun mayBeEmpty() =
|
||||||
if (items.isEmpty()) {
|
if (items.isEmpty()) {
|
||||||
@@ -444,6 +465,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
appendResults: Boolean,
|
appendResults: Boolean,
|
||||||
itemType: ItemType,
|
itemType: ItemType,
|
||||||
) {
|
) {
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
repository.displayedItems = itemType
|
repository.displayedItems = itemType
|
||||||
@@ -455,6 +477,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
handleListResult()
|
handleListResult()
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,8 +488,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
when (oldManager) {
|
when (oldManager) {
|
||||||
is StaggeredGridLayoutManager ->
|
is StaggeredGridLayoutManager ->
|
||||||
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
|
oldManager.findFirstCompletelyVisibleItemPositions(null).last()
|
||||||
|
|
||||||
is GridLayoutManager ->
|
is GridLayoutManager ->
|
||||||
oldManager.findFirstCompletelyVisibleItemPosition()
|
oldManager.findFirstCompletelyVisibleItemPosition()
|
||||||
|
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,7 +523,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
binding.recyclerView.adapter = recyclerAdapter
|
binding.recyclerView.adapter = recyclerAdapter
|
||||||
} else {
|
} else {
|
||||||
(recyclerAdapter as ItemsAdapter<*>).updateAllItems(items)
|
recyclerAdapter!!.updateAllItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadBadges()
|
reloadBadges()
|
||||||
@@ -507,8 +532,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
|
|
||||||
private fun reloadBadges() {
|
private fun reloadBadges() {
|
||||||
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
|
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.reloadBadges()
|
repository.reloadBadges()
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -544,7 +571,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.actionView as SearchView
|
||||||
searchView.setOnQueryTextListener(this)
|
searchView.setOnQueryTextListener(this)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -555,7 +582,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
messageRes: Int,
|
messageRes: Int,
|
||||||
doFn: () -> Unit,
|
doFn: () -> Unit,
|
||||||
) {
|
) {
|
||||||
AlertDialog.Builder(this@HomeActivity)
|
AlertDialog
|
||||||
|
.Builder(this@HomeActivity)
|
||||||
.setMessage(messageRes)
|
.setMessage(messageRes)
|
||||||
.setTitle(titleRes)
|
.setTitle(titleRes)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
|
.setPositiveButton(android.R.string.ok) { _, _ -> doFn() }
|
||||||
@@ -567,70 +595,82 @@ 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 -> {
|
R.id.issue_tracker -> {
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
|
baseContext.openUrlInBrowser(AppSettingsService.TRACKER_URL)
|
||||||
startActivity(browserIntent)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_filter -> {
|
R.id.action_filter -> {
|
||||||
val filterSheetFragment = FilterSheetFragment()
|
val filterSheetFragment = FilterSheetFragment()
|
||||||
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
|
filterSheetFragment.show(supportFragmentManager, FilterSheetFragment.TAG)
|
||||||
return true
|
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()
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val updatedRemote = repository.updateRemote()
|
val updatedRemote = repository.updateRemote()
|
||||||
if (updatedRemote) {
|
if (updatedRemote) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.refresh_success_response,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_LONG,
|
R.string.refresh_success_response,
|
||||||
)
|
Toast.LENGTH_LONG,
|
||||||
.show()
|
).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.refresh_failer_message,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.refresh_failer_message,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.readAll -> {
|
R.id.readAll -> {
|
||||||
if (elementsShown == ItemType.UNREAD) {
|
if (elementsShown == ItemType.UNREAD) {
|
||||||
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
|
||||||
binding.swipeRefreshLayout.isRefreshing = true
|
binding.swipeRefreshLayout.isRefreshing = true
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val success = repository.markAllAsRead(items)
|
val success = repository.markAllAsRead(items)
|
||||||
if (success) {
|
if (success) {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.all_posts_read,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.all_posts_read,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
tabNewBadge.removeBadge()
|
tabNewBadge.removeBadge()
|
||||||
|
|
||||||
getElementsAccordingToTab()
|
getElementsAccordingToTab()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast
|
||||||
this@HomeActivity,
|
.makeText(
|
||||||
R.string.all_posts_not_read,
|
this@HomeActivity,
|
||||||
Toast.LENGTH_SHORT,
|
R.string.all_posts_not_read,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
handleListResult()
|
handleListResult()
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
binding.swipeRefreshLayout.isRefreshing = false
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_disconnect -> {
|
R.id.action_disconnect -> {
|
||||||
needsConfirmation(R.string.confirm_disconnect_title, R.string.confirm_disconnect_description) {
|
needsConfirmation(
|
||||||
|
R.string.confirm_disconnect_title,
|
||||||
|
R.string.confirm_disconnect_description,
|
||||||
|
) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.logout()
|
repository.logout()
|
||||||
}
|
}
|
||||||
@@ -640,14 +680,17 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_settings -> {
|
R.id.action_settings -> {
|
||||||
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
|
settingsLauncher.launch(Intent(this, SettingsActivity::class.java))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.action_sources -> {
|
R.id.action_sources -> {
|
||||||
startActivity(Intent(this, SourcesActivity::class.java))
|
startActivity(Intent(this, SourcesActivity::class.java))
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> return super.onOptionsItemSelected(item)
|
else -> return super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -667,21 +710,29 @@ 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)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val backgroundWork =
|
val backgroundWork =
|
||||||
PeriodicWorkRequestBuilder<LoadingWorker>(appSettingsService.getRefreshMinutes(), TimeUnit.MINUTES)
|
PeriodicWorkRequestBuilder<LoadingWorker>(
|
||||||
.setConstraints(myConstraints)
|
appSettingsService.getRefreshMinutes(),
|
||||||
|
TimeUnit.MINUTES,
|
||||||
|
).setConstraints(myConstraints)
|
||||||
.addTag("selfoss-loading")
|
.addTag("selfoss-loading")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
WorkManager.getInstance(
|
WorkManager
|
||||||
baseContext,
|
.getInstance(
|
||||||
).enqueueUniquePeriodicWork("selfoss-loading", ExistingPeriodicWorkPolicy.KEEP, backgroundWork)
|
baseContext,
|
||||||
|
).enqueueUniquePeriodicWork(
|
||||||
|
"selfoss-loading",
|
||||||
|
ExistingPeriodicWorkPolicy.KEEP,
|
||||||
|
backgroundWork,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ 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
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
|
import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlInvalid
|
||||||
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
|
||||||
@@ -29,7 +30,9 @@ 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
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -102,9 +105,14 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun goToMain() {
|
private fun goToMain() {
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
repository.updateApiInformation()
|
repository.updateApiInformation()
|
||||||
ACRA.errorReporter.putCustomData("SELFOSS_API_VERSION", appSettingsService.getApiVersion().toString())
|
ACRA.errorReporter.putCustomData(
|
||||||
|
"SELFOSS_API_VERSION",
|
||||||
|
appSettingsService.getApiVersion().toString(),
|
||||||
|
)
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
val intent = Intent(this, HomeActivity::class.java)
|
val intent = Intent(this, HomeActivity::class.java)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@@ -139,27 +147,35 @@ class LoginActivity : AppCompatActivity(), DIAware {
|
|||||||
|
|
||||||
repository.refreshLoginInformation(url, login, password)
|
repository.refreshLoginInformation(url, login, password)
|
||||||
|
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
repository.updateApiInformation()
|
try {
|
||||||
val result = repository.login()
|
repository.updateApiInformation()
|
||||||
if (result) {
|
} catch (e: Exception) {
|
||||||
val (errorFetching, displaySelfossOnly) = repository.shouldBeSelfossInstance()
|
if (e.message?.startsWith("No transformation found") == true) {
|
||||||
if (!errorFetching && !displaySelfossOnly) {
|
Toast
|
||||||
goToMain()
|
.makeText(
|
||||||
} else {
|
|
||||||
if (displaySelfossOnly) {
|
|
||||||
Toast.makeText(
|
|
||||||
applicationContext,
|
applicationContext,
|
||||||
R.string.application_selfoss_only,
|
R.string.application_selfoss_only,
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
).show()
|
).show()
|
||||||
}
|
preferenceError()
|
||||||
|
showProgress(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val result = repository.login()
|
||||||
|
if (result) {
|
||||||
|
val errorFetching = repository.checkIfFetchFails()
|
||||||
|
if (!errorFetching) {
|
||||||
|
goToMain()
|
||||||
|
} else {
|
||||||
preferenceError()
|
preferenceError()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
preferenceError()
|
preferenceError()
|
||||||
}
|
}
|
||||||
showProgress(false)
|
showProgress(false)
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,19 +272,24 @@ 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 -> {
|
R.id.issue_tracker -> {
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.trackerUrl))
|
val browserIntent =
|
||||||
|
Intent(Intent.ACTION_VIEW, Uri.parse(AppSettingsService.TRACKER_URL))
|
||||||
startActivity(browserIntent)
|
startActivity(browserIntent)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.about -> {
|
R.id.about -> {
|
||||||
LibsBuilder()
|
LibsBuilder()
|
||||||
.withAboutIconShown(true)
|
.withAboutIconShown(true)
|
||||||
.withAboutVersionShown(true)
|
.withAboutVersionShown(true)
|
||||||
.withAboutSpecial2("Bug reports").withAboutSpecial2Description(AppSettingsService.trackerUrl)
|
.withAboutSpecial2("Bug reports")
|
||||||
.withAboutSpecial1("Project Page").withAboutSpecial1Description(AppSettingsService.sourceUrl)
|
.withAboutSpecial2Description(AppSettingsService.TRACKER_URL)
|
||||||
|
.withAboutSpecial1("Project Page")
|
||||||
|
.withAboutSpecial1Description(AppSettingsService.SOURCE_URL)
|
||||||
.start(this)
|
.start(this)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,10 +9,11 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDexApplication
|
import androidx.multidex.MultiDexApplication
|
||||||
import bou.amine.apps.readerforselfossv2.DI.networkModule
|
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
|
||||||
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
|
||||||
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
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.di.networkModule
|
||||||
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.github.ln_12.library.ConnectivityStatus
|
import com.github.ln_12.library.ConnectivityStatus
|
||||||
@@ -29,11 +30,17 @@ import org.acra.config.toast
|
|||||||
import org.acra.data.StringFormat
|
import org.acra.data.StringFormat
|
||||||
import org.acra.ktx.initAcra
|
import org.acra.ktx.initAcra
|
||||||
import org.acra.sender.HttpSender
|
import org.acra.sender.HttpSender
|
||||||
import org.kodein.di.*
|
import org.kodein.di.DI
|
||||||
|
import org.kodein.di.DIAware
|
||||||
|
import org.kodein.di.bind
|
||||||
|
import org.kodein.di.instance
|
||||||
|
import org.kodein.di.singleton
|
||||||
|
|
||||||
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()) }
|
bind<AppSettingsService>() with singleton { AppSettingsService(ACRA.isACRASenderServiceProcess() || TestingHelper().isUnitTest()) }
|
||||||
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()) }
|
||||||
@@ -84,11 +91,12 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
R.string.network_connectivity_lost
|
R.string.network_connectivity_lost
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast.makeText(
|
Toast
|
||||||
applicationContext,
|
.makeText(
|
||||||
toastMessage,
|
applicationContext,
|
||||||
Toast.LENGTH_SHORT,
|
toastMessage,
|
||||||
).show()
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,13 +154,13 @@ class MyApp : MultiDexApplication(), DIAware {
|
|||||||
|
|
||||||
val name = getString(R.string.notification_channel_sync)
|
val name = getString(R.string.notification_channel_sync)
|
||||||
val importance = NotificationManager.IMPORTANCE_LOW
|
val importance = NotificationManager.IMPORTANCE_LOW
|
||||||
val mChannel = NotificationChannel(AppSettingsService.syncChannelId, name, importance)
|
val mChannel = NotificationChannel(AppSettingsService.SYNC_CHANNEL_ID, name, importance)
|
||||||
|
|
||||||
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(
|
NotificationChannel(
|
||||||
AppSettingsService.newItemsChannelId,
|
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||||
newItemsChannelname,
|
newItemsChannelname,
|
||||||
newItemsChannelimportance,
|
newItemsChannelimportance,
|
||||||
)
|
)
|
||||||
|
@@ -114,15 +114,17 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
currentFragment.scrollDown()
|
currentFragment.volumeButtonScrollDown()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||||
val currentFragment =
|
val currentFragment =
|
||||||
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
supportFragmentManager.findFragmentByTag("f" + binding.pager.currentItem) as ArticleFragment
|
||||||
currentFragment.scrollUp()
|
currentFragment.volumeButtonScrollUp()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
super.onKeyDown(keyCode, event)
|
super.onKeyDown(keyCode, event)
|
||||||
}
|
}
|
||||||
@@ -187,6 +189,7 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
onBackPressedDispatcher.onBackPressed()
|
onBackPressedDispatcher.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 {
|
||||||
@@ -200,10 +203,12 @@ class ReaderActivity : AppCompatActivity(), DIAware {
|
|||||||
afterSave()
|
afterSave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.align_left -> {
|
R.id.align_left -> {
|
||||||
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
|
switchAlignmentSetting(AppSettingsService.ALIGN_LEFT)
|
||||||
refreshFragment()
|
refreshFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.align_justify -> {
|
R.id.align_justify -> {
|
||||||
switchAlignmentSetting(AppSettingsService.JUSTIFY)
|
switchAlignmentSetting(AppSettingsService.JUSTIFY)
|
||||||
refreshFragment()
|
refreshFragment()
|
||||||
|
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
|
||||||
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 kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -36,7 +37,8 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
|
||||||
binding.fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
binding.fab.rippleColor = resources.getColor(R.color.colorAccentDark)
|
||||||
binding.fab.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
|
binding.fab.backgroundTintList =
|
||||||
|
ColorStateList.valueOf(resources.getColor(R.color.colorAccent))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
@@ -53,6 +55,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
binding.recyclerView.setHasFixedSize(true)
|
binding.recyclerView.setHasFixedSize(true)
|
||||||
binding.recyclerView.layoutManager = mLayoutManager
|
binding.recyclerView.layoutManager = mLayoutManager
|
||||||
|
|
||||||
|
CountingIdlingResourceSingleton.increment()
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
val response = repository.getSourcesDetails()
|
val response = repository.getSourcesDetails()
|
||||||
if (response.isNotEmpty()) {
|
if (response.isNotEmpty()) {
|
||||||
@@ -71,6 +74,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
|||||||
Toast.LENGTH_SHORT,
|
Toast.LENGTH_SHORT,
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
CountingIdlingResourceSingleton.decrement()
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.fab.setOnClickListener {
|
binding.fab.setOnClickListener {
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.adapters
|
package bou.amine.apps.readerforselfossv2.android.adapters
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -10,10 +9,10 @@ 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.utils.LinkOnTouchListener
|
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
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.circularDrawable
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
|
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
|
||||||
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
|
||||||
@@ -31,10 +30,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 val items: ArrayList<SelfossModel.Item>,
|
||||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
||||||
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
) : ItemsAdapter<ItemCardAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
override lateinit var binding: CardItemBinding
|
||||||
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()
|
||||||
|
|
||||||
@@ -46,10 +45,39 @@ class ItemCardAdapter(
|
|||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int,
|
viewType: Int,
|
||||||
): ViewHolder {
|
): ViewHolder {
|
||||||
val binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
binding = CardItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ViewHolder(binding)
|
return ViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleClickListeners(
|
||||||
|
holderBinding: CardItemBinding,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
|
holderBinding.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.openItemUrlInBrowserAsNewTask(items[position])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
override fun onBindViewHolder(
|
||||||
holder: ViewHolder,
|
holder: ViewHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
@@ -57,6 +85,9 @@ class ItemCardAdapter(
|
|||||||
with(holder) {
|
with(holder) {
|
||||||
val itm = items[position]
|
val itm = items[position]
|
||||||
|
|
||||||
|
handleClickListeners(binding, position)
|
||||||
|
handleLinkOpening(binding, position)
|
||||||
|
|
||||||
binding.favButton.isSelected = itm.starred
|
binding.favButton.isSelected = itm.starred
|
||||||
if (appSettingsService.getPublicAccess()) {
|
if (appSettingsService.getPublicAccess()) {
|
||||||
binding.favButton.visibility = View.GONE
|
binding.favButton.visibility = View.GONE
|
||||||
@@ -68,7 +99,13 @@ class ItemCardAdapter(
|
|||||||
|
|
||||||
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
|
||||||
|
|
||||||
binding.sourceTitleAndDate.text = itm.sourceAuthorAndDate()
|
binding.sourceTitleAndDate.text =
|
||||||
|
try {
|
||||||
|
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
|
||||||
@@ -92,52 +129,5 @@ class ItemCardAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
inner class ViewHolder(val binding: CardItemBinding) : RecyclerView.ViewHolder(binding.root)
|
||||||
return items.size
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
repository.setReaderItems(items)
|
|
||||||
c.openItemUrl(
|
|
||||||
bindingAdapterPosition,
|
|
||||||
items[bindingAdapterPosition].getLinkDecoded(),
|
|
||||||
appSettingsService.isArticleViewerEnabled(),
|
|
||||||
app,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.adapters
|
package bou.amine.apps.readerforselfossv2.android.adapters
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
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.utils.LinkOnTouchListener
|
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularDrawable
|
||||||
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
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
@@ -22,10 +21,10 @@ import org.kodein.di.instance
|
|||||||
|
|
||||||
class ItemListAdapter(
|
class ItemListAdapter(
|
||||||
override val app: Activity,
|
override val app: Activity,
|
||||||
override var items: ArrayList<SelfossModel.Item>,
|
override val items: ArrayList<SelfossModel.Item>,
|
||||||
override val updateItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
override val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit,
|
||||||
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
) : ItemsAdapter<ItemListAdapter.ViewHolder>() {
|
||||||
private val c: Context = app.baseContext
|
override lateinit var binding: ListItemBinding
|
||||||
|
|
||||||
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()
|
||||||
@@ -35,7 +34,7 @@ class ItemListAdapter(
|
|||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int,
|
viewType: Int,
|
||||||
): ViewHolder {
|
): ViewHolder {
|
||||||
val binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
binding = ListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
return ViewHolder(binding)
|
return ViewHolder(binding)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +45,21 @@ class ItemListAdapter(
|
|||||||
with(holder) {
|
with(holder) {
|
||||||
val itm = items[position]
|
val itm = items[position]
|
||||||
|
|
||||||
|
handleLinkOpening(binding, position)
|
||||||
|
|
||||||
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 = itm.sourceAuthorAndDate()
|
binding.sourceTitleAndDate.text =
|
||||||
|
try {
|
||||||
|
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()) {
|
||||||
@@ -66,23 +73,5 @@ class ItemListAdapter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|
||||||
repository.setReaderItems(items)
|
|
||||||
c.openItemUrl(
|
|
||||||
bindingAdapterPosition,
|
|
||||||
items[bindingAdapterPosition].getLinkDecoded(),
|
|
||||||
appSettingsService.isArticleViewerEnabled(),
|
|
||||||
app,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,31 +1,40 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.adapters
|
package bou.amine.apps.readerforselfossv2.android.adapters
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.viewbinding.ViewBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
|
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
|
||||||
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.Enums.ItemType
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
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.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
|
|
||||||
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapter<VH>(), DIAware {
|
abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> :
|
||||||
abstract var items: ArrayList<SelfossModel.Item>
|
RecyclerView.Adapter<VH>(),
|
||||||
|
DIAware {
|
||||||
|
abstract val items: ArrayList<SelfossModel.Item>
|
||||||
abstract val repository: Repository
|
abstract val repository: Repository
|
||||||
|
abstract val binding: ViewBinding
|
||||||
abstract val appSettingsService: AppSettingsService
|
abstract val appSettingsService: AppSettingsService
|
||||||
abstract val app: Activity
|
abstract val app: Activity
|
||||||
abstract val updateItems: (ArrayList<SelfossModel.Item>) -> Unit
|
abstract val updateHomeItems: (ArrayList<SelfossModel.Item>) -> Unit
|
||||||
|
|
||||||
|
protected val c: Context get() = app.baseContext
|
||||||
|
|
||||||
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
|
fun updateAllItems(items: ArrayList<SelfossModel.Item>) {
|
||||||
this.items = items
|
this.items.clear()
|
||||||
|
this.items.addAll(items)
|
||||||
|
updateHomeItems(items)
|
||||||
notifyDataSetChanged()
|
notifyDataSetChanged()
|
||||||
updateItems(this.items)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun unmarkSnackbar(
|
private fun unmarkSnackbar(
|
||||||
@@ -38,8 +47,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
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) {
|
|
||||||
unreadItemAtIndex(item, position, false)
|
unreadItemAtIndex(item, position, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,8 +67,7 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +77,21 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
s.show()
|
s.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun handleLinkOpening(
|
||||||
|
holderBinding: ViewBinding,
|
||||||
|
position: Int,
|
||||||
|
) {
|
||||||
|
holderBinding.root.setOnClickListener {
|
||||||
|
repository.setReaderItems(items)
|
||||||
|
c.openItemUrl(
|
||||||
|
position,
|
||||||
|
items[position].getLinkDecoded(),
|
||||||
|
appSettingsService.isArticleViewerEnabled(),
|
||||||
|
app,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleItemAtIndex(position: Int) {
|
fun handleItemAtIndex(position: Int) {
|
||||||
if (items[position].unread) {
|
if (items[position].unread) {
|
||||||
readItemAtIndex(items[position], position)
|
readItemAtIndex(items[position], position)
|
||||||
@@ -89,7 +111,8 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
if (repository.displayedItems == ItemType.UNREAD) {
|
if (repository.displayedItems == ItemType.UNREAD) {
|
||||||
items.remove(item)
|
items.remove(item)
|
||||||
notifyItemRemoved(position)
|
notifyItemRemoved(position)
|
||||||
updateItems(items)
|
notifyItemRangeChanged(position, itemCount)
|
||||||
|
updateHomeItems(items)
|
||||||
} else {
|
} else {
|
||||||
notifyItemChanged(position)
|
notifyItemChanged(position)
|
||||||
}
|
}
|
||||||
@@ -118,13 +141,15 @@ abstract class ItemsAdapter<VH : RecyclerView.ViewHolder?> : RecyclerView.Adapte
|
|||||||
) {
|
) {
|
||||||
items.add(position, item)
|
items.add(position, item)
|
||||||
notifyItemInserted(position)
|
notifyItemInserted(position)
|
||||||
updateItems(items)
|
updateHomeItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
|
fun addItemsAtEnd(newItems: List<SelfossModel.Item>) {
|
||||||
val oldSize = items.size
|
val oldSize = items.size
|
||||||
items.addAll(newItems)
|
items.addAll(newItems)
|
||||||
notifyItemRangeInserted(oldSize, newItems.size)
|
notifyItemRangeInserted(oldSize, newItems.size)
|
||||||
updateItems(items)
|
updateHomeItems(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = items.size
|
||||||
}
|
}
|
||||||
|
@@ -50,6 +50,33 @@ class SourcesListAdapter(
|
|||||||
) {
|
) {
|
||||||
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())
|
binding.itemImage.setBackgroundAndText(itm.title.getHtmlDecoded())
|
||||||
} else {
|
} else {
|
||||||
@@ -72,38 +99,5 @@ class SourcesListAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
|
|
||||||
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
|
inner class ViewHolder(val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView)
|
||||||
init {
|
|
||||||
handleClickListeners()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleClickListeners() {
|
|
||||||
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
|
|
||||||
|
|
||||||
deleteBtn.setOnClickListener {
|
|
||||||
val (id, title) = items[bindingAdapterPosition]
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
val successfullyDeletedSource = repository.deleteSource(id, title)
|
|
||||||
if (successfullyDeletedSource) {
|
|
||||||
items.removeAt(bindingAdapterPosition)
|
|
||||||
notifyItemRemoved(bindingAdapterPosition)
|
|
||||||
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
R.string.can_delete_source,
|
|
||||||
Toast.LENGTH_SHORT,
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mView.setOnClickListener {
|
|
||||||
val source = items[bindingAdapterPosition]
|
|
||||||
|
|
||||||
repository.setSelectedSource(source)
|
|
||||||
app.startActivity(Intent(app, UpsertSourceActivity::class.java))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -23,11 +23,13 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import java.util.*
|
import java.util.Timer
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
class LoadingWorker(val context: Context, params: WorkerParameters) :
|
class LoadingWorker(
|
||||||
Worker(context, params),
|
val context: Context,
|
||||||
|
params: WorkerParameters,
|
||||||
|
) : Worker(context, params),
|
||||||
DIAware {
|
DIAware {
|
||||||
override val di by lazy { (applicationContext as MyApp).di }
|
override val di by lazy { (applicationContext as MyApp).di }
|
||||||
private val repository: Repository by instance()
|
private val repository: Repository by instance()
|
||||||
@@ -40,12 +42,13 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
|||||||
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
|
||||||
val notification =
|
val notification =
|
||||||
NotificationCompat.Builder(applicationContext, AppSettingsService.syncChannelId)
|
NotificationCompat
|
||||||
|
.Builder(applicationContext, AppSettingsService.SYNC_CHANNEL_ID)
|
||||||
.setContentTitle(context.getString(R.string.loading_notification_title))
|
.setContentTitle(context.getString(R.string.loading_notification_title))
|
||||||
.setContentText(context.getString(R.string.loading_notification_text))
|
.setContentText(context.getString(R.string.loading_notification_text))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setPriority(PRIORITY_LOW)
|
.setPriority(PRIORITY_LOW)
|
||||||
.setChannelId(AppSettingsService.syncChannelId)
|
.setChannelId(AppSettingsService.SYNC_CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
.setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp)
|
||||||
|
|
||||||
notificationManager.notify(1, notification.build())
|
notificationManager.notify(1, notification.build())
|
||||||
@@ -87,19 +90,18 @@ class LoadingWorker(val context: Context, params: WorkerParameters) :
|
|||||||
PendingIntent.getActivity(context, 0, intent, pflags)
|
PendingIntent.getActivity(context, 0, intent, pflags)
|
||||||
|
|
||||||
val newItemsNotification =
|
val newItemsNotification =
|
||||||
NotificationCompat.Builder(
|
NotificationCompat
|
||||||
applicationContext,
|
.Builder(
|
||||||
AppSettingsService.newItemsChannelId,
|
applicationContext,
|
||||||
)
|
AppSettingsService.NEW_ITEMS_CHANNEL_ID,
|
||||||
.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.NEW_ITEMS_CHANNEL_ID)
|
||||||
.setChannelId(AppSettingsService.newItemsChannelId)
|
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
.setSmallIcon(R.drawable.ic_tab_fiber_new_black_24dp)
|
@@ -1,16 +1,22 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.fragments
|
package bou.amine.apps.readerforselfossv2.android.fragments
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.Context
|
||||||
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
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.*
|
import android.util.TypedValue.DATA_NULL_UNDEFINED
|
||||||
|
import android.view.GestureDetector
|
||||||
|
import android.view.InflateException
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
import android.webkit.WebResourceResponse
|
import android.webkit.WebResourceResponse
|
||||||
import android.webkit.WebSettings
|
import android.webkit.WebSettings
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
@@ -25,10 +31,11 @@ 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.acra.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.isUrlValid
|
||||||
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
|
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrlInBrowserAsNewTask
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
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.MercuryModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
@@ -47,14 +54,14 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
|
|||||||
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.ktx.sendSilentlyWithAcra
|
||||||
import org.kodein.di.DI
|
import org.kodein.di.DI
|
||||||
import org.kodein.di.DIAware
|
import org.kodein.di.DIAware
|
||||||
import org.kodein.di.android.x.closestDI
|
import org.kodein.di.android.x.closestDI
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.SocketTimeoutException
|
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
|
|
||||||
private const val IMAGE_JPG = "image/jpg"
|
private const val IMAGE_JPG = "image/jpg"
|
||||||
@@ -99,11 +106,22 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
try {
|
try {
|
||||||
binding = FragmentArticleBinding.inflate(inflater, container, false)
|
binding = FragmentArticleBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
url = item.getLinkDecoded()
|
try {
|
||||||
|
url = item.getLinkDecoded()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.sendSilentlyWithAcra()
|
||||||
|
}
|
||||||
|
|
||||||
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 = item.sourceAuthorAndDate()
|
contentSource =
|
||||||
|
try {
|
||||||
|
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()
|
||||||
@@ -148,7 +166,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
)
|
)
|
||||||
} catch (e: InflateException) {
|
} catch (e: InflateException) {
|
||||||
e.sendSilentlyWithAcraWithName("webview not available")
|
e.sendSilentlyWithAcraWithName("webview not available")
|
||||||
if (context != null) {
|
try {
|
||||||
AlertDialog.Builder(requireContext())
|
AlertDialog.Builder(requireContext())
|
||||||
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
.setMessage(requireContext().getString(R.string.webview_dialog_issue_message))
|
||||||
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
.setTitle(requireContext().getString(R.string.webview_dialog_issue_title))
|
||||||
@@ -160,6 +178,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
.create()
|
.create()
|
||||||
.show()
|
.show()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,16 +227,16 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
override fun onItemClick(item: MenuItem) {
|
override fun onItemClick(item: MenuItem) {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
R.id.share_action -> requireActivity().shareLink(url, contentTitle)
|
||||||
R.id.open_action -> requireActivity().openInBrowserAsNewTask(this@ArticleFragment.item)
|
R.id.open_action -> requireActivity().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
R.id.unread_action ->
|
R.id.unread_action ->
|
||||||
if (context != null) {
|
try {
|
||||||
if (this@ArticleFragment.item.unread) {
|
if (this@ArticleFragment.item.unread) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
repository.markAsRead(this@ArticleFragment.item)
|
repository.markAsRead(this@ArticleFragment.item)
|
||||||
}
|
}
|
||||||
this@ArticleFragment.item.unread = false
|
this@ArticleFragment.item.unread = false
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
context,
|
requireContext(),
|
||||||
R.string.marked_as_read,
|
R.string.marked_as_read,
|
||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
).show()
|
).show()
|
||||||
@@ -231,7 +251,10 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
Toast.LENGTH_LONG,
|
Toast.LENGTH_LONG,
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,10 +287,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else {
|
} else {
|
||||||
openInBrowserAfterFailing()
|
openInBrowserAfterFailing()
|
||||||
}
|
}
|
||||||
} catch (e: SocketTimeoutException) {
|
|
||||||
openInBrowserAfterFailing()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.sendSilentlyWithAcraWithName("getContentFromMercury > $url")
|
|
||||||
openInBrowserAfterFailing()
|
openInBrowserAfterFailing()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +307,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
contentText = data.content.orEmpty()
|
contentText = data.content.orEmpty()
|
||||||
htmlToWebview()
|
htmlToWebview()
|
||||||
|
|
||||||
handleLeadImage(data?.lead_image_url)
|
handleLeadImage(data.lead_image_url)
|
||||||
|
|
||||||
binding.nestedScrollView.scrollTo(0, 0)
|
binding.nestedScrollView.scrollTo(0, 0)
|
||||||
binding.progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
@@ -318,12 +338,11 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
view: WebView?,
|
view: WebView?,
|
||||||
url: String,
|
url: String,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return if (context != null && url.isUrlValid() && binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
|
return if (context != null &&
|
||||||
try {
|
url.isUrlValid() &&
|
||||||
requireContext().startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
binding.webcontent.hitTestResult.type != WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE
|
||||||
} catch (e: ActivityNotFoundException) {
|
) {
|
||||||
e.sendSilentlyWithAcraWithName("activityNotFound > $url")
|
requireContext().openUrlInBrowser(url)
|
||||||
}
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
@@ -342,7 +361,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
"UTF-8",
|
"UTF-8",
|
||||||
@@ -354,7 +374,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else if (url.lowercase(Locale.US).contains(".png")) {
|
} else if (url.lowercase(Locale.US).contains(".png")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
"UTF-8",
|
"UTF-8",
|
||||||
@@ -366,7 +387,8 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
} else if (url.lowercase(Locale.US).contains(".webp")) {
|
||||||
try {
|
try {
|
||||||
val image =
|
val image =
|
||||||
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit().get()
|
Glide.with(view).asBitmap().apply(glideOptions).load(url).submit()
|
||||||
|
.get()
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
IMAGE_JPG,
|
IMAGE_JPG,
|
||||||
"UTF-8",
|
"UTF-8",
|
||||||
@@ -383,19 +405,44 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun htmlToWebview() {
|
private fun htmlToWebview() {
|
||||||
if (context != null) {
|
val context: Context
|
||||||
|
try {
|
||||||
|
context = requireContext()
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorOnSurface = TypedValue()
|
||||||
|
val colorSurface = TypedValue()
|
||||||
|
|
||||||
|
try {
|
||||||
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 = context.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
|
||||||
|
|
||||||
val colorOnSurface = TypedValue()
|
context.theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorOnSurface, colorOnSurface, true)
|
|
||||||
|
|
||||||
val colorSurface = TypedValue()
|
context.theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
||||||
requireContext().theme.resolveAttribute(R.attr.colorSurface, colorSurface, true)
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context issue when setting attributes, but context wasn't null before")
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorSurfaceString =
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
0xFFFFFF and (if (colorSurface.data != DATA_NULL_UNDEFINED) colorSurface.data else 0xFFFFFF),
|
||||||
|
)
|
||||||
|
|
||||||
|
val colorOnSurfaceString =
|
||||||
|
String.format(
|
||||||
|
"#%06X",
|
||||||
|
0xFFFFFF and (if (colorOnSurface.data != DATA_NULL_UNDEFINED) colorOnSurface.data else 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
binding.webcontent.settings.useWideViewPort = true
|
binding.webcontent.settings.useWideViewPort = true
|
||||||
binding.webcontent.settings.loadWithOverviewMode = true
|
binding.webcontent.settings.loadWithOverviewMode = true
|
||||||
binding.webcontent.settings.javaScriptEnabled = false
|
binding.webcontent.settings.javaScriptEnabled = false
|
||||||
@@ -412,13 +459,21 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context is null but wasn't, and that's causing issues with webview config")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
var baseUrl: String? = null
|
var baseUrl: String? = null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val itemUrl = URL(url)
|
val itemUrl = URL(url)
|
||||||
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
baseUrl = itemUrl.protocol + "://" + itemUrl.host
|
||||||
@@ -468,12 +523,12 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| color: ${
|
| color: ${
|
||||||
String.format(
|
String.format(
|
||||||
"#%06X",
|
"#%06X",
|
||||||
0xFFFFFF and resources.getColor(R.color.colorAccent),
|
0xFFFFFF and context.resources.getColor(R.color.colorAccent),
|
||||||
)
|
)
|
||||||
} !important;
|
} !important;
|
||||||
| }
|
| }
|
||||||
| *:not(a) {
|
| *:not(a) {
|
||||||
| color: ${String.format("#%06X", 0xFFFFFF and colorOnSurface.data)};
|
| color: $colorOnSurfaceString;
|
||||||
| }
|
| }
|
||||||
| * {
|
| * {
|
||||||
| font-size: ${fontSize}px;
|
| font-size: ${fontSize}px;
|
||||||
@@ -481,26 +536,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: $colorSurfaceString;
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| body, html {
|
| body, html {
|
||||||
| background-color: ${
|
| background-color: $colorSurfaceString !important;
|
||||||
String.format(
|
| border-color: $colorSurfaceString !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;
|
||||||
| }
|
| }
|
||||||
@@ -510,12 +550,7 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
| pre, code {
|
| pre, code {
|
||||||
| white-space: pre-wrap;
|
| white-space: pre-wrap;
|
||||||
| width:100%;
|
| width:100%;
|
||||||
| background-color: ${
|
| background-color: $colorSurfaceString;
|
||||||
String.format(
|
|
||||||
"#%06X",
|
|
||||||
0xFFFFFF and colorSurface.data,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
| }
|
| }
|
||||||
| </style>
|
| </style>
|
||||||
| $fontLinkAndStyle
|
| $fontLinkAndStyle
|
||||||
@@ -528,25 +563,27 @@ class ArticleFragment : Fragment(), DIAware {
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
null,
|
null,
|
||||||
)
|
)
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
e.sendSilentlyWithAcraWithName("Context required is still null ?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scrollDown() {
|
fun volumeButtonScrollDown() {
|
||||||
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 volumeButtonScrollUp() {
|
||||||
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) {
|
try {
|
||||||
requireContext().openInBrowserAsNewTask(this@ArticleFragment.item)
|
requireContext().openItemUrlInBrowserAsNewTask(this@ArticleFragment.item)
|
||||||
} else {
|
} catch (e: IllegalStateException) {
|
||||||
Exception("openInBrowserAfterFailing context is null").sendSilentlyWithAcraWithName("openInBrowserAfterFailing > $context")
|
e.sendSilentlyWithAcraWithName("Context required is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,8 +15,9 @@ import android.view.ViewGroup
|
|||||||
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
import bou.amine.apps.readerforselfossv2.android.HomeActivity
|
||||||
import bou.amine.apps.readerforselfossv2.android.R
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
import bou.amine.apps.readerforselfossv2.android.databinding.FilterFragmentBinding
|
||||||
import bou.amine.apps.readerforselfossv2.android.sendSilentlyWithAcraWithName
|
import bou.amine.apps.readerforselfossv2.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
import bou.amine.apps.readerforselfossv2.repository.Repository
|
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.getHtmlDecoded
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
import bou.amine.apps.readerforselfossv2.utils.getIcon
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
@@ -51,19 +52,17 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
val context: Context? = context
|
try {
|
||||||
|
|
||||||
if (context == null) {
|
|
||||||
dismiss()
|
|
||||||
Exception("FilterSheetFragment context is null").sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
|
|
||||||
} else {
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
handleTagChips(context)
|
handleTagChips(requireContext())
|
||||||
handleSourceChips(context)
|
handleSourceChips(requireContext())
|
||||||
|
|
||||||
binding.progressBar2.visibility = GONE
|
binding.progressBar2.visibility = GONE
|
||||||
binding.filterView.visibility = VISIBLE
|
binding.filterView.visibility = VISIBLE
|
||||||
}
|
}
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
dismiss()
|
||||||
|
e.sendSilentlyWithAcraWithName("FilterSheetFragment > onCreateView")
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.floatingActionButton2.setOnClickListener {
|
binding.floatingActionButton2.setOnClickListener {
|
||||||
@@ -147,9 +146,9 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
|||||||
val gd = GradientDrawable()
|
val gd = GradientDrawable()
|
||||||
val gdColor =
|
val gdColor =
|
||||||
try {
|
try {
|
||||||
Color.parseColor(tag.color)
|
Color.parseColor(tag.getColorHexCode())
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
e.sendSilentlyWithAcraWithName("color issue " + tag.color)
|
e.sendSilentlyWithAcraWithName("color issue " + tag.color + " / " + tag.getColorHexCode())
|
||||||
resources.getColor(R.color.colorPrimary)
|
resources.getColor(R.color.colorPrimary)
|
||||||
}
|
}
|
||||||
gd.setColor(gdColor)
|
gd.setColor(gdColor)
|
||||||
|
@@ -2,7 +2,7 @@ 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.android.utils.acra.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
|
||||||
|
@@ -1,7 +1,5 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.settings
|
package bou.amine.apps.readerforselfossv2.android.settings
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.InputFilter
|
import android.text.InputFilter
|
||||||
@@ -16,8 +14,12 @@ 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.android.utils.acra.sendSilentlyWithAcraWithName
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.utils.openUrlInBrowser
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.API_ITEMS_NUMBER
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.CURRENT_THEME
|
||||||
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService.Companion.READER_FONT_SIZE
|
||||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
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
|
||||||
@@ -62,15 +64,14 @@ class SettingsActivity :
|
|||||||
outState.putCharSequence(TITLE_TAG, title)
|
outState.putCharSequence(TITLE_TAG, title)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSupportNavigateUp(): Boolean {
|
override fun onSupportNavigateUp(): Boolean =
|
||||||
return if (supportFragmentManager.popBackStackImmediate()) {
|
if (supportFragmentManager.popBackStackImmediate()) {
|
||||||
supportActionBar?.title = getText(R.string.title_activity_settings)
|
supportActionBar?.title = getText(R.string.title_activity_settings)
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPreferenceStartFragment(
|
override fun onPreferenceStartFragment(
|
||||||
caller: PreferenceFragmentCompat,
|
caller: PreferenceFragmentCompat,
|
||||||
@@ -79,15 +80,17 @@ class SettingsActivity :
|
|||||||
// 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
|
||||||
classLoader,
|
.instantiate(
|
||||||
pref.fragment.toString(),
|
classLoader,
|
||||||
).apply {
|
pref.fragment.toString(),
|
||||||
arguments = args
|
).apply {
|
||||||
setTargetFragment(caller, 0)
|
arguments = args
|
||||||
}
|
setTargetFragment(caller, 0)
|
||||||
|
}
|
||||||
// Replace the existing Fragment with the new Fragment
|
// Replace the existing Fragment with the new Fragment
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
.replace(R.id.settings, fragment)
|
.replace(R.id.settings, fragment)
|
||||||
.addToBackStack(null)
|
.addToBackStack(null)
|
||||||
.commit()
|
.commit()
|
||||||
@@ -103,9 +106,11 @@ class SettingsActivity :
|
|||||||
) {
|
) {
|
||||||
setPreferencesFromResource(R.xml.pref_main, rootKey)
|
setPreferencesFromResource(R.xml.pref_main, rootKey)
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener =
|
preferenceManager.findPreference<Preference>(CURRENT_THEME)?.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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +134,8 @@ class SettingsActivity :
|
|||||||
) {
|
) {
|
||||||
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>(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 =
|
||||||
@@ -139,8 +145,12 @@ class SettingsActivity :
|
|||||||
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
|
||||||
Toast.makeText(activity, R.string.items_number_should_be_number, Toast.LENGTH_LONG).show()
|
.makeText(
|
||||||
|
activity,
|
||||||
|
R.string.items_number_should_be_number,
|
||||||
|
Toast.LENGTH_LONG,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
""
|
""
|
||||||
},
|
},
|
||||||
@@ -156,7 +166,7 @@ class SettingsActivity :
|
|||||||
) {
|
) {
|
||||||
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 {
|
||||||
@@ -213,25 +223,9 @@ class SettingsActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ThemePreferenceFragment : PreferenceFragmentCompat() {
|
|
||||||
override fun onCreatePreferences(
|
|
||||||
savedInstanceState: Bundle?,
|
|
||||||
rootKey: String?,
|
|
||||||
) {
|
|
||||||
setPreferencesFromResource(R.xml.pref_theme, rootKey)
|
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("currentMode")?.onPreferenceChangeListener =
|
|
||||||
Preference.OnPreferenceChangeListener { _, newValue ->
|
|
||||||
AppCompatDelegate.setDefaultNightMode(newValue.toString().toInt()) // ListPreference Only takes string-arrays ¯\_(ツ)_/¯
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LinksPreferenceFragment : PreferenceFragmentCompat() {
|
class LinksPreferenceFragment : PreferenceFragmentCompat() {
|
||||||
private fun openUrl(uri: Uri?) {
|
private fun openUrl(url: String) {
|
||||||
val browserIntent = Intent(Intent.ACTION_VIEW, uri)
|
context?.openUrlInBrowser(url)
|
||||||
startActivity(browserIntent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreatePreferences(
|
override fun onCreatePreferences(
|
||||||
@@ -242,19 +236,19 @@ class SettingsActivity :
|
|||||||
|
|
||||||
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("trackerLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(Uri.parse(AppSettingsService.trackerUrl))
|
openUrl(AppSettingsService.TRACKER_URL)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("sourceLink")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(Uri.parse(AppSettingsService.sourceUrl))
|
openUrl(AppSettingsService.SOURCE_URL)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener =
|
preferenceManager.findPreference<Preference>("translation")?.onPreferenceClickListener =
|
||||||
Preference.OnPreferenceClickListener {
|
Preference.OnPreferenceClickListener {
|
||||||
openUrl(Uri.parse(AppSettingsService.translationUrl))
|
openUrl(AppSettingsService.TRANSLATION_URL)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,20 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.testing
|
||||||
|
|
||||||
|
import androidx.test.espresso.idling.CountingIdlingResource
|
||||||
|
|
||||||
|
object CountingIdlingResourceSingleton {
|
||||||
|
private const val RESOURCE = "GLOBAL"
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val countingIdlingResource = CountingIdlingResource(RESOURCE)
|
||||||
|
|
||||||
|
fun increment() {
|
||||||
|
countingIdlingResource.increment()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decrement() {
|
||||||
|
if (!countingIdlingResource.isIdleNow) {
|
||||||
|
countingIdlingResource.decrement()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.testing
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
|
||||||
|
class TestingHelper {
|
||||||
|
fun isUnitTest(): Boolean {
|
||||||
|
var device = Build.DEVICE
|
||||||
|
var product = Build.PRODUCT
|
||||||
|
if (device == null) {
|
||||||
|
device = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (product == null) {
|
||||||
|
product = ""
|
||||||
|
}
|
||||||
|
return device == "robolectric" && product == "robolectric"
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android.utils
|
package bou.amine.apps.readerforselfossv2.android.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -35,10 +36,7 @@ fun Context.openItemUrl(
|
|||||||
intent.putExtra("currentItem", currentItem)
|
intent.putExtra("currentItem", currentItem)
|
||||||
app.startActivity(intent)
|
app.startActivity(intent)
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
this.openUrlInBrowserAsNewTask(linkDecoded)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
intent.data = Uri.parse(linkDecoded.toStringUriWithHttp())
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,11 +54,29 @@ fun String.isBaseUrlInvalid(): Boolean {
|
|||||||
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
|
return !(Patterns.WEB_URL.matcher(this).matches() && existsAndEndsWithSlash)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.openInBrowserAsNewTask(i: SelfossModel.Item) {
|
fun Context.openItemUrlInBrowserAsNewTask(i: SelfossModel.Item) {
|
||||||
|
this.openUrlInBrowserAsNewTask(i.getLinkDecoded().toStringUriWithHttp())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openUrlInBrowserAsNewTask(url: String) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW)
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
intent.data = Uri.parse(i.getLinkDecoded().toStringUriWithHttp())
|
intent.data = Uri.parse(url)
|
||||||
startActivity(intent)
|
this.mayBeStartActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openUrlInBrowser(url: String) {
|
||||||
|
val intent = Intent(Intent.ACTION_VIEW)
|
||||||
|
intent.data = Uri.parse(url)
|
||||||
|
this.mayBeStartActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.mayBeStartActivity(intent: Intent) {
|
||||||
|
try {
|
||||||
|
this.startActivity(intent)
|
||||||
|
} catch (e: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(this, getString(R.string.no_browser), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LinkOnTouchListener : View.OnTouchListener {
|
class LinkOnTouchListener : View.OnTouchListener {
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.android
|
package bou.amine.apps.readerforselfossv2.android.utils.acra
|
||||||
|
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
import org.acra.ktx.sendSilentlyWithAcra
|
import org.acra.ktx.sendSilentlyWithAcra
|
@@ -0,0 +1,26 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.utils.acra
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.DeadSystemException
|
||||||
|
import com.google.auto.service.AutoService
|
||||||
|
import org.acra.builder.ReportBuilder
|
||||||
|
import org.acra.config.CoreConfiguration
|
||||||
|
import org.acra.config.ReportingAdministrator
|
||||||
|
import org.acra.data.CrashReportData
|
||||||
|
|
||||||
|
@AutoService(ReportingAdministrator::class)
|
||||||
|
class AcraReportingAdministrator : ReportingAdministrator {
|
||||||
|
override fun shouldStartCollecting(
|
||||||
|
context: Context,
|
||||||
|
config: CoreConfiguration,
|
||||||
|
reportBuilder: ReportBuilder,
|
||||||
|
): Boolean =
|
||||||
|
reportBuilder.exception !is DeadSystemException &&
|
||||||
|
(reportBuilder.exception != null && reportBuilder.exception!!::class.simpleName != "CannotDeliverBroadcastException")
|
||||||
|
|
||||||
|
override fun shouldSendReport(
|
||||||
|
context: Context,
|
||||||
|
config: CoreConfiguration,
|
||||||
|
crashReportData: CrashReportData,
|
||||||
|
): Boolean = crashReportData.get("BRAND") != "redroid"
|
||||||
|
}
|
@@ -19,11 +19,10 @@ 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
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,6 +23,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="@dimen/activity_horizontal_margin">
|
android:padding="@dimen/activity_horizontal_margin">
|
||||||
<!-- Login progress -->
|
<!-- Login progress -->
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/loginForm"
|
android:id="@+id/loginForm"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
@@ -100,4 +101,4 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@@ -54,19 +54,24 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/sourceImage"
|
app:layout_constraintStart_toEndOf="@+id/sourceImage"
|
||||||
app:layout_constraintTop_toTopOf="@+id/sourceImage"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Titre" />
|
tools:text="Titre" />
|
||||||
|
|
||||||
<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_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
|
@@ -10,6 +10,8 @@
|
|||||||
android:layout_width="46dp"
|
android:layout_width="46dp"
|
||||||
android:layout_height="46dp"
|
android:layout_height="46dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:fontFamily="sans-serif"
|
android:fontFamily="sans-serif"
|
||||||
android:maxLines="3"
|
android:maxLines="3"
|
||||||
@@ -38,15 +40,17 @@
|
|||||||
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_marginEnd="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
android:gravity="start"
|
android:gravity="start"
|
||||||
android:maxLines="1"
|
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?android:textColorPrimary"
|
android:textColor="?android:textColorPrimary"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/itemImage"
|
app:layout_constraintStart_toEndOf="@+id/itemImage"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/itemImage"
|
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||||
tools:text="Google Actualité Il y a 5h" />
|
tools:text="Google Actualité Il y a 5h" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Lector per a Selfoss"</string>
|
<string name="app_name">"Lector per a Selfoss"</string>
|
||||||
<string name="title_activity_login">"Inicia la sessió"</string>
|
<string name="title_activity_login">"Inicia la sessió"</string>
|
||||||
<string name="prompt_password">"Contrasenya"</string>
|
<string name="prompt_password">"Contrasenya"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"La contrasenya és massa curta"</string>
|
<string name="error_invalid_password">"La contrasenya és massa curta"</string>
|
||||||
<string name="error_field_required">"Camp necessari"</string>
|
<string name="error_field_required">"Camp necessari"</string>
|
||||||
<string name="prompt_url">"URL"</string>
|
<string name="prompt_url">"URL"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,13 +23,6 @@
|
|||||||
<string name="wrong_infos">"Torneu a comprovar la informació."</string>
|
<string name="wrong_infos">"Torneu a comprovar la informació."</string>
|
||||||
<string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string>
|
<string name="all_posts_not_read">"No s'han llegit totes les publicacions"</string>
|
||||||
<string name="all_posts_read">"S'han llegit totes les publicacions"</string>
|
<string name="all_posts_read">"S'han llegit totes les publicacions"</string>
|
||||||
<string name="nothing_here">"No hi ha res"</string>
|
|
||||||
<string name="tab_new">"Nou"</string>
|
|
||||||
<string name="tab_read">"Tot"</string>
|
|
||||||
<string name="tab_favs">"Preferits"</string>
|
|
||||||
<string name="action_about">"Quant a"</string>
|
|
||||||
<string name="marked_as_read">"Element llegit"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Desfés"</string>
|
<string name="undo_string">"Desfés"</string>
|
||||||
<string name="addStringNoUrl">"Inicieu la sessió per afegir fonts."</string>
|
<string name="addStringNoUrl">"Inicieu la sessió per afegir fonts."</string>
|
||||||
<string name="cant_get_sources">"No es pot obtenir la llista de fonts."</string>
|
<string name="cant_get_sources">"No es pot obtenir la llista de fonts."</string>
|
||||||
@@ -90,7 +84,7 @@
|
|||||||
<string name="pref_switch_items_caching">Guarda els elements per utilitzar-los sense connexió</string>
|
<string name="pref_switch_items_caching">Guarda els elements per utilitzar-los sense connexió</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">"Network connection lost"</string>
|
<string name="network_connectivity_lost">"Sense connexió!"</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">Sincronitza els articles</string>
|
<string name="pref_switch_periodic_refresh">Sincronitza els articles</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
|
<string name="pref_switch_periodic_refresh_off">Els articles no se sincronitzaran en segon pla</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"No hi ha res"</string>
|
||||||
|
<string name="tab_new">"Nou"</string>
|
||||||
|
<string name="tab_read">"Tot"</string>
|
||||||
|
<string name="tab_favs">"Preferits"</string>
|
||||||
|
<string name="action_about">"Quant a"</string>
|
||||||
|
<string name="marked_as_read">"Element llegit"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,12 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader für selfoss"</string>
|
<string name="app_name">"Reader für Selfoss"</string>
|
||||||
<string name="title_activity_login">"Anmelden"</string>
|
<string name="title_activity_login">"Anmelden"</string>
|
||||||
<string name="prompt_password">"Passwort"</string>
|
<string name="prompt_password">"Passwort"</string>
|
||||||
<string name="action_sign_in">"Fortfahren"</string>
|
<string name="action_sign_in">"Fortfahren"</string>
|
||||||
<string name="error_invalid_password">"Passwort ist nicht lang genug"</string>
|
<string name="error_invalid_password">"Passwort ist nicht lang genug"</string>
|
||||||
<string name="error_field_required">"Pflichtfeld"</string>
|
<string name="error_field_required">"Pflichtfeld"</string>
|
||||||
<string name="prompt_url">"URL"</string>
|
<string name="prompt_url">"URL"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,22 +23,15 @@
|
|||||||
<string name="wrong_infos">"Überprüfe deine Angaben noch einmal."</string>
|
<string name="wrong_infos">"Überprüfe deine Angaben noch einmal."</string>
|
||||||
<string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string>
|
<string name="all_posts_not_read">"Nicht alle Beiträge wurden gelesen"</string>
|
||||||
<string name="all_posts_read">"Alle Beiträge wurden gelesen"</string>
|
<string name="all_posts_read">"Alle Beiträge wurden gelesen"</string>
|
||||||
<string name="nothing_here">"Keine Einträge vorhanden"</string>
|
|
||||||
<string name="tab_new">"Neu"</string>
|
|
||||||
<string name="tab_read">"Alle"</string>
|
|
||||||
<string name="tab_favs">"Favoriten"</string>
|
|
||||||
<string name="action_about">"Über"</string>
|
|
||||||
<string name="marked_as_read">"Artikel gelesen"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Rückgängig"</string>
|
<string name="undo_string">"Rückgängig"</string>
|
||||||
<string name="addStringNoUrl">"Melde dich an um Quellen hinzuzufügen."</string>
|
<string name="addStringNoUrl">"Melde dich an um Quellen hinzuzufügen."</string>
|
||||||
<string name="cant_get_sources">"Quellen können nicht abgerufen werden."</string>
|
<string name="cant_get_sources">"Quellen können nicht abgerufen werden."</string>
|
||||||
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
|
<string name="cant_create_source">"Quelle kann nicht gespeichert werden."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Fehler beim Laden der Spouts-Liste aufgrund von Netzwerkproblemen."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Fehler beim Laden der Spouts-Liste, möglicherweise aufgrund eines API-Fehlers."</string>
|
||||||
<string name="form_not_complete">"Das Formular ist nicht vollständig"</string>
|
<string name="form_not_complete">"Das Formular ist nicht vollständig"</string>
|
||||||
<string name="pref_header_links">"Links"</string>
|
<string name="pref_header_links">"Links"</string>
|
||||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
<string name="issue_tracker_link">"Ticketsystem"</string>
|
||||||
<string name="issue_tracker_summary">"Melde einen Bug oder rege ein neues Feature an"</string>
|
<string name="issue_tracker_summary">"Melde einen Bug oder rege ein neues Feature an"</string>
|
||||||
<string name="warning_wrong_url">"WARNUNG"</string>
|
<string name="warning_wrong_url">"WARNUNG"</string>
|
||||||
<string name="pref_switch_card_view_title">"Kachelansicht"</string>
|
<string name="pref_switch_card_view_title">"Kachelansicht"</string>
|
||||||
@@ -64,71 +58,78 @@
|
|||||||
<string name="filter_item_tags">Tags</string>
|
<string name="filter_item_tags">Tags</string>
|
||||||
<string name="filter_item_sources">Quellen</string>
|
<string name="filter_item_sources">Quellen</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">Quelle konnte nicht gelöscht werden…</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="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">Anzahl der zu ladenden Artikel</string>
|
||||||
<string name="pref_general_infinite_loading_title">Load more articles on scroll</string>
|
<string name="pref_general_infinite_loading_title">Weitere Artikel beim Navigieren laden</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">Der Artikel-Link ist ungültig. Ich such nach einer Lösung dieses Problems, damit die App nicht abstürzt.</string>
|
||||||
<string name="items_number_should_be_number">The items number should be an integer.</string>
|
<string name="items_number_should_be_number">Die Anzahl der Artikel sollte eine Ganzzahl sein.</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">Artikel als gelesen markieren, wenn zwischen den Artikeln gewischt wird.</string>
|
||||||
<string name="add_to_favs_reader">Zu Favoriten hinzufügen</string>
|
<string name="add_to_favs_reader">Zu Favoriten hinzufügen</string>
|
||||||
<string name="pref_content_reader_font_size">Article reader content font size</string>
|
<string name="pref_content_reader_font_size">Schriftgröße im Lesemodus</string>
|
||||||
<string name="pref_header_viewer">Article viewer</string>
|
<string name="pref_header_viewer">Lesemodus</string>
|
||||||
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</string>
|
<string name="refresh_dialog_message">Dies wird die Selfoss-Instanz aktualisieren.</string>
|
||||||
<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">Artikel nicht als gelesen markieren, wenn zwischen den Artikeln gewischt wird.</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-Modus und 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">Artikel werden nicht lokal zwischengespeichert wodurch die App nicht offline nutzbar ist.</string>
|
||||||
<string name="pref_switch_items_caching_on">Articles will be saved to the device memory and will be used for offline use.</string>
|
<string name="pref_switch_items_caching_on">Artikel werden lokal zwischengespeichert wodurch die App offline nutzbar ist.</string>
|
||||||
<string name="pref_switch_items_caching">Save items for offline use</string>
|
<string name="pref_switch_items_caching">Artikel lokal zwischenspeichern</string>
|
||||||
<string name="pref_switch_update_sources">Check for new sources and tags</string>
|
<string name="pref_switch_update_sources">Nach neuen Quellen und Tags suchen</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">Diese Funktion sollte deaktiviert werden, wenn der Server übermäßig viele Datenbankanfragen erhält.</string>
|
||||||
<string name="network_connectivity_lost">"Die Netzwerkverbindung wurde unterbrochen"</string>
|
<string name="network_connectivity_lost">"Die Netzwerkverbindung wurde unterbrochen"</string>
|
||||||
<string name="network_connectivity_retrieved">"Netzwerkverbindung ist jetzt verfügbar"</string>
|
<string name="network_connectivity_retrieved">"Netzwerkverbindung ist jetzt verfügbar"</string>
|
||||||
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
|
<string name="pref_switch_periodic_refresh">Synchronisiere Artikel</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
|
<string name="pref_switch_periodic_refresh_off">Artikel werden nicht im Hintergrund synchronisiert</string>
|
||||||
<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[Aktualisierungsintervall (>= 15 Minuten)]]></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">Synchronisationsbenachrichtigung</string>
|
||||||
<string name="new_items_channel_sync">New items notification</string>
|
<string name="new_items_channel_sync">Benachrichtigung bei neuen Artikeln</string>
|
||||||
<string name="new_items_notification_title">New items !</string>
|
<string name="new_items_notification_title">Neue Artikel!</string>
|
||||||
<string name="new_items_notification_text">%1$d new items loaded.</string>
|
<string name="new_items_notification_text">%1$d neue Artikel geladen.</string>
|
||||||
<string name="pref_switch_notify_new_items">Notify on new items synced.</string>
|
<string name="pref_switch_notify_new_items">Benachrichtigung bei neuen Artikeln</string>
|
||||||
<string name="shortcut_offline">Offline</string>
|
<string name="shortcut_offline">Offline</string>
|
||||||
<string name="pref_api_timeout">API-Zeitüberschreitung</string>
|
<string name="pref_api_timeout">API-Zeitüberschreitung</string>
|
||||||
<string name="pref_header_experimental">Experimentell</string>
|
<string name="pref_header_experimental">Experimentell</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 ist nicht verfügbar. Deaktiviere den Lesemodus, um zukünftige Abstürze zu vermeiden. Lade von nun an die Nachrichten in deinen Browser.</string>
|
||||||
<string name="webview_dialog_issue_title">Webview issue</string>
|
<string name="webview_dialog_issue_title">Webview-Probleme</string>
|
||||||
<string name="reader_text_align_left">Align left</string>
|
<string name="reader_text_align_left">Linksbündig</string>
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
<string name="reader_text_align_justify">Blocksatz</string>
|
||||||
<string name="settings_reader_font">Reader font</string>
|
<string name="settings_reader_font">Schriftgröße im Lesemodus</string>
|
||||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
<string name="reader_static_bar_title">Statische untere Leiste im Lesemodus</string>
|
||||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
<string name="reader_static_bar_on">Die untere Leiste wird dauerhaft angezeigt</string>
|
||||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
<string name="reader_static_bar_off">Die untere Leiste kann über einen schwebenden Button angezeigt werden</string>
|
||||||
<string name="remove_source">Remove source</string>
|
<string name="remove_source">Quelle entfernen</string>
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
<string name="pref_theme_title">Heller/Dunkler Modus</string>
|
||||||
<string name="mode_dark">Dark mode</string>
|
<string name="mode_dark">Dunkler Modus</string>
|
||||||
<string name="mode_system">Follow the system setting</string>
|
<string name="mode_system">Systemeinstellungen übernehmen</string>
|
||||||
<string name="mode_light">Light mode</string>
|
<string name="mode_light">Heller Modus</string>
|
||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">Diese App teilt keine persönlichen Daten.</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="gdpr_dialog_message"><![CDATA[Das Senden von Absturzberichten ist jetzt aktiviert. Die Funktion kann auf der Einstellungsseite deaktiviert werden, beachte aber bitte, dass Absturzberichte für die Anwendungsentwicklung von entscheidender Bedeutung sind.]]></string>
|
||||||
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
|
<string name="crash_toast_text">Die App ist abgestürzt. Details werden an den Entwickler gesendet.</string>
|
||||||
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
|
<string name="pref_switch_disable_acra">"Automatische Fehlerberichterstattung deaktivieren."</string>
|
||||||
<string name="menu_home_filter">Filters</string>
|
<string name="menu_home_filter">Filter</string>
|
||||||
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
|
<string name="application_selfoss_only">Diese App funktioniert nur mit einer Selfoss-Instanz, nicht mit einzelnen RSS-Feeds.</string>
|
||||||
<string name="menu_home_sources">Sources</string>
|
<string name="menu_home_sources">Quellen</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Quelle aktualisieren</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Verbindung trennen?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">Die Verbindung zur Selfoss-Instanz wird getrennt.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Keine Einträge vorhanden"</string>
|
||||||
|
<string name="tab_new">"Neu"</string>
|
||||||
|
<string name="tab_read">"Alle"</string>
|
||||||
|
<string name="tab_favs">"Favoriten"</string>
|
||||||
|
<string name="action_about">"Über"</string>
|
||||||
|
<string name="marked_as_read">"Artikel gelesen"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Lector para Selfoss"</string>
|
<string name="app_name">"Lector para Selfoss"</string>
|
||||||
<string name="title_activity_login">"Iniciar sesión"</string>
|
<string name="title_activity_login">"Iniciar sesión"</string>
|
||||||
<string name="prompt_password">"Contraseña"</string>
|
<string name="prompt_password">"Contraseña"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"La contraseña no es suficientemente larga"</string>
|
<string name="error_invalid_password">"La contraseña no es suficientemente larga"</string>
|
||||||
<string name="error_field_required">"Campo requerido"</string>
|
<string name="error_field_required">"Campo requerido"</string>
|
||||||
<string name="prompt_url">"Url"</string>
|
<string name="prompt_url">"Url"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"Revise sus datos de nuevo."</string>
|
<string name="wrong_infos">"Revise sus datos de nuevo."</string>
|
||||||
<string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string>
|
<string name="all_posts_not_read">"No todas las publicaciones fueron leídas"</string>
|
||||||
<string name="all_posts_read">"Todas las publicaciones fueron leídas"</string>
|
<string name="all_posts_read">"Todas las publicaciones fueron leídas"</string>
|
||||||
<string name="nothing_here">"Nada aquí"</string>
|
|
||||||
<string name="tab_new">"Nuevo"</string>
|
|
||||||
<string name="tab_read">"Todo"</string>
|
|
||||||
<string name="tab_favs">"Favoritos"</string>
|
|
||||||
<string name="action_about">"Acerca de"</string>
|
|
||||||
<string name="marked_as_read">"Artículo leído"</string>
|
|
||||||
<string name="marked_as_unread">"Artículo no leído"</string>
|
|
||||||
<string name="undo_string">"Deshacer"</string>
|
<string name="undo_string">"Deshacer"</string>
|
||||||
<string name="addStringNoUrl">"Iniciar sesión para añadir fuentes."</string>
|
<string name="addStringNoUrl">"Iniciar sesión para añadir fuentes."</string>
|
||||||
<string name="cant_get_sources">"No se puede obtener la lista de fuentes."</string>
|
<string name="cant_get_sources">"No se puede obtener la lista de fuentes."</string>
|
||||||
<string name="cant_create_source">"No se puede crear la fuente."</string>
|
<string name="cant_create_source">"No se puede crear la fuente."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"No se puede obtener la lista de fuentes."</string>
|
||||||
<string name="form_not_complete">"El formulario no está completo"</string>
|
<string name="form_not_complete">"El formulario no está completo"</string>
|
||||||
<string name="pref_header_links">"Enlaces"</string>
|
<string name="pref_header_links">"Enlaces"</string>
|
||||||
<string name="issue_tracker_link">"Rastreador de Incidencias"</string>
|
<string name="issue_tracker_link">"Rastreador de Incidencias"</string>
|
||||||
@@ -90,7 +84,7 @@
|
|||||||
<string name="pref_switch_items_caching">Guardar elementos para uso sin conexión</string>
|
<string name="pref_switch_items_caching">Guardar elementos para uso sin conexión</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">"Network connection lost"</string>
|
<string name="network_connectivity_lost">"Sin conexión!"</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">Sincronizar artículos</string>
|
<string name="pref_switch_periodic_refresh">Sincronizar artículos</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
|
<string name="pref_switch_periodic_refresh_off">Los artículos no se sincronizarán en segundo plano</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Nada aquí"</string>
|
||||||
|
<string name="tab_new">"Nuevo"</string>
|
||||||
|
<string name="tab_read">"Todo"</string>
|
||||||
|
<string name="tab_favs">"Favoritos"</string>
|
||||||
|
<string name="action_about">"Acerca de"</string>
|
||||||
|
<string name="marked_as_read">"Artículo leído"</string>
|
||||||
|
<string name="marked_as_unread">"Artículo no leído"</string>
|
||||||
|
</resources>
|
@@ -1,134 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
|
||||||
<string name="title_activity_login">"Log in"</string>
|
|
||||||
<string name="prompt_password">"Password"</string>
|
|
||||||
<string name="action_sign_in">"Go"</string>
|
|
||||||
<string name="error_invalid_password">"Password not long enough"</string>
|
|
||||||
<string name="error_field_required">"Field required"</string>
|
|
||||||
<string name="prompt_url">"Url"</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="prompt_login">"Username"</string>
|
|
||||||
<string name="readAll">"Read all"</string>
|
|
||||||
<string name="action_disconnect">"Disconnect"</string>
|
|
||||||
<string name="title_activity_settings">"Settings"</string>
|
|
||||||
<string name="pref_header_general">"General"</string>
|
|
||||||
<string name="add_source_hint_tags">"Tag1, Tag2, Tag3"</string>
|
|
||||||
<string name="add_source_hint_url">"Link"</string>
|
|
||||||
<string name="add_source_hint_name">"Name"</string>
|
|
||||||
<string name="add_source">"Add a source"</string>
|
|
||||||
<string name="add_source_save">"Save"</string>
|
|
||||||
<string name="wrong_infos">"Check your details again."</string>
|
|
||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
|
||||||
<string name="all_posts_read">"All posts were read"</string>
|
|
||||||
<string name="nothing_here">"Nothing here"</string>
|
|
||||||
<string name="tab_new">"New"</string>
|
|
||||||
<string name="tab_read">"All"</string>
|
|
||||||
<string name="tab_favs">"Favorites"</string>
|
|
||||||
<string name="action_about">"About"</string>
|
|
||||||
<string name="marked_as_read">"Item read"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Undo"</string>
|
|
||||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
|
||||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
|
||||||
<string name="cant_create_source">"Can't create source."</string>
|
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
|
||||||
<string name="form_not_complete">"The form is not complete"</string>
|
|
||||||
<string name="pref_header_links">"Links"</string>
|
|
||||||
<string name="issue_tracker_link">"Issue Tracker"</string>
|
|
||||||
<string name="issue_tracker_summary">"Report a bug or ask for a new feature"</string>
|
|
||||||
<string name="warning_wrong_url">"WARNING"</string>
|
|
||||||
<string name="pref_switch_card_view_title">"Card View"</string>
|
|
||||||
<string name="share">"Share"</string>
|
|
||||||
<string name="switch_unread_count">"Display the unread count as a badge for the bottom bar."</string>
|
|
||||||
<string name="switch_unread_count_title">"Display unread count"</string>
|
|
||||||
<string name="display_all_counts_title">"Display count for favorite and read"</string>
|
|
||||||
<string name="text_wrong_url">"You seem to be trying to use an invalid URL. Make sure it is correct, and if the problem persists, contact me (via the store contact link). Please note that the app needs you to be using Selfoss. You can't access RSS feeds without it."</string>
|
|
||||||
<string name="pref_article_viewer_title">"Open links inside the app"</string>
|
|
||||||
<string name="pref_article_viewer_on">"Articles will open inside the app"</string>
|
|
||||||
<string name="pref_article_viewer_off">"Articles will open with your default browser"</string>
|
|
||||||
<string name="pref_general_category_links">"Link handling"</string>
|
|
||||||
<string name="pref_general_category_displaying">"Displaying"</string>
|
|
||||||
<string name="pref_switch_card_view_on">"The articles will be displayed as cards"</string>
|
|
||||||
<string name="pref_switch_card_view_off">"The articles will be displayed as a list"</string>
|
|
||||||
<string name="menu_home_refresh">"Update remote"</string>
|
|
||||||
<string name="refresh_success_response">"The remote is updated, you can now reload the articles list"</string>
|
|
||||||
<string name="refresh_failer_message">"The update didn't work, try again later, or check your selfoss logs."</string>
|
|
||||||
<string name="refresh_in_progress">"Refresh in progress"</string>
|
|
||||||
<string name="card_height_title">Full height cards</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="source_code">Source code</string>
|
|
||||||
<string name="filter_item_tags">Tags</string>
|
|
||||||
<string name="filter_item_sources">Sources</string>
|
|
||||||
<string name="menu_home_search">Search</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="pref_header_theme">Themes</string>
|
|
||||||
<string name="pref_selfoss_category">Selfoss Api</string>
|
|
||||||
<string name="pref_api_items_number_title">Loaded items number</string>
|
|
||||||
<string name="pref_general_infinite_loading_title">Load more articles on scroll</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="items_number_should_be_number">The items number should be an integer.</string>
|
|
||||||
<string name="reader_action_open">Open in browser</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="add_to_favs_reader">Add to favorites</string>
|
|
||||||
<string name="pref_content_reader_font_size">Article reader content font size</string>
|
|
||||||
<string name="pref_header_viewer">Article viewer</string>
|
|
||||||
<string name="refresh_dialog_message">This will refresh your Selfoss instance.</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_off">Don\'t mark articles as read when swiping.</string>
|
|
||||||
<string name="unmark">Mark item as unread</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_on">Articles will be saved to the device memory and will be used 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_summary">Disable this if your server is receiving excessive amounts of database queries.</string>
|
|
||||||
<string name="network_connectivity_lost">"Network connection lost"</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_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_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="loading_notification_title">Loading …</string>
|
|
||||||
<string name="loading_notification_text">Selfoss is syncing your articles</string>
|
|
||||||
<string name="notification_channel_sync">Sync notification</string>
|
|
||||||
<string name="new_items_channel_sync">New items notification</string>
|
|
||||||
<string name="new_items_notification_title">New items !</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="shortcut_offline">Offline</string>
|
|
||||||
<string name="pref_api_timeout">Api Timeout</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_title">Webview issue</string>
|
|
||||||
<string name="reader_text_align_left">Align left</string>
|
|
||||||
<string name="reader_text_align_justify">Justify</string>
|
|
||||||
<string name="settings_reader_font">Reader font</string>
|
|
||||||
<string name="reader_static_bar_title">Static bottom bar in the article viewer</string>
|
|
||||||
<string name="reader_static_bar_on">The bottom bar will always be displayed</string>
|
|
||||||
<string name="reader_static_bar_off">The bottom bar can be shown through the floating button</string>
|
|
||||||
<string name="remove_source">Remove source</string>
|
|
||||||
<string name="pref_theme_title">Light/Dark mode</string>
|
|
||||||
<string name="mode_dark">Dark mode</string>
|
|
||||||
<string name="mode_system">Follow the system setting</string>
|
|
||||||
<string name="mode_light">Light mode</string>
|
|
||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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>
|
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
|
||||||
</resources>
|
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"Login"</string>
|
<string name="title_activity_login">"Login"</string>
|
||||||
<string name="prompt_password">"Mot de passe"</string>
|
<string name="prompt_password">"Mot de passe"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Mot de passe trop court"</string>
|
<string name="error_invalid_password">"Mot de passe trop court"</string>
|
||||||
<string name="error_field_required">"Champ requis"</string>
|
<string name="error_field_required">"Champ requis"</string>
|
||||||
<string name="prompt_url">"Url Selfoss"</string>
|
<string name="prompt_url">"Url Selfoss"</string>
|
||||||
|
<string name="disable_ssl">"Désactiver la vérification SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,16 +23,9 @@
|
|||||||
<string name="wrong_infos">"Vérifiez vos informations."</string>
|
<string name="wrong_infos">"Vérifiez vos informations."</string>
|
||||||
<string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string>
|
<string name="all_posts_not_read">"Tous les posts n'ont pas été lus"</string>
|
||||||
<string name="all_posts_read">"Tous les posts sont lus"</string>
|
<string name="all_posts_read">"Tous les posts sont lus"</string>
|
||||||
<string name="nothing_here">"Il n'y a rien ici !"</string>
|
|
||||||
<string name="tab_new">"Non lus"</string>
|
|
||||||
<string name="tab_read">"Tous"</string>
|
|
||||||
<string name="tab_favs">"Favoris"</string>
|
|
||||||
<string name="action_about">"À propos"</string>
|
|
||||||
<string name="marked_as_read">"Marqué comme lu"</string>
|
|
||||||
<string name="marked_as_unread">"Marqué comme non lu"</string>
|
|
||||||
<string name="undo_string">"Annuler"</string>
|
<string name="undo_string">"Annuler"</string>
|
||||||
<string name="addStringNoUrl">"Identifiez-vous pour ajouter une source."</string>
|
<string name="addStringNoUrl">"Identifiez-vous pour ajouter une source."</string>
|
||||||
<string name="cant_get_sources">"Impossible de récupérer la liste des sources"</string>
|
<string name="cant_get_sources">"Impossible de récupérer la liste des sources."</string>
|
||||||
<string name="cant_create_source">"Impossible de créer la source."</string>
|
<string name="cant_create_source">"Impossible de créer la source."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string>
|
<string name="cant_get_spouts_no_network">"Impossible d'obtenir la liste des spouts en raison d'un problème de réseau."</string>
|
||||||
<string name="cant_get_spouts">"Impossible d'obtenir la liste des spouts. Cela pourrait venir de l'api."</string>
|
<string name="cant_get_spouts">"Impossible d'obtenir la liste des spouts. Cela pourrait venir de l'api."</string>
|
||||||
@@ -42,13 +36,13 @@
|
|||||||
<string name="warning_wrong_url">"ATTENTION"</string>
|
<string name="warning_wrong_url">"ATTENTION"</string>
|
||||||
<string name="pref_switch_card_view_title">"Vue en carte"</string>
|
<string name="pref_switch_card_view_title">"Vue en carte"</string>
|
||||||
<string name="share">"Partager"</string>
|
<string name="share">"Partager"</string>
|
||||||
<string name="switch_unread_count">"Afficher le nombre d'articles non lus sur la barre en bas de l'écran"</string>
|
<string name="switch_unread_count">"Afficher le nombre d'articles non lus sur la barre en bas de l'écran."</string>
|
||||||
<string name="switch_unread_count_title">"Afficher le nombre de non lus"</string>
|
<string name="switch_unread_count_title">"Afficher le nombre de non lus"</string>
|
||||||
<string name="display_all_counts_title">"Afficher le nombre de favoris et d'articles lus"</string>
|
<string name="display_all_counts_title">"Afficher le nombre de favoris et d'articles lus"</string>
|
||||||
<string name="text_wrong_url">"Vous semblez essayer de vous connecter avec une URL invalide. Assurez-vous que c'est la bonne, et si le problème persiste, contactez-moi via le lien du play store. Notez aussi que l'application ne peut fonctionner sans l'application web Selfoss. Vous ne pouvez pas utiliser l'application pour accéder directement aux flux RSS."</string>
|
<string name="text_wrong_url">"Vous semblez essayer de vous connecter avec une URL invalide. Assurez-vous que c'est la bonne, et si le problème persiste, contactez-moi via le lien du play store. Notez aussi que l'application ne peut fonctionner sans l'application web Selfoss. Vous ne pouvez pas utiliser l'application pour accéder directement aux flux RSS."</string>
|
||||||
<string name="pref_article_viewer_title">"Ouvrir les liens dans l'application"</string>
|
<string name="pref_article_viewer_title">"Ouvrir les liens dans l'application"</string>
|
||||||
<string name="pref_article_viewer_on">"Les articles s'ouvriront dans l'application"</string>
|
<string name="pref_article_viewer_on">"Les articles s'ouvriront dans l'application"</string>
|
||||||
<string name="pref_article_viewer_off">"Les articles s'ouvriront dans votre naviguateur par défaut"</string>
|
<string name="pref_article_viewer_off">"Les articles s'ouvriront dans votre navigateur par défaut"</string>
|
||||||
<string name="pref_general_category_links">"Gestion des liens"</string>
|
<string name="pref_general_category_links">"Gestion des liens"</string>
|
||||||
<string name="pref_general_category_displaying">"Affichage"</string>
|
<string name="pref_general_category_displaying">"Affichage"</string>
|
||||||
<string name="pref_switch_card_view_on">"Les articles seront affichés en forme de carte"</string>
|
<string name="pref_switch_card_view_on">"Les articles seront affichés en forme de carte"</string>
|
||||||
@@ -65,7 +59,7 @@
|
|||||||
<string name="filter_item_sources">Sources</string>
|
<string name="filter_item_sources">Sources</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ème 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="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>
|
||||||
@@ -81,7 +75,7 @@
|
|||||||
<string name="pref_header_viewer">Lecteur d\'articles</string>
|
<string name="pref_header_viewer">Lecteur d\'articles</string>
|
||||||
<string name="refresh_dialog_message">En validant, votre instance Selfoss sera mise à jour.</string>
|
<string name="refresh_dialog_message">En validant, votre instance Selfoss sera mise à jour.</string>
|
||||||
<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="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>
|
||||||
@@ -93,9 +87,9 @@
|
|||||||
<string name="network_connectivity_lost">"Connexion au réseau perdue"</string>
|
<string name="network_connectivity_lost">"Connexion au réseau perdue"</string>
|
||||||
<string name="network_connectivity_retrieved">"Connexion réseau de nouveau disponible"</string>
|
<string name="network_connectivity_retrieved">"Connexion réseau de nouveau disponible"</string>
|
||||||
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
|
<string name="pref_switch_periodic_refresh">Synchroniser les articles</string>
|
||||||
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière plan</string>
|
<string name="pref_switch_periodic_refresh_off">Les articles ne seront pas synchronisés en arrière-plan</string>
|
||||||
<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és</string>
|
||||||
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Interval de synchronisation ( >= 15 minutes)]]></string>
|
<string name="pref_periodic_refresh_minutes_title"><![CDATA[Intervalle 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>
|
||||||
@@ -107,7 +101,7 @@
|
|||||||
<string name="shortcut_offline">Hors ligne</string>
|
<string name="shortcut_offline">Hors ligne</string>
|
||||||
<string name="pref_api_timeout">Timeout de l\'api</string>
|
<string name="pref_api_timeout">Timeout de l\'api</string>
|
||||||
<string name="pref_header_experimental">Expérimental</string>
|
<string name="pref_header_experimental">Expérimental</string>
|
||||||
<string name="webview_dialog_issue_message">Pas de Webview disponible. Désactivation du lecteur d\'article pour éviter les futures erreurs. Les articles seront lu via votre navigateur à l\'avenir.</string>
|
<string name="webview_dialog_issue_message">Pas de Webview disponible. Désactivation du lecteur d\'article pour éviter les futures erreurs. Les articles seront lus via votre navigateur à l\'avenir.</string>
|
||||||
<string name="webview_dialog_issue_title">Problème de Webview</string>
|
<string name="webview_dialog_issue_title">Problème de Webview</string>
|
||||||
<string name="reader_text_align_left">Aligner à gauche</string>
|
<string name="reader_text_align_left">Aligner à gauche</string>
|
||||||
<string name="reader_text_align_justify">Justifier le texte</string>
|
<string name="reader_text_align_justify">Justifier le texte</string>
|
||||||
@@ -120,15 +114,22 @@
|
|||||||
<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="gdpr_dialog_title">L\'application ne partage aucune information personnelle.</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="gdpr_dialog_message"><![CDATA[Le rapport de plantage est activés par défaut. Il peut être désactivé depuis les paramètres de l\'application. Notez que les rapports de plantage sont essentiels pour le développement de l\'application.]]></string>
|
||||||
<string name="crash_toast_text">A crash occured. Sending the details to the developper.</string>
|
<string name="crash_toast_text">Un bug s\'est produit. Le développeur en sera informé.</string>
|
||||||
<string name="pref_switch_disable_acra">"Disable automatic bug reporting. "</string>
|
<string name="pref_switch_disable_acra">"Désactiver les rapports de plantage."</string>
|
||||||
<string name="menu_home_filter">Filters</string>
|
<string name="menu_home_filter">Filtres</string>
|
||||||
<string name="application_selfoss_only">This app only works with a Selfoss instance, and no other RSS feed.</string>
|
<string name="application_selfoss_only">Cette application ne fonctionne qu\'avec l\'api de Selfoss, et aucun autre.</string>
|
||||||
<string name="menu_home_sources">Sources</string>
|
<string name="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Mise à jour des sources</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Se déconnecter ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">Vous allez être déconnecté de votre instance Selfoss.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Il n'y a rien ici !"</string>
|
||||||
|
<string name="tab_new">"Non lus"</string>
|
||||||
|
<string name="tab_read">"Tous"</string>
|
||||||
|
<string name="tab_favs">"Favoris"</string>
|
||||||
|
<string name="action_about">"À propos"</string>
|
||||||
|
<string name="marked_as_read">"Marqué comme lu"</string>
|
||||||
|
<string name="marked_as_unread">"Marqué comme non lu"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Lector para selfoss"</string>
|
<string name="app_name">"Lector para selfoss"</string>
|
||||||
<string name="title_activity_login">"Conectar"</string>
|
<string name="title_activity_login">"Conectar"</string>
|
||||||
<string name="prompt_password">"Contrasinal"</string>
|
<string name="prompt_password">"Contrasinal"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"O contrasinal non é suficientemente longo"</string>
|
<string name="error_invalid_password">"O contrasinal non é suficientemente longo"</string>
|
||||||
<string name="error_field_required">"Campo requirido"</string>
|
<string name="error_field_required">"Campo requirido"</string>
|
||||||
<string name="prompt_url">"URL"</string>
|
<string name="prompt_url">"URL"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,13 +23,6 @@
|
|||||||
<string name="wrong_infos">"Comprobar os teus detalles de novo."</string>
|
<string name="wrong_infos">"Comprobar os teus detalles de novo."</string>
|
||||||
<string name="all_posts_not_read">"Non se leron todas as publicacións"</string>
|
<string name="all_posts_not_read">"Non se leron todas as publicacións"</string>
|
||||||
<string name="all_posts_read">"Leronse todas as publicacións"</string>
|
<string name="all_posts_read">"Leronse todas as publicacións"</string>
|
||||||
<string name="nothing_here">"Non hai nada aquí"</string>
|
|
||||||
<string name="tab_new">"Novo"</string>
|
|
||||||
<string name="tab_read">"Todos"</string>
|
|
||||||
<string name="tab_favs">"Favoritos"</string>
|
|
||||||
<string name="action_about">"Acerca de"</string>
|
|
||||||
<string name="marked_as_read">"Elemento lido"</string>
|
|
||||||
<string name="marked_as_unread">"Elemento non lido"</string>
|
|
||||||
<string name="undo_string">"Desfacer"</string>
|
<string name="undo_string">"Desfacer"</string>
|
||||||
<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>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</string>
|
<string name="gdpr_dialog_title">A aplicación non comparte ningún dato persoal seu.</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="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="crash_toast_text">Ocurriu un erro. Enviando os detalles o desenvolvedor.</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="pref_switch_disable_acra">"Deshabilitar o reporte automático de erros."</string>
|
||||||
<string name="menu_home_filter">Filtros</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Non hai nada aquí"</string>
|
||||||
|
<string name="tab_new">"Novo"</string>
|
||||||
|
<string name="tab_read">"Todos"</string>
|
||||||
|
<string name="tab_favs">"Favoritos"</string>
|
||||||
|
<string name="action_about">"Acerca de"</string>
|
||||||
|
<string name="marked_as_read">"Elemento lido"</string>
|
||||||
|
<string name="marked_as_unread">"Elemento non lido"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"Masuk"</string>
|
<string name="title_activity_login">"Masuk"</string>
|
||||||
<string name="prompt_password">"Kata sandi"</string>
|
<string name="prompt_password">"Kata sandi"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Kata sandinya tidak cukup panjang"</string>
|
<string name="error_invalid_password">"Kata sandinya tidak cukup panjang"</string>
|
||||||
<string name="error_field_required">"Kolom wajib diisi"</string>
|
<string name="error_field_required">"Kolom wajib diisi"</string>
|
||||||
<string name="prompt_url">"URL"</string>
|
<string name="prompt_url">"URL"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"Periksa kembali detail Anda."</string>
|
<string name="wrong_infos">"Periksa kembali detail Anda."</string>
|
||||||
<string name="all_posts_not_read">"Semua pos belum dibaca"</string>
|
<string name="all_posts_not_read">"Semua pos belum dibaca"</string>
|
||||||
<string name="all_posts_read">"Semua pos sudah dibaca"</string>
|
<string name="all_posts_read">"Semua pos sudah dibaca"</string>
|
||||||
<string name="nothing_here">"Tidak ada di sini"</string>
|
|
||||||
<string name="tab_new">"Baru"</string>
|
|
||||||
<string name="tab_read">"Semua"</string>
|
|
||||||
<string name="tab_favs">"Favorit"</string>
|
|
||||||
<string name="action_about">"Tentang"</string>
|
|
||||||
<string name="marked_as_read">"Membaca item"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Urung"</string>
|
<string name="undo_string">"Urung"</string>
|
||||||
<string name="addStringNoUrl">"Masuk untuk menambah sumber."</string>
|
<string name="addStringNoUrl">"Masuk untuk menambah sumber."</string>
|
||||||
<string name="cant_get_sources">"Tidak bisa mendapatkan daftar sumber."</string>
|
<string name="cant_get_sources">"Tidak bisa mendapatkan daftar sumber."</string>
|
||||||
<string name="cant_create_source">"Tidak dapat membuat sumber."</string>
|
<string name="cant_create_source">"Tidak dapat membuat sumber."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Tidak bisa masuk ke daftar Spouts."</string>
|
||||||
<string name="form_not_complete">"Formulirnya belum selesai"</string>
|
<string name="form_not_complete">"Formulirnya belum selesai"</string>
|
||||||
<string name="pref_header_links">"Tautan"</string>
|
<string name="pref_header_links">"Tautan"</string>
|
||||||
<string name="issue_tracker_link">"Pelacak Masalah"</string>
|
<string name="issue_tracker_link">"Pelacak Masalah"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Tidak ada di sini"</string>
|
||||||
|
<string name="tab_new">"Baru"</string>
|
||||||
|
<string name="tab_read">"Semua"</string>
|
||||||
|
<string name="tab_favs">"Favorit"</string>
|
||||||
|
<string name="action_about">"Tentang"</string>
|
||||||
|
<string name="marked_as_read">"Membaca item"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Lettore RSS per Selfoss"</string>
|
<string name="app_name">"Lettore RSS per Selfoss"</string>
|
||||||
<string name="title_activity_login">"Accedi"</string>
|
<string name="title_activity_login">"Accedi"</string>
|
||||||
<string name="prompt_password">"Password"</string>
|
<string name="prompt_password">"Password"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"La password non è sufficientemente lunga"</string>
|
<string name="error_invalid_password">"La password non è sufficientemente lunga"</string>
|
||||||
<string name="error_field_required">"Campo obbligatorio"</string>
|
<string name="error_field_required">"Campo obbligatorio"</string>
|
||||||
<string name="prompt_url">"URL"</string>
|
<string name="prompt_url">"URL"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,13 +23,6 @@
|
|||||||
<string name="wrong_infos">"Controlla nuovamente i dati."</string>
|
<string name="wrong_infos">"Controlla nuovamente i dati."</string>
|
||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"Tutti i messaggi sono stati letti"</string>
|
<string name="all_posts_read">"Tutti i messaggi sono stati letti"</string>
|
||||||
<string name="nothing_here">"Non c'è niente qui"</string>
|
|
||||||
<string name="tab_new">"Nuovi"</string>
|
|
||||||
<string name="tab_read">"Tutti"</string>
|
|
||||||
<string name="tab_favs">"Preferiti"</string>
|
|
||||||
<string name="action_about">"Informazioni"</string>
|
|
||||||
<string name="marked_as_read">"Articolo letto"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Annulla"</string>
|
<string name="undo_string">"Annulla"</string>
|
||||||
<string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string>
|
<string name="addStringNoUrl">"Autenticati per aggiungere fonti."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Non c'è niente qui"</string>
|
||||||
|
<string name="tab_new">"Nuovi"</string>
|
||||||
|
<string name="tab_read">"Tutti"</string>
|
||||||
|
<string name="tab_favs">"Preferiti"</string>
|
||||||
|
<string name="action_about">"Informazioni"</string>
|
||||||
|
<string name="marked_as_read">"Articolo letto"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"로그인"</string>
|
<string name="title_activity_login">"로그인"</string>
|
||||||
<string name="prompt_password">"비밀번호"</string>
|
<string name="prompt_password">"비밀번호"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"패스워드가 짧습니다."</string>
|
<string name="error_invalid_password">"패스워드가 짧습니다."</string>
|
||||||
<string name="error_field_required">"필수 항목"</string>
|
<string name="error_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">"로그인이 필요합니까?"</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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"세부 정보를 다시 확인하세요."</string>
|
<string name="wrong_infos">"세부 정보를 다시 확인하세요."</string>
|
||||||
<string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string>
|
<string name="all_posts_not_read">"모든 게시물을 읽지 않았습니다."</string>
|
||||||
<string name="all_posts_read">"모든 게시물을 읽었습니다."</string>
|
<string name="all_posts_read">"모든 게시물을 읽었습니다."</string>
|
||||||
<string name="nothing_here">"비어있음"</string>
|
|
||||||
<string name="tab_new">"새로운"</string>
|
|
||||||
<string name="tab_read">"전체"</string>
|
|
||||||
<string name="tab_favs">"즐겨찾기"</string>
|
|
||||||
<string name="action_about">"정보"</string>
|
|
||||||
<string name="marked_as_read">"항목 읽기"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"실행 취소"</string>
|
<string name="undo_string">"실행 취소"</string>
|
||||||
<string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string>
|
<string name="addStringNoUrl">"로그인 소스를 추가 해야 합니다."</string>
|
||||||
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
|
<string name="cant_get_sources">"소스 리스트를 얻을 수 없습니다."</string>
|
||||||
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
|
<string name="cant_create_source">"소스를 만들 수 없습니다."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Spouts 목록을 가져올 수 없습니다."</string>
|
||||||
<string name="form_not_complete">"양식이 완료되지 않았습니다."</string>
|
<string name="form_not_complete">"양식이 완료되지 않았습니다."</string>
|
||||||
<string name="pref_header_links">"링크"</string>
|
<string name="pref_header_links">"링크"</string>
|
||||||
<string name="issue_tracker_link">"이슈 트래커"</string>
|
<string name="issue_tracker_link">"이슈 트래커"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"비어있음"</string>
|
||||||
|
<string name="tab_new">"새로운"</string>
|
||||||
|
<string name="tab_read">"전체"</string>
|
||||||
|
<string name="tab_favs">"즐겨찾기"</string>
|
||||||
|
<string name="action_about">"정보"</string>
|
||||||
|
<string name="marked_as_read">"항목 읽기"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Selfoss Reader"</string>
|
<string name="app_name">"Selfoss Reader"</string>
|
||||||
<string name="title_activity_login">"Inloggen"</string>
|
<string name="title_activity_login">"Inloggen"</string>
|
||||||
<string name="prompt_password">"Wachtwoord"</string>
|
<string name="prompt_password">"Wachtwoord"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Wachtwoord niet lang genoeg"</string>
|
<string name="error_invalid_password">"Wachtwoord niet lang genoeg"</string>
|
||||||
<string name="error_field_required">"Dit veld is verplicht"</string>
|
<string name="error_field_required">"Dit veld is verplicht"</string>
|
||||||
<string name="prompt_url">"Selfoss server"</string>
|
<string name="prompt_url">"Selfoss server"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,11 @@
|
|||||||
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
<string name="wrong_infos">"Controleer de gegevens nogmaals."</string>
|
||||||
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
|
<string name="all_posts_not_read">"Fout bij markeren als gelezen"</string>
|
||||||
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
|
<string name="all_posts_read">"Alle artikelen gemarkeerd als gelezen"</string>
|
||||||
<string name="nothing_here">"Niets gevonden"</string>
|
|
||||||
<string name="tab_new">"Nieuw"</string>
|
|
||||||
<string name="tab_read">"Alle"</string>
|
|
||||||
<string name="tab_favs">"Favorieten"</string>
|
|
||||||
<string name="action_about">"Over"</string>
|
|
||||||
<string name="marked_as_read">"Artikel gelezen"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Ongedaan maken"</string>
|
|
||||||
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
|
<string name="addStringNoUrl">"Login om bronnen toe te voegen"</string>
|
||||||
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
|
<string name="cant_get_sources">"Kan de lijst met bronnen niet ophalen"</string>
|
||||||
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
<string name="cant_create_source">"Kan bron niet creëeren"</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Ophalen spouts mislukt"</string>
|
||||||
<string name="form_not_complete">"Formulier is niet volledig ingevuld"</string>
|
<string name="form_not_complete">"Formulier is niet volledig ingevuld"</string>
|
||||||
<string name="pref_header_links">"Links"</string>
|
<string name="pref_header_links">"Links"</string>
|
||||||
<string name="issue_tracker_link">"Bug tracker"</string>
|
<string name="issue_tracker_link">"Bug tracker"</string>
|
||||||
@@ -92,7 +85,7 @@
|
|||||||
<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">"Network connection lost"</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">Artikel synchronisieren</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>
|
||||||
@@ -123,12 +116,20 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Niets gevonden"</string>
|
||||||
|
<string name="tab_new">"Nieuw"</string>
|
||||||
|
<string name="tab_read">"Alle"</string>
|
||||||
|
<string name="tab_favs">"Favorieten"</string>
|
||||||
|
<string name="action_about">"Over"</string>
|
||||||
|
<string name="marked_as_read">"Artikel gelezen"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
<string name="undo_string">"Ongedaan maken"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"Entrar"</string>
|
<string name="title_activity_login">"Entrar"</string>
|
||||||
<string name="prompt_password">"Senha"</string>
|
<string name="prompt_password">"Senha"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Senha muito pequena"</string>
|
<string name="error_invalid_password">"Senha muito pequena"</string>
|
||||||
<string name="error_field_required">"Campo obrigatório"</string>
|
<string name="error_field_required">"Campo obrigatório"</string>
|
||||||
<string name="prompt_url">"Url"</string>
|
<string name="prompt_url">"Url"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"Verifique os detalhes novamente."</string>
|
<string name="wrong_infos">"Verifique os detalhes novamente."</string>
|
||||||
<string name="all_posts_not_read">"Nenhum post foi lido"</string>
|
<string name="all_posts_not_read">"Nenhum post foi lido"</string>
|
||||||
<string name="all_posts_read">"Todos os posts foram lidos"</string>
|
<string name="all_posts_read">"Todos os posts foram lidos"</string>
|
||||||
<string name="nothing_here">"Nada aqui"</string>
|
|
||||||
<string name="tab_new">"Novo"</string>
|
|
||||||
<string name="tab_read">"Todos"</string>
|
|
||||||
<string name="tab_favs">"Favoritos"</string>
|
|
||||||
<string name="action_about">"Sobre"</string>
|
|
||||||
<string name="marked_as_read">"Item lido"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Desfazer"</string>
|
<string name="undo_string">"Desfazer"</string>
|
||||||
<string name="addStringNoUrl">"Faça login para adicionar fontes."</string>
|
<string name="addStringNoUrl">"Faça login para adicionar fontes."</string>
|
||||||
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
||||||
<string name="cant_create_source">"Não é possível criar fonte."</string>
|
<string name="cant_create_source">"Não é possível criar fonte."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Não é possível obter a lista de spouts."</string>
|
||||||
<string name="form_not_complete">"O formulário não está completo"</string>
|
<string name="form_not_complete">"O formulário não está completo"</string>
|
||||||
<string name="pref_header_links">"Links"</string>
|
<string name="pref_header_links">"Links"</string>
|
||||||
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Nada aqui"</string>
|
||||||
|
<string name="tab_new">"Novo"</string>
|
||||||
|
<string name="tab_read">"Todos"</string>
|
||||||
|
<string name="tab_favs">"Favoritos"</string>
|
||||||
|
<string name="action_about">"Sobre"</string>
|
||||||
|
<string name="marked_as_read">"Item lido"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Leitor para Selfoss"</string>
|
<string name="app_name">"Leitor para Selfoss"</string>
|
||||||
<string name="title_activity_login">"Iniciar sessão"</string>
|
<string name="title_activity_login">"Iniciar sessão"</string>
|
||||||
<string name="prompt_password">"Palavra passe"</string>
|
<string name="prompt_password">"Palavra passe"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Senha não é longa o suficiente"</string>
|
<string name="error_invalid_password">"Senha não é longa o suficiente"</string>
|
||||||
<string name="error_field_required">"Campo obrigatório"</string>
|
<string name="error_field_required">"Campo obrigatório"</string>
|
||||||
<string name="prompt_url">"Url"</string>
|
<string name="prompt_url">"Url"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"Verifique seus dados novamente."</string>
|
<string name="wrong_infos">"Verifique seus dados novamente."</string>
|
||||||
<string name="all_posts_not_read">"Todas as postagens não foram lidas"</string>
|
<string name="all_posts_not_read">"Todas as postagens não foram lidas"</string>
|
||||||
<string name="all_posts_read">"Todas as postagens foram lidas"</string>
|
<string name="all_posts_read">"Todas as postagens foram lidas"</string>
|
||||||
<string name="nothing_here">"Nada aqui"</string>
|
|
||||||
<string name="tab_new">"Novo"</string>
|
|
||||||
<string name="tab_read">"Tudo"</string>
|
|
||||||
<string name="tab_favs">"Favoritos"</string>
|
|
||||||
<string name="action_about">"Sobre"</string>
|
|
||||||
<string name="marked_as_read">"Item lido"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Desfazer"</string>
|
<string name="undo_string">"Desfazer"</string>
|
||||||
<string name="addStringNoUrl">"Logar para adicionar fontes."</string>
|
<string name="addStringNoUrl">"Logar para adicionar fontes."</string>
|
||||||
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
<string name="cant_get_sources">"Não é possível obter a lista de fontes."</string>
|
||||||
<string name="cant_create_source">"Não é possível criar a fonte."</string>
|
<string name="cant_create_source">"Não é possível criar a fonte."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Não é possível obter a lista de bicos."</string>
|
||||||
<string name="form_not_complete">"O formulário não está completo"</string>
|
<string name="form_not_complete">"O formulário não está completo"</string>
|
||||||
<string name="pref_header_links">"Links"</string>
|
<string name="pref_header_links">"Links"</string>
|
||||||
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
<string name="issue_tracker_link">"Rastreador de problemas"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Nada aqui"</string>
|
||||||
|
<string name="tab_new">"Novo"</string>
|
||||||
|
<string name="tab_read">"Tudo"</string>
|
||||||
|
<string name="tab_favs">"Favoritos"</string>
|
||||||
|
<string name="action_about">"Sobre"</string>
|
||||||
|
<string name="marked_as_read">"Item lido"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"පිවිසෙන්න"</string>
|
<string name="title_activity_login">"පිවිසෙන්න"</string>
|
||||||
<string name="prompt_password">"මුර පදය"</string>
|
<string name="prompt_password">"මුර පදය"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<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">"පරිශීලක නාමය"</string>
|
<string name="prompt_login">"පරිශීලක නාමය"</string>
|
||||||
@@ -22,13 +23,6 @@
|
|||||||
<string name="wrong_infos">"Check your details again."</string>
|
<string name="wrong_infos">"Check your details again."</string>
|
||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"All posts were read"</string>
|
<string name="all_posts_read">"All posts were read"</string>
|
||||||
<string name="nothing_here">"Nothing here"</string>
|
|
||||||
<string name="tab_new">"New"</string>
|
|
||||||
<string name="tab_read">"සියල්ල"</string>
|
|
||||||
<string name="tab_favs">"Favorites"</string>
|
|
||||||
<string name="action_about">"මේ ගැන"</string>
|
|
||||||
<string name="marked_as_read">"Item read"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Undo"</string>
|
<string name="undo_string">"Undo"</string>
|
||||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Nothing here"</string>
|
||||||
|
<string name="tab_new">"New"</string>
|
||||||
|
<string name="tab_read">"සියල්ල"</string>
|
||||||
|
<string name="tab_favs">"Favorites"</string>
|
||||||
|
<string name="action_about">"මේ ගැන"</string>
|
||||||
|
<string name="marked_as_read">"Item read"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Selfoss için okuyucu"</string>
|
<string name="app_name">"Selfoss için okuyucu"</string>
|
||||||
<string name="title_activity_login">"Giriş"</string>
|
<string name="title_activity_login">"Giriş"</string>
|
||||||
<string name="prompt_password">"Şifre"</string>
|
<string name="prompt_password">"Şifre"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"Parola yeterince uzun değil"</string>
|
<string name="error_invalid_password">"Parola yeterince uzun değil"</string>
|
||||||
<string name="error_field_required">"Alan gereklidir"</string>
|
<string name="error_field_required">"Alan gereklidir"</string>
|
||||||
<string name="prompt_url">"Url"</string>
|
<string name="prompt_url">"Url"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"Detaylarınızı tekrar kontrol edin."</string>
|
<string name="wrong_infos">"Detaylarınızı tekrar kontrol edin."</string>
|
||||||
<string name="all_posts_not_read">"Tüm mesajlar okunmadı"</string>
|
<string name="all_posts_not_read">"Tüm mesajlar okunmadı"</string>
|
||||||
<string name="all_posts_read">"Tüm mesajlar okundu"</string>
|
<string name="all_posts_read">"Tüm mesajlar okundu"</string>
|
||||||
<string name="nothing_here">"Burada hiçbir şey yok"</string>
|
|
||||||
<string name="tab_new">"Yeni"</string>
|
|
||||||
<string name="tab_read">"Tüm"</string>
|
|
||||||
<string name="tab_favs">"Favoriler"</string>
|
|
||||||
<string name="action_about">"Hakkında"</string>
|
|
||||||
<string name="marked_as_read">"Öğeleri oku"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Geri al"</string>
|
<string name="undo_string">"Geri al"</string>
|
||||||
<string name="addStringNoUrl">"Kaynakları eklemek için giriş yapın."</string>
|
<string name="addStringNoUrl">"Kaynakları eklemek için giriş yapın."</string>
|
||||||
<string name="cant_get_sources">"Kaynakları listesi alınamıyor."</string>
|
<string name="cant_get_sources">"Kaynakları listesi alınamıyor."</string>
|
||||||
<string name="cant_create_source">"Kaynak oluşturulamıyor."</string>
|
<string name="cant_create_source">"Kaynak oluşturulamıyor."</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"Spouts listesine girilemiyor."</string>
|
||||||
<string name="form_not_complete">"Form tamamlanamadı"</string>
|
<string name="form_not_complete">"Form tamamlanamadı"</string>
|
||||||
<string name="pref_header_links">"Bağlantılar"</string>
|
<string name="pref_header_links">"Bağlantılar"</string>
|
||||||
<string name="issue_tracker_link">"Sorun İzleyici"</string>
|
<string name="issue_tracker_link">"Sorun İzleyici"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"Burada hiçbir şey yok"</string>
|
||||||
|
<string name="tab_new">"Yeni"</string>
|
||||||
|
<string name="tab_read">"Tüm"</string>
|
||||||
|
<string name="tab_favs">"Favoriler"</string>
|
||||||
|
<string name="action_about">"Hakkında"</string>
|
||||||
|
<string name="marked_as_read">"Öğeleri oku"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Selfoss 阅读器"</string>
|
<string name="app_name">"Selfoss 阅读器"</string>
|
||||||
<string name="title_activity_login">"登录"</string>
|
<string name="title_activity_login">"登录"</string>
|
||||||
<string name="prompt_password">"密码"</string>
|
<string name="prompt_password">"密码"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"密码不够长"</string>
|
<string name="error_invalid_password">"密码不够长"</string>
|
||||||
<string name="error_field_required">"必填字段"</string>
|
<string name="error_field_required">"必填字段"</string>
|
||||||
<string name="prompt_url">"网址"</string>
|
<string name="prompt_url">"网址"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,13 +23,6 @@
|
|||||||
<string name="wrong_infos">"再次检查您的详细信息。"</string>
|
<string name="wrong_infos">"再次检查您的详细信息。"</string>
|
||||||
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
||||||
<string name="all_posts_read">"所有帖子已读"</string>
|
<string name="all_posts_read">"所有帖子已读"</string>
|
||||||
<string name="nothing_here">"暂无内容!"</string>
|
|
||||||
<string name="tab_new">"新建"</string>
|
|
||||||
<string name="tab_read">"所有"</string>
|
|
||||||
<string name="tab_favs">"收藏夹"</string>
|
|
||||||
<string name="action_about">"关于我们"</string>
|
|
||||||
<string name="marked_as_read">"已读"</string>
|
|
||||||
<string name="marked_as_unread">"未读条目"</string>
|
|
||||||
<string name="undo_string">"撤销"</string>
|
<string name="undo_string">"撤销"</string>
|
||||||
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string>
|
<string name="gdpr_dialog_title">该应用不分享任何关于您的个人数据。</string>
|
||||||
<string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></string>
|
<string name="gdpr_dialog_message"><![CDATA[崩溃报告发送现已启用。 可以从设置页面禁用它。 请记住,崩溃报告对于应用程序开发是必需的。]]></string>
|
||||||
<string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string>
|
<string name="crash_toast_text">发生崩溃。请将细节发送给开发人员。</string>
|
||||||
<string name="pref_switch_disable_acra">"禁用自动错误报告 "</string>
|
<string name="pref_switch_disable_acra">"禁用自动错误报告"</string>
|
||||||
<string name="menu_home_filter">筛选器</string>
|
<string name="menu_home_filter">筛选器</string>
|
||||||
<string name="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string>
|
<string name="application_selfoss_only">此应用只适用于 Selfoss 实例,不适用于其他 RSS 。</string>
|
||||||
<string name="menu_home_sources">源</string>
|
<string name="menu_home_sources">源</string>
|
||||||
<string name="update_source">更新源</string>
|
<string name="update_source">更新源</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">断开连接?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">您将断开与 selfoss 实例的连接。</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"暂无内容!"</string>
|
||||||
|
<string name="tab_new">"新建"</string>
|
||||||
|
<string name="tab_read">"所有"</string>
|
||||||
|
<string name="tab_favs">"收藏夹"</string>
|
||||||
|
<string name="action_about">"关于我们"</string>
|
||||||
|
<string name="marked_as_read">"已读"</string>
|
||||||
|
<string name="marked_as_unread">"未读条目"</string>
|
||||||
|
</resources>
|
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Selfoss 阅读器"</string>
|
<string name="app_name">"Selfoss 阅读器"</string>
|
||||||
<string name="title_activity_login">"登录"</string>
|
<string name="title_activity_login">"登录"</string>
|
||||||
<string name="prompt_password">"密码"</string>
|
<string name="prompt_password">"密码"</string>
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
<string name="error_invalid_password">"密码不够长"</string>
|
<string name="error_invalid_password">"密码不够长"</string>
|
||||||
<string name="error_field_required">"欄位必填"</string>
|
<string name="error_field_required">"欄位必填"</string>
|
||||||
<string name="prompt_url">"网址"</string>
|
<string name="prompt_url">"网址"</string>
|
||||||
|
<string name="disable_ssl">"Disable SSL"</string>
|
||||||
<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>
|
||||||
@@ -22,19 +23,12 @@
|
|||||||
<string name="wrong_infos">"再次检查您的详细信息。"</string>
|
<string name="wrong_infos">"再次检查您的详细信息。"</string>
|
||||||
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
<string name="all_posts_not_read">"所有帖子都未读"</string>
|
||||||
<string name="all_posts_read">"所有帖子已读"</string>
|
<string name="all_posts_read">"所有帖子已读"</string>
|
||||||
<string name="nothing_here">"暂无内容!"</string>
|
|
||||||
<string name="tab_new">"新建"</string>
|
|
||||||
<string name="tab_read">"所有"</string>
|
|
||||||
<string name="tab_favs">"收藏夹"</string>
|
|
||||||
<string name="action_about">"关于我们"</string>
|
|
||||||
<string name="marked_as_read">"已读"</string>
|
|
||||||
<string name="marked_as_unread">"未讀項目"</string>
|
|
||||||
<string name="undo_string">"撤销"</string>
|
<string name="undo_string">"撤销"</string>
|
||||||
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
<string name="addStringNoUrl">"登录以添加数据源。"</string>
|
||||||
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
<string name="cant_get_sources">"无法获取数据列表。"</string>
|
||||||
<string name="cant_create_source">"无法创建源数据。"</string>
|
<string name="cant_create_source">"无法创建源数据。"</string>
|
||||||
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
<string name="cant_get_spouts_no_network">"Can't get spouts list because of a network issue."</string>
|
||||||
<string name="cant_get_spouts">"Can't get spouts list. There may ben an api issue."</string>
|
<string name="cant_get_spouts">"无法获取数据列表"</string>
|
||||||
<string name="form_not_complete">"窗体未完成"</string>
|
<string name="form_not_complete">"窗体未完成"</string>
|
||||||
<string name="pref_header_links">"链接"</string>
|
<string name="pref_header_links">"链接"</string>
|
||||||
<string name="issue_tracker_link">"问题追踪器"</string>
|
<string name="issue_tracker_link">"问题追踪器"</string>
|
||||||
@@ -123,12 +117,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
<string name="disable_ssl">Disable SSL</string>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
</resources>
|
<string name="nothing_here">"暂无内容!"</string>
|
||||||
|
<string name="tab_new">"新建"</string>
|
||||||
|
<string name="tab_read">"所有"</string>
|
||||||
|
<string name="tab_favs">"收藏夹"</string>
|
||||||
|
<string name="action_about">"关于我们"</string>
|
||||||
|
<string name="marked_as_read">"已读"</string>
|
||||||
|
<string name="marked_as_unread">"未讀項目"</string>
|
||||||
|
</resources>
|
@@ -1,4 +1,4 @@
|
|||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources>
|
||||||
<string name="app_name">"Reader for Selfoss"</string>
|
<string name="app_name">"Reader for Selfoss"</string>
|
||||||
<string name="title_activity_login">"Log in"</string>
|
<string name="title_activity_login">"Log in"</string>
|
||||||
<string name="prompt_password">"Password"</string>
|
<string name="prompt_password">"Password"</string>
|
||||||
@@ -22,13 +22,6 @@
|
|||||||
<string name="wrong_infos">"Check your details again."</string>
|
<string name="wrong_infos">"Check your details again."</string>
|
||||||
<string name="all_posts_not_read">"All posts weren't read"</string>
|
<string name="all_posts_not_read">"All posts weren't read"</string>
|
||||||
<string name="all_posts_read">"All posts were read"</string>
|
<string name="all_posts_read">"All posts were read"</string>
|
||||||
<string name="nothing_here">"Nothing here"</string>
|
|
||||||
<string name="tab_new">"New"</string>
|
|
||||||
<string name="tab_read">"All"</string>
|
|
||||||
<string name="tab_favs">"Favorites"</string>
|
|
||||||
<string name="action_about">"About"</string>
|
|
||||||
<string name="marked_as_read">"Item read"</string>
|
|
||||||
<string name="marked_as_unread">"Item unread"</string>
|
|
||||||
<string name="undo_string">"Undo"</string>
|
<string name="undo_string">"Undo"</string>
|
||||||
<string name="addStringNoUrl">"Log in to add sources."</string>
|
<string name="addStringNoUrl">"Log in to add sources."</string>
|
||||||
<string name="cant_get_sources">"Can't get sources list."</string>
|
<string name="cant_get_sources">"Can't get sources list."</string>
|
||||||
@@ -55,8 +48,7 @@
|
|||||||
<string name="pref_switch_card_view_off">"The articles will be displayed as a list"</string>
|
<string name="pref_switch_card_view_off">"The articles will be displayed as a list"</string>
|
||||||
<string name="menu_home_refresh">"Update remote"</string>
|
<string name="menu_home_refresh">"Update remote"</string>
|
||||||
<string name="refresh_success_response">"The remote is updated, you can now reload the articles list"</string>
|
<string name="refresh_success_response">"The remote is updated, you can now reload the articles list"</string>
|
||||||
<string
|
<string name="refresh_failer_message">"The update didn't work, try again later, or check your selfoss logs."</string>
|
||||||
name="refresh_failer_message">"The update didn't work, try again later, or check your selfoss logs."</string>
|
|
||||||
<string name="refresh_in_progress">"Refresh in progress"</string>
|
<string name="refresh_in_progress">"Refresh in progress"</string>
|
||||||
<string name="card_height_title">Full height cards</string>
|
<string name="card_height_title">Full height cards</string>
|
||||||
<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>
|
||||||
@@ -127,11 +119,19 @@
|
|||||||
<string name="gdpr_dialog_title">The app does not share any personal data about you.</string>
|
<string name="gdpr_dialog_title">The app does not share any personal data about you.</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="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="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="pref_switch_disable_acra">"Disable automatic bug reporting."</string>
|
||||||
<string name="menu_home_filter">Filters</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="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="menu_home_sources">Sources</string>
|
||||||
<string name="update_source">Update source</string>
|
<string name="update_source">Update source</string>
|
||||||
<string name="confirm_disconnect_title">Disconnect ?</string>
|
<string name="confirm_disconnect_title">Disconnect ?</string>
|
||||||
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
<string name="confirm_disconnect_description">You will be disconnected from your selfoss instance.</string>
|
||||||
</resources>
|
<string name="no_browser">Can\'t open a link without a browser installed</string>
|
||||||
|
<string name="nothing_here">"Nothing here"</string>
|
||||||
|
<string name="tab_new">"New"</string>
|
||||||
|
<string name="tab_read">"All"</string>
|
||||||
|
<string name="tab_favs">"Favorites"</string>
|
||||||
|
<string name="action_about">"About"</string>
|
||||||
|
<string name="marked_as_read">"Item read"</string>
|
||||||
|
<string name="marked_as_unread">"Item unread"</string>
|
||||||
|
</resources>
|
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<ListPreference
|
|
||||||
android:defaultValue="0"
|
|
||||||
android:entries="@array/ModeTitles"
|
|
||||||
android:entryValues="@array/ModeValues"
|
|
||||||
android:key="currentMode"
|
|
||||||
app:iconSpaceReserved="false"
|
|
||||||
android:title="@string/pref_theme_title"
|
|
||||||
app:useSimpleSummaryProvider="false" />
|
|
||||||
</PreferenceScreen>
|
|
@@ -1,59 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.repository
|
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
|
||||||
|
|
||||||
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
|
|
||||||
return listOf(
|
|
||||||
ITEM(
|
|
||||||
id = item.id,
|
|
||||||
datetime = item.datetime,
|
|
||||||
title = item.title,
|
|
||||||
content = item.content,
|
|
||||||
unread = item.unread,
|
|
||||||
starred = item.starred,
|
|
||||||
thumbnail = item.thumbnail,
|
|
||||||
icon = item.icon,
|
|
||||||
link = item.link,
|
|
||||||
sourcetitle = item.sourcetitle,
|
|
||||||
tags = item.tags,
|
|
||||||
author = item.author,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
|
|
||||||
return listOf(
|
|
||||||
SelfossModel.Item(
|
|
||||||
id = item.id.toInt(),
|
|
||||||
datetime = item.datetime,
|
|
||||||
title = item.title,
|
|
||||||
content = item.content,
|
|
||||||
unread = item.unread,
|
|
||||||
starred = item.starred,
|
|
||||||
thumbnail = item.thumbnail,
|
|
||||||
icon = item.icon,
|
|
||||||
link = item.link,
|
|
||||||
sourcetitle = item.sourcetitle,
|
|
||||||
tags = item.tags.split(','),
|
|
||||||
author = item.author,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
class FakeItemParameters {
|
|
||||||
var id = "20"
|
|
||||||
var datetime = "2022-09-09T03:32:01-04:00"
|
|
||||||
val title = "Etica della ricerca sotto i riflettori."
|
|
||||||
val content =
|
|
||||||
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>L’ultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui l’intelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne l’origine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dell’innovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto all’attenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>L’argomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>L’embrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nell’offerta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso l’Ateneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
|
|
||||||
var unread = true
|
|
||||||
var starred = true
|
|
||||||
val thumbnail = null
|
|
||||||
val icon = "ba79e238383ce83c23a169929c8906ef.png"
|
|
||||||
val link =
|
|
||||||
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
|
|
||||||
var sourcetitle = "La Chimica e la Società"
|
|
||||||
var tags = "Chimica, Testing"
|
|
||||||
var author = "Someone important"
|
|
||||||
}
|
|
@@ -0,0 +1,27 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.tests.robolectric
|
||||||
|
|
||||||
|
import android.view.Menu
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.robolectric.shadows.ShadowDialog
|
||||||
|
|
||||||
|
fun dialogMessage(): String {
|
||||||
|
val latestDialog = ShadowDialog.getLatestDialog()
|
||||||
|
return latestDialog.findViewById<TextView>(android.R.id.message)?.text.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Menu.assertClickable(
|
||||||
|
@IdRes id: Int,
|
||||||
|
) {
|
||||||
|
this.assertVisible(id)
|
||||||
|
val item = this.findItem(id)
|
||||||
|
assertTrue(item.isEnabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Menu.assertVisible(
|
||||||
|
@IdRes id: Int,
|
||||||
|
) {
|
||||||
|
val item = this.findItem(id)
|
||||||
|
assertTrue(item.isVisible)
|
||||||
|
}
|
@@ -0,0 +1,75 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.tests.robolectric
|
||||||
|
|
||||||
|
import android.widget.Button
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.LoginActivity
|
||||||
|
import bou.amine.apps.readerforselfossv2.android.R
|
||||||
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.Robolectric
|
||||||
|
|
||||||
|
@RunWith(RobotElectriqueRunner::class)
|
||||||
|
class LoginActivityTest {
|
||||||
|
@Test
|
||||||
|
fun login_shouldDisplay() {
|
||||||
|
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||||
|
controller.setup() // Moves the Activity to the RESUMED state
|
||||||
|
|
||||||
|
val activity = controller.get()
|
||||||
|
assert(activity.findViewById<EditText>(R.id.urlView).isVisible)
|
||||||
|
assert(activity.findViewById<SwitchMaterial>(R.id.selfSigned).isVisible)
|
||||||
|
assert(activity.findViewById<SwitchMaterial>(R.id.selfSigned).isChecked.not())
|
||||||
|
assert(activity.findViewById<SwitchMaterial>(R.id.withLogin).isVisible)
|
||||||
|
assert(activity.findViewById<SwitchMaterial>(R.id.withLogin).isChecked.not())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun urlError() {
|
||||||
|
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||||
|
controller.setup() // Moves the Activity to the RESUMED state
|
||||||
|
val activity = controller.get()
|
||||||
|
|
||||||
|
val urlView = activity.findViewById<EditText>(R.id.urlView)
|
||||||
|
urlView.setText("172.17.0.1:8888")
|
||||||
|
|
||||||
|
activity.findViewById<Button>(R.id.signInButton).performClick()
|
||||||
|
|
||||||
|
urlView.performClick()
|
||||||
|
assertEquals(activity.getString(R.string.login_url_problem), urlView.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun multiError() {
|
||||||
|
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||||
|
controller.setup() // Moves the Activity to the RESUMED state
|
||||||
|
val activity = controller.get()
|
||||||
|
|
||||||
|
val signInButton = activity.findViewById<Button>(R.id.signInButton)
|
||||||
|
repeat(3) { signInButton.performClick() }
|
||||||
|
|
||||||
|
// Vérifie que l'avertissement est affiché
|
||||||
|
assertEquals(activity.getString(R.string.text_wrong_url), dialogMessage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @Test
|
||||||
|
fun connect() {
|
||||||
|
Robolectric.buildActivity(LoginActivity::class.java).use { controller ->
|
||||||
|
controller.setup() // Moves the Activity to the RESUMED state
|
||||||
|
val activity = controller.get()
|
||||||
|
val signInButton = activity.findViewById<Button>(R.id.signInButton)
|
||||||
|
val urlView = activity.findViewById<EditText>(R.id.urlView)
|
||||||
|
urlView.setText("http://10.0.2.2:8888")
|
||||||
|
signInButton.performClick()
|
||||||
|
|
||||||
|
val expectedIntent = Intent(activity, HomeActivity::class.java)
|
||||||
|
val actual = shadowOf(activity).nextStartedActivity
|
||||||
|
assertEquals(expectedIntent.component, actual.component)
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.android.tests.robolectric
|
||||||
|
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
class RobotElectriqueRunner(
|
||||||
|
testClass: Class<*>?,
|
||||||
|
) : RobolectricTestRunner(testClass) {
|
||||||
|
override fun buildGlobalConfig(): Config = Config.Builder().setSdk(25, 30, 33).build()
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.repository
|
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
||||||
@@ -6,12 +6,22 @@ import bou.amine.apps.readerforselfossv2.dao.TAG
|
|||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
||||||
|
import bou.amine.apps.readerforselfossv2.repository.Repository
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
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.toView
|
import bou.amine.apps.readerforselfossv2.utils.toView
|
||||||
import io.mockk.*
|
import io.mockk.clearAllMocks
|
||||||
import junit.framework.TestCase.*
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import junit.framework.TestCase.assertEquals
|
||||||
|
import junit.framework.TestCase.assertFalse
|
||||||
|
import junit.framework.TestCase.assertNotSame
|
||||||
|
import junit.framework.TestCase.assertSame
|
||||||
|
import junit.framework.TestCase.assertTrue
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.assertNotEquals
|
import org.junit.Assert.assertNotEquals
|
||||||
@@ -32,14 +42,15 @@ private const val FEED_URL = "https://test.com/feed"
|
|||||||
|
|
||||||
private const val TAGS = "Test, New"
|
private const val TAGS = "Test, New"
|
||||||
|
|
||||||
|
private const val NUMBER_ARTICLES = 100
|
||||||
|
private const val NUMBER_UNREAD = 50
|
||||||
|
private const val NUMBER_STARRED = 20
|
||||||
|
|
||||||
class RepositoryTest {
|
class RepositoryTest {
|
||||||
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
|
||||||
private val appSettingsService = mockk<AppSettingsService>()
|
private val appSettingsService = mockk<AppSettingsService>()
|
||||||
private val api = mockk<SelfossApi>()
|
private val api = mockk<SelfossApi>()
|
||||||
|
|
||||||
private val NUMBER_ARTICLES = 100
|
|
||||||
private val NUMBER_UNREAD = 50
|
|
||||||
private val NUMBER_STARRED = 20
|
|
||||||
private lateinit var repository: Repository
|
private lateinit var repository: Repository
|
||||||
|
|
||||||
private fun initializeRepository(
|
private fun initializeRepository(
|
||||||
@@ -67,7 +78,12 @@ class RepositoryTest {
|
|||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true)),
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
|
"2.19-ba1e8e3",
|
||||||
|
"4.0.0",
|
||||||
|
SelfossModel.ApiConfiguration(false, true),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
coEvery { api.stats() } returns
|
coEvery { api.stats() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
@@ -121,7 +137,12 @@ class RepositoryTest {
|
|||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true)),
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
|
"2.19-ba1e8e3",
|
||||||
|
"4.0.0",
|
||||||
|
SelfossModel.ApiConfiguration(true, true),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
||||||
@@ -137,7 +158,12 @@ class RepositoryTest {
|
|||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, true)),
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
|
"2.19-ba1e8e3",
|
||||||
|
"4.0.0",
|
||||||
|
SelfossModel.ApiConfiguration(true, true),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns "username"
|
every { appSettingsService.getUserName() } returns "username"
|
||||||
|
|
||||||
@@ -153,7 +179,12 @@ class RepositoryTest {
|
|||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(true, false)),
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
|
"2.19-ba1e8e3",
|
||||||
|
"4.0.0",
|
||||||
|
SelfossModel.ApiConfiguration(true, false),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
||||||
@@ -169,7 +200,12 @@ class RepositoryTest {
|
|||||||
coEvery { api.apiInformation() } returns
|
coEvery { api.apiInformation() } returns
|
||||||
StatusAndData(
|
StatusAndData(
|
||||||
success = true,
|
success = true,
|
||||||
data = SelfossModel.ApiInformation("2.19-ba1e8e3", "4.0.0", SelfossModel.ApiConfiguration(false, true)),
|
data =
|
||||||
|
SelfossModel.ApiInformation(
|
||||||
|
"2.19-ba1e8e3",
|
||||||
|
"4.0.0",
|
||||||
|
SelfossModel.ApiConfiguration(false, true),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
every { appSettingsService.getUserName() } returns ""
|
every { appSettingsService.getUserName() } returns ""
|
||||||
|
|
@@ -0,0 +1,57 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.tests.repository
|
||||||
|
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||||
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
|
|
||||||
|
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> =
|
||||||
|
listOf(
|
||||||
|
ITEM(
|
||||||
|
id = item.id,
|
||||||
|
datetime = item.datetime,
|
||||||
|
title = item.title,
|
||||||
|
content = item.content,
|
||||||
|
unread = item.unread,
|
||||||
|
starred = item.starred,
|
||||||
|
thumbnail = item.thumbnail,
|
||||||
|
icon = item.icon,
|
||||||
|
link = item.link,
|
||||||
|
sourcetitle = item.sourcetitle,
|
||||||
|
tags = item.tags,
|
||||||
|
author = item.author,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> =
|
||||||
|
listOf(
|
||||||
|
SelfossModel.Item(
|
||||||
|
id = item.id.toInt(),
|
||||||
|
datetime = item.datetime,
|
||||||
|
title = item.title,
|
||||||
|
content = item.content,
|
||||||
|
unread = item.unread,
|
||||||
|
starred = item.starred,
|
||||||
|
thumbnail = item.thumbnail,
|
||||||
|
icon = item.icon,
|
||||||
|
link = item.link,
|
||||||
|
sourcetitle = item.sourcetitle,
|
||||||
|
tags = item.tags.split(','),
|
||||||
|
author = item.author,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
class FakeItemParameters {
|
||||||
|
var id = "20"
|
||||||
|
var datetime = "2022-09-09T03:32:01-04:00"
|
||||||
|
val title = "Etica della ricerca sotto i riflettori."
|
||||||
|
val content =
|
||||||
|
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>L’etica della scienza è di certo ambito di cui continuiamo</p>"
|
||||||
|
var unread = true
|
||||||
|
var starred = true
|
||||||
|
val thumbnail = null
|
||||||
|
val icon = "ba79e238383ce83c23a169929c8906ef.png"
|
||||||
|
val link =
|
||||||
|
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
|
||||||
|
var sourcetitle = "La Chimica e la Società"
|
||||||
|
var tags = "Chimica, Testing"
|
||||||
|
var author = "Someone important"
|
||||||
|
}
|
@@ -1,18 +1,11 @@
|
|||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
// SqlDelight
|
|
||||||
classpath("com.squareup.sqldelight:gradle-plugin:1.5.5")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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("8.7.3").apply(false)
|
||||||
id("com.android.library").version("8.1.2").apply(false)
|
id("com.android.library").version("8.7.3").apply(false)
|
||||||
id("org.jetbrains.kotlin.android").version("1.9.10").apply(false)
|
id("org.jetbrains.kotlin.android").version("2.1.0").apply(false)
|
||||||
kotlin("multiplatform").version("1.9.10").apply(false)
|
kotlin("multiplatform").version("2.1.0").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)
|
id("org.jetbrains.kotlinx.kover") version "0.9.0" apply true
|
||||||
}
|
}
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
@@ -25,9 +18,10 @@ allprojects {
|
|||||||
|
|
||||||
|
|
||||||
tasks.register("clean", Delete::class) {
|
tasks.register("clean", Delete::class) {
|
||||||
delete(rootProject.buildDir)
|
delete(layout.buildDirectory)
|
||||||
}
|
}
|
||||||
|
|
||||||
koverMerged {
|
dependencies {
|
||||||
enable()
|
kover(project(":shared"))
|
||||||
|
kover(project(":androidApp"))
|
||||||
}
|
}
|
@@ -3,3 +3,4 @@ files:
|
|||||||
translation: /androidApp/src/main/res/values-%android_code%/%original_file_name%
|
translation: /androidApp/src/main/res/values-%android_code%/%original_file_name%
|
||||||
translate_attributes: '0'
|
translate_attributes: '0'
|
||||||
content_segmentation: '0'
|
content_segmentation: '0'
|
||||||
|
preserve_hierarchy: true
|
0
fastlane/metadata/android/en-US/changelogs/.gitkeep
Normal file
0
fastlane/metadata/android/en-US/changelogs/.gitkeep
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
**v124113311**
|
||||||
|
|
||||||
|
- chore: update versions. (#165)
|
||||||
|
- chore: fastlane changelog.
|
||||||
|
- chore: fastlane fixes.
|
||||||
|
- Changelog for v124113301
|
@@ -0,0 +1,4 @@
|
|||||||
|
**v124123421**
|
||||||
|
|
||||||
|
- fix: Trying to fix the serialization issue.
|
||||||
|
- Changelog for v124113311
|
@@ -0,0 +1,8 @@
|
|||||||
|
**v124123641**
|
||||||
|
|
||||||
|
- Chore: no tests on build.
|
||||||
|
- Merge pull request 'testing' (#170) from testing into master
|
||||||
|
- fix: Displaying fixes. Fixes #155
|
||||||
|
- test: coverage
|
||||||
|
- chore: update and use multiplatform datetime
|
||||||
|
- Changelog for v124123421
|
16
fastlane/metadata/android/en-US/changelogs/v124123651.txt
Normal file
16
fastlane/metadata/android/en-US/changelogs/v124123651.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
**v124123651**
|
||||||
|
|
||||||
|
- Merge pull request 'Bugfixes' (#171) from bugfixes into master
|
||||||
|
- config: crowdin
|
||||||
|
- chore: can links be empty ?
|
||||||
|
- fix: Context issues in article fragment.
|
||||||
|
- fix: Context issues in fragment sheet.
|
||||||
|
- fix: build.
|
||||||
|
- chore: compile issue fix.
|
||||||
|
- chore: filter some bugs.
|
||||||
|
- bugfix: catch users using something other than selfoss.
|
||||||
|
- bugfix: No browser, no link.
|
||||||
|
- translations
|
||||||
|
- chore: remove log.
|
||||||
|
- translation
|
||||||
|
- Changelog for v124123641
|
14
fastlane/metadata/android/en-US/changelogs/v125010031.txt
Normal file
14
fastlane/metadata/android/en-US/changelogs/v125010031.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
**v125010031**
|
||||||
|
|
||||||
|
- Merge pull request 'Bump dependencies' (#173) from upgarde into master
|
||||||
|
- chore: "faster" action.
|
||||||
|
- fastlane: icon change.
|
||||||
|
- chore: ignoring a pixel issue.
|
||||||
|
- test: fixed an ui test issue.
|
||||||
|
- fix: center the loading thing.
|
||||||
|
- test: items displaying.
|
||||||
|
- bump: sqldelight.
|
||||||
|
- bump: material, desugar jdk, jsoup, kodein, settings, napier, mock.
|
||||||
|
- bump: androix and coroutines.
|
||||||
|
- bump: ktor. Closes #67.
|
||||||
|
- Changelog for v124123651
|
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@@ -1 +1 @@
|
|||||||
A new RSS reader for <a href="http://selfoss.aditu.de/">selfoss</a>.
|
A new RSS reader for selfoss (http://selfoss.aditu.de/)
|
||||||
|
@@ -26,3 +26,4 @@ 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
|
||||||
|
org.gradle.configureondemand=true
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
#Thu Jul 13 11:41:19 CEST 2023
|
#Mon Nov 25 22:48:24 CET 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@@ -173,7 +173,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode\n";
|
shellScript = "export JAVA_HOME=/Users/amine/.sdkman/candidates/java/17.0.8.1-jbr\ncd \"$SRCROOT/..\"\n./gradlew :shared:embedAndSignAppleFrameworkForXcode --stacktrace\n";
|
||||||
};
|
};
|
||||||
/* End PBXShellScriptBuildPhase section */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
val ktorVersion = "2.3.2"
|
val ktorVersion = "3.0.3"
|
||||||
|
|
||||||
object SqlDelight {
|
object SqlDelight {
|
||||||
const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
|
const val runtime = "app.cash.sqldelight:runtime:2.0.2"
|
||||||
const val android = "com.squareup.sqldelight:android-driver:1.5.4"
|
const val android = "app.cash.sqldelight:android-driver:2.0.2"
|
||||||
const val native = "com.squareup.sqldelight:native-driver:1.5.4"
|
const val native = "app.cash.sqldelight:native-driver:2.0.2"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("multiplatform")
|
kotlin("multiplatform")
|
||||||
id("com.android.library")
|
id("com.android.library")
|
||||||
id("com.squareup.sqldelight")
|
|
||||||
kotlin("plugin.serialization") version "1.9.0"
|
kotlin("plugin.serialization") version "1.9.0"
|
||||||
id("org.jetbrains.kotlinx.kover")
|
id("org.jetbrains.kotlinx.kover")
|
||||||
|
id("app.cash.sqldelight") version "2.0.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
@@ -37,7 +37,7 @@ kotlin {
|
|||||||
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-auth:$ktorVersion")
|
implementation("io.ktor:ktor-client-auth:$ktorVersion")
|
||||||
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
implementation("io.ktor:ktor-client-cio:$ktorVersion")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
|
||||||
|
|
||||||
implementation("org.jsoup:jsoup:1.15.4")
|
implementation("org.jsoup:jsoup:1.15.4")
|
||||||
|
|
||||||
@@ -52,6 +52,9 @@ kotlin {
|
|||||||
|
|
||||||
// Sql
|
// Sql
|
||||||
implementation(SqlDelight.runtime)
|
implementation(SqlDelight.runtime)
|
||||||
|
|
||||||
|
// Sql
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
@@ -63,7 +66,6 @@ kotlin {
|
|||||||
val androidMain by getting {
|
val androidMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.squareup.okhttp3:okhttp:4.11.0")
|
implementation("com.squareup.okhttp3:okhttp:4.11.0")
|
||||||
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
|
||||||
@@ -80,24 +82,15 @@ kotlin {
|
|||||||
val iosArm64Main by getting
|
val iosArm64Main by getting
|
||||||
// val iosSimulatorArm64Main by getting
|
// val iosSimulatorArm64Main by getting
|
||||||
val iosMain by creating {
|
val iosMain by creating {
|
||||||
dependsOn(commonMain)
|
|
||||||
iosX64Main.dependsOn(this)
|
|
||||||
iosArm64Main.dependsOn(this)
|
|
||||||
// iosSimulatorArm64Main.dependsOn(this)
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(SqlDelight.native)
|
implementation(SqlDelight.native)
|
||||||
implementation("io.ktor:ktor-client-ios:2.1.1")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val iosX64Test by getting
|
val iosX64Test by getting
|
||||||
val iosArm64Test by getting
|
val iosArm64Test by getting
|
||||||
// val iosSimulatorArm64Test by getting
|
// val iosSimulatorArm64Test by getting
|
||||||
val iosTest by creating {
|
val iosTest by creating {
|
||||||
dependsOn(commonTest)
|
|
||||||
iosX64Test.dependsOn(this)
|
|
||||||
iosArm64Test.dependsOn(this)
|
|
||||||
// iosSimulatorArm64Test.dependsOn(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,11 +108,10 @@ android {
|
|||||||
namespace = "bou.amine.apps.readerforselfossv2"
|
namespace = "bou.amine.apps.readerforselfossv2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sqldelight {
|
sqldelight {
|
||||||
database("ReaderForSelfossDB") {
|
databases {
|
||||||
packageName = "bou.amine.apps.readerforselfossv2.dao"
|
create("ReaderForSelfossDB") {
|
||||||
sourceFolders = listOf("sqldelight")
|
packageName.set("bou.amine.apps.readerforselfossv2.dao")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,10 +0,0 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.dao
|
|
||||||
import android.content.Context
|
|
||||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
|
||||||
import com.squareup.sqldelight.db.SqlDriver
|
|
||||||
|
|
||||||
actual class DriverFactory(private val context: Context) {
|
|
||||||
actual fun createDriver(): SqlDriver {
|
|
||||||
return AndroidSqliteDriver(ReaderForSelfossDB.Schema, context, "ReaderForSelfossV2-android.db")
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,15 @@
|
|||||||
|
package bou.amine.apps.readerforselfossv2.dao
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
|
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
|
||||||
|
|
||||||
|
actual class DriverFactory(private val context: Context) {
|
||||||
|
actual fun createDriver(): SqlDriver {
|
||||||
|
return AndroidSqliteDriver(
|
||||||
|
ReaderForSelfossDB.Schema,
|
||||||
|
context,
|
||||||
|
"ReaderForSelfossV2-android.db",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -8,16 +8,18 @@ class NaiveTrustManager : X509TrustManager {
|
|||||||
override fun checkClientTrusted(
|
override fun checkClientTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<out X509Certificate>?,
|
chain: Array<out X509Certificate>?,
|
||||||
authType: String?,
|
authType: String?,
|
||||||
) {}
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers(): Array<out X509Certificate> = arrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun setupInsecureHTTPEngine(config: CIOEngineConfig) {
|
actual fun setupInsecureHttpEngine(config: CIOEngineConfig) {
|
||||||
config.https.trustManager = NaiveTrustManager()
|
config.https.trustManager = NaiveTrustManager()
|
||||||
}
|
}
|
||||||
|
@@ -1,34 +1,12 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
import android.text.format.DateUtils
|
import android.text.format.DateUtils
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
// We may need to consider moving the formatting to platform specific code, even if the tests are doubled
|
|
||||||
// For now, we handle this in a hacky way, because kotlin only accepts iso formats
|
|
||||||
actual fun parseDate(dateString: String): Long {
|
|
||||||
var isoDateString: String =
|
|
||||||
if (dateString.matches(oldVersionFormat)) {
|
|
||||||
dateString.replace(" ", "T")
|
|
||||||
} else if (dateString.matches(newVersionFormat)) {
|
|
||||||
dateString.split("+")[0]
|
|
||||||
} else {
|
|
||||||
throw Exception("Unrecognized format for $dateString")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = dateString.toParsedDate()
|
||||||
|
|
||||||
return " " +
|
return " " +
|
||||||
DateUtils.getRelativeTimeSpanString(
|
DateUtils.getRelativeTimeSpanString(
|
||||||
|
@@ -4,19 +4,13 @@ import android.net.Uri
|
|||||||
import android.text.Html
|
import android.text.Html
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
actual fun String.getHtmlDecoded(): String {
|
actual fun String.getHtmlDecoded(): String = Html.fromHtml(this).toString()
|
||||||
return Html.fromHtml(this).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getIcon(baseUrl: String): String {
|
actual fun SelfossModel.Item.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String {
|
actual fun SelfossModel.Item.getThumbnail(baseUrl: String): String = constructUrl(baseUrl, "thumbnails", thumbnail)
|
||||||
return constructUrl(baseUrl, "thumbnails", thumbnail)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
||||||
val allImages = ArrayList<String>()
|
val allImages = ArrayList<String>()
|
||||||
@@ -34,16 +28,14 @@ actual fun SelfossModel.Item.getImages(): ArrayList<String> {
|
|||||||
return allImages
|
return allImages
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun SelfossModel.Source.getIcon(baseUrl: String): String {
|
actual fun SelfossModel.Source.getIcon(baseUrl: String): String = constructUrl(baseUrl, "favicons", icon)
|
||||||
return constructUrl(baseUrl, "favicons", icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual fun constructUrl(
|
actual fun constructUrl(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
path: String,
|
path: String,
|
||||||
file: String?,
|
file: String?,
|
||||||
): String {
|
): String =
|
||||||
return if (file == null || file == "null" || file.isEmpty()) {
|
if (file == null || file == "null" || file.isEmpty()) {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
val baseUriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||||
@@ -51,4 +43,3 @@ actual fun constructUrl(
|
|||||||
|
|
||||||
baseUriBuilder.toString()
|
baseUriBuilder.toString()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.dao
|
package bou.amine.apps.readerforselfossv2.dao
|
||||||
|
|
||||||
import com.squareup.sqldelight.db.SqlDriver
|
import app.cash.sqldelight.db.SqlDriver
|
||||||
|
|
||||||
expect class DriverFactory {
|
expect class DriverFactory {
|
||||||
fun createDriver(): SqlDriver
|
fun createDriver(): SqlDriver
|
@@ -1,4 +1,4 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.DI
|
package bou.amine.apps.readerforselfossv2.di
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
@@ -7,7 +7,7 @@ class MercuryModel {
|
|||||||
class ParsedContent(
|
class ParsedContent(
|
||||||
val title: String? = null,
|
val title: String? = null,
|
||||||
val content: String? = null,
|
val content: String? = null,
|
||||||
val lead_image_url: String? = null, // NOSONAR
|
val lead_image_url: String? = null,
|
||||||
val url: String? = null,
|
val url: String? = null,
|
||||||
val error: Boolean? = null,
|
val error: Boolean? = null,
|
||||||
val message: String? = null,
|
val message: String? = null,
|
||||||
|
@@ -3,19 +3,20 @@ package bou.amine.apps.readerforselfossv2.model
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class SuccessResponse(val success: Boolean) {
|
class SuccessResponse(
|
||||||
|
val success: Boolean,
|
||||||
|
) {
|
||||||
val isSuccess: Boolean
|
val isSuccess: Boolean
|
||||||
get() = success
|
get() = success
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatusAndData<T>(val success: Boolean, val data: T? = null) {
|
class StatusAndData<T>(
|
||||||
|
val success: Boolean,
|
||||||
|
val data: T? = null,
|
||||||
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> succes(d: T): StatusAndData<T> {
|
fun <T> succes(d: T): StatusAndData<T> = StatusAndData(true, d)
|
||||||
return StatusAndData(true, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> error(): StatusAndData<T> {
|
fun <T> error(): StatusAndData<T> = StatusAndData(false)
|
||||||
return StatusAndData(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ package bou.amine.apps.readerforselfossv2.model
|
|||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||||
@@ -10,7 +11,12 @@ import kotlinx.serialization.descriptors.SerialDescriptor
|
|||||||
import kotlinx.serialization.encoding.Decoder
|
import kotlinx.serialization.encoding.Decoder
|
||||||
import kotlinx.serialization.encoding.Encoder
|
import kotlinx.serialization.encoding.Encoder
|
||||||
import kotlinx.serialization.encoding.encodeCollection
|
import kotlinx.serialization.encoding.encodeCollection
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.JsonArray
|
||||||
|
import kotlinx.serialization.json.JsonDecoder
|
||||||
|
import kotlinx.serialization.json.boolean
|
||||||
|
import kotlinx.serialization.json.booleanOrNull
|
||||||
|
import kotlinx.serialization.json.int
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
|
||||||
class SelfossModel {
|
class SelfossModel {
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -134,6 +140,10 @@ class SelfossModel {
|
|||||||
stringUrl = "http:$stringUrl"
|
stringUrl = "http:$stringUrl"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stringUrl.isEmptyOrNullOrNullString()) {
|
||||||
|
throw Exception("Link $link was translated to $stringUrl, but was empty. Handle this.")
|
||||||
|
}
|
||||||
|
|
||||||
return stringUrl
|
return stringUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +156,14 @@ class SelfossModel {
|
|||||||
return txt
|
return txt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sourceAuthorOnly(): String {
|
||||||
|
var txt = this.sourcetitle.getHtmlDecoded()
|
||||||
|
if (!this.author.isNullOrBlank()) {
|
||||||
|
txt += " (by ${this.author}) "
|
||||||
|
}
|
||||||
|
return txt
|
||||||
|
}
|
||||||
|
|
||||||
fun toggleStar(): Item {
|
fun toggleStar(): Item {
|
||||||
this.starred = !this.starred
|
this.starred = !this.starred
|
||||||
return this
|
return this
|
||||||
@@ -154,12 +172,11 @@ class SelfossModel {
|
|||||||
|
|
||||||
// TODO: this seems to be super slow.
|
// TODO: this seems to be super slow.
|
||||||
object TagsListSerializer : KSerializer<List<String>> {
|
object TagsListSerializer : KSerializer<List<String>> {
|
||||||
override fun deserialize(decoder: Decoder): List<String> {
|
override fun deserialize(decoder: Decoder): List<String> =
|
||||||
return when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
when (val json = ((decoder as JsonDecoder).decodeJsonElement())) {
|
||||||
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
|
is JsonArray -> json.toList().map { it.toString().replace("^\"|\"$".toRegex(), "") }
|
||||||
else -> json.toString().split(",")
|
else -> json.toString().split(",")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
|
get() = PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING)
|
||||||
@@ -168,7 +185,10 @@ class SelfossModel {
|
|||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
value: List<String>,
|
value: List<String>,
|
||||||
) {
|
) {
|
||||||
encoder.encodeCollection(PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING), value.size) { this.toString() }
|
encoder.encodeCollection(
|
||||||
|
PrimitiveSerialDescriptor("tags", PrimitiveKind.STRING),
|
||||||
|
value.size,
|
||||||
|
) { this.toString() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +203,11 @@ class SelfossModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val descriptor: SerialDescriptor
|
override val descriptor: SerialDescriptor
|
||||||
get() = PrimitiveSerialDescriptor("b", PrimitiveKind.BOOLEAN)
|
get() =
|
||||||
|
PrimitiveSerialDescriptor(
|
||||||
|
"BooleanOrIntForSomeSelfossVersions",
|
||||||
|
PrimitiveKind.BOOLEAN,
|
||||||
|
)
|
||||||
|
|
||||||
override fun serialize(
|
override fun serialize(
|
||||||
encoder: Encoder,
|
encoder: Encoder,
|
||||||
|
@@ -1,14 +1,22 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.repository
|
package bou.amine.apps.readerforselfossv2.repository
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.dao.*
|
import bou.amine.apps.readerforselfossv2.dao.ACTION
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.ITEM
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
||||||
|
import bou.amine.apps.readerforselfossv2.dao.TAG
|
||||||
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
|
||||||
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
import bou.amine.apps.readerforselfossv2.model.SelfossModel
|
||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
|
||||||
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
|
||||||
import bou.amine.apps.readerforselfossv2.utils.*
|
import bou.amine.apps.readerforselfossv2.utils.Enums.ItemType
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toEntity
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toParsedDate
|
||||||
|
import bou.amine.apps.readerforselfossv2.utils.toView
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.call.*
|
|
||||||
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
|
||||||
@@ -28,26 +36,26 @@ class Repository(
|
|||||||
|
|
||||||
var displayedItems = ItemType.UNREAD
|
var displayedItems = ItemType.UNREAD
|
||||||
|
|
||||||
private var _tagFilter = MutableStateFlow<SelfossModel.Tag?>(null)
|
private var tagFilterFlow = MutableStateFlow<SelfossModel.Tag?>(null)
|
||||||
var tagFilter = _tagFilter.asStateFlow()
|
var tagFilter = tagFilterFlow.asStateFlow()
|
||||||
private var _sourceFilter = MutableStateFlow<SelfossModel.Source?>(null)
|
private var sourceFilterFlow = MutableStateFlow<SelfossModel.Source?>(null)
|
||||||
var sourceFilter = _sourceFilter.asStateFlow()
|
var sourceFilter = sourceFilterFlow.asStateFlow()
|
||||||
var searchFilter: String? = null
|
var searchFilter: String? = null
|
||||||
|
|
||||||
var offlineOverride = false
|
var offlineOverride = false
|
||||||
|
|
||||||
private val _badgeUnread = MutableStateFlow(0)
|
private val badgeUnreadFlow = MutableStateFlow(0)
|
||||||
val badgeUnread = _badgeUnread.asStateFlow()
|
val badgeUnread = badgeUnreadFlow.asStateFlow()
|
||||||
private val _badgeAll = MutableStateFlow(0)
|
private val badgeAllFlow = MutableStateFlow(0)
|
||||||
val badgeAll = _badgeAll.asStateFlow()
|
val badgeAll = badgeAllFlow.asStateFlow()
|
||||||
private val _badgeStarred = MutableStateFlow(0)
|
private val badgeStarredFlow = MutableStateFlow(0)
|
||||||
val badgeStarred = _badgeStarred.asStateFlow()
|
val badgeStarred = badgeStarredFlow.asStateFlow()
|
||||||
|
|
||||||
private var fetchedTags = false
|
private var fetchedTags = false
|
||||||
private var fetchedSources = false
|
private var fetchedSources = false
|
||||||
|
|
||||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
private var readerItems = ArrayList<SelfossModel.Item>()
|
||||||
private var _selectedSource: SelfossModel.SourceDetail? = null
|
private var selectedSource: SelfossModel.SourceDetail? = null
|
||||||
|
|
||||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||||
@@ -75,7 +83,7 @@ class Repository(
|
|||||||
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
dbItems = dbItems.filter { it.sourcetitle == sourceFilter.value!!.title }
|
||||||
}
|
}
|
||||||
val itemsList = ArrayList(dbItems.map { it.toView() })
|
val itemsList = ArrayList(dbItems.map { it.toView() })
|
||||||
itemsList.sortByDescending { DateUtils.parseDate(it.datetime) }
|
itemsList.sortByDescending { it.datetime.toParsedDate() }
|
||||||
fetchedItems =
|
fetchedItems =
|
||||||
StatusAndData.succes(
|
StatusAndData.succes(
|
||||||
itemsList,
|
itemsList,
|
||||||
@@ -136,17 +144,17 @@ class Repository(
|
|||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val response = api.stats()
|
val response = api.stats()
|
||||||
if (response.success && response.data != null) {
|
if (response.success && response.data != null) {
|
||||||
_badgeUnread.value = response.data.unread ?: 0
|
badgeUnreadFlow.value = response.data.unread ?: 0
|
||||||
_badgeAll.value = response.data.total
|
badgeAllFlow.value = response.data.total
|
||||||
_badgeStarred.value = response.data.starred ?: 0
|
badgeStarredFlow.value = response.data.starred ?: 0
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
} else if (appSettingsService.isItemCachingEnabled()) {
|
} else if (appSettingsService.isItemCachingEnabled()) {
|
||||||
// TODO: do this differently, because it's not efficient
|
// TODO: do this differently, because it's not efficient
|
||||||
val dbItems = getDBItems()
|
val dbItems = getDBItems()
|
||||||
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
|
badgeUnreadFlow.value = dbItems.filter { item -> item.unread }.size
|
||||||
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
|
badgeStarredFlow.value = dbItems.filter { item -> item.starred }.size
|
||||||
_badgeAll.value = dbItems.size
|
badgeAllFlow.value = dbItems.size
|
||||||
success = true
|
success = true
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
@@ -171,8 +179,8 @@ class Repository(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSpouts(): Map<String, SelfossModel.Spout> {
|
suspend fun getSpouts(): Map<String, SelfossModel.Spout> =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
val spouts = api.spouts()
|
val spouts = api.spouts()
|
||||||
if (spouts.success && spouts.data != null) {
|
if (spouts.success && spouts.data != null) {
|
||||||
spouts.data
|
spouts.data
|
||||||
@@ -182,7 +190,6 @@ class Repository(
|
|||||||
} else {
|
} else {
|
||||||
throw NetworkUnavailableException()
|
throw NetworkUnavailableException()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||||
var sources = ArrayList<SelfossModel.Source>()
|
var sources = ArrayList<SelfossModel.Source>()
|
||||||
@@ -235,14 +242,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun markAsReadById(id: Int): Boolean {
|
private suspend fun markAsReadById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.markAsRead(id.toString()).isSuccess
|
api.markAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), read = true)
|
insertDBAction(id.toString(), read = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean {
|
||||||
val success = unmarkAsReadById(item.id)
|
val success = unmarkAsReadById(item.id)
|
||||||
@@ -253,14 +259,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unmarkAsReadById(id: Int): Boolean {
|
private suspend fun unmarkAsReadById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unmarkAsRead(id.toString()).isSuccess
|
api.unmarkAsRead(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), unread = true)
|
insertDBAction(id.toString(), unread = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun starr(item: SelfossModel.Item): Boolean {
|
suspend fun starr(item: SelfossModel.Item): Boolean {
|
||||||
val success = starrById(item.id)
|
val success = starrById(item.id)
|
||||||
@@ -271,14 +276,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun starrById(id: Int): Boolean {
|
private suspend fun starrById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.starr(id.toString()).isSuccess
|
api.starr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
suspend fun unstarr(item: SelfossModel.Item): Boolean {
|
||||||
val success = unstarrById(item.id)
|
val success = unstarrById(item.id)
|
||||||
@@ -289,14 +293,13 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unstarrById(id: Int): Boolean {
|
private suspend fun unstarrById(id: Int): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.unstarr(id.toString()).isSuccess
|
api.unstarr(id.toString()).isSuccess
|
||||||
} else {
|
} else {
|
||||||
insertDBAction(id.toString(), starred = true)
|
insertDBAction(id.toString(), starred = true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
suspend fun markAllAsRead(items: ArrayList<SelfossModel.Item>): Boolean {
|
||||||
var success = false
|
var success = false
|
||||||
@@ -313,7 +316,7 @@ class Repository(
|
|||||||
private fun markAsReadLocally(item: SelfossModel.Item) {
|
private fun markAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (item.unread) {
|
if (item.unread) {
|
||||||
item.unread = false
|
item.unread = false
|
||||||
_badgeUnread.value -= 1
|
badgeUnreadFlow.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -324,7 +327,7 @@ class Repository(
|
|||||||
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
|
||||||
if (!item.unread) {
|
if (!item.unread) {
|
||||||
item.unread = true
|
item.unread = true
|
||||||
_badgeUnread.value += 1
|
badgeUnreadFlow.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -335,7 +338,7 @@ class Repository(
|
|||||||
private fun starrLocally(item: SelfossModel.Item) {
|
private fun starrLocally(item: SelfossModel.Item) {
|
||||||
if (!item.starred) {
|
if (!item.starred) {
|
||||||
item.starred = true
|
item.starred = true
|
||||||
_badgeStarred.value += 1
|
badgeStarredFlow.value += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -346,7 +349,7 @@ class Repository(
|
|||||||
private fun unstarrLocally(item: SelfossModel.Item) {
|
private fun unstarrLocally(item: SelfossModel.Item) {
|
||||||
if (item.starred) {
|
if (item.starred) {
|
||||||
item.starred = false
|
item.starred = false
|
||||||
_badgeStarred.value -= 1
|
badgeStarredFlow.value -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
@@ -362,12 +365,13 @@ class Repository(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
var response = false
|
var response = false
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
response = api.createSourceForVersion(
|
response = api
|
||||||
title,
|
.createSourceForVersion(
|
||||||
url,
|
title,
|
||||||
spout,
|
url,
|
||||||
tags,
|
spout,
|
||||||
).isSuccess == true
|
tags,
|
||||||
|
).isSuccess == true
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -408,13 +412,12 @@ class Repository(
|
|||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateRemote(): Boolean {
|
suspend fun updateRemote(): Boolean =
|
||||||
return if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
api.update().data.equals("finished")
|
api.update().data.equals("finished")
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun login(): Boolean {
|
suspend fun login(): Boolean {
|
||||||
var result = false
|
var result = false
|
||||||
@@ -423,28 +426,25 @@ class Repository(
|
|||||||
val response = api.login()
|
val response = api.login()
|
||||||
result = response.isSuccess == true
|
result = response.isSuccess == true
|
||||||
} catch (cause: Throwable) {
|
} catch (cause: Throwable) {
|
||||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.login")
|
Napier.e("login failed", cause, tag = "Repository.login")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun shouldBeSelfossInstance(): Pair<Boolean, Boolean> {
|
suspend fun checkIfFetchFails(): Boolean {
|
||||||
var fetchFailed = true
|
var fetchFailed = true
|
||||||
var showSelfossOnlyModal = false
|
|
||||||
if (isNetworkAvailable()) {
|
if (isNetworkAvailable()) {
|
||||||
try {
|
try {
|
||||||
// Trying to fetch one item, and check someone is trying to use the app with
|
// Trying to fetch one item, and check someone is trying to use the app with
|
||||||
// a random rss feed, that would throw a NoTransformationFoundException
|
// a random rss feed, that would throw a NoTransformationFoundException
|
||||||
fetchFailed = !api.getItemsWithoutCatch().success
|
fetchFailed = !api.getItemsWithoutCatch().success
|
||||||
} catch (e: NoTransformationFoundException) {
|
|
||||||
showSelfossOnlyModal = true
|
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Napier.e(e.stackTraceToString(), tag = "RepositoryImpl.shouldBeSelfossInstance")
|
Napier.e("checkIfFetchFails failed", e, tag = "Repository.shouldBeSelfossInstance")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Pair(fetchFailed, showSelfossOnlyModal)
|
return fetchFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logout() {
|
suspend fun logout() {
|
||||||
@@ -452,10 +452,10 @@ class Repository(
|
|||||||
try {
|
try {
|
||||||
val response = api.logout()
|
val response = api.logout()
|
||||||
if (!response.isSuccess) {
|
if (!response.isSuccess) {
|
||||||
Napier.e("Couldn't logout.", tag = "RepositoryImpl.logout")
|
Napier.e("Couldn't logout.", tag = "Repository.logout")
|
||||||
}
|
}
|
||||||
} catch (cause: Throwable) {
|
} catch (cause: Throwable) {
|
||||||
Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.logout")
|
Napier.e("logout failed", cause, tag = "Repository.logout")
|
||||||
}
|
}
|
||||||
appSettingsService.clearAll()
|
appSettingsService.clearAll()
|
||||||
} else {
|
} else {
|
||||||
@@ -582,16 +582,19 @@ class Repository(
|
|||||||
markAsReadById(action.articleid.toInt()),
|
markAsReadById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.unread ->
|
action.unread ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
unmarkAsReadById(action.articleid.toInt()),
|
unmarkAsReadById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.starred ->
|
action.starred ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
starrById(action.articleid.toInt()),
|
starrById(action.articleid.toInt()),
|
||||||
action,
|
action,
|
||||||
)
|
)
|
||||||
|
|
||||||
action.unstarred ->
|
action.unstarred ->
|
||||||
doAndReportOnFail(
|
doAndReportOnFail(
|
||||||
unstarrById(action.articleid.toInt()),
|
unstarrById(action.articleid.toInt()),
|
||||||
@@ -611,34 +614,30 @@ class Repository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setTagFilter(tag: SelfossModel.Tag?) {
|
fun setTagFilter(tag: SelfossModel.Tag?) {
|
||||||
_tagFilter.value = tag
|
tagFilterFlow.value = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSourceFilter(source: SelfossModel.Source?) {
|
fun setSourceFilter(source: SelfossModel.Source?) {
|
||||||
_sourceFilter.value = source
|
sourceFilterFlow.value = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
fun setReaderItems(readerItems: ArrayList<SelfossModel.Item>) {
|
||||||
_readerItems = readerItems
|
this.readerItems = readerItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getReaderItems(): ArrayList<SelfossModel.Item> {
|
fun getReaderItems(): ArrayList<SelfossModel.Item> = readerItems
|
||||||
return _readerItems
|
|
||||||
}
|
|
||||||
|
|
||||||
fun migrate(driverFactory: DriverFactory) {
|
fun migrate(driverFactory: DriverFactory) {
|
||||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||||
_selectedSource = source
|
selectedSource = source
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsetSelectedSource() {
|
fun unsetSelectedSource() {
|
||||||
_selectedSource = null
|
selectedSource = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
fun getSelectedSource(): SelfossModel.SourceDetail? = selectedSource
|
||||||
return _selectedSource
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -1,22 +1,26 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.rest
|
package bou.amine.apps.readerforselfossv2.rest
|
||||||
|
|
||||||
import bou.amine.apps.readerforselfossv2.model.*
|
import bou.amine.apps.readerforselfossv2.model.MercuryModel
|
||||||
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.plugins.cache.*
|
import io.ktor.client.plugins.cache.HttpCache
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.client.plugins.logging.*
|
import io.ktor.client.plugins.logging.LogLevel
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.plugins.logging.Logger
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
import io.ktor.client.plugins.logging.Logging
|
||||||
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.parameter
|
||||||
|
import io.ktor.serialization.kotlinx.json.json
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class MercuryApi() {
|
class MercuryApi {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
private fun createHttpClient(): HttpClient {
|
private fun createHttpClient(): HttpClient {
|
||||||
return HttpClient {
|
return HttpClient {
|
||||||
|
install(HttpCache)
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
install(HttpCache)
|
|
||||||
json(
|
json(
|
||||||
Json {
|
Json {
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
|
@@ -3,23 +3,28 @@ package bou.amine.apps.readerforselfossv2.rest
|
|||||||
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
import bou.amine.apps.readerforselfossv2.model.StatusAndData
|
||||||
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
import bou.amine.apps.readerforselfossv2.model.SuccessResponse
|
||||||
import io.github.aakira.napier.Napier
|
import io.github.aakira.napier.Napier
|
||||||
import io.ktor.client.*
|
import io.ktor.client.HttpClient
|
||||||
import io.ktor.client.call.*
|
import io.ktor.client.call.body
|
||||||
import io.ktor.client.request.*
|
import io.ktor.client.request.HttpRequestBuilder
|
||||||
import io.ktor.client.request.forms.*
|
import io.ktor.client.request.delete
|
||||||
import io.ktor.client.statement.*
|
import io.ktor.client.request.forms.submitForm
|
||||||
import io.ktor.http.*
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.url
|
||||||
|
import io.ktor.client.statement.HttpResponse
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
|
import io.ktor.http.Parameters
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
|
|
||||||
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse {
|
suspend fun responseOrSuccessIf404(r: HttpResponse?): SuccessResponse =
|
||||||
return if (r != null && r.status === HttpStatusCode.NotFound) {
|
if (r != null && r.status === HttpStatusCode.NotFound) {
|
||||||
SuccessResponse(true)
|
SuccessResponse(true)
|
||||||
} else {
|
} else {
|
||||||
maybeResponse(r)
|
maybeResponse(r)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
suspend fun maybeResponse(r: HttpResponse?): SuccessResponse =
|
||||||
return if (r != null && r.status.isSuccess()) {
|
if (r != null && r.status.isSuccess()) {
|
||||||
r.body()
|
r.body()
|
||||||
} else {
|
} else {
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
@@ -27,13 +32,16 @@ suspend fun maybeResponse(r: HttpResponse?): SuccessResponse {
|
|||||||
}
|
}
|
||||||
SuccessResponse(false)
|
SuccessResponse(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
suspend inline fun <reified T> bodyOrFailure(r: HttpResponse?): StatusAndData<T> {
|
||||||
return if (r != null && r.status.isSuccess()) {
|
try {
|
||||||
StatusAndData.succes(r.body())
|
return if (r != null && r.status.isSuccess()) {
|
||||||
} else {
|
StatusAndData.succes(r.body())
|
||||||
StatusAndData.error()
|
} else {
|
||||||
|
StatusAndData.error()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return StatusAndData.error()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -33,20 +33,22 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
expect fun setupInsecureHTTPEngine(config: CIOEngineConfig)
|
expect fun setupInsecureHttpEngine(config: CIOEngineConfig)
|
||||||
|
|
||||||
class SelfossApi(private val appSettingsService: AppSettingsService) {
|
class SelfossApi(
|
||||||
|
private val appSettingsService: AppSettingsService,
|
||||||
|
) {
|
||||||
var client = createHttpClient()
|
var client = createHttpClient()
|
||||||
|
|
||||||
fun createHttpClient() =
|
fun createHttpClient() =
|
||||||
HttpClient(CIO) {
|
HttpClient(CIO) {
|
||||||
if (appSettingsService.getSelfSigned()) {
|
if (appSettingsService.getSelfSigned()) {
|
||||||
engine {
|
engine {
|
||||||
setupInsecureHTTPEngine(this)
|
setupInsecureHttpEngine(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
install(HttpCache)
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
install(HttpCache)
|
|
||||||
json(
|
json(
|
||||||
Json {
|
Json {
|
||||||
prettyPrint = true
|
prettyPrint = true
|
||||||
@@ -105,12 +107,14 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
|
|
||||||
private fun hasLoginInfo() =
|
private fun hasLoginInfo() =
|
||||||
appSettingsService.getUserName().isNotEmpty() &&
|
appSettingsService.getUserName().isNotEmpty() &&
|
||||||
appSettingsService.getPassword()
|
appSettingsService
|
||||||
|
.getPassword()
|
||||||
.isNotEmpty()
|
.isNotEmpty()
|
||||||
|
|
||||||
suspend fun login(): SuccessResponse =
|
suspend fun login(): SuccessResponse =
|
||||||
if (appSettingsService.getUserName().isNotEmpty() &&
|
if (appSettingsService.getUserName().isNotEmpty() &&
|
||||||
appSettingsService.getPassword()
|
appSettingsService
|
||||||
|
.getPassword()
|
||||||
.isNotEmpty()
|
.isNotEmpty()
|
||||||
) {
|
) {
|
||||||
if (shouldHavePostLogin()) {
|
if (shouldHavePostLogin()) {
|
||||||
@@ -127,7 +131,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToGet(url("/login")) {
|
client.tryToGet(url("/login")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -148,7 +156,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
client.tryToPost(url("/login")) {
|
client.tryToPost(url("/login")) {
|
||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -176,7 +188,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
private suspend fun maybeLogoutIfAvailable() =
|
private suspend fun maybeLogoutIfAvailable() =
|
||||||
responseOrSuccessIf404(
|
responseOrSuccessIf404(
|
||||||
client.tryToGet(url("/logout")) {
|
client.tryToGet(url("/logout")) {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -195,7 +211,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
private suspend fun doLogout() =
|
private suspend fun doLogout() =
|
||||||
maybeResponse(
|
maybeResponse(
|
||||||
client.tryToDelete(url("/api/session/current")) {
|
client.tryToDelete(url("/api/session/current")) {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -233,7 +253,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("updatedsince", updatedSince)
|
parameter("updatedsince", updatedSince)
|
||||||
parameter("items", items ?: appSettingsService.getItemsNumber())
|
parameter("items", items ?: appSettingsService.getItemsNumber())
|
||||||
parameter("offset", offset)
|
parameter("offset", offset)
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -258,7 +282,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
}
|
}
|
||||||
parameter("type", "all")
|
parameter("type", "all")
|
||||||
parameter("items", 1)
|
parameter("items", 1)
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -281,7 +309,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -304,7 +336,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -327,7 +363,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -350,7 +390,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -373,7 +417,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -396,7 +444,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -415,7 +467,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
suspend fun apiInformation(): StatusAndData<SelfossModel.ApiInformation> =
|
||||||
bodyOrFailure(
|
bodyOrFailure(
|
||||||
client.tryToGet(url("/api/about")) {
|
client.tryToGet(url("/api/about")) {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -438,7 +494,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -461,7 +521,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -484,7 +548,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -507,7 +575,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -536,7 +608,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
ids.map { append("ids[]", it) }
|
ids.map { append("ids[]", it) }
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -588,7 +664,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
append(tagsParamName, tags)
|
append(tagsParamName, tags)
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -641,7 +721,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
append(tagsParamName, tags)
|
append(tagsParamName, tags)
|
||||||
},
|
},
|
||||||
block = {
|
block = {
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
@@ -664,7 +748,11 @@ class SelfossApi(private val appSettingsService: AppSettingsService) {
|
|||||||
parameter("username", appSettingsService.getUserName())
|
parameter("username", appSettingsService.getUserName())
|
||||||
parameter("password", appSettingsService.getPassword())
|
parameter("password", appSettingsService.getPassword())
|
||||||
}
|
}
|
||||||
if (appSettingsService.getBasicUserName().isNotEmpty() && appSettingsService.getBasicPassword().isNotEmpty()) {
|
if (appSettingsService
|
||||||
|
.getBasicUserName()
|
||||||
|
.isNotEmpty() &&
|
||||||
|
appSettingsService.getBasicPassword().isNotEmpty()
|
||||||
|
) {
|
||||||
headers {
|
headers {
|
||||||
append(
|
append(
|
||||||
HttpHeaders.Authorization,
|
HttpHeaders.Authorization,
|
||||||
|
@@ -6,9 +6,16 @@ import com.russhwolf.settings.Settings
|
|||||||
// This is to fix ACRA not sending reports anymore.
|
// This is to fix ACRA not sending reports anymore.
|
||||||
// See https://www.acra.ch/docs/Troubleshooting-Guide#applicationoncreate
|
// See https://www.acra.ch/docs/Troubleshooting-Guide#applicationoncreate
|
||||||
class ACRASettings : Settings {
|
class ACRASettings : Settings {
|
||||||
override val keys: Set<String> = emptySet()
|
override val keys: MutableSet<String> = mutableSetOf()
|
||||||
override val size: Int = 0
|
override val size: Int = 0
|
||||||
|
|
||||||
|
val bools: MutableMap<String, Boolean> = mutableMapOf()
|
||||||
|
val doubles: MutableMap<String, Double> = mutableMapOf()
|
||||||
|
val floats: MutableMap<String, Float> = mutableMapOf()
|
||||||
|
val ints: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
val longs: MutableMap<String, Long> = mutableMapOf()
|
||||||
|
val strings: MutableMap<String, String> = mutableMapOf()
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
@@ -16,90 +23,102 @@ class ACRASettings : Settings {
|
|||||||
override fun getBoolean(
|
override fun getBoolean(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Boolean,
|
defaultValue: Boolean,
|
||||||
): Boolean = false
|
): Boolean = bools[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getBooleanOrNull(key: String): Boolean? = null
|
override fun getBooleanOrNull(key: String): Boolean? = bools[key]
|
||||||
|
|
||||||
override fun getDouble(
|
override fun getDouble(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Double,
|
defaultValue: Double,
|
||||||
): Double = 0.0
|
): Double = doubles[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getDoubleOrNull(key: String): Double? = null
|
override fun getDoubleOrNull(key: String): Double? = doubles[key]
|
||||||
|
|
||||||
override fun getFloat(
|
override fun getFloat(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Float,
|
defaultValue: Float,
|
||||||
): Float = 0.0F
|
): Float = floats[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getFloatOrNull(key: String): Float? = null
|
override fun getFloatOrNull(key: String): Float? = floats[key]
|
||||||
|
|
||||||
override fun getInt(
|
override fun getInt(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Int,
|
defaultValue: Int,
|
||||||
): Int = 0
|
): Int = ints[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getIntOrNull(key: String): Int? = null
|
override fun getIntOrNull(key: String): Int? = ints[key]
|
||||||
|
|
||||||
override fun getLong(
|
override fun getLong(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: Long,
|
defaultValue: Long,
|
||||||
): Long = 0
|
): Long = longs[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getLongOrNull(key: String): Long? = null
|
override fun getLongOrNull(key: String): Long? = longs[key]
|
||||||
|
|
||||||
override fun getString(
|
override fun getString(
|
||||||
key: String,
|
key: String,
|
||||||
defaultValue: String,
|
defaultValue: String,
|
||||||
): String = "0"
|
): String = strings[key] ?: defaultValue
|
||||||
|
|
||||||
override fun getStringOrNull(key: String): String? = null
|
override fun getStringOrNull(key: String): String? = strings[key]
|
||||||
|
|
||||||
override fun hasKey(key: String): Boolean = false
|
override fun hasKey(key: String): Boolean = keys.contains(key)
|
||||||
|
|
||||||
override fun putBoolean(
|
override fun putBoolean(
|
||||||
key: String,
|
key: String,
|
||||||
value: Boolean,
|
value: Boolean,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
bools[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putDouble(
|
override fun putDouble(
|
||||||
key: String,
|
key: String,
|
||||||
value: Double,
|
value: Double,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
doubles[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putFloat(
|
override fun putFloat(
|
||||||
key: String,
|
key: String,
|
||||||
value: Float,
|
value: Float,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
floats[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putInt(
|
override fun putInt(
|
||||||
key: String,
|
key: String,
|
||||||
value: Int,
|
value: Int,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
ints[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putLong(
|
override fun putLong(
|
||||||
key: String,
|
key: String,
|
||||||
value: Long,
|
value: Long,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
longs[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putString(
|
override fun putString(
|
||||||
key: String,
|
key: String,
|
||||||
value: String,
|
value: String,
|
||||||
) {
|
) {
|
||||||
// Nothing
|
keys.add(key)
|
||||||
|
strings[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(key: String) {
|
override fun remove(key: String) {
|
||||||
// Nothing
|
keys.remove(key)
|
||||||
|
bools.remove(key)
|
||||||
|
doubles.remove(key)
|
||||||
|
floats.remove(key)
|
||||||
|
ints.remove(key)
|
||||||
|
longs.remove(key)
|
||||||
|
strings.remove(key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,9 @@ package bou.amine.apps.readerforselfossv2.service
|
|||||||
|
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
|
|
||||||
class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
class AppSettingsService(
|
||||||
|
acraSenderServiceProcess: Boolean = false,
|
||||||
|
) {
|
||||||
val settings: Settings =
|
val settings: Settings =
|
||||||
if (acraSenderServiceProcess) {
|
if (acraSenderServiceProcess) {
|
||||||
ACRASettings()
|
ACRASettings()
|
||||||
@@ -11,37 +13,37 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Api related
|
// Api related
|
||||||
private var _apiVersion: Int = -1
|
private var apiVersion: Int = -1
|
||||||
private var _publicAccess: Boolean? = null
|
private var publicAccess: Boolean? = null
|
||||||
private var _selfSigned: Boolean? = null
|
private var selfSigned: Boolean? = null
|
||||||
private var _baseUrl: String = ""
|
private var baseUrl: String = ""
|
||||||
private var _userName: String = ""
|
private var userName: String = ""
|
||||||
private var _basicUserName: String = ""
|
private var basicUserName: String = ""
|
||||||
private var _password: String = ""
|
private var password: String = ""
|
||||||
private var _basicPassword: String = ""
|
private var basicPassword: String = ""
|
||||||
|
|
||||||
// User settings related
|
// User settings related
|
||||||
private var _itemsCaching: Boolean? = null
|
private var itemsCaching: Boolean? = null
|
||||||
private var _articleViewer: Boolean? = null
|
private var articleViewer: Boolean? = null
|
||||||
private var _shouldBeCardView: Boolean? = null
|
private var shouldBeCardView: Boolean? = null
|
||||||
private var _displayUnreadCount: Boolean? = null
|
private var displayUnreadCount: Boolean? = null
|
||||||
private var _displayAllCount: Boolean? = null
|
private var displayAllCount: Boolean? = null
|
||||||
private var _fullHeightCards: Boolean? = null
|
private var fullHeightCards: Boolean? = null
|
||||||
private var _updateSources: Boolean? = null
|
private var updateSources: Boolean? = null
|
||||||
private var _periodicRefresh: Boolean? = null
|
private var periodicRefresh: Boolean? = null
|
||||||
private var _refreshWhenChargingOnly: Boolean? = null
|
private var refreshWhenChargingOnly: Boolean? = null
|
||||||
private var _infiniteLoading: Boolean? = null
|
private var infiniteLoading: Boolean? = null
|
||||||
private var _notifyNewItems: Boolean? = null
|
private var notifyNewItems: Boolean? = null
|
||||||
private var _itemsNumber: Int? = null
|
private var itemsNumber: Int? = null
|
||||||
private var _apiTimeout: Long? = null
|
private var apiTimeout: Long? = null
|
||||||
private var _refreshMinutes: Long = 360
|
private var refreshMinutes: Long = 360
|
||||||
private var _markOnScroll: Boolean? = null
|
private var markOnScroll: Boolean? = null
|
||||||
private var _activeAlignment: Int? = null
|
private var activeAlignment: Int? = null
|
||||||
|
|
||||||
private var _fontSize: Int? = null
|
private var fontSize: Int? = null
|
||||||
private var _staticBar: Boolean? = null
|
private var staticBar: Boolean? = null
|
||||||
private var _font: String = ""
|
private var font: String = ""
|
||||||
private var _theme: Int? = null
|
private var theme: Int? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
refreshApiSettings()
|
refreshApiSettings()
|
||||||
@@ -49,11 +51,11 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getApiVersion(): Int {
|
fun getApiVersion(): Int {
|
||||||
if (_apiVersion == -1) {
|
if (apiVersion == -1) {
|
||||||
refreshApiVersion()
|
refreshApiVersion()
|
||||||
return _apiVersion
|
return apiVersion
|
||||||
}
|
}
|
||||||
return _apiVersion
|
return apiVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateApiVersion(apiMajorVersion: Int) {
|
fun updateApiVersion(apiMajorVersion: Int) {
|
||||||
@@ -62,14 +64,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshApiVersion() {
|
private fun refreshApiVersion() {
|
||||||
_apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
apiVersion = settings.getInt(API_VERSION_MAJOR, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPublicAccess(): Boolean {
|
fun getPublicAccess(): Boolean {
|
||||||
if (_publicAccess == null) {
|
if (publicAccess == null) {
|
||||||
refreshPublicAccess()
|
refreshPublicAccess()
|
||||||
}
|
}
|
||||||
return _publicAccess!!
|
return publicAccess!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePublicAccess(publicAccess: Boolean) {
|
fun updatePublicAccess(publicAccess: Boolean) {
|
||||||
@@ -78,14 +80,14 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPublicAccess() {
|
private fun refreshPublicAccess() {
|
||||||
_publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
publicAccess = settings.getBoolean(API_PUBLIC_ACCESS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSelfSigned(): Boolean {
|
fun getSelfSigned(): Boolean {
|
||||||
if (_selfSigned == null) {
|
if (selfSigned == null) {
|
||||||
refreshSelfSigned()
|
refreshSelfSigned()
|
||||||
}
|
}
|
||||||
return _selfSigned!!
|
return selfSigned!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSelfSigned(selfSigned: Boolean) {
|
fun updateSelfSigned(selfSigned: Boolean) {
|
||||||
@@ -94,53 +96,53 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshSelfSigned() {
|
private fun refreshSelfSigned() {
|
||||||
_selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
selfSigned = settings.getBoolean(API_SELF_SIGNED, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBaseUrl(): String {
|
fun getBaseUrl(): String {
|
||||||
if (_baseUrl.isEmpty()) {
|
if (baseUrl.isEmpty()) {
|
||||||
refreshBaseUrl()
|
refreshBaseUrl()
|
||||||
}
|
}
|
||||||
return _baseUrl
|
return baseUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUserName(): String {
|
fun getUserName(): String {
|
||||||
if (_userName.isEmpty()) {
|
if (userName.isEmpty()) {
|
||||||
refreshUsername()
|
refreshUsername()
|
||||||
}
|
}
|
||||||
return _userName
|
return userName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPassword(): String {
|
fun getPassword(): String {
|
||||||
if (_password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
refreshPassword()
|
refreshPassword()
|
||||||
}
|
}
|
||||||
return _password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBasicUserName(): String {
|
fun getBasicUserName(): String {
|
||||||
if (_basicUserName.isEmpty()) {
|
if (basicUserName.isEmpty()) {
|
||||||
refreshBasicUsername()
|
refreshBasicUsername()
|
||||||
}
|
}
|
||||||
return _basicUserName
|
return basicUserName
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBasicPassword(): String {
|
fun getBasicPassword(): String {
|
||||||
if (_basicPassword.isEmpty()) {
|
if (basicPassword.isEmpty()) {
|
||||||
refreshBasicPassword()
|
refreshBasicPassword()
|
||||||
}
|
}
|
||||||
return _basicPassword
|
return basicPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getItemsNumber(): Int {
|
fun getItemsNumber(): Int {
|
||||||
if (_itemsNumber == null) {
|
if (itemsNumber == null) {
|
||||||
refreshItemsNumber()
|
refreshItemsNumber()
|
||||||
}
|
}
|
||||||
return _itemsNumber!!
|
return itemsNumber!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshItemsNumber() {
|
private fun refreshItemsNumber() {
|
||||||
_itemsNumber =
|
itemsNumber =
|
||||||
try {
|
try {
|
||||||
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
settings.getString(API_ITEMS_NUMBER, "20").toInt()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -150,16 +152,16 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getApiTimeout(): Long {
|
fun getApiTimeout(): Long {
|
||||||
if (_apiTimeout == null) {
|
if (apiTimeout == null) {
|
||||||
refreshApiTimeout()
|
refreshApiTimeout()
|
||||||
}
|
}
|
||||||
return _apiTimeout!!
|
return apiTimeout!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun secToMs(n: Long) = n * 1000
|
private fun secToMs(n: Long) = n * 1000
|
||||||
|
|
||||||
private fun refreshApiTimeout() {
|
private fun refreshApiTimeout() {
|
||||||
_apiTimeout =
|
apiTimeout =
|
||||||
secToMs(
|
secToMs(
|
||||||
try {
|
try {
|
||||||
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
val settingsTimeout = settings.getString(API_TIMEOUT, "60")
|
||||||
@@ -177,229 +179,229 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBaseUrl() {
|
private fun refreshBaseUrl() {
|
||||||
_baseUrl = settings.getString(BASE_URL, "")
|
baseUrl = settings.getString(BASE_URL, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshUsername() {
|
private fun refreshUsername() {
|
||||||
_userName = settings.getString(LOGIN, "")
|
userName = settings.getString(LOGIN, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPassword() {
|
private fun refreshPassword() {
|
||||||
_password = settings.getString(PASSWORD, "")
|
password = settings.getString(PASSWORD, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBasicUsername() {
|
private fun refreshBasicUsername() {
|
||||||
_basicUserName = settings.getString(BASIC_LOGIN, "")
|
basicUserName = settings.getString(BASIC_LOGIN, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshBasicPassword() {
|
private fun refreshBasicPassword() {
|
||||||
_basicPassword = settings.getString(BASIC_PASSWORD, "")
|
basicPassword = settings.getString(BASIC_PASSWORD, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshArticleViewerEnabled() {
|
private fun refreshArticleViewerEnabled() {
|
||||||
_articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
articleViewer = settings.getBoolean(PREFER_ARTICLE_VIEWER, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isArticleViewerEnabled(): Boolean {
|
fun isArticleViewerEnabled(): Boolean {
|
||||||
if (_articleViewer != null) {
|
if (articleViewer != null) {
|
||||||
refreshArticleViewerEnabled()
|
refreshArticleViewerEnabled()
|
||||||
}
|
}
|
||||||
return _articleViewer == true
|
return articleViewer == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshShouldBeCardViewEnabled() {
|
private fun refreshShouldBeCardViewEnabled() {
|
||||||
_shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
shouldBeCardView = settings.getBoolean(CARD_VIEW_ACTIVE, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isCardViewEnabled(): Boolean {
|
fun isCardViewEnabled(): Boolean {
|
||||||
if (_shouldBeCardView != null) {
|
if (shouldBeCardView != null) {
|
||||||
refreshShouldBeCardViewEnabled()
|
refreshShouldBeCardViewEnabled()
|
||||||
}
|
}
|
||||||
return _shouldBeCardView == true
|
return shouldBeCardView == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDisplayUnreadCountEnabled() {
|
private fun refreshDisplayUnreadCountEnabled() {
|
||||||
_displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
displayUnreadCount = settings.getBoolean(DISPLAY_UNREAD_COUNT, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDisplayUnreadCountEnabled(): Boolean {
|
fun isDisplayUnreadCountEnabled(): Boolean {
|
||||||
if (_displayUnreadCount != null) {
|
if (displayUnreadCount != null) {
|
||||||
refreshDisplayUnreadCountEnabled()
|
refreshDisplayUnreadCountEnabled()
|
||||||
}
|
}
|
||||||
return _displayUnreadCount == true
|
return displayUnreadCount == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshDisplayAllCountEnabled() {
|
private fun refreshDisplayAllCountEnabled() {
|
||||||
_displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
displayAllCount = settings.getBoolean(DISPLAY_OTHER_COUNT, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isDisplayAllCountEnabled(): Boolean {
|
fun isDisplayAllCountEnabled(): Boolean {
|
||||||
if (_displayAllCount != null) {
|
if (displayAllCount != null) {
|
||||||
refreshDisplayAllCountEnabled()
|
refreshDisplayAllCountEnabled()
|
||||||
}
|
}
|
||||||
return _displayAllCount == true
|
return displayAllCount == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFullHeightCardsEnabled() {
|
private fun refreshFullHeightCardsEnabled() {
|
||||||
_fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
fullHeightCards = settings.getBoolean(FULL_HEIGHT_CARDS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isFullHeightCardsEnabled(): Boolean {
|
fun isFullHeightCardsEnabled(): Boolean {
|
||||||
if (_fullHeightCards != null) {
|
if (fullHeightCards != null) {
|
||||||
refreshFullHeightCardsEnabled()
|
refreshFullHeightCardsEnabled()
|
||||||
}
|
}
|
||||||
return _fullHeightCards == true
|
return fullHeightCards == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshUpdateSourcesEnabled() {
|
private fun refreshUpdateSourcesEnabled() {
|
||||||
_updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
updateSources = settings.getBoolean(UPDATE_SOURCES, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isUpdateSourcesEnabled(): Boolean {
|
fun isUpdateSourcesEnabled(): Boolean {
|
||||||
if (_updateSources != null) {
|
if (updateSources != null) {
|
||||||
refreshUpdateSourcesEnabled()
|
refreshUpdateSourcesEnabled()
|
||||||
}
|
}
|
||||||
return _updateSources == true
|
return updateSources == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshPeriodicRefreshEnabled() {
|
private fun refreshPeriodicRefreshEnabled() {
|
||||||
_periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
periodicRefresh = settings.getBoolean(PERIODIC_REFRESH, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPeriodicRefreshEnabled(): Boolean {
|
fun isPeriodicRefreshEnabled(): Boolean {
|
||||||
if (_periodicRefresh != null) {
|
if (periodicRefresh != null) {
|
||||||
refreshPeriodicRefreshEnabled()
|
refreshPeriodicRefreshEnabled()
|
||||||
}
|
}
|
||||||
return _periodicRefresh == true
|
return periodicRefresh == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
private fun refreshRefreshWhenChargingOnlyEnabled() {
|
||||||
_refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
refreshWhenChargingOnly = settings.getBoolean(REFRESH_WHEN_CHARGING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
fun isRefreshWhenChargingOnlyEnabled(): Boolean {
|
||||||
if (_refreshWhenChargingOnly != null) {
|
if (refreshWhenChargingOnly != null) {
|
||||||
refreshRefreshWhenChargingOnlyEnabled()
|
refreshRefreshWhenChargingOnlyEnabled()
|
||||||
}
|
}
|
||||||
return _refreshWhenChargingOnly == true
|
return refreshWhenChargingOnly == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshRefreshMinutes() {
|
private fun refreshRefreshMinutes() {
|
||||||
_refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
refreshMinutes = settings.getString(PERIODIC_REFRESH_MINUTES, "360").toLong()
|
||||||
if (_refreshMinutes <= 15) {
|
if (refreshMinutes <= 15) {
|
||||||
_refreshMinutes = 15
|
refreshMinutes = 15
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRefreshMinutes(): Long {
|
fun getRefreshMinutes(): Long {
|
||||||
if (_refreshMinutes != 360L) {
|
if (refreshMinutes != 360L) {
|
||||||
refreshRefreshMinutes()
|
refreshRefreshMinutes()
|
||||||
}
|
}
|
||||||
return _refreshMinutes
|
return refreshMinutes
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshInfiniteLoadingEnabled() {
|
private fun refreshInfiniteLoadingEnabled() {
|
||||||
_infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
infiniteLoading = settings.getBoolean(INFINITE_LOADING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInfiniteLoadingEnabled(): Boolean {
|
fun isInfiniteLoadingEnabled(): Boolean {
|
||||||
if (_infiniteLoading != null) {
|
if (infiniteLoading != null) {
|
||||||
refreshInfiniteLoadingEnabled()
|
refreshInfiniteLoadingEnabled()
|
||||||
}
|
}
|
||||||
return _infiniteLoading == true
|
return infiniteLoading == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshItemCachingEnabled() {
|
private fun refreshItemCachingEnabled() {
|
||||||
_itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
itemsCaching = settings.getBoolean(ITEMS_CACHING, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isItemCachingEnabled(): Boolean {
|
fun isItemCachingEnabled(): Boolean {
|
||||||
if (_itemsCaching != null) {
|
if (itemsCaching != null) {
|
||||||
refreshItemCachingEnabled()
|
refreshItemCachingEnabled()
|
||||||
}
|
}
|
||||||
return _itemsCaching == true
|
return itemsCaching == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshNotifyNewItemsEnabled() {
|
private fun refreshNotifyNewItemsEnabled() {
|
||||||
_notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
notifyNewItems = settings.getBoolean(NOTIFY_NEW_ITEMS, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNotifyNewItemsEnabled(): Boolean {
|
fun isNotifyNewItemsEnabled(): Boolean {
|
||||||
if (_notifyNewItems != null) {
|
if (notifyNewItems != null) {
|
||||||
refreshNotifyNewItemsEnabled()
|
refreshNotifyNewItemsEnabled()
|
||||||
}
|
}
|
||||||
return _notifyNewItems == true
|
return notifyNewItems == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshMarkOnScrollEnabled() {
|
private fun refreshMarkOnScrollEnabled() {
|
||||||
_markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
markOnScroll = settings.getBoolean(MARK_ON_SCROLL, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isMarkOnScrollEnabled(): Boolean {
|
fun isMarkOnScrollEnabled(): Boolean {
|
||||||
if (_markOnScroll != null) {
|
if (markOnScroll != null) {
|
||||||
refreshMarkOnScrollEnabled()
|
refreshMarkOnScrollEnabled()
|
||||||
}
|
}
|
||||||
return _markOnScroll == true
|
return markOnScroll == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshActiveAllignment() {
|
private fun refreshActiveAllignment() {
|
||||||
_activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
activeAlignment = settings.getInt(TEXT_ALIGN, JUSTIFY)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getActiveAllignment(): Int {
|
fun getActiveAllignment(): Int {
|
||||||
if (_activeAlignment != null) {
|
if (activeAlignment != null) {
|
||||||
refreshActiveAllignment()
|
refreshActiveAllignment()
|
||||||
}
|
}
|
||||||
return _activeAlignment ?: JUSTIFY
|
return activeAlignment ?: JUSTIFY
|
||||||
}
|
}
|
||||||
|
|
||||||
fun changeAllignment(allignment: Int) {
|
fun changeAllignment(allignment: Int) {
|
||||||
settings.putInt(TEXT_ALIGN, allignment)
|
settings.putInt(TEXT_ALIGN, allignment)
|
||||||
_activeAlignment = allignment
|
activeAlignment = allignment
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFontSize() {
|
private fun refreshFontSize() {
|
||||||
_fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
fontSize = settings.getString(READER_FONT_SIZE, "16").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFontSize(): Int {
|
fun getFontSize(): Int {
|
||||||
if (_fontSize != null) {
|
if (fontSize != null) {
|
||||||
refreshFontSize()
|
refreshFontSize()
|
||||||
}
|
}
|
||||||
return _fontSize ?: 16
|
return fontSize ?: 16
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshStaticBarEnabled() {
|
private fun refreshStaticBarEnabled() {
|
||||||
_staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
staticBar = settings.getBoolean(READER_STATIC_BAR, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isStaticBarEnabled(): Boolean {
|
fun isStaticBarEnabled(): Boolean {
|
||||||
if (_staticBar != null) {
|
if (staticBar != null) {
|
||||||
refreshStaticBarEnabled()
|
refreshStaticBarEnabled()
|
||||||
}
|
}
|
||||||
return _staticBar == true
|
return staticBar == true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshFont() {
|
private fun refreshFont() {
|
||||||
_font = settings.getString(READER_FONT, "")
|
font = settings.getString(READER_FONT, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFont(): String {
|
fun getFont(): String {
|
||||||
if (_font.isEmpty()) {
|
if (font.isEmpty()) {
|
||||||
refreshFont()
|
refreshFont()
|
||||||
}
|
}
|
||||||
return _font
|
return font
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshCurrentTheme() {
|
private fun refreshCurrentTheme() {
|
||||||
_theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
theme = settings.getString(CURRENT_THEME, "-1").toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentTheme(): Int {
|
fun getCurrentTheme(): Int {
|
||||||
if (_theme == null) {
|
if (theme == null) {
|
||||||
refreshCurrentTheme()
|
refreshCurrentTheme()
|
||||||
}
|
}
|
||||||
return _theme ?: -1
|
return theme ?: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshApiSettings() {
|
fun refreshApiSettings() {
|
||||||
@@ -441,7 +443,7 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
login: String,
|
login: String,
|
||||||
password: String,
|
password: String,
|
||||||
) {
|
) {
|
||||||
val regex = """\/\/(\D+):(\D+)@""".toRegex()
|
val regex = """\/\/(\S+):(\S+)@""".toRegex()
|
||||||
val matchResult = regex.find(url)
|
val matchResult = regex.find(url)
|
||||||
if (matchResult != null) {
|
if (matchResult != null) {
|
||||||
val (basicLogin, basicPassword) = matchResult.destructured
|
val (basicLogin, basicPassword) = matchResult.destructured
|
||||||
@@ -478,15 +480,15 @@ class AppSettingsService(acraSenderServiceProcess: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val translationUrl = "https://crwd.in/readerforselfoss"
|
const val TRANSLATION_URL = "https://crwd.in/readerforselfoss"
|
||||||
|
|
||||||
const val sourceUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
const val SOURCE_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform"
|
||||||
|
|
||||||
const val trackerUrl = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
const val TRACKER_URL = "https://gitea.amine-bouabdallaoui.fr/Louvorg/ReaderForSelfoss-multiplatform/issues"
|
||||||
|
|
||||||
const val syncChannelId = "sync-channel-id"
|
const val SYNC_CHANNEL_ID = "sync-channel-id"
|
||||||
|
|
||||||
const val newItemsChannelId = "new-items-channel-id"
|
const val NEW_ITEMS_CHANNEL_ID = "new-items-channel-id"
|
||||||
|
|
||||||
const val JUSTIFY = 1
|
const val JUSTIFY = 1
|
||||||
|
|
||||||
|
@@ -1,9 +1,35 @@
|
|||||||
package bou.amine.apps.readerforselfossv2.utils
|
package bou.amine.apps.readerforselfossv2.utils
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
import kotlinx.datetime.TimeZone
|
||||||
|
import kotlinx.datetime.toInstant
|
||||||
|
|
||||||
|
fun String.toParsedDate(): Long {
|
||||||
|
// Possible formats are
|
||||||
|
// yyyy-mm-dd hh:mm:ss format
|
||||||
|
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)
|
||||||
|
val newVersionFormat = "(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})[+-](\\d{2}(:\\d{2})?)?".toRegex()
|
||||||
|
|
||||||
|
val isoDateString: String =
|
||||||
|
try {
|
||||||
|
if (this.matches(oldVersionFormat)) {
|
||||||
|
this.replace(" ", "T")
|
||||||
|
} else if (this.matches(newVersionFormat)) {
|
||||||
|
newVersionFormat.find(this)?.groups?.get(1)?.value ?: throw Exception("Couldn't parse $this")
|
||||||
|
} else {
|
||||||
|
throw Exception("Unrecognized format for $this")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Exception("parseDate failed for $this", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalDateTime.parse(isoDateString).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
|
||||||
|
}
|
||||||
|
|
||||||
expect class DateUtils() {
|
expect class DateUtils() {
|
||||||
companion object {
|
companion object {
|
||||||
fun parseDate(dateString: String): Long
|
|
||||||
|
|
||||||
fun parseRelativeDate(dateString: String): String
|
fun parseRelativeDate(dateString: String): String
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -73,3 +73,13 @@ fun SelfossModel.Item.toEntity(): ITEM =
|
|||||||
this.tags.joinToString(","),
|
this.tags.joinToString(","),
|
||||||
this.author,
|
this.author,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun SelfossModel.Tag.getColorHexCode(): String =
|
||||||
|
if (this.color.length == 4) { // #000
|
||||||
|
val char1 = this.color.get(1)
|
||||||
|
val char2 = this.color.get(2)
|
||||||
|
val char3 = this.color.get(3)
|
||||||
|
"#$char1$char1$char2$char2$char3$char3"
|
||||||
|
} else {
|
||||||
|
this.color
|
||||||
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user