Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ab7bbb652 | ||
|
|
aeef8274e2 | ||
|
|
b263033912 | ||
|
|
1c54db5f16 | ||
|
|
1497ca4c5f | ||
|
|
397f60dbfb | ||
|
|
a72522bf44 | ||
|
|
e46c928af9 | ||
|
|
a9e2af5626 | ||
|
|
9ffeef5bed | ||
|
|
df55659293 | ||
|
|
ad021bc9e8 | ||
|
|
3bfc9b91d1 | ||
|
|
7699cde92b | ||
|
|
ee5378546e | ||
|
|
50d2ef28f7 | ||
|
|
833a130ecd | ||
|
|
3556b18fe1 | ||
|
|
bd72089ad9 | ||
|
|
545a0d8c3b | ||
|
|
10068bde93 | ||
|
|
c94f842cd4 | ||
|
|
1fde5359ca | ||
|
|
6d62d7cfaf | ||
|
|
ff03d4eda6 | ||
|
|
355262210f | ||
|
|
65b75cf908 | ||
|
|
e614f00c1e | ||
|
|
d09527427a | ||
|
|
3f328c2985 | ||
|
|
245ccf5242 | ||
|
|
d5f85309d0 | ||
|
|
86bf7f5163 | ||
|
|
41a163b537 | ||
|
|
754b4dbc11 | ||
|
|
b39edd9fa1 | ||
|
|
b3a9b06333 | ||
|
|
8825335dd7 | ||
|
|
7f0edc8e90 | ||
|
|
08e22097c8 | ||
|
|
92cfb7b5f7 | ||
|
|
669777b999 | ||
|
|
153b85d4c8 | ||
|
|
0259c08c78 | ||
|
|
aafbc5effe | ||
|
|
def4311a87 | ||
|
|
c27ffb0be9 | ||
|
|
10b2b26713 | ||
|
|
37d52bbd84 | ||
|
|
b9dc3a02b4 | ||
|
|
47c06f4e52 | ||
|
|
7accb69fd0 | ||
|
|
2b83638417 | ||
|
|
8bb65e8637 | ||
|
|
ba434d4c00 | ||
|
|
5ce6ffae82 | ||
|
|
bf8b20e094 | ||
|
|
a18a13dbcd | ||
|
|
567e6bca2f | ||
|
|
26ca1db904 | ||
|
|
f70b0fd99f | ||
|
|
2dd4670f36 | ||
|
|
453a766545 | ||
|
|
f52d7a379b | ||
|
|
43885be65d | ||
|
|
58978b4883 | ||
|
|
43be8c5cab | ||
|
|
6e6e7bdfc7 | ||
|
|
d868de7c84 | ||
|
|
f60fde40fb | ||
|
|
7cf9153d10 | ||
|
|
c10770ebd7 | ||
|
|
25347f40ca | ||
|
|
121d75d128 | ||
|
|
4c611ed07d | ||
|
|
463853a5f7 | ||
|
|
231008dd26 | ||
|
|
638c61adaf | ||
|
|
5ddaa03d93 | ||
|
|
62a053b32a | ||
|
|
c72c7423aa | ||
|
|
2cfcbd260d | ||
|
|
5f72004594 | ||
|
|
fb4ba75bd5 | ||
|
|
a6e1480cb7 | ||
|
|
10791d33aa | ||
|
|
9b6219ceca | ||
|
|
1f7a083856 | ||
|
|
133a8771c2 | ||
|
|
7926449fd9 | ||
|
|
40445fd83f | ||
|
|
1f7d5e4324 | ||
|
|
6d94748ffa | ||
|
|
ae62666dfc | ||
|
|
d99f2e9556 | ||
|
|
0f217440a7 | ||
|
|
9ee558b92c | ||
|
|
ad2694ccbf | ||
|
|
830b6f6f46 | ||
|
|
10db9a5935 | ||
|
|
a68a007e1a | ||
|
|
d3f5ea5953 | ||
|
|
ffd73a65cb | ||
|
|
1b9f35222d | ||
|
|
12110a6ee3 | ||
|
|
15a7232a4e | ||
|
|
babb8685ac | ||
|
|
7844be0e69 | ||
|
|
21e10904e6 | ||
|
|
401000755e | ||
|
|
cb39460219 | ||
|
|
991a9714d4 | ||
|
|
c24f9c2958 | ||
|
|
a87e961bea | ||
|
|
faa0963b14 | ||
|
|
6a59ef6620 | ||
|
|
3a4e7a6b8d | ||
|
|
440b424e67 | ||
|
|
139882d707 | ||
|
|
015cc818c0 | ||
|
|
213c94ffd5 | ||
|
|
ab187a7f09 | ||
|
|
506b518484 | ||
|
|
a5b8210231 | ||
|
|
7c3fc6d81a | ||
|
|
ad94fbd737 | ||
|
|
7a95c4aee3 | ||
|
|
9528ec3b7f | ||
|
|
446d18756c | ||
|
|
4d8df534f0 | ||
|
|
5c6c48547d | ||
|
|
463806c654 | ||
|
|
5182f9b4da | ||
|
|
8043218df4 | ||
|
|
ff3ffce5a7 | ||
|
|
b97d6d7dac | ||
|
|
c9e8f37089 | ||
|
|
ba98beb055 | ||
|
|
9882eaf342 | ||
|
|
8b2a2216e7 | ||
|
|
75eda29055 | ||
|
|
6c611c69a0 | ||
|
|
628e4c6616 | ||
|
|
1b466dd8ad | ||
|
|
30668f1e6e | ||
|
|
6ecca17e75 | ||
|
|
5a511f8726 | ||
|
|
c60c266ec9 | ||
|
|
e798db33ff | ||
|
|
7935058a83 | ||
|
|
71aaa2ca38 | ||
|
|
46cbab7b46 | ||
|
|
28f9539943 | ||
|
|
56960199c1 | ||
|
|
f14f45bcea | ||
|
|
24c2746056 | ||
|
|
f646302b84 | ||
|
|
c62646310f | ||
|
|
7b7b950da3 | ||
|
|
cfb6669846 | ||
|
|
700f4cf21d | ||
|
|
501a927484 | ||
|
|
6220042cb1 | ||
|
|
18e9e19e1c | ||
|
|
96bdc91f14 | ||
|
|
b8eab6ef1d | ||
|
|
9b7fe19226 | ||
|
|
500a72d781 | ||
|
|
c596d137fc | ||
|
|
52fe342902 | ||
|
|
1c60d9e7bd | ||
|
|
bc0281e45a | ||
|
|
fae9729a93 | ||
|
|
c12638c00b | ||
|
|
18f942c433 | ||
|
|
59737309d1 | ||
|
|
89989fd92c | ||
|
|
8d78b927a9 | ||
|
|
18c49b39e3 | ||
|
|
11a8607abe | ||
|
|
90700f8b9c | ||
|
|
14dd28ffa8 | ||
|
|
30295a35fe | ||
|
|
67637090a0 | ||
|
|
b3b3347c46 | ||
|
|
227ec4a191 | ||
|
|
6492125d2b | ||
|
|
017e485802 | ||
|
|
46371777b5 | ||
|
|
b12b017990 | ||
|
|
999a482e28 | ||
|
|
5fd1e1f187 | ||
|
|
96536187bc | ||
|
|
788eaeaf61 | ||
|
|
7e2a58b111 | ||
|
|
e66f118720 | ||
|
|
57175c64f1 | ||
|
|
ad8d1bd172 | ||
|
|
6a28a10991 | ||
|
|
f8f8c74e3d | ||
|
|
683d123199 | ||
|
|
39645fe603 | ||
|
|
46d5e586a8 | ||
|
|
09a0de6579 | ||
|
|
2ddebfc5bf | ||
|
|
e9c1f6d37c | ||
|
|
af9477da4b | ||
|
|
70be7d7067 | ||
|
|
2e2d000d6c | ||
|
|
d04dcf38bf | ||
|
|
38264c31cb | ||
|
|
a83fa2e680 | ||
|
|
872dc6f42b | ||
|
|
a0e2e8abf6 | ||
|
|
bfe826caaa | ||
|
|
a91f6e9f64 | ||
|
|
99d3d03825 | ||
|
|
a66b9d849b | ||
|
|
c5b12bc191 | ||
|
|
3714378ad9 | ||
|
|
21da1211da | ||
|
|
7a09c312f2 | ||
|
|
039fc46797 | ||
|
|
30d93e6074 | ||
|
|
833ff65c81 | ||
|
|
fd8a7109c4 | ||
|
|
22668cb851 | ||
|
|
dfd4636b64 | ||
|
|
c8bc147546 | ||
|
|
c516400c2d | ||
|
|
7ad6a3fbc4 | ||
|
|
90574f5b6f | ||
|
|
08e14547f2 | ||
|
|
4dea376d7c | ||
|
|
7bdb4b8006 | ||
|
|
d33e383409 | ||
|
|
b90fb0b70b | ||
|
|
49bc624887 | ||
|
|
f4a95ef5b1 | ||
|
|
3a88af9b12 | ||
|
|
5c0429dcf2 | ||
|
|
850f85db97 | ||
|
|
9806ebb5b7 | ||
|
|
eb2c475241 | ||
|
|
70fbff7b9b | ||
|
|
e820116fbe | ||
|
|
832c57a5a5 | ||
|
|
b23befd4a5 | ||
|
|
2d1c7f4b90 | ||
|
|
952740430c | ||
|
|
df80c5a8fa | ||
|
|
06b6281cbc | ||
|
|
f9496e053d | ||
|
|
62417f3369 | ||
|
|
c944f37d52 | ||
|
|
787d034ef6 | ||
|
|
5229fa0bc8 | ||
|
|
9b6dc066be | ||
|
|
8a9456f3e8 | ||
|
|
b6245a34ca | ||
|
|
cc9e3f831d | ||
|
|
52162aec81 | ||
|
|
cc1764c55d | ||
|
|
8e6ee13849 | ||
|
|
da5826a74b | ||
|
|
ae88a8abae |
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generates a Play Store "What's New" directory from the Fastlane changelogs of
|
||||
# the current versionCode.
|
||||
set -euo pipefail
|
||||
|
||||
VERSION_CODE="${1:?versionCode required}"
|
||||
OUT_DIR="${2:-distribution/whatsnew}"
|
||||
META_DIR="fastlane/metadata/android"
|
||||
|
||||
MAX_LEN=500
|
||||
|
||||
rm -rf "$OUT_DIR"
|
||||
mkdir -p "$OUT_DIR"
|
||||
|
||||
found=0
|
||||
for dir in "$META_DIR"/*/; do
|
||||
locale="$(basename "$dir")"
|
||||
changelog="${dir}changelogs/${VERSION_CODE}.txt"
|
||||
[[ -f "$changelog" ]] || continue
|
||||
|
||||
text="$(cat "$changelog")"
|
||||
if [[ "${#text}" -gt "$MAX_LEN" ]]; then
|
||||
echo "::warning::Changelog for $locale ($VERSION_CODE) is ${#text} chars (>$MAX_LEN); truncating."
|
||||
text="${text:0:$((MAX_LEN - 10))}..."
|
||||
fi
|
||||
|
||||
printf '%s' "$text" > "$OUT_DIR/whatsnew-${locale}"
|
||||
echo " $locale -> whatsnew-${locale}"
|
||||
found=$((found + 1))
|
||||
done
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
echo "::warning::No Fastlane changelogs found for versionCode $VERSION_CODE."
|
||||
fi
|
||||
echo "Generated $found What's-New file(s) in $OUT_DIR"
|
||||
@@ -14,19 +14,21 @@ jobs:
|
||||
#1 Checkout Repository
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
#2 Setup Java
|
||||
- name: Set Up Java
|
||||
uses: actions/setup-java@v3.12.0
|
||||
with:
|
||||
distribution: 'oracle'
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
|
||||
#3 Setup Flutter
|
||||
- name: Set Up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.38.5'
|
||||
flutter-version: '3.44.2'
|
||||
channel: 'stable'
|
||||
|
||||
#4 Install Dependencies
|
||||
@@ -57,6 +59,23 @@ jobs:
|
||||
- name: Build APKs (Split per ABI)
|
||||
run: flutter build apk --release --split-per-abi --no-tree-shake-icons
|
||||
|
||||
#6.3 Version + previous tag (used by Play changelogs and release notes)
|
||||
- name: Extract version and previous tag
|
||||
id: version
|
||||
run: |
|
||||
full=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r ')
|
||||
version="${full%%+*}"
|
||||
code="${full##*+}"
|
||||
prev_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
echo "VERSION=$version" >> "$GITHUB_ENV"
|
||||
echo "VERSION_CODE=$code" >> "$GITHUB_ENV"
|
||||
echo "PREV_TAG=$prev_tag" >> "$GITHUB_ENV"
|
||||
echo "Version $version (code $code), previous tag: '${prev_tag:-none}'"
|
||||
|
||||
#6.4 Generate Play Store "What's New" from the Fastlane changelogs
|
||||
- name: Generate Play Store changelogs
|
||||
run: bash .github/scripts/generate_whatsnew.sh "$VERSION_CODE"
|
||||
|
||||
#7 Upload to Play Store
|
||||
- name: Upload to Google Play
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
@@ -64,9 +83,10 @@ jobs:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: de.doen1el.calibreWebCompanion
|
||||
releaseFiles: build/app/outputs/bundle/release/app-release.aab
|
||||
track: production
|
||||
tracks: production
|
||||
status: completed
|
||||
releaseName: 'Release ${{ github.run_number }}'
|
||||
whatsNewDirectory: distribution/whatsnew
|
||||
|
||||
# #8 Build iOS (.app)
|
||||
# - name: Build iOS (Unsigned)
|
||||
@@ -87,12 +107,36 @@ jobs:
|
||||
path: |
|
||||
build/app/outputs/flutter-apk/*.apk
|
||||
|
||||
#11 Extract Version
|
||||
- name: Extract version from pubspec.yaml
|
||||
id: extract_version
|
||||
#11 Build the GitHub release body from the fastlane changelog and template
|
||||
- name: Build release notes
|
||||
run: |
|
||||
version=$(grep '^version: ' pubspec.yaml | cut -d ' ' -f 2 | tr -d '\r' | sed 's/+.*//')
|
||||
echo "VERSION=$version" >> $GITHUB_ENV
|
||||
changelog="fastlane/metadata/android/en-US/changelogs/${VERSION_CODE}.txt"
|
||||
{
|
||||
echo "## v${VERSION}"
|
||||
echo
|
||||
echo "_TODO: Write a short summary of this release._"
|
||||
echo
|
||||
echo "Special thanks to:"
|
||||
echo "_TODO: @mention the contributors_"
|
||||
echo
|
||||
echo "And everyone else who created issues! ❤️"
|
||||
echo
|
||||
echo "## What's Changed"
|
||||
if [ -f "$changelog" ]; then
|
||||
# Normalise trailing newlines so the next heading renders.
|
||||
printf '%s\n' "$(cat "$changelog")"
|
||||
else
|
||||
echo "- _No changelog found for build ${VERSION_CODE}._"
|
||||
fi
|
||||
echo
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
echo
|
||||
echo "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...v${VERSION}"
|
||||
fi
|
||||
echo "## 📥 Download"
|
||||
} > release_notes.md
|
||||
echo "----- release_notes.md -----"
|
||||
cat release_notes.md
|
||||
|
||||
#12 Create GitHub Release
|
||||
- name: Create Release Draft
|
||||
@@ -103,4 +147,4 @@ jobs:
|
||||
draft: true
|
||||
token: ${{ secrets.TOKEN }}
|
||||
name: 'Release v${{ env.VERSION }}'
|
||||
generateReleaseNotes: true
|
||||
bodyFile: release_notes.md
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
name: 'Mirror to Codeberg'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
tags:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
mirror:
|
||||
name: Mirror to Codeberg
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Push to Codeberg
|
||||
uses: yesolutions/mirror-action@master
|
||||
with:
|
||||
REMOTE: 'https://codeberg.org/doen1el/calibre-web-companion.git'
|
||||
GIT_USERNAME: doen1el
|
||||
GIT_PASSWORD: ${{ secrets.CODEBERG_TOKEN }}
|
||||
@@ -46,3 +46,9 @@ app.*.map.json
|
||||
|
||||
*.jks
|
||||
de.doen1el.calibreWebCompanion.yml
|
||||
|
||||
# Don't track generated localization files
|
||||
lib/l10n/app_localizations*.dart
|
||||
test/test_env.dart
|
||||
release_notes.md
|
||||
distribution/whatsnew/
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
> [!CAUTION]
|
||||
> ‼️ Android will become a locked-down platform. Learn more: https://keepandroidopen.org/
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/icon/icon.png" alt="App Icon" width="100" />
|
||||
<br>
|
||||
v1.8.1
|
||||
v2.2.2
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/doen1el/calibre-web-companion/releases">
|
||||
<img src="https://img.shields.io/github/downloads/doen1el/calibre-web-companion/total?color=green&label=github%20downloads" alt="GitHub all releases">
|
||||
<img src="https://img.shields.io/github/downloads/doen1el/calibre-web-companion/total?color=green&label=Downloads%20(GitHub)" alt="GitHub all releases">
|
||||
</a>
|
||||
<a href="https://f-droid.org/en/packages/de.doen1el.calibreWebCompanion/" >
|
||||
<img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgithub.com%2Fkitswas%2Ffdroid-metrics-dashboard%2Fraw%2Frefs%2Fheads%2Fmain%2Fprocessed%2Ftotal%2Fde.doen1el.calibreWebCompanion.json&query=%24.total_downloads&logo=fdroid&label=Downloads%20(F-Droid)">
|
||||
</a>
|
||||
<a href="https://github.com/doen1el/calibre-web-companion/releases">
|
||||
<img src="https://img.shields.io/github/v/release/doen1el/calibre-web-companion?color=green&label=download&sort=semver" alt="GitHub release (latest SemVer)">
|
||||
@@ -17,11 +23,14 @@
|
||||
<a href="https://hosted.weblate.org/engage/calibre-web-companion/">
|
||||
<img src="https://hosted.weblate.org/widget/calibre-web-companion/svg-badge.svg" alt="Translation status" />
|
||||
</a>
|
||||
<a href="https://codeberg.org/doen1el/calibre-web-companion">
|
||||
<img src="https://img.shields.io/badge/mirror-Codeberg-2185D0?logo=codeberg&logoColor=white" alt="Codeberg mirror">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
# Calibre Web Companion
|
||||
|
||||
This is an unofficial companion application for [Calibre Web](https://github.com/janeczku/calibre-web) (which also works for [Calibre Web Automated](https://github.com/crocodilestick/Calibre-Web-Automated)) that allows you to browse your book collection and download books directly to your device. You can also interact with your books by marking them as read, unread or bookmarked. It is also possible to send books directly to your e-reader (Kindle/Kobo) thanks to the great work of [send2ereader](https://github.com/daniel-j/send2ereader).
|
||||
This is an unofficial companion application for [Calibre Web](https://github.com/janeczku/calibre-web) (which also works for [Calibre Web Automated](https://github.com/crocodilestick/Calibre-Web-Automated), [Grimmory](https://github.com/grimmory-tools/grimmory), [Calibre 'Sharing over the net'](https://github.com/kovidgoyal/calibre) and other OPDS providers (beta)), that allows you to browse your book collection and download books directly to your device. You can also interact with your books by marking them as read, unread or bookmarked. It is also possible to send books directly to your e-reader (Kindle/Kobo) thanks to the great work of [send2ereader](https://github.com/daniel-j/send2ereader).
|
||||
|
||||
The app is built with [Flutter](https://github.com/flutter/flutter) and uses **Material You**. It is currently available for **Android** only.
|
||||
|
||||
@@ -44,18 +53,21 @@ The app is built with [Flutter](https://github.com/flutter/flutter) and uses **M
|
||||
|
||||
## 💪 Features
|
||||
|
||||
- Log into your Calibre Web (Automated) server.
|
||||
- Enjoy seamless navigation through your book collection.
|
||||
- Explore books organized by various categories, such as author, series, trending, and more.
|
||||
- View detailed information about each book.
|
||||
- Easily mark books as read or unread, organize them into shelves, or archive them.
|
||||
- Access comprehensive collection statistics at a glance.
|
||||
- Download books directly onto your device.
|
||||
- Instantly send books to your e-reader using [send2ereader](https://github.com/daniel-j/send2ereader) or via Calibre Web's mail function. You can also use your own send2ereader instance.
|
||||
- Automatically download books to your collection with [calibre-web-automated-book-downloader](https://github.com/calibrain/calibre-web-automated-book-downloader).
|
||||
- Manage and customize your shelves.
|
||||
- Upload books to your Calibre Web server.
|
||||
- Edit your book's metadata and upload new covers.
|
||||
- Connect to your Calibre-Web (Automated), Grimmory, Calibre and OPDS servers, including reverse proxy/SSO setups, custom HTTP headers and self-signed certificates.
|
||||
- Browse your whole library with smooth, fast navigation.
|
||||
- Discover books by category, authors, series, publishers, ratings, hot & trending, and more.
|
||||
- View rich details for every book, edit its metadata, and upload new covers.
|
||||
- Mark books as read or unread, archive them, and organize them into shelves.
|
||||
- Create, edit and browse Magic Shelves, dynamic, rule‑based shelves (Calibre‑Web Automated only).
|
||||
- Add books quickly by scanning their ISBN barcode.
|
||||
- Read books in the built‑in eBook reader and sync your reading progress across devices via WebDAV.
|
||||
- Send books to your e‑reader via [send2ereader](https://github.com/daniel-j/send2ereader) (or your own instance) or Calibre‑Web's email function.
|
||||
- Download books straight into your collection with [shelfmark](https://github.com/calibrain/shelfmark).
|
||||
- ⚠️ This app does **not** support, encourage or facilitate the piracy of copyrighted works. Please only download content you are legally entitled to, respecting copyright is your responsibility.
|
||||
- Upload books to your Calibre‑Web server.
|
||||
- Sync your whole library or selected books for offline reading.
|
||||
- Check your collection statistics at a glance.
|
||||
- Make it yours: reorder or hide book actions, detail sections and Discover sections, choose a theme, enable e‑ink mode, and adjust the text size, available in 15 languages.
|
||||
|
||||
## 🖼️ Impressions
|
||||
|
||||
@@ -80,24 +92,15 @@ You can help translate Calibre Web Companion on [Weblate](https://hosted.weblate
|
||||
|
||||
You can of course open issues for bugs, feedback, and feature ideas. All suggestions are very welcome :)
|
||||
|
||||
## 🚧 Roadmap
|
||||
|
||||
- [x] Add localization
|
||||
- [x] Add dark mode
|
||||
- [x] Toggle read/unread status
|
||||
- [x] Add bookmark function
|
||||
- [x] Calibre Web Automated Download integration
|
||||
- [x] Add and remove from shelfs
|
||||
- [x] Code refactoring
|
||||
- [x] Better error handling
|
||||
- [x] Integrate [Weblate](https://hosted.weblate.org/) for translations
|
||||
The source code is also mirrored on [Codeberg](https://codeberg.org/doen1el/calibre-web-companion). GitHub remains the primary repository, so please open issues and pull requests here.
|
||||
|
||||
## 📜 Credits
|
||||
|
||||
- [Calibre Web](https://github.com/janeczku/calibre-web)
|
||||
- [Calibre Web Automated](https://github.com/crocodilestick/Calibre-Web-Automated)
|
||||
- [calibre-web-automated-book-downloader](https://github.com/calibrain/calibre-web-automated-book-downloader)
|
||||
- [shelfmark](https://github.com/calibrain/shelfmark)
|
||||
- [send2ereader](https://github.com/daniel-j/send2ereader)
|
||||
- [Flutter](https://github.com/flutter/flutter)
|
||||
- [IconKitchen](https://icon.kitchen)
|
||||
- [Weblate](https://hosted.weblate.org/)
|
||||
- [CosmosEpub](https://github.com/Mamasodikov/cosmos_epub)
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
# packages, and plugins designed to encourage good coding practices.
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- packages/**
|
||||
|
||||
linter:
|
||||
# The lint rules applied to this project can be customized in the
|
||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||
|
||||
@@ -29,7 +29,7 @@ require(detStorePassword != null) { "storePassword not found in key.properties f
|
||||
android {
|
||||
namespace = "de.doen1el.calibreWebCompanion"
|
||||
compileSdk = 36
|
||||
ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "28.2.13676358"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
@@ -61,10 +61,28 @@ android {
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
|
||||
isDebuggable = true
|
||||
}
|
||||
|
||||
getByName("release") {
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-keep class com.vocsy.epub_viewer.** { *; }
|
||||
|
||||
-keep class com.fasterxml.jackson.** { *; }
|
||||
-keepnames class com.fasterxml.jackson.** { *; }
|
||||
|
||||
-dontwarn java.beans.**
|
||||
-dontwarn javax.xml.**
|
||||
-dontwarn org.w3c.dom.bootstrap.**
|
||||
|
||||
-keep class com.folioreader.** { *; }
|
||||
-keep interface com.folioreader.** { *; }
|
||||
|
||||
-dontwarn org.joda.convert.**
|
||||
-dontwarn org.joda.time.**
|
||||
|
||||
-keep class org.greenrobot.eventbus.** { *; }
|
||||
-keepclassmembers class ** {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
-keep class retrofit2.** { *; }
|
||||
-dontwarn retrofit2.**
|
||||
|
||||
-keep class okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
-dontwarn okio.**
|
||||
|
||||
-keep class androidx.** { *; }
|
||||
-keep interface androidx.** { *; }
|
||||
-dontwarn androidx.**
|
||||
|
||||
-dontwarn com.google.android.play.**
|
||||
|
||||
-keep class org.readium.** { *; }
|
||||
-keepclassmembers class org.readium.** {
|
||||
public <init>();
|
||||
}
|
||||
|
||||
-keep class org.nanohttpd.** { *; }
|
||||
-keepclassmembers class org.nanohttpd.** {
|
||||
public <init>();
|
||||
}
|
||||
@@ -2,14 +2,13 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
|
||||
<application
|
||||
android:label="Calibre Web Companion"
|
||||
android:name="${applicationName}"
|
||||
@@ -28,10 +27,6 @@
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
@@ -41,17 +36,78 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
|
||||
<receiver
|
||||
android:name=".CurrentBookWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_current_book_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/current_book_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".LibraryStatsWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_stats_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/library_stats_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".ShelfWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_shelf_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/shelf_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".QuickActionsWidgetProvider"
|
||||
android:exported="true"
|
||||
android:label="@string/widget_quick_actions_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/quick_actions_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".ShelfWidgetService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<receiver
|
||||
android:name="es.antonborri.home_widget.HomeWidgetBackgroundReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="es.antonborri.home_widget.action.BACKGROUND" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name="es.antonborri.home_widget.HomeWidgetBackgroundService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
import java.io.File
|
||||
|
||||
class CurrentBookWidgetProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
) {
|
||||
for (widgetId in appWidgetIds) {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_current_book)
|
||||
|
||||
val uuid = widgetData.getString("cb_uuid", "") ?: ""
|
||||
val title = widgetData.getString("cb_title", "") ?: ""
|
||||
val authors = widgetData.getString("cb_authors", "") ?: ""
|
||||
val coverPath = widgetData.getString("cb_cover", "") ?: ""
|
||||
val progress = (widgetData.getString("cb_progress", "0") ?: "0").toIntOrNull() ?: 0
|
||||
|
||||
if (uuid.isEmpty()) {
|
||||
views.setViewVisibility(R.id.widget_empty, View.VISIBLE)
|
||||
views.setViewVisibility(R.id.widget_content, View.GONE)
|
||||
} else {
|
||||
views.setViewVisibility(R.id.widget_empty, View.GONE)
|
||||
views.setViewVisibility(R.id.widget_content, View.VISIBLE)
|
||||
views.setTextViewText(R.id.widget_title, title)
|
||||
views.setTextViewText(R.id.widget_authors, authors)
|
||||
|
||||
val decoded =
|
||||
if (coverPath.isNotEmpty() && File(coverPath).exists()) {
|
||||
WidgetImages.decodeScaledCover(coverPath)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (decoded != null) {
|
||||
val radius = 10f * context.resources.displayMetrics.density
|
||||
views.setImageViewBitmap(
|
||||
R.id.widget_cover,
|
||||
WidgetImages.roundBitmap(decoded, radius)
|
||||
)
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_cover, R.drawable.widget_cover_placeholder)
|
||||
}
|
||||
|
||||
if (progress in 1..99) {
|
||||
views.setViewVisibility(R.id.widget_percent_container, View.VISIBLE)
|
||||
views.setTextViewText(R.id.widget_percent, "$progress%")
|
||||
} else {
|
||||
views.setViewVisibility(R.id.widget_percent_container, View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
WidgetTheming.palette(context, widgetData)?.let { p ->
|
||||
views.setInt(R.id.widget_bg, "setColorFilter", p.background)
|
||||
views.setTextColor(R.id.widget_title, p.onBackground)
|
||||
views.setTextColor(R.id.widget_empty, p.onBackground)
|
||||
views.setTextColor(R.id.widget_authors, WidgetTheming.muted(p.onBackground))
|
||||
views.setInt(R.id.widget_percent_bg, "setColorFilter", p.accent)
|
||||
views.setTextColor(R.id.widget_percent, p.onAccent)
|
||||
}
|
||||
|
||||
val uri = Uri.parse("calibrewebcompanion://widget/current?uuid=$uuid")
|
||||
val pendingIntent =
|
||||
HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java, uri)
|
||||
views.setOnClickPendingIntent(R.id.widget_root, pendingIntent)
|
||||
|
||||
appWidgetManager.updateAppWidget(widgetId, views)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class LibraryStatsWidgetProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
) {
|
||||
for (widgetId in appWidgetIds) {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_library_stats)
|
||||
|
||||
views.setTextViewText(R.id.stat_books_value, widgetData.getString("st_books", "0"))
|
||||
views.setTextViewText(R.id.stat_authors_value, widgetData.getString("st_authors", "0"))
|
||||
views.setTextViewText(
|
||||
R.id.stat_categories_value,
|
||||
widgetData.getString("st_categories", "0")
|
||||
)
|
||||
views.setTextViewText(R.id.stat_series_value, widgetData.getString("st_series", "0"))
|
||||
|
||||
WidgetTheming.palette(context, widgetData)?.let { p ->
|
||||
views.setInt(R.id.widget_bg, "setColorFilter", p.background)
|
||||
views.setTextColor(R.id.stats_label, WidgetTheming.muted(p.onBackground))
|
||||
|
||||
for (tileBg in intArrayOf(
|
||||
R.id.tile_bg_books,
|
||||
R.id.tile_bg_authors,
|
||||
R.id.tile_bg_categories,
|
||||
R.id.tile_bg_series
|
||||
)) {
|
||||
views.setInt(tileBg, "setColorFilter", p.tile)
|
||||
}
|
||||
for (value in intArrayOf(
|
||||
R.id.stat_books_value,
|
||||
R.id.stat_authors_value,
|
||||
R.id.stat_categories_value,
|
||||
R.id.stat_series_value
|
||||
)) {
|
||||
views.setTextColor(value, p.onTile)
|
||||
}
|
||||
for (label in intArrayOf(
|
||||
R.id.stat_books_label,
|
||||
R.id.stat_authors_label,
|
||||
R.id.stat_categories_label,
|
||||
R.id.stat_series_label
|
||||
)) {
|
||||
views.setTextColor(label, WidgetTheming.muted(p.onTile))
|
||||
}
|
||||
}
|
||||
|
||||
val uri = Uri.parse("calibrewebcompanion://widget/stats")
|
||||
val pendingIntent =
|
||||
HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java, uri)
|
||||
views.setOnClickPendingIntent(R.id.stats_root, pendingIntent)
|
||||
|
||||
appWidgetManager.updateAppWidget(widgetId, views)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,32 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
class MainActivity : FlutterActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
restoreWidgetUri(intent)
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
restoreWidgetUri(intent)
|
||||
super.onNewIntent(intent)
|
||||
}
|
||||
|
||||
private fun restoreWidgetUri(intent: Intent?) {
|
||||
if (intent == null) return
|
||||
if (intent.action != HomeWidgetLaunchIntent.HOME_WIDGET_LAUNCH_ACTION) return
|
||||
if (intent.data != null) return
|
||||
|
||||
val uri = intent.getStringExtra(EXTRA_WIDGET_URI) ?: return
|
||||
intent.data = Uri.parse(uri)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_WIDGET_URI = "widget_uri"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class QuickActionsWidgetProvider : HomeWidgetProvider() {
|
||||
|
||||
private data class Action(
|
||||
val slotId: Int,
|
||||
val tileId: Int,
|
||||
val iconId: Int,
|
||||
val labelId: Int,
|
||||
val key: String
|
||||
)
|
||||
|
||||
private val actions = listOf(
|
||||
Action(R.id.qa_slot_search, R.id.qa_tile_search, R.id.qa_icon_search, R.id.qa_label_search, "search"),
|
||||
Action(R.id.qa_slot_scan, R.id.qa_tile_scan, R.id.qa_icon_scan, R.id.qa_label_scan, "scan"),
|
||||
Action(R.id.qa_slot_read, R.id.qa_tile_read, R.id.qa_icon_read, R.id.qa_label_read, "read"),
|
||||
Action(R.id.qa_slot_downloads, R.id.qa_tile_downloads, R.id.qa_icon_downloads, R.id.qa_label_downloads, "downloads")
|
||||
)
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
) {
|
||||
val downloadsEnabled = widgetData.getString("qa_downloads", "0") == "1"
|
||||
val palette = WidgetTheming.palette(context, widgetData)
|
||||
|
||||
for (widgetId in appWidgetIds) {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_quick_actions)
|
||||
|
||||
palette?.let { views.setInt(R.id.widget_bg, "setColorFilter", it.background) }
|
||||
|
||||
for (action in actions) {
|
||||
if (action.key == "downloads" && !downloadsEnabled) {
|
||||
views.setViewVisibility(action.slotId, View.GONE)
|
||||
continue
|
||||
}
|
||||
|
||||
views.setViewVisibility(action.slotId, View.VISIBLE)
|
||||
views.setOnClickPendingIntent(
|
||||
action.slotId,
|
||||
HomeWidgetLaunchIntent.getActivity(
|
||||
context,
|
||||
MainActivity::class.java,
|
||||
Uri.parse("calibrewebcompanion://widget/action?do=${action.key}")
|
||||
)
|
||||
)
|
||||
|
||||
palette?.let { p ->
|
||||
views.setInt(action.tileId, "setColorFilter", p.tile)
|
||||
views.setInt(action.iconId, "setColorFilter", p.onTile)
|
||||
views.setTextColor(action.labelId, p.onBackground)
|
||||
}
|
||||
}
|
||||
|
||||
appWidgetManager.updateAppWidget(widgetId, views)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
|
||||
import es.antonborri.home_widget.HomeWidgetLaunchIntent
|
||||
import es.antonborri.home_widget.HomeWidgetProvider
|
||||
|
||||
class ShelfWidgetProvider : HomeWidgetProvider() {
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray,
|
||||
widgetData: SharedPreferences
|
||||
) {
|
||||
for (widgetId in appWidgetIds) {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_shelf)
|
||||
|
||||
val title = widgetData.getString("sh_title", "") ?: ""
|
||||
views.setTextViewText(
|
||||
R.id.shelf_title,
|
||||
if (title.isEmpty()) context.getString(R.string.widget_shelf_recent) else title
|
||||
)
|
||||
|
||||
val adapterIntent = Intent(context, ShelfWidgetService::class.java).apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||
// Makes the intent unique per widget so each keeps its own adapter.
|
||||
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||
}
|
||||
views.setRemoteAdapter(R.id.shelf_grid, adapterIntent)
|
||||
views.setEmptyView(R.id.shelf_grid, R.id.shelf_empty)
|
||||
|
||||
val template = Intent(context, MainActivity::class.java).apply {
|
||||
action = HomeWidgetLaunchIntent.HOME_WIDGET_LAUNCH_ACTION
|
||||
}
|
||||
views.setPendingIntentTemplate(
|
||||
R.id.shelf_grid,
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
template,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
)
|
||||
)
|
||||
|
||||
WidgetTheming.palette(context, widgetData)?.let { p ->
|
||||
views.setInt(R.id.widget_bg, "setColorFilter", p.background)
|
||||
views.setTextColor(R.id.shelf_title, p.onBackground)
|
||||
views.setTextColor(R.id.shelf_empty, WidgetTheming.muted(p.onBackground))
|
||||
views.setInt(R.id.shelf_refresh, "setColorFilter", p.accent)
|
||||
}
|
||||
|
||||
// Reloads the shelf in a background isolate, without opening the app.
|
||||
val refresh = HomeWidgetBackgroundIntent.getBroadcast(
|
||||
context,
|
||||
Uri.parse("calibrewebcompanion://widget/refresh")
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.shelf_refresh, refresh)
|
||||
views.setOnClickPendingIntent(R.id.shelf_empty, refresh)
|
||||
|
||||
views.setOnClickPendingIntent(
|
||||
R.id.shelf_header,
|
||||
HomeWidgetLaunchIntent.getActivity(
|
||||
context,
|
||||
MainActivity::class.java,
|
||||
Uri.parse("calibrewebcompanion://widget/shelf")
|
||||
)
|
||||
)
|
||||
|
||||
appWidgetManager.updateAppWidget(widgetId, views)
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(widgetId, R.id.shelf_grid)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService
|
||||
import es.antonborri.home_widget.HomeWidgetPlugin
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private const val MIN_COVER_DIMEN = 96
|
||||
private const val MAX_COVER_DIMEN = 256
|
||||
|
||||
class ShelfWidgetService : RemoteViewsService() {
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory =
|
||||
ShelfRemoteViewsFactory(applicationContext)
|
||||
}
|
||||
|
||||
private data class ShelfItem(
|
||||
val uuid: String,
|
||||
val title: String,
|
||||
val authors: String,
|
||||
val cover: String
|
||||
)
|
||||
|
||||
private class ShelfRemoteViewsFactory(private val context: Context) :
|
||||
RemoteViewsService.RemoteViewsFactory {
|
||||
|
||||
private var items: List<ShelfItem> = emptyList()
|
||||
private var palette: WidgetTheming.Palette? = null
|
||||
private var coverDimen: Int = MAX_COVER_DIMEN
|
||||
|
||||
override fun onCreate() = Unit
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
val data = HomeWidgetPlugin.getData(context)
|
||||
items = parse(data.getString("sh_json", "") ?: "")
|
||||
palette = WidgetTheming.palette(context, data)
|
||||
coverDimen = coverDimenFor(items.size)
|
||||
}
|
||||
|
||||
private fun coverDimenFor(count: Int): Int {
|
||||
if (count <= 0) return MAX_COVER_DIMEN
|
||||
|
||||
val metrics = context.resources.displayMetrics
|
||||
val hostLimit =
|
||||
metrics.widthPixels.toLong() * metrics.heightPixels.toLong() * 4L * 3L / 2L
|
||||
val perItem = hostLimit * 30 / 100 / count
|
||||
val height = sqrt(perItem / 2.7).toInt()
|
||||
|
||||
return height.coerceIn(MIN_COVER_DIMEN, MAX_COVER_DIMEN)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
items = emptyList()
|
||||
}
|
||||
|
||||
override fun getCount(): Int = items.size
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews {
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_shelf_item)
|
||||
val item = items.getOrNull(position) ?: return views
|
||||
|
||||
views.setTextViewText(R.id.item_title, item.title)
|
||||
views.setTextViewText(R.id.item_authors, item.authors)
|
||||
views.setContentDescription(R.id.item_cover, item.title)
|
||||
|
||||
val decoded =
|
||||
if (item.cover.isNotEmpty() && File(item.cover).exists()) {
|
||||
WidgetImages.decodeScaledCover(item.cover, maxDimen = coverDimen)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (decoded != null) {
|
||||
val radius = 8f * context.resources.displayMetrics.density
|
||||
views.setImageViewBitmap(R.id.item_cover, WidgetImages.roundBitmap(decoded, radius))
|
||||
} else {
|
||||
views.setImageViewResource(R.id.item_cover, R.drawable.widget_cover_placeholder)
|
||||
}
|
||||
|
||||
palette?.let { p ->
|
||||
views.setTextColor(R.id.item_title, p.onBackground)
|
||||
views.setTextColor(R.id.item_authors, WidgetTheming.muted(p.onBackground))
|
||||
}
|
||||
|
||||
val target = "calibrewebcompanion://widget/book?uuid=${item.uuid}"
|
||||
views.setOnClickFillInIntent(
|
||||
R.id.item_root,
|
||||
Intent()
|
||||
.setData(Uri.parse(target))
|
||||
.putExtra(MainActivity.EXTRA_WIDGET_URI, target)
|
||||
)
|
||||
|
||||
return views
|
||||
}
|
||||
|
||||
override fun getLoadingView(): RemoteViews? = null
|
||||
|
||||
override fun getViewTypeCount(): Int = 1
|
||||
|
||||
override fun getItemId(position: Int): Long = position.toLong()
|
||||
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
private fun parse(json: String): List<ShelfItem> {
|
||||
if (json.isEmpty()) return emptyList()
|
||||
return runCatching {
|
||||
val array = JSONArray(json)
|
||||
(0 until array.length()).mapNotNull { index ->
|
||||
val entry = array.optJSONObject(index) ?: return@mapNotNull null
|
||||
ShelfItem(
|
||||
uuid = entry.optString("uuid"),
|
||||
title = entry.optString("title"),
|
||||
authors = entry.optString("authors"),
|
||||
cover = entry.optString("cover")
|
||||
)
|
||||
}
|
||||
}.getOrDefault(emptyList())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.BitmapShader
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Shader
|
||||
|
||||
object WidgetImages {
|
||||
fun decodeScaledCover(path: String, maxDimen: Int = 512): Bitmap? {
|
||||
val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||
BitmapFactory.decodeFile(path, bounds)
|
||||
if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null
|
||||
|
||||
var sampleSize = 1
|
||||
val longestEdge = maxOf(bounds.outWidth, bounds.outHeight)
|
||||
while (longestEdge / sampleSize > maxDimen * 2) {
|
||||
sampleSize *= 2
|
||||
}
|
||||
|
||||
val options = BitmapFactory.Options().apply { inSampleSize = sampleSize }
|
||||
val decoded = BitmapFactory.decodeFile(path, options) ?: return null
|
||||
|
||||
val longestDecoded = maxOf(decoded.width, decoded.height)
|
||||
if (longestDecoded <= maxDimen) return decoded
|
||||
|
||||
val scale = maxDimen.toFloat() / longestDecoded
|
||||
val width = (decoded.width * scale).toInt().coerceAtLeast(1)
|
||||
val height = (decoded.height * scale).toInt().coerceAtLeast(1)
|
||||
val scaled = Bitmap.createScaledBitmap(decoded, width, height, true)
|
||||
if (scaled != decoded) decoded.recycle()
|
||||
return scaled
|
||||
}
|
||||
|
||||
fun roundBitmap(src: Bitmap, radius: Float): Bitmap {
|
||||
val output = Bitmap.createBitmap(src.width, src.height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(output)
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
shader = BitmapShader(src, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
|
||||
}
|
||||
val rect = RectF(0f, 0f, src.width.toFloat(), src.height.toFloat())
|
||||
canvas.drawRoundRect(rect, radius, radius, paint)
|
||||
return output
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package de.doen1el.calibreWebCompanion
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
|
||||
object WidgetTheming {
|
||||
data class Palette(
|
||||
val background: Int,
|
||||
val onBackground: Int,
|
||||
val tile: Int,
|
||||
val onTile: Int,
|
||||
val accent: Int,
|
||||
val onAccent: Int
|
||||
)
|
||||
|
||||
fun palette(context: Context, data: SharedPreferences): Palette? {
|
||||
val night = (context.resources.configuration.uiMode and
|
||||
Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
val suffix = if (night) "dark" else "light"
|
||||
|
||||
val background = parse(data, "th_bg_$suffix") ?: return null
|
||||
val onBackground = parse(data, "th_on_bg_$suffix") ?: return null
|
||||
val tile = parse(data, "th_tile_$suffix") ?: background
|
||||
val onTile = parse(data, "th_on_tile_$suffix") ?: onBackground
|
||||
val accent = parse(data, "th_accent_$suffix") ?: onBackground
|
||||
val onAccent = parse(data, "th_on_accent_$suffix") ?: background
|
||||
|
||||
return Palette(background, onBackground, tile, onTile, accent, onAccent)
|
||||
}
|
||||
|
||||
fun muted(color: Int): Int = (color and 0x00FFFFFF) or (0xB3 shl 24)
|
||||
|
||||
private fun parse(data: SharedPreferences, key: String): Int? =
|
||||
data.getString(key, null)?.let { runCatching { Color.parseColor(it) }.getOrNull() }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M18,2H6c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zM6,4h5v8l-2.5,-1.5L6,12V4z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -8,8s3.57,8 8,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M3,5v4h2V5h4V3H5C3.9,3 3,3.9 3,5zM5,15H3v4c0,1.1 0.9,2 2,2h4v-2H5V15zM19,19h-4v2h4c1.1,0 2,-0.9 2,-2v-4h-2V19zM19,3h-4v2h4v4h2V5C21,3.9 20.1,3 19,3z" />
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M4,11h16v2H4z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF1A1C18"
|
||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="20dp" />
|
||||
<solid android:color="@color/widget_background" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="6dp" />
|
||||
<solid android:color="@color/widget_tile_background" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="10dp" />
|
||||
<solid android:color="#B3000000" />
|
||||
</shape>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="14dp" />
|
||||
<solid android:color="@color/widget_tile_background" />
|
||||
</shape>
|
||||
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/widget_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/widget_current_book_label"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/widget_cover_placeholder" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget_percent_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="6dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_percent_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_percent_badge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_percent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="7dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="7dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:textColor="#FFFFFFFF"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
</FrameLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_authors"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/widget_current_book_empty"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,202 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/stats_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stats_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/widget_stats_label"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="3dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tile_bg_books"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_books_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_books_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/widget_stat_books"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="3dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tile_bg_authors"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_authors_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_authors_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/widget_stat_authors"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="3dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tile_bg_categories"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_categories_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_categories_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/widget_stat_categories"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="3dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tile_bg_series"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_series_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/stat_series_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/widget_stat_series"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,186 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/qa_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qa_slot_search"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_tile_search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_icon_search"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/widget_action_search"
|
||||
android:src="@drawable/ic_widget_search" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qa_label_search"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/widget_action_search"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qa_slot_scan"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_tile_scan"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_icon_scan"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/widget_action_scan"
|
||||
android:src="@drawable/ic_widget_scan" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qa_label_scan"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/widget_action_scan"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qa_slot_read"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_tile_read"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_icon_read"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/widget_action_read"
|
||||
android:src="@drawable/ic_widget_book" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qa_label_read"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/widget_action_read"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/qa_slot_downloads"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="44dp"
|
||||
android:layout_height="44dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_tile_downloads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_tile_background" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qa_icon_downloads"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/widget_action_downloads"
|
||||
android:src="@drawable/ic_widget_download" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qa_label_downloads"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/widget_action_downloads"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="11sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/shelf_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_bg"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/widget_background" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/shelf_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shelf_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/widget_shelf_recent"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="13sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/shelf_refresh"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:contentDescription="@string/widget_shelf_refresh"
|
||||
android:src="@drawable/ic_widget_refresh" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shelf_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="@string/widget_shelf_empty"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="13sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<GridView
|
||||
android:id="@+id/shelf_grid"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:columnWidth="96dp"
|
||||
android:horizontalSpacing="6dp"
|
||||
android:numColumns="auto_fit"
|
||||
android:stretchMode="columnWidth"
|
||||
android:verticalSpacing="8dp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/item_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="2dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="130dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/widget_cover_placeholder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/widget_text_primary"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_authors"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/widget_text_secondary"
|
||||
android:textSize="10sp" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="widget_current_book_label">Aktuelles Buch</string>
|
||||
<string name="widget_current_book_description">Zeigt das Buch, das du gerade liest</string>
|
||||
<string name="widget_current_book_empty">Öffne ein Buch, um es hier zu sehen</string>
|
||||
|
||||
<string name="widget_stats_label">Bibliothek</string>
|
||||
<string name="widget_stats_description">Deine Bibliothek auf einen Blick</string>
|
||||
<string name="widget_stat_books">Bücher</string>
|
||||
<string name="widget_stat_authors">Autoren</string>
|
||||
<string name="widget_stat_categories">Kategorien</string>
|
||||
<string name="widget_stat_series">Serien</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="widget_background">#FF1A1C18</color>
|
||||
<color name="widget_tile_background">#FF2C2F27</color>
|
||||
<color name="widget_text_primary">#FFE3E3DB</color>
|
||||
<color name="widget_text_secondary">#FFC4C8BA</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="widget_background">#FFF7F7F4</color>
|
||||
<color name="widget_tile_background">#FFE7E7E1</color>
|
||||
<color name="widget_text_primary">#FF1A1C18</color>
|
||||
<color name="widget_text_secondary">#FF44483D</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="widget_current_book_label">Current book</string>
|
||||
<string name="widget_current_book_description">Shows the book you are currently reading</string>
|
||||
<string name="widget_current_book_empty">Open a book to see it here</string>
|
||||
|
||||
<string name="widget_stats_label">Library</string>
|
||||
<string name="widget_stats_description">Your library at a glance</string>
|
||||
<string name="widget_stat_books">Books</string>
|
||||
<string name="widget_stat_authors">Authors</string>
|
||||
<string name="widget_stat_categories">Categories</string>
|
||||
<string name="widget_stat_series">Series</string>
|
||||
|
||||
<string name="widget_shelf_label">Shelf</string>
|
||||
<string name="widget_shelf_description">A grid of books from a shelf, the library or your downloads</string>
|
||||
<string name="widget_shelf_recent">Recently added</string>
|
||||
<string name="widget_shelf_empty">No books yet — tap to refresh</string>
|
||||
<string name="widget_shelf_refresh">Refresh</string>
|
||||
|
||||
<string name="widget_quick_actions_label">Quick actions</string>
|
||||
<string name="widget_quick_actions_description">Search, scan and keep reading</string>
|
||||
<string name="widget_action_search">Search</string>
|
||||
<string name="widget_action_scan">Scan</string>
|
||||
<string name="widget_action_read">Read</string>
|
||||
<string name="widget_action_downloads">Downloads</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="110dp"
|
||||
android:minHeight="180dp"
|
||||
android:targetCellWidth="2"
|
||||
android:targetCellHeight="3"
|
||||
android:description="@string/widget_current_book_description"
|
||||
android:initialLayout="@layout/widget_current_book"
|
||||
android:previewLayout="@layout/widget_current_book"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen" />
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="180dp"
|
||||
android:minHeight="110dp"
|
||||
android:targetCellWidth="3"
|
||||
android:targetCellHeight="2"
|
||||
android:description="@string/widget_stats_description"
|
||||
android:initialLayout="@layout/widget_library_stats"
|
||||
android:previewLayout="@layout/widget_library_stats"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen" />
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
</network-security-config>
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="250dp"
|
||||
android:minHeight="70dp"
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="1"
|
||||
android:description="@string/widget_quick_actions_description"
|
||||
android:initialLayout="@layout/widget_quick_actions"
|
||||
android:previewLayout="@layout/widget_quick_actions"
|
||||
android:resizeMode="horizontal"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen" />
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="250dp"
|
||||
android:minHeight="200dp"
|
||||
android:targetCellWidth="4"
|
||||
android:targetCellHeight="4"
|
||||
android:description="@string/widget_shelf_description"
|
||||
android:initialLayout="@layout/widget_shelf"
|
||||
android:previewLayout="@layout/widget_shelf"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen" />
|
||||
@@ -1,3 +1,5 @@
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
@@ -5,6 +7,22 @@ allprojects {
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
plugins.withId("com.android.library") {
|
||||
if (name == "jni") {
|
||||
extensions.configure<LibraryExtension>("android") {
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments += "-DCMAKE_SHARED_LINKER_FLAGS=-Wl,--build-id=none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
@@ -16,6 +34,16 @@ subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
gradle.projectsEvaluated {
|
||||
subprojects {
|
||||
tasks.configureEach {
|
||||
if (name.contains("lintVitalAnalyzeRelease") || name.contains("lintVitalRelease") || name.contains("lintVitalReportRelease")) {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.builtInKotlin=false
|
||||
android.newDsl=false
|
||||
|
||||
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
|
||||
@@ -18,7 +18,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.1" apply false
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.3.0" apply false
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 263 KiB After Width: | Height: | Size: 359 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 118 KiB |
@@ -0,0 +1,3 @@
|
||||
- تحديث رمز الإصدار
|
||||
- إصلاح عاجل لمشكلة تنزيل الكتب
|
||||
- إزالة توصيات الكتب (مؤقتًا)
|
||||
@@ -0,0 +1 @@
|
||||
- تمت إزالة معلومات التبعيات الخاصة بنسخة F-Droid
|
||||
@@ -0,0 +1 @@
|
||||
- تم إصلاح مشكلة كان من الممكن فيها تسجيل الدخول ببيانات اعتماد خاطئة
|
||||
@@ -0,0 +1,5 @@
|
||||
- تمت إضافة اسم سلسلة قابل للنقر
|
||||
- تمت إضافة وسم قابل للنقر
|
||||
- تمت إضافة خاصية التعبئة التلقائية لصفحة تسجيل الدخول
|
||||
- تمت إضافة إمكانية فتح الكتب في برامج القراءة المثبتة
|
||||
- تمت إضافة دعم مصادقة الخادم الوكيل العكسي
|
||||
@@ -0,0 +1,5 @@
|
||||
- تمت إضافة إمكانية تغيير عدد الأعمدة في قائمة الكتب
|
||||
- تمت إضافة إمكانية رفع الكتب إلى Calibre Web
|
||||
- تمت إضافة إمكانية إضافة نسخة مخصصة من Send2eReader
|
||||
- تم إصلاح مشكلة عدم إمكانية فتح ملفات PDF في برامج القراءة المثبتة
|
||||
- تم إصلاح مشكلة عدم ظهور جميع الكتب عند فرزها حسب المؤلف
|
||||
@@ -0,0 +1,20 @@
|
||||
- أصبحت بيانات تسجيل الدخول ثابتة الآن عبر عمليات إعادة تشغيل التطبيق، وسيحاول التطبيق تسجيل الدخول تلقائيًا عند انتهاء صلاحية الجلسة.
|
||||
- أصبح الآن مسموحًا باستخدام المزيد من أنواع الملفات عند تحميل الكتب.
|
||||
- إمكانية إنشاء أرفف عامة/غير عامة.
|
||||
- تم إصلاح خطأ كان يحدث عند وجود رف واحد فقط.
|
||||
- يتم الآن عرض السلاسل بشكل صحيح في تفاصيل الكتاب.
|
||||
- تم تحديث خط أنابيب البناء لدعم أنواع APK الأخرى، ونسخة iOS والنشر في سوق Play.
|
||||
- تم إصلاح مشكلة عند تنزيل الكتب بتنسيقات متعددة.
|
||||
- تم تحديث تكامل Calibre Web Automated Book Downloader إلى أحدث إصدار.
|
||||
- تمت إزالة عناصر HTML من تعليقات الكتب.
|
||||
- تمت إضافة الترتيب الأبجدي في صفحة ”اكتشاف“.
|
||||
- تمت إضافة إمكانية تغيير ”إرسال إلى قارئ كتب إلكترونية“ إلى ”قراءة الآن“.
|
||||
- تمت إضافة عرض قائمة إلى الصفحة الرئيسية.
|
||||
- تم تحسين تحميل الكتب من الرفوف وصفحة ”اكتشاف“.
|
||||
- تم تنفيذ إمكانية تغيير بيانات تعريف الكتب.
|
||||
- تم تنفيذ إمكانية تغيير أغلفة الكتب.
|
||||
- تمت إضافة تعليقات أفضل في حالة تعطيل فئة ”اكتشاف“.
|
||||
- تم تحسين وظيفة العنوان المخصص.
|
||||
- تمت إضافة دعم ترجمة Weblate.
|
||||
- تمت إضافة الترجمة البرتغالية.
|
||||
- تم إصلاح بعض الأخطاء الطفيفة
|
||||
@@ -0,0 +1,13 @@
|
||||
- تم تحديث مسار المعالجة لإزالة الوسوم المنشأة بشكل خاطئ
|
||||
- تم تحديث لون خلفية أزرار أيقونة تعديل البيانات الوصفية
|
||||
- تمت إضافة برنامج نصي لإنشاء ملفات .ipa لـ iOS
|
||||
- تمت إضافة إعدادات اتصال جديدة لـ Booklore وOPDS (إصدار تجريبي)
|
||||
- تمت إضافة خاصية المصادقة لبرنامج Calibre Web Automated Book Downloader
|
||||
- تمت إضافة إمكانية تحميل إعدادات برنامج Calibre Web Automated Book Downloader من الخادم
|
||||
- تمت إضافة رأسيات المصادقة لطلبات برنامج Calibre Web Automated Book Downloader
|
||||
- تمت إضافة ترجمات إلى اللغتين الإستونية والإيطالية
|
||||
- تمت إضافة قارئ EPUB داخلي جديد
|
||||
- تمت إضافة إمكانية مزامنة تقدم القراءة عبر WebDAV مع أجهزة أخرى
|
||||
- تم تحسين معالجة تسجيل الدخول الموحد
|
||||
- تمت إضافة صفحة التراخيص إلى الإعدادات
|
||||
- بعض الإصلاحات والتحسينات الطفيفة
|
||||
@@ -0,0 +1,8 @@
|
||||
- تمت إضافة إمكانية تغيير اتجاه القراءة للقارئ الداخلي إلى الإعدادات.
|
||||
- سيتم الآن جلب تكوين أداة التنزيل مباشرةً عند تمكينها في الإعدادات.
|
||||
- تم إصلاح الخلل الذي كان يتسبب في ظهور الكتب المكررة في عرض الكتب.
|
||||
- تم إصلاح الخلل الذي كان يمنع المستخدمين من إرسال الكتب الإلكترونية عبر البريد الإلكتروني دون أذون التحميل.
|
||||
- يتم الآن تخزين عوامل تصفية أداة التنزيل عبر عمليات إعادة تشغيل التطبيق.
|
||||
- تم إصلاح مشكلة إنشاء F-Droid.
|
||||
- تم إجراء بعض التحسينات على رمز CSRF.
|
||||
- تمت إضافة الترجمات الإيطالية والهنغارية.
|
||||
@@ -0,0 +1 @@
|
||||
- تحديث: إصلاحات لمشكلة تعطل قارئ الكتب الإلكترونية الداخلي
|
||||
@@ -0,0 +1,6 @@
|
||||
- تم تغيير اسم Calibre-Web-Book-Downloader إلى Shelfmark
|
||||
- تمت إضافة ترجمات إلى الهنغارية والسويدية والتركية
|
||||
- تم إصلاح مشكلة المصادقة عند استخدام Booklore
|
||||
- تم إصلاح مشكلة المصادقة عند استخدام Shelfmark
|
||||
- تمت إضافة آلية تحقق محسّنة للتأكد من تحميل الكتاب مسبقًا
|
||||
- تمت إضافة ميزة مزامنة المكتبة لتحميل الكتب دفعة واحدة
|
||||
@@ -0,0 +1 @@
|
||||
- إصلاح لسوق Google Play
|
||||
@@ -0,0 +1,8 @@
|
||||
- تمت إضافة الترجمة القطلونية.
|
||||
- تم إصلاح نص الإنشاء البرمجي لملفات .ipa.
|
||||
- ستعمل ميزة المزامنة على الاستعلام عن التنسيقات المتاحة من الخادم.
|
||||
- تمت إضافة مؤشر حالة القراءة إلى الصفحة الرئيسة.
|
||||
- تم إصلاح مشكلة علامة حالة القراءة.
|
||||
- تمت إضافة خيار فرز المؤلف إلى إعدادات مسار التنزيل.
|
||||
- تمت إضافة إمكانية التبديل السريع بين نسخ Calibre Web (وغيرها).
|
||||
- تم إصلاح مشكلة في خيار "إضافة إلى الرف" في صفحة وصف الكتاب.
|
||||
@@ -0,0 +1,4 @@
|
||||
- تم التحديث: تم تعديل الكود ليتوافق مع تنسيق قاعدة بيانات Calibre الجديد.
|
||||
- تمت الإضافة: دعم اللغة الروسية.
|
||||
- تمت الإضافة: يمكنك الآن إخفاء زر "إرسال إلى قارئ إلكتروني".
|
||||
- تم التحديث: تم ضبط خيار التنزيل الافتراضي على اللغة الإنجليزية (Shelfmark).
|
||||
@@ -0,0 +1,2 @@
|
||||
- تمت إضافة إمكانية حذف الكتب من الخادم عبر صفحة تفاصيل الكتاب.
|
||||
- تمت إضافة خيار لتعطيل رسوميات التطبيق المتحركة على أجهزة الحبر الإلكتروني
|
||||
@@ -0,0 +1,10 @@
|
||||
- تمت إضافة رسالة خطأ أفضل إلى صفحة إعدادات المزامنة عند تعطيل قسم Calibre Web.
|
||||
- تمت إضافة إمكانية عرض سجلات التطبيق ونسخها للإبلاغ عن الأخطاء.
|
||||
- تمت إضافة إمكانية إعادة ترتيب أقسام صفحة ”اكتشاف“ وتعطيلها عبر الإعدادات.
|
||||
- تمت إضافة إمكانية إعادة ترتيب تفاصيل الكتب وإخفائها عبر الإعدادات.
|
||||
- تمت إضافة إمكانية تغيير ترتيب ورؤية إجراءات الكتاب عبر الإعدادات.
|
||||
- تم إصلاح مشكلة (خطأ 401 (غير مصرّح)) على أغلفة الرفوف.
|
||||
- تم إصلاح مشكلة تعطل إضافة/إزالة الكتب من الرفوف بسبب عدم وجود csrfTokenUrl (خطأ 405).
|
||||
- تمت إعادة تصميم صفحة الإعدادات.
|
||||
- تحسينات طفيفة على واجهة المستخدم.
|
||||
- تمت إعادة تصميم صفحة تفاصيل الكتب.
|
||||
@@ -0,0 +1,3 @@
|
||||
- تمت إضافة ترجمات عربية وصينية.
|
||||
- تم إصلاح: تم تعديل تكامل Shelfmark للإصدار الجديد.
|
||||
- تمت إضافة: خيار لحفظ الكتب المرسلة إلى قارئ الكتب الإلكترونية في مجلد التنزيلات المحدّد.
|
||||
@@ -0,0 +1,5 @@
|
||||
- تمت إضافة إمكانية تغيير لون السمة
|
||||
- تمت إضافة إمكانية تحديد مخططات مجلدات مختلفة
|
||||
- تمت إضافة صفحة توصيات الكتب
|
||||
- تمت إضافة إمكانية التنزيل إلى صفحة التوصيات
|
||||
- إصلاحات طفيفة لبعض الأخطاء وتحسينات في واجهة المستخدم
|
||||
@@ -0,0 +1,4 @@
|
||||
- تمت إضافة مجلد تنزيل مجموعة القدرات على مستوى النظام
|
||||
- تمت إضافة مخطط تنزيل مجموعة القدرات على مستوى النظام
|
||||
- تم إصلاح خطأ سلسلة مخطط التنزيل
|
||||
- تم تحديث سير العمل لتجنب تعارض التحديثات
|
||||
@@ -0,0 +1,2 @@
|
||||
- تمت إضافة ترجمة لإعدادات التنزيل
|
||||
- تم تغيير ترتيب الحصول على أذون المجلد
|
||||
@@ -0,0 +1,35 @@
|
||||
تطبيق مصاحب غير رسمي لـ Calibre Web و Calibre Web Automated يتيح لك تصفح مجموعتك من الكتب وإدارة مكتبتك الرقمية أثناء التنقل.
|
||||
|
||||
تم تصميمه باستخدام <b>Flutter</b> و <b>Material You</b> لتوفير تجربة مستخدم حديثة وبديهية وسريعة الاستجابة.
|
||||
|
||||
<b>الميزات المدعومة:</b>
|
||||
<ul>
|
||||
<li>تصفح مكتبة Calibre Web الخاصة بك</li>
|
||||
<ul>
|
||||
<li>عرض الكتب حسب المؤلف والسلسلة والاتجاهات وغيرها</li>
|
||||
<li>عرض معلومات مفصلة عن الكتب</li>
|
||||
<li>الوصول إلى إحصائيات شاملة عن المجموعة</li>
|
||||
</ul>
|
||||
<li>إدارة المكتبة</li>
|
||||
<ul>
|
||||
<li>وضع علامة على الكتب كمقروءة أو غير مقروءة</li>
|
||||
<li>أرشفة الكتب وإلغاء أرشفتها</li>
|
||||
<li>تنظيم الكتب في أرفف</li>
|
||||
<li>إدارة أرففك وتخصيصها</li>
|
||||
</ul>
|
||||
<li>القراءة والأجهزة</li>
|
||||
<ul>
|
||||
<li>تنزيل الكتب مباشرةً على جهازك</li>
|
||||
<li>إرسال الكتب إلى قارئك الإلكتروني (Kindle/Kobo) عبر send2ereader</li>
|
||||
<li>استخدام وظيفة البريد في Calibre Web لتسليم الأجهزة</li>
|
||||
</ul>
|
||||
<li>تكامل الأتمتة</li>
|
||||
<ul>
|
||||
<li>يعمل مع Calibre Web Automated</li>
|
||||
<li>تنزيل الكتب تلقائيًا باستخدام calibre-web-automated-book-downloader</li>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
يُرجى ملاحظة أن التطبيق قيد التطوير حاليًا. يمكنك فتح مشكلات على GitHub للإبلاغ عن الأخطاء وتقديم الملاحظات واقتراح الأفكار الخاصة بالميزات.
|
||||
|
||||
الاعتمادات: Calibre Web، Calibre Web Automated، send2ereader، calibre-web-automated-book-downloader
|
||||
@@ -0,0 +1 @@
|
||||
تطبيق مصاحب غير رسمي لبرنامج Calibre Web (تلقائي)
|
||||
@@ -0,0 +1 @@
|
||||
Calibre Web Companion
|
||||
@@ -0,0 +1,8 @@
|
||||
- The ability to change the reading direction for the internal reader has been added to the settings.
|
||||
- The downloader configuration will now be fetched directly when enabled in the settings.
|
||||
- Fixed bug where duplicate books appeared in Books view.
|
||||
- Fixed a bug where users could not send ebooks via email without upload permissions.
|
||||
- Downloader filters are now stored across app restarts.
|
||||
- Fixed F-Droid building issue.
|
||||
- Made some CSRF token improvements.
|
||||
- Added Italian and Hungarian translations.
|
||||
@@ -0,0 +1 @@
|
||||
- Hotfix: Internal Epub Reader Crash Fixes
|
||||
@@ -0,0 +1,6 @@
|
||||
- changed Calibre-Web-Book-Downloader to Shelfmark
|
||||
- added hungarian, swedish and turkish translations
|
||||
- fixed authentication issue when using Booklore
|
||||
- fixed authentication issue when using Shelfmark
|
||||
- added better check when book is already downloaded
|
||||
- added library sync feature to bulk download books
|
||||
@@ -0,0 +1 @@
|
||||
- Fix for Google Play
|
||||
@@ -0,0 +1,8 @@
|
||||
- Catalan translation added.
|
||||
- Fixed the building script for .ipa files.
|
||||
- The sync feature will query the available formats from the server.
|
||||
- Read status indicator added to homepage.
|
||||
- Fixed the read status flag problem.
|
||||
- Added the authorSort option to the download path settings.
|
||||
- The ability to quickly switch between Calibre Web (and other) instances has been added.
|
||||
- Fixed a problem with the 'Add to Shelf' option on the book description page.
|
||||
@@ -0,0 +1,4 @@
|
||||
- Updated: Adjusted code for the new Calibre database format.
|
||||
- Added: Russian language support
|
||||
- Added: You can now hide the 'Send to Ereader' button.
|
||||
- Updated: Set the default download option to English (Shelfmark).
|
||||
@@ -0,0 +1,2 @@
|
||||
- Added ability to delete books from the server via the book details page.
|
||||
- Added option to disable app animations for eink devices
|
||||
@@ -0,0 +1,10 @@
|
||||
- Added a better error message to the Sync Settings page when the Calibre Web section is disabled.
|
||||
- Added the ability to view and copy app logs for bug reports.
|
||||
- Added the ability to reorder and disable Discover sections via Settings.
|
||||
- Added the ability to reorder and hide book details via settings.
|
||||
- Added the ability to change the order and visibility of book actions via settings.
|
||||
- Fixed: Resolved 401 Unauthorized on Shelf Covers.
|
||||
- Fixed: Shelf add/remove broken due to missing csrfTokenUrl (405 error).
|
||||
- Redesigned the Settings page.
|
||||
- Small UI improvements
|
||||
- Reworked the book details page.
|
||||
@@ -0,0 +1,3 @@
|
||||
- Arabic and Chinese translations have been added.
|
||||
- Fixed: Adjusted Shelfmark integration for the new version.
|
||||
- Added: Option to save books sent to eReader to specified downloads folder.
|
||||
@@ -0,0 +1,9 @@
|
||||
- New: Magic Shelves, create, edit and browse dynamic, rule-based shelves (Calibre-Web Automated only).
|
||||
- New: Scan a book's ISBN barcode to add it to Calibre or download it via Shelfmark.
|
||||
- New: Connection Diagnostics screen plus improved reverse-proxy / SSO login.
|
||||
- New: Search book metadata from online providers while editing a book.
|
||||
- New: Series index and series filter on book cards, with search autosuggestions.
|
||||
- New: Pagination and a list/grid toggle in shelves; adjustable font size and grid/list view in Discover.
|
||||
- New: Refreshed built-in eBook reader with selectable reading format.
|
||||
- Added Dutch translation and many translation updates.
|
||||
- Fixed: self-signed certificates, "logged in but no data" on reverse proxies, Shelfmark config, the Author (A-Z) filter and a swipe-back crash.
|
||||
@@ -0,0 +1 @@
|
||||
- Fixed: swapped mobile_scanner package to barcode_scan2
|
||||
@@ -0,0 +1 @@
|
||||
- Fixed: libisar.so was removed and the cosmos_epub package was changed to enable a reproducible build for F-Droid.
|
||||
@@ -0,0 +1,6 @@
|
||||
- Added: new Ukrainian (@Vitali) and Tamil (@தமிழ்நேரம்) translations.
|
||||
- Feature: Offline mode has been added to the app, allowing users to access their downloaded books without an internet connection.
|
||||
- Feature: Support added for the WebServer provided by the Calibre Desktop application.
|
||||
- Fixed: the 'Read Now' button now correctly downloads the book.
|
||||
- Fix: When editing a book and then using the system back button, the changes are now saved correctly.
|
||||
- Fix: Shelfmark integration will no longer fall back to 'German' by default.
|
||||
@@ -0,0 +1,4 @@
|
||||
- Feature: Homescreen widgets added (Quick Actions, Stats, Currently Reading Book, Shelf Grid).
|
||||
- Fix: Fixed an issue where FileOpener was not using the correct MIME type for EPUB files, causing some apps to fail to open them correctly.
|
||||
- Fix: Updated SSO login flow and enhance cookie handling in LoginRemoteDataSource
|
||||
- Language: Updated translations: Spanish (@Libre), Italian (@Michele Abbondanza), Hungarian (@Márton Kónya) and Chinese (Simplified Han script) (@Mas Wang).
|
||||
@@ -0,0 +1,5 @@
|
||||
- Feature: New shelf view on the home screen.
|
||||
- Feature: Added ability to change probe endpoint for reachability checks.
|
||||
- Fix: A reauth popup has been added when the SSO session expires.
|
||||
- Fix: Fixed an issue with identifying offline status.
|
||||
- Language: Updated translations: Ukrainian (@Vitalii), Catalan (@Nric Pagès i Gassull).
|
||||
@@ -0,0 +1 @@
|
||||
- Feature: ISBN scan now queries several catalogues at once (Open Library, Google Books, BnF, optionally Hardcover and ISBNdb) and merges their metadata. Sources can be switched on/off and reordered in the settings.
|
||||
@@ -1,35 +1,21 @@
|
||||
Unofficial companion application for Calibre Web and Calibre Web Automated that lets you browse your book collection and manage your digital library on the go.
|
||||
Unofficial companion app for Calibre-Web and Calibre-Web Automated. Browse your book collection and manage your digital library on the go.
|
||||
|
||||
Built with <b>Flutter</b> and <b>Material You</b> to deliver a modern, intuitive and responsive user experience.
|
||||
Built with Flutter and Material You for a modern, intuitive and responsive experience. Available for Android, in 15 languages.
|
||||
|
||||
<b>Supported features:</b>
|
||||
<ul>
|
||||
<li>Browse your Calibre Web library</li>
|
||||
<ul>
|
||||
<li>View books by author, series, trending, and more</li>
|
||||
<li>View detailed book information</li>
|
||||
<li>Access comprehensive collection statistics</li>
|
||||
</ul>
|
||||
<li>Library management</li>
|
||||
<ul>
|
||||
<li>Mark books as read or unread</li>
|
||||
<li>Archive and unarchive books</li>
|
||||
<li>Organize books into shelves</li>
|
||||
<li>Manage and customize your shelves</li>
|
||||
</ul>
|
||||
<li>Reading & devices</li>
|
||||
<ul>
|
||||
<li>Download books directly to your device</li>
|
||||
<li>Send books to your e-reader (Kindle/Kobo) via send2ereader</li>
|
||||
<li>Use Calibre Web's mail function for device delivery</li>
|
||||
</ul>
|
||||
<li>Automation integration</li>
|
||||
<ul>
|
||||
<li>Works with Calibre Web Automated</li>
|
||||
<li>Automatically download books with calibre-web-automated-book-downloader</li>
|
||||
</ul>
|
||||
</ul>
|
||||
Features:
|
||||
|
||||
Please note that the app is currently in active development. You can open issues on GitHub for bugs, feedback, and feature ideas.
|
||||
- Connect to your Calibre-Web (Automated) server, including reverse-proxy / SSO setups, custom HTTP headers and self-signed certificates.
|
||||
- Browse your whole library and discover books by author, series, publisher, rating, hot & trending, and more.
|
||||
- View rich details for every book, edit its metadata, and upload new covers.
|
||||
- Mark books as read or unread, archive them, and organize them into shelves.
|
||||
- Create, edit and browse Magic Shelves: dynamic, rule-based shelves (Calibre-Web Automated only).
|
||||
- Add books quickly by scanning their ISBN barcode.
|
||||
- Read books in the built-in eBook reader and sync your reading progress across devices via WebDAV.
|
||||
- Send books to your e-reader (Kindle/Kobo) via send2ereader or Calibre-Web's email function.
|
||||
- Download books into your collection with Shelfmark. This app does not support or encourage the piracy of copyrighted works; please only download content you are legally entitled to.
|
||||
- Upload books to your server, and sync your whole library or selected books for offline reading.
|
||||
- Make it yours: reorder or hide book actions and sections, choose a theme, enable e-ink mode, and adjust the text size.
|
||||
|
||||
Credits: Calibre Web, Calibre Web Automated, send2ereader, calibre-web-automated-book-downloader
|
||||
The app is in active development. You can open issues on GitHub for bugs, feedback and feature ideas.
|
||||
|
||||
Credits: Calibre-Web, Calibre-Web Automated, Shelfmark, send2ereader, Flutter.
|
||||
|
Before Width: | Height: | Size: 263 KiB After Width: | Height: | Size: 359 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 285 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 118 KiB |
@@ -0,0 +1,3 @@
|
||||
- Verzió kód generálás frissítve
|
||||
- Hot fix könyvletöltéshez
|
||||
- Könyvajánlások eltávolítva (egyelőre)
|
||||
@@ -0,0 +1 @@
|
||||
- Függőség info eltávolítva f-droid verziónál
|
||||
@@ -0,0 +1 @@
|
||||
- Javítva a hiba amivel úgymond be lehetett jelentkezni hibás bejelentkezési adatokkal
|
||||
@@ -0,0 +1,5 @@
|
||||
- Kattintható sorozatnév hozzáadva
|
||||
- Kattintható címke hozzáadva
|
||||
- Automatikus kitöltés bejelentkezéskor
|
||||
- Könyvek megnyitása telepített olvasóban
|
||||
- Reverse proxy hitelesítés hozzáadva
|
||||