mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-30 11:25:54 +02:00
Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
222e111806 | |||
8489b0dd8b | |||
88ed634978 | |||
32188f9f65 | |||
05efc4ebeb | |||
65bfa083f2 | |||
b8a9998bbd | |||
d736bec003 | |||
348b23a9fd | |||
121b2ec829 | |||
1dd130df9e | |||
e17d87f357 | |||
de75561402 | |||
58085336a5 | |||
89ea0a271b | |||
e3f33e24f5 | |||
9fd1419142 | |||
cb06898430 | |||
39407407f2 | |||
a024218410 | |||
26815c7356 | |||
e0deeb8008 | |||
38d6ab80ce | |||
78e66fd8d3 | |||
26aa126ecb | |||
e4a65656e7 | |||
6018aa99e2 | |||
99fd2731f5 | |||
e34043f1fe | |||
3c3a1cd448 | |||
0a2df21c5b | |||
277be02682 | |||
1849715418 | |||
dc6d4f9917 | |||
a9d98e5048 | |||
653940613d | |||
23a2d816e4 | |||
8a3a9146db |
.github
.idea
README.mdapp
build.gradle.kts
src
debug
main
java
eu
kanade
domain
chapter
interactor
extension
source
interactor
track
presentation
browse
more
settings
screen
reader
track
tachiyomi
data
backup
database
models
download
track
extension
ui
util
test
res
core-metadata
core
data
domain
i18n/src/commonMain/resources/MR
ar
be
ceb
cv
de
eu
fa
fi
fil
fr
he
in
it
ja
jv
ka-rGE
ne
pl
sk
sr
sv
th
tr
uk
uz
zh-rCN
zh-rTW
presentation-core
presentation-widget
settings.gradle.ktssource-api
source-local
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1 +0,0 @@
|
||||
ko_fi: inorichi
|
8
.github/ISSUE_TEMPLATE/config.yml
vendored
8
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,11 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⚠️ Extension/source issue
|
||||
url: https://github.com/tachiyomiorg/extensions/issues/new/choose
|
||||
about: Issues and requests for official extensions and sources should be opened in the extensions repository instead
|
||||
- name: 📦 Tachiyomi extensions
|
||||
url: https://mihon.app/extensions/
|
||||
about: List of all available extensions with download links
|
||||
- name: 🖥️ Tachiyomi website
|
||||
- name: 🖥️ Mihon website
|
||||
url: https://mihon.app/
|
||||
about: Guides, troubleshooting, and answers to common questions
|
||||
|
14
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
14
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: 🐞 Issue report
|
||||
description: Report an issue in Tachiyomi
|
||||
description: Report an issue in Mihon
|
||||
labels: [Bug]
|
||||
body:
|
||||
|
||||
@ -48,12 +48,12 @@ body:
|
||||
You can paste the crash logs in plain text or upload it as an attachment.
|
||||
|
||||
- type: input
|
||||
id: tachiyomi-version
|
||||
id: mihon-version
|
||||
attributes:
|
||||
label: Tachiyomi version
|
||||
description: You can find your Tachiyomi version in **More → About**.
|
||||
label: Mihon version
|
||||
description: You can find your Mihon version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "0.15.3"
|
||||
Example: "0.16.1"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@ -94,11 +94,9 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I have updated all installed extensions.
|
||||
required: true
|
||||
|
8
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
8
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -1,5 +1,5 @@
|
||||
name: ⭐ Feature request
|
||||
description: Suggest a feature to improve Tachiyomi
|
||||
description: Suggest a feature to improve Mihon
|
||||
labels: [Feature request]
|
||||
body:
|
||||
|
||||
@ -7,7 +7,7 @@ body:
|
||||
id: feature-description
|
||||
attributes:
|
||||
label: Describe your suggested feature
|
||||
description: How can Tachiyomi be improved?
|
||||
description: How can Mihon be improved?
|
||||
placeholder: |
|
||||
Example:
|
||||
"It should work like this..."
|
||||
@ -31,9 +31,7 @@ body:
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: If this is an issue with an official extension, I should be opening an issue in the [extensions repository](https://github.com/tachiyomiorg/extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.15.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
- label: I have updated the app to version **[0.16.1](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||
required: true
|
||||
- label: I will fill out all of the requested information in this form.
|
||||
required: true
|
||||
|
BIN
.github/assets/logo.png
vendored
Normal file
BIN
.github/assets/logo.png
vendored
Normal file
Binary file not shown.
After ![]() (image error) Size: 7.5 KiB |
BIN
.github/readme-images/app-icon.png
vendored
BIN
.github/readme-images/app-icon.png
vendored
Binary file not shown.
Before ![]() (image error) Size: 1.1 KiB |
BIN
.idea/icon.png
generated
BIN
.idea/icon.png
generated
Binary file not shown.
Before ![]() (image error) Size: 14 KiB After ![]() (image error) Size: 26 KiB ![]() ![]() |
136
README.md
136
README.md
@ -1,89 +1,113 @@
|
||||
| Build | Stable | Weekly Preview | Support Server |
|
||||
|-------|----------|---------|---------|
|
||||
| [](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml) | [](https://github.com/mihonapp/mihon/releases) | [](https://github.com/mihonapp/mihon-preview/releases) | [](https://discord.gg/mihon) |
|
||||
<div align="center">
|
||||
|
||||
# Mihon
|
||||
Mihon is a free and open source manga reader for Android 8.0 and above.
|
||||
<a href="https://mihon.app">
|
||||
<img src="./.github/assets/logo.png" alt="Mihon logo" title="Mihon logo" width="80"/>
|
||||
</a>
|
||||
|
||||
# Mihon [App](#)
|
||||
|
||||
### Full-featured reader
|
||||
Discover and read manga, webtoons, comics, and more – easier than ever on your Android device.
|
||||
|
||||
[](https://discord.gg/mihon)
|
||||
[](https://github.com/mihonapp/mihon/releases)
|
||||
|
||||
[](https://github.com/mihonapp/mihon/actions/workflows/build_push.yml)
|
||||
[](/LICENSE)
|
||||
[](https://hosted.weblate.org/engage/mihon/)
|
||||
|
||||
## Download
|
||||
|
||||
[](https://github.com/mihonapp/mihon/releases)
|
||||
[](https://github.com/mihonapp/mihon-preview/releases)
|
||||
|
||||
*Requires Android 8.0 or higher.*
|
||||
|
||||
## Features
|
||||
|
||||
Features include:
|
||||
* Online reading from a variety of sources
|
||||
* Local reading of downloaded content
|
||||
<div align="left">
|
||||
|
||||
* Local reading of content.
|
||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support
|
||||
* Categories to organize your library
|
||||
* Light and dark themes
|
||||
* Schedule updating your library for new chapters
|
||||
* Create backups locally to read offline or to your desired cloud service
|
||||
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
|
||||
* Categories to organize your library.
|
||||
* Light and dark themes.
|
||||
* Schedule updating your library for new chapters.
|
||||
* Create backups locally to read offline or to your desired cloud service.
|
||||
* Plus much more...
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/mihonapp/mihon/releases).
|
||||
</div>
|
||||
|
||||
If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/mihonapp/mihon-preview/releases).
|
||||
## Contributing
|
||||
|
||||
## Issues, Feature Requests and Contributing
|
||||
[Code of conduct](./CODE_OF_CONDUCT.md) · [Contributing guide](./CONTRIBUTING.md)
|
||||
|
||||
Please make sure to read the full guidelines. Your issue may be closed without warning if you do not.
|
||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||
|
||||
<details><summary>Issues</summary>
|
||||
If you got any questions, [join our Discord server](https://discord.gg/mihon).
|
||||
|
||||
1. **Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://mihon.app/changelogs/) and the already opened [issues](https://github.com/mihonapp/mihon/issues).**
|
||||
2. If you are unsure, ask here: [](https://discord.gg/mihon)
|
||||
<details align="center"><summary>Issues</summary><div align="left">
|
||||
|
||||
</details>
|
||||
Before reporting a new issue, take a look at the [FAQ](https://mihon.app/docs/faq/general), the [changelog](https://mihon.app/changelogs/) and the already opened [issues](https://github.com/mihonapp/mihon/issues).
|
||||
|
||||
<details><summary>Bugs</summary>
|
||||
</div></details>
|
||||
|
||||
* Include version (More → About → Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen on the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
<details align="center"><summary>Bugs</summary><div align="left">
|
||||
|
||||
* Include version (**More → About → Version**).
|
||||
* If not latest, try updating, it may have already been solved.
|
||||
* Beta version is equal to the number of commits as seen on the main page.
|
||||
* Include steps to reproduce (if not obvious from description).
|
||||
* Include screenshot (if needed).
|
||||
* If it could be device-dependent, try reproducing on another device (if possible).
|
||||
* Don't group unrelated requests into one issue
|
||||
- **DO:** [#24](https://git.mihon.dev/tachiyomi/tachiyomi/issues/24), [#71](https://git.mihon.dev/tachiyomi/tachiyomi/issues/71)
|
||||
- **DON'T:** [#75](https://git.mihon.dev/tachiyomi/tachiyomi/issues/75)
|
||||
|
||||
</details>
|
||||
</div></details>
|
||||
|
||||
<details><summary>Feature Requests</summary>
|
||||
<details align="center"><summary>Feature requests</summary><div align="left">
|
||||
|
||||
* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does"
|
||||
* Write a detailed issue, explaining what it should do or how.
|
||||
* Avoid writing just "like X app does";
|
||||
* Include screenshot (if needed)
|
||||
* Source requests are not accepted.
|
||||
|
||||
Source requests are not accepted.
|
||||
</details>
|
||||
</div></details>
|
||||
|
||||
<details><summary>Contributing</summary>
|
||||
### Repositories
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
</details>
|
||||
[](https://github.com/mihonapp/website/)
|
||||
|
||||
<details><summary>Code of Conduct</summary>
|
||||
### Credits
|
||||
|
||||
See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md).
|
||||
</details>
|
||||
Thank you to all the people who have contributed!
|
||||
|
||||
## FAQ
|
||||
<a href="https://github.com/mihonapp/mihon/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=mihonapp/mihon" alt="Mihon app contributors" title="Mihon app contributors" width="800"/>
|
||||
</a>
|
||||
|
||||
[See our website.](https://mihon.app/)
|
||||
You can also reach out to us on [Discord](https://discord.gg/mihon).
|
||||
### Disclaimer
|
||||
|
||||
## License
|
||||
The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content.
|
||||
|
||||
Copyright 2015 Javier Tomás
|
||||
### License
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
```
|
||||
Copyright © 2015 Javier Tomás
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
## Disclaimer
|
||||
Modifications Copyright © 2024 The Mihon Open Source Project
|
||||
```
|
||||
|
||||
The developer of this application does not have any affiliation with the content providers available.
|
||||
</div>
|
@ -22,8 +22,8 @@ android {
|
||||
defaultConfig {
|
||||
applicationId = "app.mihon"
|
||||
|
||||
versionCode = 1
|
||||
versionName = "0.16.0"
|
||||
versionCode = 3
|
||||
versionName = "0.16.2"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
@ -139,15 +139,15 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":i18n"))
|
||||
implementation(project(":core"))
|
||||
implementation(project(":core-metadata"))
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":source-local"))
|
||||
implementation(project(":data"))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":presentation-core"))
|
||||
implementation(project(":presentation-widget"))
|
||||
implementation(projects.i18n)
|
||||
implementation(projects.core)
|
||||
implementation(projects.coreMetadata)
|
||||
implementation(projects.sourceApi)
|
||||
implementation(projects.sourceLocal)
|
||||
implementation(projects.data)
|
||||
implementation(projects.domain)
|
||||
implementation(projects.presentationCore)
|
||||
implementation(projects.presentationWidget)
|
||||
|
||||
// Compose
|
||||
implementation(platform(compose.bom))
|
||||
|
@ -3,18 +3,20 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<group android:scaleX="0.67"
|
||||
android:scaleY="0.67"
|
||||
android:translateX="71.28"
|
||||
android:translateY="71.28">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h432v432h-432z"/>
|
||||
<path
|
||||
android:pathData="M0,0h432v432h-432z"
|
||||
android:fillColor="#FAFAFA"/>
|
||||
<path
|
||||
android:pathData="M0,0h432v432h-432z"
|
||||
android:fillColor="#2E3943"/>
|
||||
<path
|
||||
android:pathData="M377,216C377,302.16 304.92,372 216,372C127.08,372 55,302.16 55,216C55,129.84 127.08,60 216,60C304.92,60 377,129.84 377,216Z"
|
||||
android:pathData="M322.13,215.5C322.13,272.66 274.64,319 216.07,319C157.49,319 110,272.66 110,215.5C110,158.34 157.49,112 216.07,112C274.64,112 322.13,158.34 322.13,215.5Z"
|
||||
android:fillColor="#F2FAFF"/>
|
||||
<path
|
||||
android:pathData="M216,342.75C288.25,342.75 346.81,286 346.81,216C346.81,146 288.25,89.25 216,89.25C143.75,89.25 85.19,146 85.19,216C85.19,286 143.75,342.75 216,342.75ZM216,372C304.92,372 377,302.16 377,216C377,129.84 304.92,60 216,60C127.08,60 55,129.84 55,216C55,302.16 127.08,372 216,372Z"
|
||||
android:pathData="M216.07,299.59C263.66,299.59 302.24,261.94 302.24,215.5C302.24,169.06 263.66,131.41 216.07,131.41C168.47,131.41 129.89,169.06 129.89,215.5C129.89,261.94 168.47,299.59 216.07,299.59ZM216.07,319C274.64,319 322.13,272.66 322.13,215.5C322.13,158.34 274.64,112 216.07,112C157.49,112 110,158.34 110,215.5C110,272.66 157.49,319 216.07,319Z"
|
||||
android:fillColor="#7EBBED"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
|
@ -3,12 +3,7 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<group android:scaleX="0.67"
|
||||
android:scaleY="0.67"
|
||||
android:translateX="71.28"
|
||||
android:translateY="71.28">
|
||||
<path
|
||||
android:pathData="M162.3,173.59L161.22,148.63C164.47,149.25 168.35,149.41 177.18,149.41C187.87,149.41 201.98,148.79 209.26,147.86C212.36,147.55 213.6,147.09 215.61,146L232.35,160.26C230.8,162.43 230.34,163.36 228.63,167.7C227.24,171.11 220.88,190.79 218.4,199.16C229.87,201.48 236.22,203.18 244.9,206.75C245.99,199.16 246.14,195.13 246.14,181.33C246.14,177.77 245.99,175.76 245.52,172.5L272.49,173.43C271.71,177.15 271.56,178.7 271.4,184.74C270.78,199.31 270.16,206.29 268.61,216.82C279.31,222.25 279.31,222.25 284.73,225.19C287.52,226.74 288.14,227.05 290,227.67L281.01,256.65C276.67,252.78 270.63,248.59 261.8,243.63C254.05,262.08 241.18,275.56 221.66,286.25C215.15,277.57 210.19,272.3 202.29,266.11C213.75,260.68 219.02,257.27 225.07,251.54C230.96,245.8 234.83,240.22 238.55,231.85C228.63,227.36 222.28,225.35 211.27,223.02C204.92,241.93 199.8,254.02 195.31,261.3C189.27,271.06 181.05,276.18 171.6,276.18C164.32,276.18 156.88,272.92 151.45,267.35C145.25,260.99 142,252.16 142,241.93C142,226.74 149.28,213.57 161.99,205.35C170.21,200.09 178.88,197.76 192.68,196.99C195.47,187.84 197.79,179.94 199.96,171.11C193.14,171.73 184.62,172.19 174.24,172.65C168.66,172.81 166.8,172.96 162.3,173.59ZM185.86,220.7C178.57,221.94 174.24,224.26 170.36,229.22C167.42,232.63 166.02,236.66 166.02,241C166.02,245.8 168.35,249.37 171.29,249.37C174.85,249.37 178.88,241.31 185.86,220.7Z"
|
||||
android:pathData="M182.03,188.7L181.33,172.69C183.42,173.09 185.91,173.19 191.57,173.19C198.44,173.19 207.49,172.79 212.16,172.19C214.15,171.99 214.95,171.7 216.24,171L226.98,180.15C225.98,181.54 225.68,182.14 224.59,184.92C223.7,187.11 219.62,199.74 218.03,205.11C225.39,206.6 229.46,207.7 235.03,209.98C235.73,205.11 235.83,202.52 235.83,193.67C235.83,191.39 235.73,190.09 235.43,188.01L252.74,188.6C252.24,190.99 252.14,191.98 252.04,195.86C251.64,205.21 251.24,209.68 250.25,216.45C257.11,219.93 257.11,219.93 260.59,221.82C262.38,222.81 262.78,223.01 263.97,223.41L258.2,242.01C255.42,239.52 251.54,236.83 245.87,233.65C240.9,245.49 232.65,254.14 220.12,261C215.94,255.43 212.76,252.05 207.68,248.07C215.04,244.59 218.43,242.4 222.3,238.72C226.08,235.04 228.57,231.46 230.96,226.09C224.59,223.21 220.51,221.92 213.45,220.43C209.38,232.56 206.09,240.32 203.21,244.99C199.33,251.25 194.06,254.54 187.99,254.54C183.32,254.54 178.55,252.45 175.07,248.87C171.09,244.79 169,239.12 169,232.56C169,222.81 173.67,214.36 181.83,209.09C187.1,205.71 192.67,204.21 201.52,203.72C203.31,197.85 204.8,192.78 206.19,187.11C201.82,187.51 196.35,187.81 189.68,188.1C186.1,188.2 184.91,188.3 182.03,188.7ZM197.14,218.93C192.47,219.73 189.68,221.22 187.2,224.4C185.31,226.59 184.41,229.18 184.41,231.96C184.41,235.04 185.91,237.33 187.8,237.33C190.08,237.33 192.67,232.16 197.14,218.93Z"
|
||||
android:fillColor="#031019"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
@ -163,7 +163,7 @@ class SyncChaptersWithSource(
|
||||
var updatedToAdd = newChapters.map { toAddItem ->
|
||||
var chapter = toAddItem.copy(dateFetch = nowMillis + itemCount--)
|
||||
|
||||
if (chapter.isRecognizedNumber.not() || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
if (!chapter.isRecognizedNumber || chapter.chapterNumber !in deletedChapterNumbers) return@map chapter
|
||||
|
||||
chapter = chapter.copy(
|
||||
read = chapter.chapterNumber in deletedReadChapterNumbers,
|
||||
|
@ -23,7 +23,7 @@ class GetExtensionSources(
|
||||
ExtensionSourceItem(
|
||||
source = source,
|
||||
enabled = source.isEnabled(),
|
||||
labelAsName = isMultiSource && isMultiLangSingleSource.not(),
|
||||
labelAsName = isMultiSource && !isMultiLangSingleSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -22,9 +22,9 @@ class GetExtensionsByType(
|
||||
extensionManager.availableExtensionsFlow,
|
||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
||||
val (updates, installed) = _installed
|
||||
.filter { (showNsfwSources || it.isNsfw.not()) }
|
||||
.filter { (showNsfwSources || !it.isNsfw) }
|
||||
.sortedWith(
|
||||
compareBy<Extension.Installed> { it.isObsolete.not() }
|
||||
compareBy<Extension.Installed> { !it.isObsolete }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
.partition { it.hasUpdate }
|
||||
@ -36,7 +36,7 @@ class GetExtensionsByType(
|
||||
.filter { extension ->
|
||||
_installed.none { it.pkgName == extension.pkgName } &&
|
||||
_untrusted.none { it.pkgName == extension.pkgName } &&
|
||||
(showNsfwSources || extension.isNsfw.not())
|
||||
(showNsfwSources || !extension.isNsfw)
|
||||
}
|
||||
.flatMap { ext ->
|
||||
if (ext.sources.isEmpty()) {
|
||||
|
@ -34,15 +34,15 @@ class GetSourcesWithFavoriteCount(
|
||||
when (sorting) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
a.first.isStub && !b.first.isStub -> -1
|
||||
b.first.isStub && !a.first.isStub -> 1
|
||||
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||
}
|
||||
}
|
||||
SetMigrateSorting.Mode.TOTAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
a.first.isStub && !b.first.isStub -> -1
|
||||
b.first.isStub && !a.first.isStub -> 1
|
||||
else -> a.second.compareTo(b.second)
|
||||
}
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ class RefreshTracks(
|
||||
.map { (track, service) ->
|
||||
async {
|
||||
return@async try {
|
||||
val updatedTrack = service!!.refresh(track.toDbTrack())
|
||||
insertTrack.await(updatedTrack.toDomainTrack()!!)
|
||||
syncChapterProgressWithTrack.await(mangaId, track, service)
|
||||
val updatedTrack = service!!.refresh(track.toDbTrack()).toDomainTrack()!!
|
||||
insertTrack.await(updatedTrack)
|
||||
syncChapterProgressWithTrack.await(mangaId, updatedTrack, service)
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
service to e
|
||||
|
@ -19,30 +19,28 @@ fun Track.toDbTrack(): DbTrack = DbTrack.create(trackerId).also {
|
||||
it.remote_id = remoteId
|
||||
it.library_id = libraryId
|
||||
it.title = title
|
||||
it.last_chapter_read = lastChapterRead.toFloat()
|
||||
it.total_chapters = totalChapters.toInt()
|
||||
it.status = status.toInt()
|
||||
it.score = score.toFloat()
|
||||
it.last_chapter_read = lastChapterRead
|
||||
it.total_chapters = totalChapters
|
||||
it.status = status
|
||||
it.score = score
|
||||
it.tracking_url = remoteUrl
|
||||
it.started_reading_date = startDate
|
||||
it.finished_reading_date = finishDate
|
||||
}
|
||||
|
||||
fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
|
||||
val trackId = id ?: if (idRequired.not()) -1 else return null
|
||||
val trackId = id ?: if (!idRequired) -1 else return null
|
||||
return Track(
|
||||
id = trackId,
|
||||
mangaId = manga_id,
|
||||
trackerId = tracker_id.toLong(),
|
||||
trackerId = tracker_id,
|
||||
remoteId = remote_id,
|
||||
libraryId = library_id,
|
||||
title = title,
|
||||
lastChapterRead = last_chapter_read.toDouble(),
|
||||
totalChapters = total_chapters.toLong(),
|
||||
status = status.toLong(),
|
||||
// Jank workaround due to precision issues while converting
|
||||
// See https://github.com/tachiyomiorg/tachiyomi/issues/10343
|
||||
score = score.toString().toDouble(),
|
||||
lastChapterRead = last_chapter_read,
|
||||
totalChapters = total_chapters,
|
||||
status = status,
|
||||
score = score,
|
||||
remoteUrl = tracking_url,
|
||||
startDate = started_reading_date,
|
||||
finishDate = finished_reading_date,
|
||||
|
@ -19,9 +19,15 @@ class TrackPreferences(
|
||||
"",
|
||||
)
|
||||
|
||||
fun trackAuthExpired(tracker: Tracker) = preferenceStore.getBoolean(
|
||||
Preference.privateKey("pref_tracker_auth_expired_${tracker.id}"),
|
||||
false,
|
||||
)
|
||||
|
||||
fun setCredentials(tracker: Tracker, username: String, password: String) {
|
||||
trackUsername(tracker).set(username)
|
||||
trackPassword(tracker).set(password)
|
||||
trackAuthExpired(tracker).set(false)
|
||||
}
|
||||
|
||||
fun trackToken(tracker: Tracker) = preferenceStore.getString(Preference.privateKey("track_token_${tracker.id}"), "")
|
||||
|
@ -203,7 +203,13 @@ private fun ExtensionContent(
|
||||
items(
|
||||
items = items,
|
||||
contentType = { "item" },
|
||||
key = { "extension-${it.hashCode()}" },
|
||||
key = { item ->
|
||||
when (item.extension) {
|
||||
is Extension.Untrusted -> "extension-untrusted-${item.hashCode()}"
|
||||
is Extension.Installed -> "extension-installed-${item.hashCode()}"
|
||||
is Extension.Available -> "extension-available-${item.hashCode()}"
|
||||
}
|
||||
},
|
||||
) { item ->
|
||||
ExtensionItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
@ -7,8 +7,11 @@ import android.net.Uri
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
@ -189,9 +192,11 @@ object SettingsDataScreen : SearchableSettings {
|
||||
MultiChoiceSegmentedButtonRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(intrinsicSize = IntrinsicSize.Min)
|
||||
.padding(horizontal = PrefsHorizontalPadding),
|
||||
) {
|
||||
SegmentedButton(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
checked = false,
|
||||
onCheckedChange = { navigator.push(CreateBackupScreen()) },
|
||||
shape = SegmentedButtonDefaults.itemShape(0, 2),
|
||||
@ -199,6 +204,7 @@ object SettingsDataScreen : SearchableSettings {
|
||||
Text(stringResource(MR.strings.pref_create_backup))
|
||||
}
|
||||
SegmentedButton(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
checked = false,
|
||||
onCheckedChange = {
|
||||
if (!BackupRestoreJob.isRunning(context)) {
|
||||
|
@ -247,7 +247,7 @@ object AboutScreen : Screen() {
|
||||
}
|
||||
}
|
||||
BuildConfig.PREVIEW -> {
|
||||
"Preview r${BuildConfig.COMMIT_COUNT}".let {
|
||||
"Beta r${BuildConfig.COMMIT_COUNT}".let {
|
||||
if (withBuildDate) {
|
||||
"$it (${BuildConfig.COMMIT_SHA}, ${getFormattedBuildTime()})"
|
||||
} else {
|
||||
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@Stable
|
||||
class DisplayRefreshHost {
|
||||
@ -30,15 +31,15 @@ fun DisplayRefreshHost(
|
||||
val currentDisplayRefresh = hostState.currentDisplayRefresh
|
||||
LaunchedEffect(currentDisplayRefresh) {
|
||||
if (currentDisplayRefresh) {
|
||||
delay(1500)
|
||||
delay(1.5.seconds)
|
||||
hostState.currentDisplayRefresh = false
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDisplayRefresh) {
|
||||
Canvas(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
if (currentDisplayRefresh) {
|
||||
drawRect(Color.Black)
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ fun TrackInfoDialogHome(
|
||||
TrackInfoItem(
|
||||
title = item.track.title,
|
||||
tracker = item.tracker,
|
||||
status = item.tracker.getStatus(item.track.status.toInt()),
|
||||
status = item.tracker.getStatus(item.track.status),
|
||||
onStatusClick = { onStatusClick(item) },
|
||||
chapters = "${item.track.lastChapterRead.toInt()}".let {
|
||||
val totalChapters = item.track.totalChapters
|
||||
|
@ -48,9 +48,9 @@ import tachiyomi.presentation.core.util.isScrolledToStart
|
||||
|
||||
@Composable
|
||||
fun TrackStatusSelector(
|
||||
selection: Int,
|
||||
onSelectionChange: (Int) -> Unit,
|
||||
selections: Map<Int, StringResource?>,
|
||||
selection: Long,
|
||||
onSelectionChange: (Long) -> Unit,
|
||||
selections: Map<Long, StringResource?>,
|
||||
onConfirm: () -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
@ -236,12 +236,12 @@ private fun TrackStatusSelectorPreviews() {
|
||||
onSelectionChange = {},
|
||||
selections = persistentMapOf(
|
||||
// Anilist values
|
||||
1 to MR.strings.reading,
|
||||
2 to MR.strings.plan_to_read,
|
||||
3 to MR.strings.completed,
|
||||
4 to MR.strings.on_hold,
|
||||
5 to MR.strings.dropped,
|
||||
6 to MR.strings.repeating,
|
||||
1L to MR.strings.reading,
|
||||
2L to MR.strings.plan_to_read,
|
||||
3L to MR.strings.completed,
|
||||
4L to MR.strings.on_hold,
|
||||
5L to MR.strings.dropped,
|
||||
6L to MR.strings.repeating,
|
||||
),
|
||||
onConfirm = {},
|
||||
onDismissRequest = {},
|
||||
|
@ -301,7 +301,7 @@ private fun SearchResultItem(
|
||||
text = status,
|
||||
)
|
||||
}
|
||||
if (trackSearch.score != -1f) {
|
||||
if (trackSearch.score != -1.0) {
|
||||
SearchResultItemDetails(
|
||||
title = stringResource(MR.strings.score),
|
||||
text = trackSearch.score.toString(),
|
||||
|
@ -62,14 +62,14 @@ internal class TrackerSearchPreviewProvider : PreviewParameterProvider<@Composab
|
||||
private fun randTrackSearch() = TrackSearch().let {
|
||||
it.id = Random.nextLong()
|
||||
it.manga_id = Random.nextLong()
|
||||
it.tracker_id = Random.nextInt()
|
||||
it.tracker_id = Random.nextLong()
|
||||
it.remote_id = Random.nextLong()
|
||||
it.library_id = Random.nextLong()
|
||||
it.title = lorem((1..10).random()).joinToString()
|
||||
it.last_chapter_read = (0..100).random().toFloat()
|
||||
it.total_chapters = (100..1000).random()
|
||||
it.score = (0..10).random().toFloat()
|
||||
it.status = Random.nextInt()
|
||||
it.last_chapter_read = (0..100).random().toDouble()
|
||||
it.total_chapters = (100L..1000L).random()
|
||||
it.score = (0..10).random().toDouble()
|
||||
it.status = Random.nextLong()
|
||||
it.started_reading_date = 0L
|
||||
it.finished_reading_date = 0L
|
||||
it.tracking_url = "https://example.com/tracker-example"
|
||||
|
@ -30,6 +30,10 @@ class MangaBackupCreator(
|
||||
// Entry for this manga
|
||||
val mangaObject = manga.toBackupManga()
|
||||
|
||||
mangaObject.excludedScanlators = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
|
||||
}
|
||||
|
||||
if (options.chapters) {
|
||||
// Backup all the chapters
|
||||
handler.awaitList {
|
||||
|
@ -38,6 +38,7 @@ data class BackupManga(
|
||||
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||
@ProtoNumber(106) var lastModifiedAt: Long = 0,
|
||||
@ProtoNumber(107) var favoriteModifiedAt: Long? = null,
|
||||
@ProtoNumber(108) var excludedScanlators: List<String> = emptyList(),
|
||||
) {
|
||||
fun getMangaImpl(): Manga {
|
||||
return Manga.create().copy(
|
||||
|
@ -73,6 +73,7 @@ class MangaRestorer(
|
||||
backupCategories = backupCategories,
|
||||
history = backupManga.history + backupManga.brokenHistory.map { it.toBackupHistory() },
|
||||
tracks = backupManga.tracking,
|
||||
excludedScanlators = backupManga.excludedScanlators,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -264,11 +265,13 @@ class MangaRestorer(
|
||||
backupCategories: List<BackupCategory>,
|
||||
history: List<BackupHistory>,
|
||||
tracks: List<BackupTracking>,
|
||||
excludedScanlators: List<String>,
|
||||
): Manga {
|
||||
restoreCategories(manga, categories, backupCategories)
|
||||
restoreChapters(manga, chapters)
|
||||
restoreTracking(manga, tracks)
|
||||
restoreHistory(history)
|
||||
restoreExcludedScanlators(manga, excludedScanlators)
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, currentFetchWindow)
|
||||
return manga
|
||||
}
|
||||
@ -401,4 +404,25 @@ class MangaRestorer(
|
||||
}
|
||||
|
||||
private fun Track.forComparison() = this.copy(id = 0L, mangaId = 0L)
|
||||
|
||||
/**
|
||||
* Restores the excluded scanlators for the manga.
|
||||
*
|
||||
* @param manga the manga whose excluded scanlators have to be restored.
|
||||
* @param excludedScanlators the excluded scanlators to restore.
|
||||
*/
|
||||
private suspend fun restoreExcludedScanlators(manga: Manga, excludedScanlators: List<String>) {
|
||||
if (excludedScanlators.isEmpty()) return
|
||||
val existingExcludedScanlators = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(manga.id)
|
||||
}
|
||||
val toInsert = excludedScanlators.filter { it !in existingExcludedScanlators }
|
||||
if (toInsert.isNotEmpty()) {
|
||||
handler.await {
|
||||
toInsert.forEach {
|
||||
excluded_scanlatorsQueries.insert(manga.id, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ interface Track : Serializable {
|
||||
|
||||
var manga_id: Long
|
||||
|
||||
var tracker_id: Int
|
||||
var tracker_id: Long
|
||||
|
||||
var remote_id: Long
|
||||
|
||||
@ -16,13 +16,13 @@ interface Track : Serializable {
|
||||
|
||||
var title: String
|
||||
|
||||
var last_chapter_read: Float
|
||||
var last_chapter_read: Double
|
||||
|
||||
var total_chapters: Int
|
||||
var total_chapters: Long
|
||||
|
||||
var score: Float
|
||||
var score: Double
|
||||
|
||||
var status: Int
|
||||
var status: Long
|
||||
|
||||
var started_reading_date: Long
|
||||
|
||||
@ -40,7 +40,7 @@ interface Track : Serializable {
|
||||
|
||||
companion object {
|
||||
fun create(serviceId: Long): Track = TrackImpl().apply {
|
||||
tracker_id = serviceId.toInt()
|
||||
tracker_id = serviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ class TrackImpl : Track {
|
||||
|
||||
override var manga_id: Long = 0
|
||||
|
||||
override var tracker_id: Int = 0
|
||||
override var tracker_id: Long = 0
|
||||
|
||||
override var remote_id: Long = 0
|
||||
|
||||
@ -14,13 +14,13 @@ class TrackImpl : Track {
|
||||
|
||||
override lateinit var title: String
|
||||
|
||||
override var last_chapter_read: Float = 0F
|
||||
override var last_chapter_read: Double = 0.0
|
||||
|
||||
override var total_chapters: Int = 0
|
||||
override var total_chapters: Long = 0
|
||||
|
||||
override var score: Float = 0f
|
||||
override var score: Double = 0.0
|
||||
|
||||
override var status: Int = 0
|
||||
override var status: Long = 0
|
||||
|
||||
override var started_reading_date: Long = 0
|
||||
|
||||
|
@ -315,13 +315,13 @@ class DownloadManager(
|
||||
val capitalizationChanged = oldFolder.name.equals(newName, ignoreCase = true)
|
||||
if (capitalizationChanged) {
|
||||
val tempName = newName + Downloader.TMP_DIR_SUFFIX
|
||||
if (oldFolder.renameTo(tempName).not()) {
|
||||
if (!oldFolder.renameTo(tempName)) {
|
||||
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (oldFolder.renameTo(newName).not()) {
|
||||
if (!oldFolder.renameTo(newName)) {
|
||||
logcat(LogPriority.ERROR) { "Failed to rename source download folder: ${oldFolder.name}" }
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ class DownloadProvider(
|
||||
val newChapterName = sanitizeChapterName(chapterName)
|
||||
return DiskUtil.buildValidFilename(
|
||||
when {
|
||||
chapterScanlator.isNullOrBlank().not() -> "${chapterScanlator}_$newChapterName"
|
||||
!chapterScanlator.isNullOrBlank() -> "${chapterScanlator}_$newChapterName"
|
||||
else -> newChapterName
|
||||
},
|
||||
)
|
||||
|
@ -40,8 +40,8 @@ abstract class BaseTracker(
|
||||
return track.score
|
||||
}
|
||||
|
||||
override fun indexToScore(index: Int): Float {
|
||||
return index.toFloat()
|
||||
override fun indexToScore(index: Int): Double {
|
||||
return index.toDouble()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@ -70,24 +70,24 @@ abstract class BaseTracker(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setRemoteStatus(track: Track, status: Int) {
|
||||
override suspend fun setRemoteStatus(track: Track, status: Long) {
|
||||
track.status = status
|
||||
if (track.status == getCompletionStatus() && track.total_chapters != 0) {
|
||||
track.last_chapter_read = track.total_chapters.toFloat()
|
||||
if (track.status == getCompletionStatus() && track.total_chapters != 0L) {
|
||||
track.last_chapter_read = track.total_chapters.toDouble()
|
||||
}
|
||||
updateRemote(track)
|
||||
}
|
||||
|
||||
override suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int) {
|
||||
if (
|
||||
track.last_chapter_read == 0f &&
|
||||
track.last_chapter_read == 0.0 &&
|
||||
track.last_chapter_read < chapterNumber &&
|
||||
track.status != getRereadingStatus()
|
||||
) {
|
||||
track.status = getReadingStatus()
|
||||
}
|
||||
track.last_chapter_read = chapterNumber.toFloat()
|
||||
if (track.total_chapters != 0 && track.last_chapter_read.toInt() == track.total_chapters) {
|
||||
track.last_chapter_read = chapterNumber.toDouble()
|
||||
if (track.total_chapters != 0L && track.last_chapter_read.toLong() == track.total_chapters) {
|
||||
track.status = getCompletionStatus()
|
||||
track.finished_reading_date = System.currentTimeMillis()
|
||||
}
|
||||
|
@ -27,22 +27,22 @@ interface Tracker {
|
||||
@DrawableRes
|
||||
fun getLogo(): Int
|
||||
|
||||
fun getStatusList(): List<Int>
|
||||
fun getStatusList(): List<Long>
|
||||
|
||||
fun getStatus(status: Int): StringResource?
|
||||
fun getStatus(status: Long): StringResource?
|
||||
|
||||
fun getReadingStatus(): Int
|
||||
fun getReadingStatus(): Long
|
||||
|
||||
fun getRereadingStatus(): Int
|
||||
fun getRereadingStatus(): Long
|
||||
|
||||
fun getCompletionStatus(): Int
|
||||
fun getCompletionStatus(): Long
|
||||
|
||||
fun getScoreList(): ImmutableList<String>
|
||||
|
||||
// TODO: Store all scores as 10 point in the future maybe?
|
||||
fun get10PointScore(track: DomainTrack): Double
|
||||
|
||||
fun indexToScore(index: Int): Float
|
||||
fun indexToScore(index: Int): Double
|
||||
|
||||
fun displayScore(track: DomainTrack): String
|
||||
|
||||
@ -70,7 +70,7 @@ interface Tracker {
|
||||
// TODO: move this to an interactor, and update all trackers based on common data
|
||||
suspend fun register(item: Track, mangaId: Long)
|
||||
|
||||
suspend fun setRemoteStatus(track: Track, status: Int)
|
||||
suspend fun setRemoteStatus(track: Track, status: Long)
|
||||
|
||||
suspend fun setRemoteLastChapterRead(track: Track, chapterNumber: Int)
|
||||
|
||||
|
@ -20,12 +20,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
companion object {
|
||||
const val READING = 1
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 3
|
||||
const val DROPPED = 4
|
||||
const val PLAN_TO_READ = 5
|
||||
const val REREADING = 6
|
||||
const val READING = 1L
|
||||
const val COMPLETED = 2L
|
||||
const val ON_HOLD = 3L
|
||||
const val DROPPED = 4L
|
||||
const val PLAN_TO_READ = 5L
|
||||
const val REREADING = 6L
|
||||
|
||||
const val POINT_100 = "POINT_100"
|
||||
const val POINT_10 = "POINT_10"
|
||||
@ -58,11 +58,11 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(18, 25, 35)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING -> MR.strings.reading
|
||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||
COMPLETED -> MR.strings.completed
|
||||
@ -72,11 +72,11 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = REREADING
|
||||
override fun getRereadingStatus(): Long = REREADING
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> {
|
||||
return when (scorePreference.get()) {
|
||||
@ -99,24 +99,24 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
return track.score / 10.0
|
||||
}
|
||||
|
||||
override fun indexToScore(index: Int): Float {
|
||||
override fun indexToScore(index: Int): Double {
|
||||
return when (scorePreference.get()) {
|
||||
// 10 point
|
||||
POINT_10 -> index * 10f
|
||||
POINT_10 -> index * 10.0
|
||||
// 100 point
|
||||
POINT_100 -> index.toFloat()
|
||||
POINT_100 -> index.toDouble()
|
||||
// 5 stars
|
||||
POINT_5 -> when (index) {
|
||||
0 -> 0f
|
||||
else -> index * 20f - 10f
|
||||
0 -> 0.0
|
||||
else -> index * 20.0 - 10.0
|
||||
}
|
||||
// Smiley
|
||||
POINT_3 -> when (index) {
|
||||
0 -> 0f
|
||||
else -> index * 25f + 10f
|
||||
0 -> 0.0
|
||||
else -> index * 25.0 + 10.0
|
||||
}
|
||||
// 10 point decimal
|
||||
POINT_10_DECIMAL -> index.toFloat()
|
||||
POINT_10_DECIMAL -> index.toDouble()
|
||||
else -> throw Exception("Unknown score type")
|
||||
}
|
||||
}
|
||||
@ -153,12 +153,12 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
track.finished_reading_date = System.currentTimeMillis()
|
||||
} else if (track.status != REREADING) {
|
||||
track.status = READING
|
||||
if (track.last_chapter_read == 1F) {
|
||||
if (track.last_chapter_read == 1.0) {
|
||||
track.started_reading_date = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
@ -185,14 +185,14 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
val isRereading = track.status == REREADING
|
||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
||||
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||
}
|
||||
|
||||
update(track)
|
||||
} else {
|
||||
// Set default fields if it's not found in the list
|
||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||
track.score = 0F
|
||||
track.score = 0.0
|
||||
add(track)
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonObject
|
||||
import okhttp3.OkHttpClient
|
||||
@ -312,7 +313,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
struct["format"]!!.jsonPrimitive.content.replace("_", "-"),
|
||||
struct["status"]!!.jsonPrimitive.contentOrNull ?: "",
|
||||
parseDate(struct, "startDate"),
|
||||
struct["chapters"]!!.jsonPrimitive.intOrNull ?: 0,
|
||||
struct["chapters"]!!.jsonPrimitive.longOrNull ?: 0,
|
||||
struct["averageScore"]?.jsonPrimitive?.intOrNull ?: -1,
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.anilist
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
@ -40,6 +41,7 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
|
@ -19,7 +19,7 @@ data class ALManga(
|
||||
val format: String,
|
||||
val publishing_status: String,
|
||||
val start_date_fuzzy: Long,
|
||||
val total_chapters: Int,
|
||||
val total_chapters: Long,
|
||||
val average_score: Int,
|
||||
) {
|
||||
|
||||
@ -29,7 +29,7 @@ data class ALManga(
|
||||
total_chapters = this@ALManga.total_chapters
|
||||
cover_url = image_url_lge
|
||||
summary = description?.htmlDecode() ?: ""
|
||||
score = average_score.toFloat()
|
||||
score = average_score.toDouble()
|
||||
tracking_url = AnilistApi.mangaUrl(remote_id)
|
||||
publishing_status = this@ALManga.publishing_status
|
||||
publishing_type = format
|
||||
@ -58,10 +58,10 @@ data class ALUserManga(
|
||||
remote_id = manga.remote_id
|
||||
title = manga.title_user_pref
|
||||
status = toTrackStatus()
|
||||
score = score_raw.toFloat()
|
||||
score = score_raw.toDouble()
|
||||
started_reading_date = start_date_fuzzy
|
||||
finished_reading_date = completed_date_fuzzy
|
||||
last_chapter_read = chapters_read.toFloat()
|
||||
last_chapter_read = chapters_read.toDouble()
|
||||
library_id = this@ALUserManga.library_id
|
||||
total_chapters = manga.total_chapters
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
} else {
|
||||
track.status = READING
|
||||
@ -64,7 +64,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||
} else {
|
||||
// Set default fields if it's not found in the list
|
||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||
track.score = 0F
|
||||
track.score = 0.0
|
||||
add(track)
|
||||
update(track)
|
||||
}
|
||||
@ -87,11 +87,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(240, 145, 153)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING -> MR.strings.reading
|
||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||
COMPLETED -> MR.strings.completed
|
||||
@ -100,11 +100,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override suspend fun login(username: String, password: String) = login(password)
|
||||
|
||||
@ -137,11 +137,11 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val READING = 3
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 4
|
||||
const val DROPPED = 5
|
||||
const val PLAN_TO_READ = 1
|
||||
const val READING = 3L
|
||||
const val COMPLETED = 2L
|
||||
const val ON_HOLD = 4L
|
||||
const val DROPPED = 5L
|
||||
const val PLAN_TO_READ = 1L
|
||||
|
||||
private val SCORE_LIST = IntRange(0, 10)
|
||||
.map(Int::toString)
|
||||
|
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.floatOrNull
|
||||
import kotlinx.serialization.json.doubleOrNull
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
@ -105,11 +105,11 @@ class BangumiApi(
|
||||
""
|
||||
}
|
||||
val totalChapters = if (obj["eps_count"] != null) {
|
||||
obj["eps_count"]!!.jsonPrimitive.int
|
||||
obj["eps_count"]!!.jsonPrimitive.long
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val rating = obj["rating"]?.jsonObject?.get("score")?.jsonPrimitive?.floatOrNull ?: -1f
|
||||
val rating = obj["rating"]?.jsonObject?.get("score")?.jsonPrimitive?.doubleOrNull ?: -1.0
|
||||
return TrackSearch.create(trackId).apply {
|
||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||
title = obj["name_cn"]!!.jsonPrimitive.content
|
||||
@ -152,7 +152,7 @@ class BangumiApi(
|
||||
} else {
|
||||
json.decodeFromString<Collection>(responseBody).let {
|
||||
track.status = it.status?.id!!
|
||||
track.last_chapter_read = it.ep_status!!.toFloat()
|
||||
track.last_chapter_read = it.ep_status!!.toDouble()
|
||||
track.score = it.rating!!
|
||||
track
|
||||
}
|
||||
@ -182,8 +182,8 @@ class BangumiApi(
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val clientId = "bgm10555cda0762e80ca"
|
||||
private const val clientSecret = "8fff394a8627b4c388cbf349ec865775"
|
||||
private const val clientId = "bgm291665acbd06a4c28"
|
||||
private const val clientSecret = "43e5ce36b207de16e5d3cfd3e79118db"
|
||||
|
||||
private const val apiUrl = "https://api.bgm.tv"
|
||||
private const val oauthUrl = "https://bgm.tv/oauth/access_token"
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.bangumi
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Interceptor
|
||||
@ -29,22 +30,23 @@ class BangumiInterceptor(private val bangumi: Bangumi) : Interceptor {
|
||||
}
|
||||
}
|
||||
|
||||
val authRequest = if (originalRequest.method == "GET") {
|
||||
originalRequest.newBuilder()
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.url(
|
||||
originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token).build(),
|
||||
return originalRequest.newBuilder()
|
||||
.header(
|
||||
"User-Agent",
|
||||
"antsylich/Mihon/v${BuildConfig.VERSION_NAME} (Android) (http://github.com/mihonapp/mihon)",
|
||||
)
|
||||
.apply {
|
||||
if (originalRequest.method == "GET") {
|
||||
val newUrl = originalRequest.url.newBuilder()
|
||||
.addQueryParameter("access_token", currAuth.access_token)
|
||||
.build()
|
||||
url(newUrl)
|
||||
} else {
|
||||
originalRequest.newBuilder()
|
||||
.post(addToken(currAuth.access_token, originalRequest.body as FormBody))
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.build()
|
||||
post(addToken(currAuth.access_token, originalRequest.body as FormBody))
|
||||
}
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
.build()
|
||||
.let(chain::proceed)
|
||||
}
|
||||
|
||||
fun newAuth(oauth: OAuth?) {
|
||||
|
@ -16,7 +16,7 @@ data class Collection(
|
||||
val comment: String? = "",
|
||||
val ep_status: Int? = 0,
|
||||
val lasttouch: Int? = 0,
|
||||
val rating: Float? = 0f,
|
||||
val rating: Double? = 0.0,
|
||||
val status: Status? = Status(),
|
||||
val tag: List<String?>? = emptyList(),
|
||||
val user: User? = User(),
|
||||
@ -25,7 +25,7 @@ data class Collection(
|
||||
|
||||
@Serializable
|
||||
data class Status(
|
||||
val id: Int? = 0,
|
||||
val id: Long? = 0,
|
||||
val name: String? = "",
|
||||
val type: String? = "",
|
||||
)
|
||||
|
@ -22,9 +22,9 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
|
||||
companion object {
|
||||
const val UNREAD = 1
|
||||
const val READING = 2
|
||||
const val COMPLETED = 3
|
||||
const val UNREAD = 1L
|
||||
const val READING = 2L
|
||||
const val COMPLETED = 3L
|
||||
}
|
||||
|
||||
var authentications: OAuth? = null
|
||||
@ -38,20 +38,20 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(74, 198, 148)
|
||||
|
||||
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
|
||||
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
UNREAD -> MR.strings.unread
|
||||
READING -> MR.strings.reading
|
||||
COMPLETED -> MR.strings.completed
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
||||
|
||||
@ -60,7 +60,7 @@ class Kavita(id: Long) : BaseTracker(id, "Kavita"), EnhancedTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
} else {
|
||||
track.status = READING
|
||||
|
@ -93,7 +93,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
* Ignores volumes.
|
||||
* Volumes consisting of 1 file treated as chapter
|
||||
*/
|
||||
private fun getTotalChapters(url: String): Int {
|
||||
private fun getTotalChapters(url: String): Long {
|
||||
val requestUrl = getApiVolumesUrl(url)
|
||||
try {
|
||||
val listVolumeDto = with(json) {
|
||||
@ -101,13 +101,13 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
.execute()
|
||||
.parseAs<List<VolumeDto>>()
|
||||
}
|
||||
var volumeNumber = 0
|
||||
var maxChapterNumber = 0
|
||||
var volumeNumber = 0L
|
||||
var maxChapterNumber = 0L
|
||||
for (volume in listVolumeDto) {
|
||||
if (volume.chapters.maxOf { it.number!!.toFloat() } == 0f) {
|
||||
volumeNumber++
|
||||
} else if (maxChapterNumber < volume.chapters.maxOf { it.number!!.toFloat() }) {
|
||||
maxChapterNumber = volume.chapters.maxOf { it.number!!.toFloat().toInt() }
|
||||
maxChapterNumber = volume.chapters.maxOf { it.number!!.toFloat().toLong() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,17 +118,17 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLatestChapterRead(url: String): Float {
|
||||
private fun getLatestChapterRead(url: String): Double {
|
||||
val seriesId = getIdFromUrl(url)
|
||||
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$seriesId"
|
||||
try {
|
||||
with(json) {
|
||||
authClient.newCall(GET(requestUrl)).execute().use {
|
||||
if (it.code == 200) {
|
||||
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
||||
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toDouble()
|
||||
}
|
||||
if (it.code == 204) {
|
||||
return 0F
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,7 +139,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
||||
) { "Exception getting latest chapter read. Could not get itemRequest: $requestUrl" }
|
||||
throw e
|
||||
}
|
||||
return 0F
|
||||
return 0.0
|
||||
}
|
||||
|
||||
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
||||
|
@ -18,7 +18,7 @@ class KavitaInterceptor(private val kavita: Kavita) : Interceptor {
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer $jwtToken")
|
||||
.header("User-Agent", "Tachiyomi Kavita v${BuildConfig.VERSION_NAME}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
|
@ -19,11 +19,11 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||
|
||||
companion object {
|
||||
const val READING = 1
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 3
|
||||
const val DROPPED = 4
|
||||
const val PLAN_TO_READ = 5
|
||||
const val READING = 1L
|
||||
const val COMPLETED = 2L
|
||||
const val ON_HOLD = 3L
|
||||
const val DROPPED = 4L
|
||||
const val PLAN_TO_READ = 5L
|
||||
}
|
||||
|
||||
override val supportsReadingDates: Boolean = true
|
||||
@ -38,11 +38,11 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING -> MR.strings.reading
|
||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||
COMPLETED -> MR.strings.completed
|
||||
@ -51,19 +51,19 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> {
|
||||
val df = DecimalFormat("0.#")
|
||||
return (listOf("0") + IntRange(2, 20).map { df.format(it / 2f) }).toImmutableList()
|
||||
}
|
||||
|
||||
override fun indexToScore(index: Int): Float {
|
||||
return if (index > 0) (index + 1) / 2f else 0f
|
||||
override fun indexToScore(index: Int): Double {
|
||||
return if (index > 0) (index + 1) / 2.0 else 0.0
|
||||
}
|
||||
|
||||
override fun displayScore(track: DomainTrack): String {
|
||||
@ -78,12 +78,12 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
track.finished_reading_date = System.currentTimeMillis()
|
||||
} else {
|
||||
track.status = READING
|
||||
if (track.last_chapter_read == 1F) {
|
||||
if (track.last_chapter_read == 1.0) {
|
||||
track.started_reading_date = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker {
|
||||
update(track)
|
||||
} else {
|
||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||
track.score = 0F
|
||||
track.score = 0.0
|
||||
add(track)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.kitsu
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@ -34,6 +35,7 @@ class KitsuInterceptor(private val kitsu: Kitsu) : Interceptor {
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.header("Accept", "application/vnd.api+json")
|
||||
.header("Content-Type", "application/vnd.api+json")
|
||||
.build()
|
||||
|
@ -8,10 +8,10 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@ -19,7 +19,7 @@ import java.util.Locale
|
||||
class KitsuSearchManga(obj: JsonObject) {
|
||||
val id = obj["id"]!!.jsonPrimitive.long
|
||||
private val canonicalTitle = obj["canonicalTitle"]!!.jsonPrimitive.content
|
||||
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.intOrNull
|
||||
private val chapterCount = obj["chapterCount"]?.jsonPrimitive?.longOrNull
|
||||
val subType = obj["subtype"]?.jsonPrimitive?.contentOrNull
|
||||
val original = try {
|
||||
obj["posterImage"]?.jsonObject?.get("original")?.jsonPrimitive?.content
|
||||
@ -28,7 +28,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
||||
null
|
||||
}
|
||||
private val synopsis = obj["synopsis"]?.jsonPrimitive?.contentOrNull
|
||||
private val rating = obj["averageRating"]?.jsonPrimitive?.contentOrNull?.toFloatOrNull()
|
||||
private val rating = obj["averageRating"]?.jsonPrimitive?.contentOrNull?.toDoubleOrNull()
|
||||
private var startDate = obj["startDate"]?.jsonPrimitive?.contentOrNull?.let {
|
||||
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
outputDf.format(Date(it.toLong() * 1000))
|
||||
@ -43,7 +43,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
||||
cover_url = original ?: ""
|
||||
summary = synopsis ?: ""
|
||||
tracking_url = KitsuApi.mangaUrl(remote_id)
|
||||
score = rating ?: -1f
|
||||
score = rating ?: -1.0
|
||||
publishing_status = if (endDate == null) {
|
||||
"Publishing"
|
||||
} else {
|
||||
@ -57,7 +57,7 @@ class KitsuSearchManga(obj: JsonObject) {
|
||||
class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
||||
val id = manga["id"]!!.jsonPrimitive.int
|
||||
private val canonicalTitle = manga["attributes"]!!.jsonObject["canonicalTitle"]!!.jsonPrimitive.content
|
||||
private val chapterCount = manga["attributes"]!!.jsonObject["chapterCount"]?.jsonPrimitive?.intOrNull
|
||||
private val chapterCount = manga["attributes"]!!.jsonObject["chapterCount"]?.jsonPrimitive?.longOrNull
|
||||
val type = manga["attributes"]!!.jsonObject["mangaType"]?.jsonPrimitive?.contentOrNull.orEmpty()
|
||||
val original = manga["attributes"]!!.jsonObject["posterImage"]!!.jsonObject["original"]!!.jsonPrimitive.content
|
||||
private val synopsis = manga["attributes"]!!.jsonObject["synopsis"]!!.jsonPrimitive.content
|
||||
@ -82,8 +82,8 @@ class KitsuLibManga(obj: JsonObject, manga: JsonObject) {
|
||||
started_reading_date = KitsuDateHelper.parse(startedAt)
|
||||
finished_reading_date = KitsuDateHelper.parse(finishedAt)
|
||||
status = toTrackStatus()
|
||||
score = ratingTwenty?.let { it.toInt() / 2f } ?: 0f
|
||||
last_chapter_read = progress.toFloat()
|
||||
score = ratingTwenty?.let { it.toInt() / 2.0 } ?: 0.0
|
||||
last_chapter_read = progress.toDouble()
|
||||
}
|
||||
|
||||
private fun toTrackStatus() = when (status) {
|
||||
|
@ -19,9 +19,9 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||
|
||||
companion object {
|
||||
const val UNREAD = 1
|
||||
const val READING = 2
|
||||
const val COMPLETED = 3
|
||||
const val UNREAD = 1L
|
||||
const val READING = 2L
|
||||
const val COMPLETED = 3L
|
||||
}
|
||||
|
||||
override val client: OkHttpClient =
|
||||
@ -35,20 +35,20 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(51, 37, 50)
|
||||
|
||||
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
|
||||
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
UNREAD -> MR.strings.unread
|
||||
READING -> MR.strings.reading
|
||||
COMPLETED -> MR.strings.completed
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
||||
|
||||
@ -57,7 +57,7 @@ class Komga(id: Long) : BaseTracker(id, "Komga"), EnhancedTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
} else {
|
||||
track.status = READING
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.komga
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
@ -8,6 +9,7 @@ import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import logcat.LogPriority
|
||||
import okhttp3.Headers
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@ -23,6 +25,12 @@ class KomgaApi(
|
||||
private val client: OkHttpClient,
|
||||
) {
|
||||
|
||||
private val headers: Headers by lazy {
|
||||
Headers.Builder()
|
||||
.add("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
}
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
suspend fun getTrackSearch(url: String): TrackSearch =
|
||||
@ -30,12 +38,12 @@ class KomgaApi(
|
||||
try {
|
||||
val track = with(json) {
|
||||
if (url.contains(READLIST_API)) {
|
||||
client.newCall(GET(url))
|
||||
client.newCall(GET(url, headers))
|
||||
.awaitSuccess()
|
||||
.parseAs<ReadListDto>()
|
||||
.toTrack()
|
||||
} else {
|
||||
client.newCall(GET(url))
|
||||
client.newCall(GET(url, headers))
|
||||
.awaitSuccess()
|
||||
.parseAs<SeriesDto>()
|
||||
.toTrack()
|
||||
@ -43,7 +51,9 @@ class KomgaApi(
|
||||
}
|
||||
|
||||
val progress = client
|
||||
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
|
||||
.newCall(
|
||||
GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi", headers),
|
||||
)
|
||||
.awaitSuccess().let {
|
||||
with(json) {
|
||||
if (url.contains("/api/v1/series/")) {
|
||||
@ -57,7 +67,7 @@ class KomgaApi(
|
||||
track.apply {
|
||||
cover_url = "$url/thumbnail"
|
||||
tracking_url = url
|
||||
total_chapters = progress.maxNumberSort.toInt()
|
||||
total_chapters = progress.maxNumberSort.toLong()
|
||||
status = when (progress.booksCount) {
|
||||
progress.booksUnreadCount -> Komga.UNREAD
|
||||
progress.booksReadCount -> Komga.COMPLETED
|
||||
@ -80,6 +90,7 @@ class KomgaApi(
|
||||
client.newCall(
|
||||
Request.Builder()
|
||||
.url("${track.tracking_url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi")
|
||||
.headers(headers)
|
||||
.put(payload.toRequestBody("application/json".toMediaType()))
|
||||
.build(),
|
||||
)
|
||||
|
@ -65,7 +65,7 @@ data class ReadProgressUpdateDto(
|
||||
|
||||
@Serializable
|
||||
data class ReadProgressUpdateV2Dto(
|
||||
val lastBookNumberSortRead: Float,
|
||||
val lastBookNumberSortRead: Double,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
@ -91,7 +91,7 @@ data class ReadProgressDto(
|
||||
booksReadCount,
|
||||
booksUnreadCount,
|
||||
booksInProgressCount,
|
||||
lastReadContinuousIndex.toFloat(),
|
||||
lastReadContinuousIndex.toDouble(),
|
||||
booksCount.toFloat(),
|
||||
)
|
||||
}
|
||||
@ -102,6 +102,6 @@ data class ReadProgressV2Dto(
|
||||
val booksReadCount: Int,
|
||||
val booksUnreadCount: Int,
|
||||
val booksInProgressCount: Int,
|
||||
val lastReadContinuousNumberSort: Float,
|
||||
val lastReadContinuousNumberSort: Double,
|
||||
val maxNumberSort: Float,
|
||||
)
|
||||
|
@ -6,6 +6,8 @@ import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Track
|
||||
import eu.kanade.tachiyomi.data.track.BaseTracker
|
||||
import eu.kanade.tachiyomi.data.track.DeletableTracker
|
||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.ListItem
|
||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.Rating
|
||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.copyTo
|
||||
import eu.kanade.tachiyomi.data.track.mangaupdates.dto.toTrackSearch
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
@ -17,16 +19,22 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker {
|
||||
|
||||
companion object {
|
||||
const val READING_LIST = 0
|
||||
const val WISH_LIST = 1
|
||||
const val COMPLETE_LIST = 2
|
||||
const val UNFINISHED_LIST = 3
|
||||
const val ON_HOLD_LIST = 4
|
||||
const val READING_LIST = 0L
|
||||
const val WISH_LIST = 1L
|
||||
const val COMPLETE_LIST = 2L
|
||||
const val UNFINISHED_LIST = 3L
|
||||
const val ON_HOLD_LIST = 4L
|
||||
|
||||
private val SCORE_LIST = (
|
||||
(0..9)
|
||||
.flatMap { i -> (0..9).map { j -> "$i.$j" } } + listOf("10.0")
|
||||
)
|
||||
private val SCORE_LIST = (0..10)
|
||||
.flatMap { decimal ->
|
||||
when (decimal) {
|
||||
0 -> listOf("-")
|
||||
10 -> listOf("10.0")
|
||||
else -> (0..9).map { fraction ->
|
||||
"$decimal.$fraction"
|
||||
}
|
||||
}
|
||||
}
|
||||
.toImmutableList()
|
||||
}
|
||||
|
||||
@ -38,11 +46,11 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
|
||||
override fun getLogoColor(): Int = Color.rgb(146, 160, 173)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING_LIST, COMPLETE_LIST, ON_HOLD_LIST, UNFINISHED_LIST, WISH_LIST)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING_LIST -> MR.strings.reading_list
|
||||
WISH_LIST -> MR.strings.wish_list
|
||||
COMPLETE_LIST -> MR.strings.complete_list
|
||||
@ -51,15 +59,15 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING_LIST
|
||||
override fun getReadingStatus(): Long = READING_LIST
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETE_LIST
|
||||
override fun getCompletionStatus(): Long = COMPLETE_LIST
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
||||
|
||||
override fun indexToScore(index: Int): Float = SCORE_LIST[index].toFloat()
|
||||
override fun indexToScore(index: Int): Double = if (index == 0) 0.0 else SCORE_LIST[index].toDouble()
|
||||
|
||||
override fun displayScore(track: DomainTrack): String = track.score.toString()
|
||||
|
||||
@ -78,9 +86,9 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
override suspend fun bind(track: Track, hasReadChapters: Boolean): Track {
|
||||
return try {
|
||||
val (series, rating) = api.getSeriesListItem(track)
|
||||
series.copyTo(track)
|
||||
rating?.copyTo(track) ?: track
|
||||
track.copyFrom(series, rating)
|
||||
} catch (e: Exception) {
|
||||
track.score = 0.0
|
||||
api.addSeriesToList(track, hasReadChapters)
|
||||
track
|
||||
}
|
||||
@ -95,8 +103,12 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
|
||||
override suspend fun refresh(track: Track): Track {
|
||||
val (series, rating) = api.getSeriesListItem(track)
|
||||
series.copyTo(track)
|
||||
return rating?.copyTo(track) ?: track
|
||||
return track.copyFrom(series, rating)
|
||||
}
|
||||
|
||||
private fun Track.copyFrom(item: ListItem, rating: Rating?): Track = apply {
|
||||
item.copyTo(this)
|
||||
score = rating?.rating ?: 0.0
|
||||
}
|
||||
|
||||
override suspend fun login(username: String, password: String) {
|
||||
@ -106,6 +118,6 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
|
||||
}
|
||||
|
||||
fun restoreSession(): String? {
|
||||
return trackPreferences.trackPassword(this).get()
|
||||
return trackPreferences.trackPassword(this).get().ifBlank { null }
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class MangaUpdatesApi(
|
||||
.let {
|
||||
if (it.code == 200) {
|
||||
track.status = status
|
||||
track.last_chapter_read = 1f
|
||||
track.last_chapter_read = 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -133,7 +133,8 @@ class MangaUpdatesApi(
|
||||
}
|
||||
|
||||
private suspend fun updateSeriesRating(track: Track) {
|
||||
if (track.score != 0f) {
|
||||
if (track.score < 0.0) return
|
||||
if (track.score != 0.0) {
|
||||
val body = buildJsonObject {
|
||||
put("rating", track.score)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.mangaupdates
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import java.io.IOException
|
||||
@ -18,6 +19,7 @@ class MangaUpdatesInterceptor(
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer $token")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
|
@ -9,7 +9,7 @@ import kotlinx.serialization.Serializable
|
||||
data class ListItem(
|
||||
val series: Series? = null,
|
||||
@SerialName("list_id")
|
||||
val listId: Int? = null,
|
||||
val listId: Long? = null,
|
||||
val status: Status? = null,
|
||||
val priority: Int? = null,
|
||||
)
|
||||
@ -17,6 +17,6 @@ data class ListItem(
|
||||
fun ListItem.copyTo(track: Track): Track {
|
||||
return track.apply {
|
||||
this.status = listId ?: READING_LIST
|
||||
this.last_chapter_read = this@copyTo.status?.chapter?.toFloat() ?: 0f
|
||||
this.last_chapter_read = this@copyTo.status?.chapter?.toDouble() ?: 0.0
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Rating(
|
||||
val rating: Float? = null,
|
||||
val rating: Double? = null,
|
||||
)
|
||||
|
||||
fun Rating.copyTo(track: Track): Track {
|
||||
return track.apply {
|
||||
this.score = rating ?: 0f
|
||||
this.score = rating ?: 0.0
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ class TrackSearch : Track {
|
||||
|
||||
override var manga_id: Long = 0
|
||||
|
||||
override var tracker_id: Int = 0
|
||||
override var tracker_id: Long = 0
|
||||
|
||||
override var remote_id: Long = 0
|
||||
|
||||
@ -16,13 +16,13 @@ class TrackSearch : Track {
|
||||
|
||||
override lateinit var title: String
|
||||
|
||||
override var last_chapter_read: Float = 0F
|
||||
override var last_chapter_read: Double = 0.0
|
||||
|
||||
override var total_chapters: Int = 0
|
||||
override var total_chapters: Long = 0
|
||||
|
||||
override var score: Float = -1f
|
||||
override var score: Double = -1.0
|
||||
|
||||
override var status: Int = 0
|
||||
override var status: Long = 0
|
||||
|
||||
override var started_reading_date: Long = 0
|
||||
|
||||
@ -55,14 +55,14 @@ class TrackSearch : Track {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = manga_id.hashCode()
|
||||
result = 31 * result + tracker_id
|
||||
result = 31 * result + tracker_id.hashCode()
|
||||
result = 31 * result + remote_id.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(serviceId: Long): TrackSearch = TrackSearch().apply {
|
||||
tracker_id = serviceId.toInt()
|
||||
tracker_id = serviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
|
||||
companion object {
|
||||
const val READING = 1
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 3
|
||||
const val DROPPED = 4
|
||||
const val PLAN_TO_READ = 6
|
||||
const val REREADING = 7
|
||||
const val READING = 1L
|
||||
const val COMPLETED = 2L
|
||||
const val ON_HOLD = 3L
|
||||
const val DROPPED = 4L
|
||||
const val PLAN_TO_READ = 6L
|
||||
const val REREADING = 7L
|
||||
|
||||
private const val SEARCH_ID_PREFIX = "id:"
|
||||
private const val SEARCH_LIST_PREFIX = "my:"
|
||||
@ -35,7 +35,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||
private val api by lazy { MyAnimeListApi(id, client, interceptor) }
|
||||
|
||||
override val supportsReadingDates: Boolean = true
|
||||
@ -44,11 +44,11 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(46, 81, 162)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING -> MR.strings.reading
|
||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||
COMPLETED -> MR.strings.completed
|
||||
@ -58,11 +58,11 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = REREADING
|
||||
override fun getRereadingStatus(): Long = REREADING
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = SCORE_LIST
|
||||
|
||||
@ -77,12 +77,12 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
track.finished_reading_date = System.currentTimeMillis()
|
||||
} else if (track.status != REREADING) {
|
||||
track.status = READING
|
||||
if (track.last_chapter_read == 1F) {
|
||||
if (track.last_chapter_read == 1.0) {
|
||||
track.started_reading_date = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
@ -104,14 +104,14 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
val isRereading = track.status == REREADING
|
||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
||||
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||
}
|
||||
|
||||
update(track)
|
||||
} else {
|
||||
// Set default fields if it's not found in the list
|
||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||
track.score = 0F
|
||||
track.score = 0.0
|
||||
add(track)
|
||||
}
|
||||
}
|
||||
@ -155,6 +155,14 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
|
||||
interceptor.setAuth(null)
|
||||
}
|
||||
|
||||
fun getIfAuthExpired(): Boolean {
|
||||
return trackPreferences.trackAuthExpired(this).get()
|
||||
}
|
||||
|
||||
fun setAuthExpired() {
|
||||
trackPreferences.trackAuthExpired(this).set(true)
|
||||
}
|
||||
|
||||
fun saveOAuth(oAuth: OAuth?) {
|
||||
trackPreferences.trackToken(this).set(json.encodeToString(oAuth))
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.float
|
||||
import kotlinx.serialization.json.floatOrNull
|
||||
import kotlinx.serialization.json.double
|
||||
import kotlinx.serialization.json.doubleOrNull
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
@ -47,13 +47,13 @@ class MyAnimeListApi(
|
||||
suspend fun getAccessToken(authCode: String): OAuth {
|
||||
return withIOContext {
|
||||
val formBody: RequestBody = FormBody.Builder()
|
||||
.add("client_id", clientId)
|
||||
.add("client_id", CLIENT_ID)
|
||||
.add("code", authCode)
|
||||
.add("code_verifier", codeVerifier)
|
||||
.add("grant_type", "authorization_code")
|
||||
.build()
|
||||
with(json) {
|
||||
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
|
||||
client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
|
||||
.awaitSuccess()
|
||||
.parseAs()
|
||||
}
|
||||
@ -63,7 +63,7 @@ class MyAnimeListApi(
|
||||
suspend fun getCurrentUser(): String {
|
||||
return withIOContext {
|
||||
val request = Request.Builder()
|
||||
.url("$baseApiUrl/users/@me")
|
||||
.url("$BASE_API_URL/users/@me")
|
||||
.get()
|
||||
.build()
|
||||
with(json) {
|
||||
@ -77,7 +77,7 @@ class MyAnimeListApi(
|
||||
|
||||
suspend fun search(query: String): List<TrackSearch> {
|
||||
return withIOContext {
|
||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
// MAL API throws a 400 when the query is over 64 characters...
|
||||
.appendQueryParameter("q", query.take(64))
|
||||
.appendQueryParameter("nsfw", "true")
|
||||
@ -102,7 +102,7 @@ class MyAnimeListApi(
|
||||
|
||||
suspend fun getMangaDetails(id: Int): TrackSearch {
|
||||
return withIOContext {
|
||||
val url = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val url = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(id.toString())
|
||||
.appendQueryParameter(
|
||||
"fields",
|
||||
@ -119,8 +119,8 @@ class MyAnimeListApi(
|
||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||
title = obj["title"]!!.jsonPrimitive.content
|
||||
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
||||
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
||||
score = obj["mean"]?.jsonPrimitive?.floatOrNull ?: -1f
|
||||
total_chapters = obj["num_chapters"]!!.jsonPrimitive.long
|
||||
score = obj["mean"]?.jsonPrimitive?.doubleOrNull ?: -1.0
|
||||
cover_url =
|
||||
obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content
|
||||
?: ""
|
||||
@ -178,7 +178,7 @@ class MyAnimeListApi(
|
||||
|
||||
suspend fun findListItem(track: Track): Track? {
|
||||
return withIOContext {
|
||||
val uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
val uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(track.remote_id.toString())
|
||||
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
||||
.build()
|
||||
@ -187,7 +187,7 @@ class MyAnimeListApi(
|
||||
.awaitSuccess()
|
||||
.parseAs<JsonObject>()
|
||||
.let { obj ->
|
||||
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
||||
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.long
|
||||
obj.jsonObject["my_list_status"]?.jsonObject?.let {
|
||||
parseMangaItem(it, track)
|
||||
}
|
||||
@ -216,7 +216,7 @@ class MyAnimeListApi(
|
||||
|
||||
// Check next page if there's more
|
||||
if (!obj["paging"]!!.jsonObject["next"]?.jsonPrimitive?.contentOrNull.isNullOrBlank()) {
|
||||
matches + findListItems(query, offset + listPaginationAmount)
|
||||
matches + findListItems(query, offset + LIST_PAGINATION_AMOUNT)
|
||||
} else {
|
||||
matches
|
||||
}
|
||||
@ -225,9 +225,9 @@ class MyAnimeListApi(
|
||||
|
||||
private suspend fun getListPage(offset: Int): JsonObject {
|
||||
return withIOContext {
|
||||
val urlBuilder = "$baseApiUrl/users/@me/mangalist".toUri().buildUpon()
|
||||
val urlBuilder = "$BASE_API_URL/users/@me/mangalist".toUri().buildUpon()
|
||||
.appendQueryParameter("fields", "list_status{start_date,finish_date}")
|
||||
.appendQueryParameter("limit", listPaginationAmount.toString())
|
||||
.appendQueryParameter("limit", LIST_PAGINATION_AMOUNT.toString())
|
||||
if (offset > 0) {
|
||||
urlBuilder.appendQueryParameter("offset", offset.toString())
|
||||
}
|
||||
@ -249,8 +249,8 @@ class MyAnimeListApi(
|
||||
return track.apply {
|
||||
val isRereading = obj["is_rereading"]!!.jsonPrimitive.boolean
|
||||
status = if (isRereading) MyAnimeList.REREADING else getStatus(obj["status"]?.jsonPrimitive?.content)
|
||||
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.float
|
||||
score = obj["score"]!!.jsonPrimitive.int.toFloat()
|
||||
last_chapter_read = obj["num_chapters_read"]!!.jsonPrimitive.double
|
||||
score = obj["score"]!!.jsonPrimitive.int.toDouble()
|
||||
obj["start_date"]?.let {
|
||||
started_reading_date = parseDate(it.jsonPrimitive.content)
|
||||
}
|
||||
@ -277,30 +277,29 @@ class MyAnimeListApi(
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Registered under arkon's MAL account
|
||||
private const val clientId = "f46004a9c16483b6d87b5bf10de56d97"
|
||||
private const val CLIENT_ID = "c46c9e24640a64dad5be5ca7a1a53a0f"
|
||||
|
||||
private const val baseOAuthUrl = "https://myanimelist.net/v1/oauth2"
|
||||
private const val baseApiUrl = "https://api.myanimelist.net/v2"
|
||||
private const val BASE_OAUTH_URL = "https://myanimelist.net/v1/oauth2"
|
||||
private const val BASE_API_URL = "https://api.myanimelist.net/v2"
|
||||
|
||||
private const val listPaginationAmount = 250
|
||||
private const val LIST_PAGINATION_AMOUNT = 250
|
||||
|
||||
private var codeVerifier: String = ""
|
||||
|
||||
fun authUrl(): Uri = "$baseOAuthUrl/authorize".toUri().buildUpon()
|
||||
.appendQueryParameter("client_id", clientId)
|
||||
fun authUrl(): Uri = "$BASE_OAUTH_URL/authorize".toUri().buildUpon()
|
||||
.appendQueryParameter("client_id", CLIENT_ID)
|
||||
.appendQueryParameter("code_challenge", getPkceChallengeCode())
|
||||
.appendQueryParameter("response_type", "code")
|
||||
.build()
|
||||
|
||||
fun mangaUrl(id: Long): Uri = "$baseApiUrl/manga".toUri().buildUpon()
|
||||
fun mangaUrl(id: Long): Uri = "$BASE_API_URL/manga".toUri().buildUpon()
|
||||
.appendPath(id.toString())
|
||||
.appendPath("my_list_status")
|
||||
.build()
|
||||
|
||||
fun refreshTokenRequest(oauth: OAuth): Request {
|
||||
val formBody: RequestBody = FormBody.Builder()
|
||||
.add("client_id", clientId)
|
||||
.add("client_id", CLIENT_ID)
|
||||
.add("refresh_token", oauth.refresh_token)
|
||||
.add("grant_type", "refresh_token")
|
||||
.build()
|
||||
@ -312,7 +311,7 @@ class MyAnimeListApi(
|
||||
.add("Authorization", "Bearer ${oauth.access_token}")
|
||||
.build()
|
||||
|
||||
return POST("$baseOAuthUrl/token", body = formBody, headers = headers)
|
||||
return POST("$BASE_OAUTH_URL/token", body = formBody, headers = headers)
|
||||
}
|
||||
|
||||
private fun getPkceChallengeCode(): String {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
@ -7,33 +8,32 @@ import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor {
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private var oauth: OAuth? = null
|
||||
private var oauth: OAuth? = myanimelist.loadOAuth()
|
||||
private val tokenExpired get() = myanimelist.getIfAuthExpired()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (tokenExpired) {
|
||||
throw MALTokenExpired()
|
||||
}
|
||||
val originalRequest = chain.request()
|
||||
|
||||
if (token.isNullOrEmpty()) {
|
||||
throw IOException("Not authenticated with MyAnimeList")
|
||||
}
|
||||
if (oauth == null) {
|
||||
oauth = myanimelist.loadOAuth()
|
||||
}
|
||||
// Refresh access token if expired
|
||||
if (oauth != null && oauth!!.isExpired()) {
|
||||
setAuth(refreshToken(chain))
|
||||
}
|
||||
|
||||
if (oauth == null) {
|
||||
throw IOException("No authentication token")
|
||||
throw IOException("MAL: User is not authenticated")
|
||||
}
|
||||
|
||||
// Add the authorization header to the original request
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(authRequest)
|
||||
@ -50,6 +50,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
||||
|
||||
val newRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${newToken.access_token}")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(newRequest)
|
||||
@ -63,15 +64,16 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
||||
* and the oauth object.
|
||||
*/
|
||||
fun setAuth(oauth: OAuth?) {
|
||||
token = oauth?.access_token
|
||||
this.oauth = oauth
|
||||
myanimelist.saveOAuth(oauth)
|
||||
}
|
||||
|
||||
private fun refreshToken(chain: Interceptor.Chain): OAuth {
|
||||
val newOauth = runCatching {
|
||||
return runCatching {
|
||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||
|
||||
if (oauthResponse.code == 401) {
|
||||
myanimelist.setAuthExpired()
|
||||
}
|
||||
if (oauthResponse.isSuccessful) {
|
||||
with(json) { oauthResponse.parseAs<OAuth>() }
|
||||
} else {
|
||||
@ -79,11 +81,9 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (newOauth.getOrNull() == null) {
|
||||
throw IOException("Failed to refresh the access token")
|
||||
}
|
||||
|
||||
return newOauth.getOrNull()!!
|
||||
.getOrNull()
|
||||
?: throw MALTokenExpired()
|
||||
}
|
||||
}
|
||||
|
||||
class MALTokenExpired : IOException("MAL: Login has expired")
|
||||
|
@ -18,12 +18,12 @@ import tachiyomi.domain.track.model.Track as DomainTrack
|
||||
class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
|
||||
companion object {
|
||||
const val READING = 1
|
||||
const val COMPLETED = 2
|
||||
const val ON_HOLD = 3
|
||||
const val DROPPED = 4
|
||||
const val PLAN_TO_READ = 5
|
||||
const val REREADING = 6
|
||||
const val READING = 1L
|
||||
const val COMPLETED = 2L
|
||||
const val ON_HOLD = 3L
|
||||
const val DROPPED = 4L
|
||||
const val PLAN_TO_READ = 5L
|
||||
const val REREADING = 6L
|
||||
|
||||
private val SCORE_LIST = IntRange(0, 10)
|
||||
.map(Int::toString)
|
||||
@ -49,7 +49,7 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
} else if (track.status != REREADING) {
|
||||
track.status = READING
|
||||
@ -72,14 +72,14 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
|
||||
if (track.status != COMPLETED) {
|
||||
val isRereading = track.status == REREADING
|
||||
track.status = if (isRereading.not() && hasReadChapters) READING else track.status
|
||||
track.status = if (!isRereading && hasReadChapters) READING else track.status
|
||||
}
|
||||
|
||||
update(track)
|
||||
} else {
|
||||
// Set default fields if it's not found in the list
|
||||
track.status = if (hasReadChapters) READING else PLAN_TO_READ
|
||||
track.score = 0F
|
||||
track.score = 0.0
|
||||
add(track)
|
||||
}
|
||||
}
|
||||
@ -101,11 +101,11 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
|
||||
override fun getLogoColor() = Color.rgb(40, 40, 40)
|
||||
|
||||
override fun getStatusList(): List<Int> {
|
||||
override fun getStatusList(): List<Long> {
|
||||
return listOf(READING, COMPLETED, ON_HOLD, DROPPED, PLAN_TO_READ, REREADING)
|
||||
}
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
READING -> MR.strings.reading
|
||||
PLAN_TO_READ -> MR.strings.plan_to_read
|
||||
COMPLETED -> MR.strings.completed
|
||||
@ -115,11 +115,11 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker {
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = REREADING
|
||||
override fun getRereadingStatus(): Long = REREADING
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override suspend fun login(username: String, password: String) = login(password)
|
||||
|
||||
|
@ -15,7 +15,7 @@ import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.float
|
||||
import kotlinx.serialization.json.double
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
@ -102,10 +102,10 @@ class ShikimoriApi(
|
||||
return TrackSearch.create(trackId).apply {
|
||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||
title = obj["name"]!!.jsonPrimitive.content
|
||||
total_chapters = obj["chapters"]!!.jsonPrimitive.int
|
||||
total_chapters = obj["chapters"]!!.jsonPrimitive.long
|
||||
cover_url = baseUrl + obj["image"]!!.jsonObject["preview"]!!.jsonPrimitive.content
|
||||
summary = ""
|
||||
score = obj["score"]!!.jsonPrimitive.float
|
||||
score = obj["score"]!!.jsonPrimitive.double
|
||||
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
||||
publishing_status = obj["status"]!!.jsonPrimitive.content
|
||||
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
||||
@ -117,10 +117,10 @@ class ShikimoriApi(
|
||||
return Track.create(trackId).apply {
|
||||
title = mangas["name"]!!.jsonPrimitive.content
|
||||
remote_id = obj["id"]!!.jsonPrimitive.long
|
||||
total_chapters = mangas["chapters"]!!.jsonPrimitive.int
|
||||
total_chapters = mangas["chapters"]!!.jsonPrimitive.long
|
||||
library_id = obj["id"]!!.jsonPrimitive.long
|
||||
last_chapter_read = obj["chapters"]!!.jsonPrimitive.float
|
||||
score = (obj["score"]!!.jsonPrimitive.int).toFloat()
|
||||
last_chapter_read = obj["chapters"]!!.jsonPrimitive.double
|
||||
score = obj["score"]!!.jsonPrimitive.int.toDouble()
|
||||
status = toTrackStatus(obj["status"]!!.jsonPrimitive.content)
|
||||
tracking_url = baseUrl + mangas["url"]!!.jsonPrimitive.content
|
||||
}
|
||||
@ -192,8 +192,8 @@ class ShikimoriApi(
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val clientId = "1aaf4cf232372708e98b5abc813d795b539c5a916dbbfe9ac61bf02a360832cc"
|
||||
private const val clientSecret = "229942c742dd4cde803125d17d64501d91c0b12e14cb1e5120184d77d67024c0"
|
||||
private const val clientId = "PB9dq8DzI405s7wdtwTdirYqHiyVMh--djnP7lBUqSA"
|
||||
private const val clientSecret = "NajpZcOBKB9sJtgNcejf8OB9jBN1OYYoo-k4h2WWZus"
|
||||
|
||||
private const val baseUrl = "https://shikimori.one"
|
||||
private const val apiUrl = "$baseUrl/api"
|
||||
|
@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.data.track.shikimori
|
||||
|
||||
import eu.kanade.tachiyomi.BuildConfig
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
@ -33,7 +34,7 @@ class ShikimoriInterceptor(private val shikimori: Shikimori) : Interceptor {
|
||||
// Add the authorization header to the original request.
|
||||
val authRequest = originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Tachiyomi")
|
||||
.header("User-Agent", "Mihon v${BuildConfig.VERSION_NAME} (${BuildConfig.APPLICATION_ID})")
|
||||
.build()
|
||||
|
||||
return chain.proceed(authRequest)
|
||||
|
@ -23,25 +23,25 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
|
||||
override fun getLogoColor() = Color.rgb(255, 35, 35) // TODO
|
||||
|
||||
companion object {
|
||||
const val UNREAD = 1
|
||||
const val READING = 2
|
||||
const val COMPLETED = 3
|
||||
const val UNREAD = 1L
|
||||
const val READING = 2L
|
||||
const val COMPLETED = 3L
|
||||
}
|
||||
|
||||
override fun getStatusList() = listOf(UNREAD, READING, COMPLETED)
|
||||
override fun getStatusList(): List<Long> = listOf(UNREAD, READING, COMPLETED)
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
UNREAD -> MR.strings.unread
|
||||
READING -> MR.strings.reading
|
||||
COMPLETED -> MR.strings.completed
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = READING
|
||||
override fun getReadingStatus(): Long = READING
|
||||
|
||||
override fun getRereadingStatus(): Int = -1
|
||||
override fun getRereadingStatus(): Long = -1
|
||||
|
||||
override fun getCompletionStatus(): Int = COMPLETED
|
||||
override fun getCompletionStatus(): Long = COMPLETED
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = persistentListOf()
|
||||
|
||||
@ -50,7 +50,7 @@ class Suwayomi(id: Long) : BaseTracker(id, "Suwayomi"), EnhancedTracker {
|
||||
override suspend fun update(track: Track, didReadChapter: Boolean): Track {
|
||||
if (track.status != COMPLETED) {
|
||||
if (didReadChapter) {
|
||||
if (track.last_chapter_read.toInt() == track.total_chapters && track.total_chapters > 0) {
|
||||
if (track.last_chapter_read.toLong() == track.total_chapters && track.total_chapters > 0) {
|
||||
track.status = COMPLETED
|
||||
} else {
|
||||
track.status = READING
|
||||
|
@ -66,9 +66,9 @@ class SuwayomiApi(private val trackId: Long) {
|
||||
cover_url = "$url/thumbnail"
|
||||
summary = manga.description.orEmpty()
|
||||
tracking_url = url
|
||||
total_chapters = manga.chapterCount.toInt()
|
||||
total_chapters = manga.chapterCount
|
||||
publishing_status = manga.status
|
||||
last_chapter_read = manga.lastChapterRead?.chapterNumber ?: 0F
|
||||
last_chapter_read = manga.lastChapterRead?.chapterNumber ?: 0.0
|
||||
status = when (manga.unreadCount) {
|
||||
manga.chapterCount -> Suwayomi.UNREAD
|
||||
0L -> Suwayomi.COMPLETED
|
||||
|
@ -64,7 +64,7 @@ data class ChapterDataClass(
|
||||
val url: String,
|
||||
val name: String,
|
||||
val uploadDate: Long,
|
||||
val chapterNumber: Float,
|
||||
val chapterNumber: Double,
|
||||
val scanlator: String?,
|
||||
val mangaId: Int,
|
||||
|
||||
|
@ -338,6 +338,10 @@ class ExtensionManager(
|
||||
}
|
||||
|
||||
override fun onExtensionUntrusted(extension: Extension.Untrusted) {
|
||||
val installedExtension = _installedExtensionsFlow.value
|
||||
.find { it.pkgName == extension.pkgName }
|
||||
?: return
|
||||
_installedExtensionsFlow.value -= installedExtension
|
||||
_untrustedExtensionsFlow.value += extension
|
||||
}
|
||||
|
||||
|
@ -70,8 +70,7 @@ internal class ExtensionInstallReceiver(private val listener: Listener) :
|
||||
launchNow {
|
||||
when (val result = getExtensionFromIntent(context, intent)) {
|
||||
is LoadResult.Success -> listener.onExtensionUpdated(result.extension)
|
||||
// Not needed as a package can't be upgraded if the signature is different
|
||||
// is LoadResult.Untrusted -> {}
|
||||
is LoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
@ -285,13 +285,13 @@ private data class TrackStatusSelectorScreen(
|
||||
private class Model(
|
||||
private val track: Track,
|
||||
private val tracker: Tracker,
|
||||
) : StateScreenModel<Model.State>(State(track.status.toInt())) {
|
||||
) : StateScreenModel<Model.State>(State(track.status)) {
|
||||
|
||||
fun getSelections(): Map<Int, StringResource?> {
|
||||
fun getSelections(): Map<Long, StringResource?> {
|
||||
return tracker.getStatusList().associateWith { tracker.getStatus(it) }
|
||||
}
|
||||
|
||||
fun setSelection(selection: Int) {
|
||||
fun setSelection(selection: Long) {
|
||||
mutableState.update { it.copy(selection = selection) }
|
||||
}
|
||||
|
||||
@ -303,7 +303,7 @@ private data class TrackStatusSelectorScreen(
|
||||
|
||||
@Immutable
|
||||
data class State(
|
||||
val selection: Int,
|
||||
val selection: Long,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : Viewer {
|
||||
pager.addOnPageChangeListener(
|
||||
object : ViewPager.SimpleOnPageChangeListener() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
if (activity.isScrollingThroughPages.not()) {
|
||||
if (!activity.isScrollingThroughPages) {
|
||||
activity.hideMenu()
|
||||
}
|
||||
onPageChange(position)
|
||||
|
@ -21,7 +21,7 @@ class CrashLogUtil(
|
||||
|
||||
suspend fun dumpLogs() = withNonCancellableContext {
|
||||
try {
|
||||
val file = context.createFileInCacheDir("tachiyomi_crash_logs.txt")
|
||||
val file = context.createFileInCacheDir("mihon_crash_logs.txt")
|
||||
|
||||
file.appendText(getDebugInfo() + "\n\n")
|
||||
getExtensionsInfo()?.let { file.appendText("$it\n\n") }
|
||||
|
@ -18,10 +18,10 @@ data class DummyTracker(
|
||||
override val isLoggedIn: Boolean = false,
|
||||
val valLogoColor: Int = Color.rgb(18, 25, 35),
|
||||
val valLogo: Int = R.drawable.ic_tracker_anilist,
|
||||
val valStatuses: List<Int> = (1..6).toList(),
|
||||
val valReadingStatus: Int = 1,
|
||||
val valRereadingStatus: Int = 1,
|
||||
val valCompletionStatus: Int = 2,
|
||||
val valStatuses: List<Long> = (1L..6L).toList(),
|
||||
val valReadingStatus: Long = 1L,
|
||||
val valRereadingStatus: Long = 1L,
|
||||
val valCompletionStatus: Long = 2L,
|
||||
val valScoreList: ImmutableList<String> = (0..10).map(Int::toString).toImmutableList(),
|
||||
val val10PointScore: Double = 5.4,
|
||||
val valSearchResults: List<TrackSearch> = listOf(),
|
||||
@ -34,29 +34,29 @@ data class DummyTracker(
|
||||
|
||||
override fun getLogo(): Int = valLogo
|
||||
|
||||
override fun getStatusList(): List<Int> = valStatuses
|
||||
override fun getStatusList(): List<Long> = valStatuses
|
||||
|
||||
override fun getStatus(status: Int): StringResource? = when (status) {
|
||||
1 -> MR.strings.reading
|
||||
2 -> MR.strings.plan_to_read
|
||||
3 -> MR.strings.completed
|
||||
4 -> MR.strings.on_hold
|
||||
5 -> MR.strings.dropped
|
||||
6 -> MR.strings.repeating
|
||||
override fun getStatus(status: Long): StringResource? = when (status) {
|
||||
1L -> MR.strings.reading
|
||||
2L -> MR.strings.plan_to_read
|
||||
3L -> MR.strings.completed
|
||||
4L -> MR.strings.on_hold
|
||||
5L -> MR.strings.dropped
|
||||
6L -> MR.strings.repeating
|
||||
else -> null
|
||||
}
|
||||
|
||||
override fun getReadingStatus(): Int = valReadingStatus
|
||||
override fun getReadingStatus(): Long = valReadingStatus
|
||||
|
||||
override fun getRereadingStatus(): Int = valRereadingStatus
|
||||
override fun getRereadingStatus(): Long = valRereadingStatus
|
||||
|
||||
override fun getCompletionStatus(): Int = valCompletionStatus
|
||||
override fun getCompletionStatus(): Long = valCompletionStatus
|
||||
|
||||
override fun getScoreList(): ImmutableList<String> = valScoreList
|
||||
|
||||
override fun get10PointScore(track: Track): Double = val10PointScore
|
||||
|
||||
override fun indexToScore(index: Int): Float = getScoreList()[index].toFloat()
|
||||
override fun indexToScore(index: Int): Double = getScoreList()[index].toDouble()
|
||||
|
||||
override fun displayScore(track: Track): String =
|
||||
track.score.toString()
|
||||
@ -94,7 +94,7 @@ data class DummyTracker(
|
||||
|
||||
override suspend fun setRemoteStatus(
|
||||
track: eu.kanade.tachiyomi.data.database.models.Track,
|
||||
status: Int,
|
||||
status: Long,
|
||||
) = Unit
|
||||
|
||||
override suspend fun setRemoteLastChapterRead(
|
||||
|
@ -3,22 +3,17 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<group android:scaleX="0.67"
|
||||
android:scaleY="0.67"
|
||||
android:translateX="71.28"
|
||||
android:translateY="71.28">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h432v432h-432z"/>
|
||||
<path
|
||||
android:pathData="M0,0h432v432h-432z"
|
||||
android:fillColor="#FAFAFA"/>
|
||||
<path
|
||||
android:pathData="M377,216C377,302.16 304.92,372 216,372C127.08,372 55,302.16 55,216C55,129.84 127.08,60 216,60C304.92,60 377,129.84 377,216Z"
|
||||
android:pathData="M322.13,215.5C322.13,272.66 274.64,319 216.07,319C157.49,319 110,272.66 110,215.5C110,158.34 157.49,112 216.07,112C274.64,112 322.13,158.34 322.13,215.5Z"
|
||||
android:fillColor="#F2FAFF"/>
|
||||
<path
|
||||
android:pathData="M216,342.75C288.25,342.75 346.81,286 346.81,216C346.81,146 288.25,89.25 216,89.25C143.75,89.25 85.19,146 85.19,216C85.19,286 143.75,342.75 216,342.75ZM216,372C304.92,372 377,302.16 377,216C377,129.84 304.92,60 216,60C127.08,60 55,129.84 55,216C55,302.16 127.08,372 216,372Z"
|
||||
android:fillColor="#0058A0"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M216,342.75C288.25,342.75 346.81,286 346.81,216C346.81,146 288.25,89.25 216,89.25C143.75,89.25 85.19,146 85.19,216C85.19,286 143.75,342.75 216,342.75ZM216,372C304.92,372 377,302.16 377,216C377,129.84 304.92,60 216,60C127.08,60 55,129.84 55,216C55,302.16 127.08,372 216,372Z"
|
||||
android:pathData="M216.07,299.59C263.66,299.59 302.24,261.94 302.24,215.5C302.24,169.06 263.66,131.41 216.07,131.41C168.47,131.41 129.89,169.06 129.89,215.5C129.89,261.94 168.47,299.59 216.07,299.59ZM216.07,319C274.64,319 322.13,272.66 322.13,215.5C322.13,158.34 274.64,112 216.07,112C157.49,112 110,158.34 110,215.5C110,272.66 157.49,319 216.07,319Z"
|
||||
android:fillColor="#0058A0"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
|
@ -3,12 +3,7 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="432"
|
||||
android:viewportHeight="432">
|
||||
<group android:scaleX="0.67"
|
||||
android:scaleY="0.67"
|
||||
android:translateX="71.28"
|
||||
android:translateY="71.28">
|
||||
<path
|
||||
android:pathData="M162.3,173.59L161.22,148.63C164.47,149.25 168.35,149.41 177.18,149.41C187.87,149.41 201.98,148.79 209.26,147.86C212.36,147.55 213.6,147.09 215.61,146L232.35,160.26C230.8,162.43 230.34,163.36 228.63,167.7C227.24,171.11 220.88,190.79 218.4,199.16C229.87,201.48 236.22,203.18 244.9,206.75C245.99,199.16 246.14,195.13 246.14,181.33C246.14,177.77 245.99,175.76 245.52,172.5L272.49,173.43C271.71,177.15 271.56,178.7 271.4,184.74C270.78,199.31 270.16,206.29 268.61,216.82C279.31,222.25 279.31,222.25 284.73,225.19C287.52,226.74 288.14,227.05 290,227.67L281.01,256.65C276.67,252.78 270.63,248.59 261.8,243.63C254.05,262.08 241.18,275.56 221.66,286.25C215.15,277.57 210.19,272.3 202.29,266.11C213.75,260.68 219.02,257.27 225.07,251.54C230.96,245.8 234.83,240.22 238.55,231.85C228.63,227.36 222.28,225.35 211.27,223.02C204.92,241.93 199.8,254.02 195.31,261.3C189.27,271.06 181.05,276.18 171.6,276.18C164.32,276.18 156.88,272.92 151.45,267.35C145.25,260.99 142,252.16 142,241.93C142,226.74 149.28,213.57 161.99,205.35C170.21,200.09 178.88,197.76 192.68,196.99C195.47,187.84 197.79,179.94 199.96,171.11C193.14,171.73 184.62,172.19 174.24,172.65C168.66,172.81 166.8,172.96 162.3,173.59ZM185.86,220.7C178.57,221.94 174.24,224.26 170.36,229.22C167.42,232.63 166.02,236.66 166.02,241C166.02,245.8 168.35,249.37 171.29,249.37C174.85,249.37 178.88,241.31 185.86,220.7Z"
|
||||
android:pathData="M182.03,188.7L181.33,172.69C183.42,173.09 185.91,173.19 191.57,173.19C198.44,173.19 207.49,172.79 212.16,172.19C214.15,171.99 214.95,171.7 216.24,171L226.98,180.15C225.98,181.54 225.68,182.14 224.59,184.92C223.7,187.11 219.62,199.74 218.03,205.11C225.39,206.6 229.46,207.7 235.03,209.98C235.73,205.11 235.83,202.52 235.83,193.67C235.83,191.39 235.73,190.09 235.43,188.01L252.74,188.6C252.24,190.99 252.14,191.98 252.04,195.86C251.64,205.21 251.24,209.68 250.25,216.45C257.11,219.93 257.11,219.93 260.59,221.82C262.38,222.81 262.78,223.01 263.97,223.41L258.2,242.01C255.42,239.52 251.54,236.83 245.87,233.65C240.9,245.49 232.65,254.14 220.12,261C215.94,255.43 212.76,252.05 207.68,248.07C215.04,244.59 218.43,242.4 222.3,238.72C226.08,235.04 228.57,231.46 230.96,226.09C224.59,223.21 220.51,221.92 213.45,220.43C209.38,232.56 206.09,240.32 203.21,244.99C199.33,251.25 194.06,254.54 187.99,254.54C183.32,254.54 178.55,252.45 175.07,248.87C171.09,244.79 169,239.12 169,232.56C169,222.81 173.67,214.36 181.83,209.09C187.1,205.71 192.67,204.21 201.52,203.72C203.31,197.85 204.8,192.78 206.19,187.11C201.82,187.51 196.35,187.81 189.68,188.1C186.1,188.2 184.91,188.3 182.03,188.7ZM197.14,218.93C192.47,219.73 189.68,221.22 187.2,224.4C185.31,226.59 184.41,229.18 184.41,231.96C184.41,235.04 185.91,237.33 187.8,237.33C190.08,237.33 192.67,232.16 197.14,218.93Z"
|
||||
android:fillColor="#031019"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
@ -14,7 +14,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":source-api"))
|
||||
implementation(projects.sourceApi)
|
||||
|
||||
implementation(kotlinx.bundles.serialization)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":i18n"))
|
||||
implementation(projects.i18n)
|
||||
|
||||
api(libs.logcat)
|
||||
|
||||
|
@ -89,7 +89,7 @@ internal class RateLimitInterceptor(
|
||||
while (requestQueue.size >= permits) { // queue is full, remove expired entries
|
||||
val periodStart = SystemClock.elapsedRealtime() - rateLimitMillis
|
||||
var hasRemovedExpired = false
|
||||
while (requestQueue.isEmpty().not() && requestQueue.first <= periodStart) {
|
||||
while (!requestQueue.isEmpty() && requestQueue.first <= periodStart) {
|
||||
requestQueue.removeFirst()
|
||||
hasRemovedExpired = true
|
||||
}
|
||||
|
@ -24,9 +24,9 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":domain"))
|
||||
implementation(project(":core"))
|
||||
implementation(projects.sourceApi)
|
||||
implementation(projects.domain)
|
||||
implementation(projects.core)
|
||||
|
||||
api(libs.bundles.sqldelight)
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ WHERE _id IN :chapterIds;
|
||||
|
||||
insert:
|
||||
INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at)
|
||||
VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, strftime('%s', 'now'));
|
||||
VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0);
|
||||
|
||||
update:
|
||||
UPDATE chapters
|
||||
|
@ -117,7 +117,7 @@ AND source IN :sourceIds;
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, calculate_interval, last_modified_at)
|
||||
VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, strftime('%s', 'now'));
|
||||
VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0);
|
||||
|
||||
update:
|
||||
UPDATE mangas SET
|
||||
|
@ -20,7 +20,7 @@ END;
|
||||
|
||||
insert:
|
||||
INSERT INTO mangas_categories(manga_id, category_id, last_modified_at)
|
||||
VALUES (:mangaId, :categoryId, strftime('%s', 'now'));
|
||||
VALUES (:mangaId, :categoryId, 0);
|
||||
|
||||
deleteMangaCategoryByMangaId:
|
||||
DELETE FROM mangas_categories
|
||||
|
4
data/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
4
data/src/main/sqldelight/tachiyomi/migrations/1.sqm
Normal file
@ -0,0 +1,4 @@
|
||||
-- MangaUpdates score fixing --
|
||||
UPDATE manga_sync
|
||||
SET score = max(score, 0)
|
||||
WHERE sync_id = 7;
|
@ -14,8 +14,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":source-api"))
|
||||
implementation(project(":core"))
|
||||
implementation(projects.sourceApi)
|
||||
implementation(projects.core)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
|
@ -20,7 +20,7 @@ class GetApplicationRelease(
|
||||
val now = Instant.now()
|
||||
|
||||
// Limit checks to once every 3 days at most
|
||||
if (arguments.forceCheck.not() && now.isBefore(
|
||||
if (!arguments.forceCheck && now.isBefore(
|
||||
Instant.ofEpochMilli(lastChecked.get()).plus(3, ChronoUnit.DAYS),
|
||||
)
|
||||
) {
|
||||
|
@ -22,7 +22,7 @@ class StubSource(
|
||||
throw SourceNotInstalledException()
|
||||
|
||||
override fun toString(): String =
|
||||
if (isInvalid.not()) "$name (${lang.uppercase()})" else id.toString()
|
||||
if (!isInvalid) "$name (${lang.uppercase()})" else id.toString()
|
||||
|
||||
companion object {
|
||||
fun from(source: Source): StubSource {
|
||||
|
@ -128,4 +128,12 @@
|
||||
<item quantity="many">%d يومًا</item>
|
||||
<item quantity="other">%d يوم</item>
|
||||
</plurals>
|
||||
<plurals name="num_repos">
|
||||
<item quantity="zero">%d</item>
|
||||
<item quantity="one">مستودع</item>
|
||||
<item quantity="two">مستودعان</item>
|
||||
<item quantity="few">%d مستودعات</item>
|
||||
<item quantity="many">%d مستودعات</item>
|
||||
<item quantity="other"></item>
|
||||
</plurals>
|
||||
</resources>
|
@ -232,7 +232,7 @@
|
||||
<string name="label_extensions">الإضافات</string>
|
||||
<string name="label_extension_info">معلومات الإضافة</string>
|
||||
<string name="action_display_download_badge">الفصول المحملة</string>
|
||||
<string name="ext_update">تحديث</string>
|
||||
<string name="ext_update">حدث</string>
|
||||
<string name="ext_install">تثبيت</string>
|
||||
<string name="ext_pending">المُعلقة</string>
|
||||
<string name="ext_downloading">جارى التنزيل</string>
|
||||
@ -242,11 +242,9 @@
|
||||
<string name="ext_untrusted">غير موثوق فيه</string>
|
||||
<string name="ext_uninstall">إلغاء التثبيت</string>
|
||||
<string name="untrusted_extension">إضافة ذات ريبة</string>
|
||||
<string name="untrusted_extension_message">هذه اﻹضافة موقَّعة بشهادة ذات ريبة ولم تفعَّل.
|
||||
<string name="untrusted_extension_message">يمكن للملحقات الخبيثة قراءة أي بيانات اعتماد مخزنة لتسجيل الدخول أو تنفيذ تعليمات برمجية عشوائية.
|
||||
\n
|
||||
\nيمكن لأي إضافة خبيثة قراءة بيانات اعتماد تسجيل الدخول المخزَّنة أو تنفيذ تعليمات برمجية عشوائية.
|
||||
\n
|
||||
\nأنت تقبل هذه المخاطر إن وثقت بالشهادة.</string>
|
||||
\nمن خلال الوثوق بهذا الامتداد، فإنك تقبل هذه المخاطر.</string>
|
||||
<string name="pref_double_tap_anim_speed">سرعة مؤثر النقر المزدوج</string>
|
||||
<string name="pager_viewer">عارض الصفحات</string>
|
||||
<string name="double_tap_anim_speed_0">لا مؤثرات</string>
|
||||
@ -372,7 +370,7 @@
|
||||
<string name="label_data">البيانات</string>
|
||||
<string name="backup_restore_missing_sources">المصادر المفقودة:</string>
|
||||
<string name="invalid_backup_file_missing_manga">النسخة الإحتياطية لا تحتوي على أيّة إدخالات المكتبة.</string>
|
||||
<string name="invalid_backup_file">ملفُّ النسخ الاحتياطيِّ غير صالح</string>
|
||||
<string name="invalid_backup_file">ملف النسخ الاحتياطي غير صالح:</string>
|
||||
<string name="tracking_info">مزامنة أحادية تُحدِّث قراءة الفصول في المتتبعات الخارجية، ولك تعيين التتبِّع لكلِّ مدخلة على حدى، وذلك من زرِّ التتبع فيهم.</string>
|
||||
<string name="pref_library_update_refresh_metadata_summary">تحقق من وجود غلاف جديد وتفاصيل جديدة عند تحديث المكتبة</string>
|
||||
<string name="pref_library_update_refresh_metadata">تحديث البيانات الوصفية تلقائياً</string>
|
||||
@ -456,9 +454,7 @@
|
||||
<string name="action_order_by_upload_date">حسب تاريخ الرفع</string>
|
||||
<string name="action_order_by_chapter_number">حسب رقم الفصل</string>
|
||||
<string name="pref_dns_over_https">إستخدام DNS عبر HTTPS (DoH)</string>
|
||||
<string name="backup_restore_content_full">تم استيراد البيانات من ملف نسخ الاحتياطي.
|
||||
\n
|
||||
\nيتوجب تثبيت الإضافات المفقودة و تسجيل الدخول إلى منصات التعقب تالياً لاستعمالهم.</string>
|
||||
<string name="backup_restore_content_full">قد تحتاج إلى تثبيت أي إضافات مفقودة وتسجيل الدخول إلى خدمات التتبع بعد ذلك لاستخدامها.</string>
|
||||
<string name="nav_zone_right">يمين</string>
|
||||
<string name="nav_zone_left">يسار</string>
|
||||
<string name="nav_zone_next">التالي</string>
|
||||
@ -562,7 +558,7 @@
|
||||
<string name="clear_database_source_item_count">%1$d مدخلةً في قاعدة البيانات وليست في المكتبة</string>
|
||||
<string name="extension_api_error">فشل الحصول على قائمة الملحقات</string>
|
||||
<string name="ext_installer_shizuku_unavailable_dialog">ثبِّت «شيزوكو» وشغِّله لتستخدمه مثبِّت إضافات.</string>
|
||||
<string name="download_queue_size_warning">تحذير: يمكن أن تؤدِّي التنزيلات كبيرة الحجم والعدد إلى إبطاء المصادر، وقد يُحظر Tachiyomi منها بسبب ذلك. اضغط لمعرفة المزيد۔</string>
|
||||
<string name="download_queue_size_warning">تحذير: يمكن أن تؤدِّي التنزيلات كبيرة الحجم والعدد إلى إبطاء المصادر، وقد يُحظر Mihon منها بسبب ذلك. اضغط لمعرفة المزيد۔</string>
|
||||
<string name="action_show_manga">إظهار الدخول</string>
|
||||
<string name="action_display_cover_only_grid">شبكة بالاغلفة</string>
|
||||
<string name="skipped_reason_not_started">تُخُطِّيت بسبب عدم وجود فصول قُرئت</string>
|
||||
@ -773,4 +769,33 @@
|
||||
\nوالأحسن أن يكون المجلَّد مخصوصًا لذلك.
|
||||
\n
|
||||
\nالمجلَّد المحدَّد: %2$s</string>
|
||||
<string name="onboarding_permission_install_apps">إذن تثبيت التطبيقات</string>
|
||||
<string name="onboarding_permission_install_apps_description">لتثبيت ملحقات المصدر.</string>
|
||||
<string name="onboarding_permission_notifications">إذن الإشعار</string>
|
||||
<string name="onboarding_permission_notifications_description">احصل على تنبيهات لتحديثات المكتبة والمزيد.</string>
|
||||
<string name="onboarding_permission_ignore_battery_opts">استخدام البطارية في الخلفية</string>
|
||||
<string name="onboarding_permission_action_grant">منح</string>
|
||||
<string name="ext_permission_install_apps_warning">الأذونات مطلوبة لتثبيت الإضافات. انقر هنا لمنحها.</string>
|
||||
<string name="ext_revoke_trust">إبطال الإضافات غير المعروفة الموثوق بها</string>
|
||||
<string name="label_extension_repos">مستودع الإضافات</string>
|
||||
<string name="label_add_repo_input">رابط المستودع</string>
|
||||
<string name="action_add_repo_message">إضافة مستودعات إضافية إلى ميهون. يجب أن يكون هذا الرابط ينتهي بـ \"index.min.json\".</string>
|
||||
<string name="error_repo_exists">هذا المستودع موجود بالفعل!</string>
|
||||
<string name="action_delete_repo">حذف المستودع</string>
|
||||
<string name="invalid_repo_name">رابط المستودع غير صالح</string>
|
||||
<string name="delete_repo_confirmation">هل ترغب في حذف \"1%s\" من المستودع؟</string>
|
||||
<string name="action_open_repo">مستودع مفتوح المصدر</string>
|
||||
<string name="invalid_backup_file_error">الخطأ الكامل:</string>
|
||||
<string name="private_settings">تضمين إعدادات حساسة (على سبيل المثال، رموز تسجيل دخول المتتبع)</string>
|
||||
<string name="manga_interval_expected_update_soon">قريباً</string>
|
||||
<string name="manga_interval_custom_amount">تكرار تحديث مخصص:</string>
|
||||
<string name="onboarding_storage_help_info">هل تقوم بالتحديث من إصدار أقدم ولست متأكداً مما تختاره؟ ارجع إلى دليل التخزين لمزيد من المعلومات.</string>
|
||||
<string name="onboarding_storage_help_action">دليل التخزين</string>
|
||||
<string name="onboarding_permission_ignore_battery_opts_description">تجنّب الانقطاعات في تحديثات المكتبة الطويلة الأمد والتنزيلات واستعادة النسخ الاحتياطية.</string>
|
||||
<string name="pref_library_update_smart_update">تحديث ذكي</string>
|
||||
<string name="theme_nord">Nord</string>
|
||||
<string name="information_empty_repos">لم يتم تعيين أي مستودع .</string>
|
||||
<string name="action_add_repo">إضافة مستودع</string>
|
||||
<string name="manga_interval_expected_update">من المتوقع أن يتم إصدار فصول جديدة في حوالي 1%1$s، والتحقق من كل 2%2$s .</string>
|
||||
<string name="available_disk_space_info">متاح: %1$s / الكل: %2$s</string>
|
||||
</resources>
|
@ -187,7 +187,7 @@
|
||||
<string name="obsolete_extension_message">Гэта пашырэнне больш недаступна.</string>
|
||||
<string name="untrusted_extension_message">Гэта пашырэнне было падпісана ненадзейным сертыфікатам і не было актывавана.
|
||||
\n
|
||||
\nШкоднаснае пашырэнне можа счытваць любыя ўліковыя дадзеныя для ўваходу, якія захоўваюцца ў Tachiyomi, або выконваць адвольны код.
|
||||
\nШкоднаснае пашырэнне можа счытваць любыя ўліковыя дадзеныя для ўваходу, якія захоўваюцца ў Mihon, або выконваць адвольны код.
|
||||
\n
|
||||
\nДавяраючы гэтаму сертыфікату, вы прымаеце на сябе гэтыя рызыкі.</string>
|
||||
<string name="untrusted_extension">Ненадзейнае пашырэнне</string>
|
||||
|
@ -250,7 +250,7 @@
|
||||
<string name="ext_app_info">Impormasyon sa app</string>
|
||||
<string name="untrusted_extension_message">Kini nga extension gipirmahan gamit ang dili kasaligan nga sertipiko ug wala gi-aktibo.
|
||||
\n
|
||||
\nAng usa ka malisyoso nga extension mahimong makabasa sa bisan unsang mga kredensyal sa pag-login nga gitipigan sa Tachiyomi o ipatuman ang arbitraryong code.
|
||||
\nAng usa ka malisyoso nga extension mahimong makabasa sa bisan unsang mga kredensyal sa pag-login nga gitipigan sa Mihon o ipatuman ang arbitraryong code.
|
||||
\n
|
||||
\nPinaagi sa pagsalig niini nga sertipiko gidawat nimo kini nga mga risgo.</string>
|
||||
<string name="pref_cutout_short">Ipakita ang sulod sa ginunting nga lugar</string>
|
||||
@ -443,6 +443,6 @@
|
||||
<string name="label_local">Lokal</string>
|
||||
<string name="pref_appearance_summary">Tema, format sa petsa ug panahon</string>
|
||||
<string name="action_not_now">Dili karon</string>
|
||||
<string name="information_webview_required">Gikinahanlan ang WebView alang sa Tachiyomi</string>
|
||||
<string name="information_webview_required">Gikinahanlan ang WebView alang sa Mihon</string>
|
||||
<string name="information_required_plain">*gikinahanlan</string>
|
||||
</resources>
|
@ -29,12 +29,12 @@
|
||||
<item quantity="other">Ҫӗнӗ сыпӑксем %d хайлав валли тупӑннӑ</item>
|
||||
</plurals>
|
||||
<plurals name="manga_num_chapters">
|
||||
<item quantity="one">1 сыпӑк</item>
|
||||
<item quantity="one">%1$s сыпӑк</item>
|
||||
<item quantity="other">%1$s сыпӑк</item>
|
||||
</plurals>
|
||||
<plurals name="download_queue_summary">
|
||||
<item quantity="one">1 юлчӗ</item>
|
||||
<item quantity="other">%1$s юлчӗ</item>
|
||||
<item quantity="one">%1$s йулчӗ</item>
|
||||
<item quantity="other">%1$s йулчӗ</item>
|
||||
</plurals>
|
||||
<plurals name="num_trackers">
|
||||
<item quantity="one">1 сӑнану</item>
|
||||
@ -48,4 +48,20 @@
|
||||
<item quantity="one">Ӗнер</item>
|
||||
<item quantity="other">%1$d кун кайалла</item>
|
||||
</plurals>
|
||||
<plurals name="next_unread_chapters">
|
||||
<item quantity="one">Тепӗр вуламан сыпӑк</item>
|
||||
<item quantity="other">Тепӗр %d вуламан сыпӑк</item>
|
||||
</plurals>
|
||||
<plurals name="day">
|
||||
<item quantity="one">1 кун</item>
|
||||
<item quantity="other">%d кун</item>
|
||||
</plurals>
|
||||
<plurals name="missing_chapters">
|
||||
<item quantity="one">%1$s сыпӑк ҫук</item>
|
||||
<item quantity="other">%1$s сыпӑк ҫук</item>
|
||||
</plurals>
|
||||
<plurals name="download_amount">
|
||||
<item quantity="one">Тепӗр сыпӑк</item>
|
||||
<item quantity="other">Тепӗр %d сыпӑк</item>
|
||||
</plurals>
|
||||
</resources>
|
@ -261,7 +261,7 @@
|
||||
<string name="download_notifier_download_paused">Тийеве вӑхӑтлӑха чарнӑ</string>
|
||||
<string name="download_notifier_unknown_error">Кӗтмен йӑнӑша пула сыпӑксене тийесе илеймест</string>
|
||||
<string name="download_notifier_downloader_title">Тийевҫӗ</string>
|
||||
<string name="information_webview_required">Tachiyomi валли WebView кирлӗ</string>
|
||||
<string name="information_webview_required">Mihon валли WebView кирлӗ</string>
|
||||
<string name="update_check_notification_download_in_progress">Тийев…</string>
|
||||
<string name="update_check_no_new_updates">Ҫӗнетӳсем тупӑнман</string>
|
||||
<string name="update_check_confirm">Тиесе ил</string>
|
||||
|
@ -128,16 +128,16 @@
|
||||
<string name="fifth_to_last">Ab fünftletzt gelesenem Kapitel</string>
|
||||
<string name="pref_download_new">Neue Kapitel herunterladen</string>
|
||||
<string name="services">Tracker</string>
|
||||
<string name="pref_create_backup">Sicherung erstellen</string>
|
||||
<string name="pref_create_backup">Datensicherung erstellen</string>
|
||||
<string name="pref_create_backup_summ">Kann benutzt werden, um die aktuelle Bibliothek wiederherzustellen</string>
|
||||
<string name="pref_restore_backup">Sicherung wiederherstellen</string>
|
||||
<string name="pref_restore_backup">Datensicherung wiederherstellen</string>
|
||||
<string name="pref_restore_backup_summ">Bibliothek mit Hilfe einer Datensicherung wiederherstellen</string>
|
||||
<string name="pref_backup_interval">Automatische Sicherungshäufigkeit</string>
|
||||
<string name="backup_created">Sicherung erstellt</string>
|
||||
<string name="backup_created">Datensicherung erstellt</string>
|
||||
<string name="restore_completed">Wiederherstellen abgeschlossen</string>
|
||||
<string name="backup_choice">Was möchtest du sichern\?</string>
|
||||
<string name="restoring_backup">Sicherung wird wiederhergestellt</string>
|
||||
<string name="creating_backup">Sicherung wird erstellt</string>
|
||||
<string name="restoring_backup">Datensicherung wird wiederhergestellt</string>
|
||||
<string name="creating_backup">Datensicherung wird erstellt</string>
|
||||
<string name="pref_clear_chapter_cache">Kapitel-Zwischenspeicher leeren</string>
|
||||
<string name="used_cache">Belegt: %1$s</string>
|
||||
<string name="cache_deleted">Zwischenspeicher geleert, %1$d Dateien gelöscht</string>
|
||||
@ -351,11 +351,11 @@
|
||||
<string name="website">Webseite</string>
|
||||
<string name="label_downloaded_only">Nur Heruntergeladenes</string>
|
||||
<string name="recent_manga_time">Kap. %1$s - %2$s</string>
|
||||
<string name="restoring_backup_error">Sicherungswiederherstellung fehlgeschlagen</string>
|
||||
<string name="creating_backup_error">Sicherung fehlgeschlagen</string>
|
||||
<string name="restoring_backup_error">Datensicherungswiederherstellung fehlgeschlagen</string>
|
||||
<string name="creating_backup_error">Datensicherung fehlgeschlagen</string>
|
||||
<string name="restoring_backup_canceled">Wiederherstellung abgebrochen</string>
|
||||
<string name="restore_in_progress">Wiederherstellung wird bereits durchgeführt</string>
|
||||
<string name="backup_in_progress">Sicherung wird bereits durchgeführt</string>
|
||||
<string name="backup_in_progress">Datensicherung wird bereits durchgeführt</string>
|
||||
<string name="check_for_updates">Nach Aktualisierungen suchen</string>
|
||||
<string name="last_used_source">Zuletzt genutzt</string>
|
||||
<string name="local_source_help_guide">Anleitung für lokale Quellen</string>
|
||||
@ -371,7 +371,7 @@
|
||||
<string name="sort_by_upload_date">Nach Uploaddatum</string>
|
||||
<string name="label_data">Daten</string>
|
||||
<string name="backup_restore_missing_sources">Fehlende Quellen:</string>
|
||||
<string name="invalid_backup_file_missing_manga">Sicherung beinhaltet keinerlei Bibliothekseinträge.</string>
|
||||
<string name="invalid_backup_file_missing_manga">Datensicherung beinhaltet keinerlei Bibliothekseinträge.</string>
|
||||
<string name="invalid_backup_file">Ungültige Sicherungsdatei:</string>
|
||||
<string name="pref_library_update_refresh_metadata_summary">Auf neue Cover und Details überprüfen, wenn die Bibliothek aktualisiert wird</string>
|
||||
<string name="pref_library_update_refresh_metadata">Metadaten automatisch aktualisieren</string>
|
||||
@ -542,7 +542,7 @@
|
||||
<string name="action_display_language_badge">Sprache</string>
|
||||
<string name="label_warning">Warnung</string>
|
||||
<string name="notification_size_warning">Große Aktualisierungen schaden Quellen und könnten zu langsameren Aktualisierungen sowie höherem Akkuverbrauch führen. Tippe, um mehr zu erfahren.</string>
|
||||
<string name="backup_info">Du solltest Kopien der Sicherungen auch an anderen Orten aufbewahren. Sicherungen beinhalten möglicherweise sensible Daten, einschließlich gespeicherter Passwörter. Sei vorsichtig beim Teilen.</string>
|
||||
<string name="backup_info">Du solltest Kopien der Datensicherungen auch an anderen Orten aufbewahren. Datensicherungen beinhalten möglicherweise sensible Daten, einschließlich gespeicherter Passwörter. Sei vorsichtig beim Teilen.</string>
|
||||
<string name="connected_to_wifi">Nur über WLAN</string>
|
||||
<string name="update_72hour">Alle 3 Tage</string>
|
||||
<string name="download_queue_size_warning">Achtung: Große Downloads könnten dazu führen, dass Quellen langsamer werden und/oder Mihon blockieren. Tippe, um mehr zu erfahren.</string>
|
||||
@ -793,7 +793,7 @@
|
||||
<string name="label_add_repo_input">Repository-URL</string>
|
||||
<string name="action_add_repo_message">Füge zusätzliche Repositorys zu Mihon hinzu. Deren URL sollte mit „index.min.json“ enden.</string>
|
||||
<string name="invalid_repo_name">Ungültige Repository-URL</string>
|
||||
<string name="manga_interval_expected_update">Neue Kapitel vsl. in ca. %1$s, überprüfe ca. alle %2$s</string>
|
||||
<string name="manga_interval_expected_update">Ca. %1$s bis zur Veröffentlichung neuer Kapitel, wird ca. alle %2$s überprüft.</string>
|
||||
<string name="theme_nord">Nord</string>
|
||||
<string name="action_open_repo">Open-Source-Repository</string>
|
||||
<string name="manga_interval_expected_update_soon">Bald</string>
|
||||
|
@ -33,7 +33,7 @@
|
||||
<string name="local_source">Iturri lokala</string>
|
||||
<string name="latest">Azkenak</string>
|
||||
<string name="no_more_results">Ez dago emaitza gehiagorik</string>
|
||||
<string name="download_queue_size_warning">Abisua: deskarga handiek iturriak motelagoak izatea eta/edo Tachiyomi blokeatzea ekar dezakete</string>
|
||||
<string name="download_queue_size_warning">Abisua: deskarga handiek iturriak motelagoak izatea eta/edo Mihon blokeatzea ekar dezakete</string>
|
||||
<string name="creating_backup">Babeskopia sortzen</string>
|
||||
<string name="restore_duration">%02d min, %02d seg</string>
|
||||
<string name="add_tracking">Gehitu jarraipena</string>
|
||||
@ -227,7 +227,7 @@
|
||||
<string name="about_dont_kill_my_app">Fabrikatzaile batzuek aplikazioen murrizketa gehigarriak dituzte, atzeko planoko zerbitzuak akabatzen dituztenak. Webgune honek informazio gehiago du nola konpondu jakiteko.</string>
|
||||
<string name="untrusted_extension_message">Luzapen hau fidagarria ez den ziurtagiri batekin sinatu zen eta ez zen aktibatu.
|
||||
\n
|
||||
\nLuzapen maltzur batek Tachiyomi-n gordetako saio-hasierako kredentzialak irakur ditzake edo kode arbitrarioa exekutatu.
|
||||
\nLuzapen maltzur batek Mihon-n gordetako saio-hasierako kredentzialak irakur ditzake edo kode arbitrarioa exekutatu.
|
||||
\n
|
||||
\nZiurtagiri honetaz fidatzean, arrisku hauek onartzen dituzu.</string>
|
||||
<string name="ext_untrusted">Fidatu gabekoa</string>
|
||||
@ -316,7 +316,7 @@
|
||||
<string name="notification_first_add_to_library">Mesedez, gehitu manga zure liburutegian hau egin aurretik</string>
|
||||
<string name="transition_pages_loading">Orriak kargatzen…</string>
|
||||
<string name="channel_common">Arrunta</string>
|
||||
<string name="information_webview_required">WebView beharrezkoa da Tachiyomi-rentzat</string>
|
||||
<string name="information_webview_required">WebView beharrezkoa da Mihon-rentzat</string>
|
||||
<string name="download_notifier_text_only_wifi">Ez dago Wi-Fi konexiorik erabilgarri</string>
|
||||
<string name="label_default">Lehenetsia</string>
|
||||
<string name="track">Jarraipena</string>
|
||||
|
@ -607,7 +607,7 @@
|
||||
<string name="sort_category_confirmation">آیا مایلید که دسته بندی ها را به ترتیب الفبا منظم کنید؟</string>
|
||||
<string name="action_ok">باشه</string>
|
||||
<string name="action_sort_next_updated">به روز رسانی مورد انتظار بعدی</string>
|
||||
<string name="download_queue_size_warning">هشدار: حجم زیاد بارگیری ممکن است باعث اهسته تر شدن سرعت ویا مسدود کردن Tachiyomi از منبع شود. برای اطلاعات بیشتر لمس کنید.</string>
|
||||
<string name="download_queue_size_warning">هشدار: حجم زیاد بارگیری ممکن است باعث اهسته تر شدن سرعت ویا مسدود کردن Mihon از منبع شود. برای اطلاعات بیشتر لمس کنید.</string>
|
||||
<string name="skipped_reason_not_always_update">به دلیل این که این مجموعه نیازی به به روز رسانی نداشت رد شد</string>
|
||||
<string name="skipped_reason_not_caught_up">به دلیل وجود چپتر های خوانده نشده رد شد</string>
|
||||
<string name="skipped_reason_not_started">به دلیل این که هیچ چپتری خوانده نشده بود رد شد</string>
|
||||
@ -622,4 +622,40 @@
|
||||
<string name="updates_last_update_info">آخرین به روز رسانی کتابخانه: %s</string>
|
||||
<string name="pref_update_only_in_release_period">خارج از دوره انتشار موزد انتظار</string>
|
||||
<string name="pref_double_tap_zoom">برای بزرگ نمایی دوبار ضربه بزنید</string>
|
||||
<string name="action_filter_interval_custom">تناوب به روز رسانی شخسی سازی شده</string>
|
||||
<string name="onboarding_heading">خوش آمدید!</string>
|
||||
<string name="onboarding_description">بیاید برخی چیز ها را تنظیم کنیم. شما همیشه میتوانید این تنظیمات را در بخش تنظیمات تغییر دهید.</string>
|
||||
<string name="onboarding_action_next">بعدی</string>
|
||||
<string name="onboarding_permission_install_apps_description">برای نصب افزانه منبع.</string>
|
||||
<string name="onboarding_permission_notifications">دسترسی اعلان ها</string>
|
||||
<string name="onboarding_permission_notifications_description">برای به روز رسانی های کتابخانه و بیشتر مطلع شوید.</string>
|
||||
<string name="onboarding_permission_ignore_battery_opts">استفاده از باطری</string>
|
||||
<string name="onboarding_permission_ignore_battery_opts_description">از وقفه در به روز رسانی های کتاب خانه، بارگیری و پشتیبان گیری های طولانی اجتناب کنید.</string>
|
||||
<string name="onboarding_permission_action_grant">اعطا کردن</string>
|
||||
<string name="onboarding_guides_new_user">به %s نا آشنا هستید؟ ما پیشنهاد میکنیم تا یک سر به راهنمای شروع بزنید.</string>
|
||||
<string name="onboarding_guides_returning_user">نصب مجدد %s؟</string>
|
||||
<string name="pref_relative_format">زمان بندی های نسبی</string>
|
||||
<string name="pref_library_update_smart_update">به روز رسانی هوشمند</string>
|
||||
<string name="pref_chapter_swipe">کشیدن قسمت</string>
|
||||
<string name="ext_revoke_trust">لغو اعتماد افزونه های ناشناخته</string>
|
||||
<string name="label_extension_repos">مخازن افزونه ها</string>
|
||||
<string name="action_add_repo">اضافه کردن مخزن</string>
|
||||
<string name="label_add_repo_input">آدرس مخزن</string>
|
||||
<string name="error_repo_exists">این مخزن در حال حاضر وجود دارد!</string>
|
||||
<string name="action_delete_repo">حذف مخزن</string>
|
||||
<string name="invalid_repo_name">آدرس مخزن بی اعتبار است</string>
|
||||
<string name="delete_repo_confirmation">آشا شما میخواهید تا \"%s\" مخزن را حذف کنید؟</string>
|
||||
<string name="pref_page_rotate_invert">جهت چرخش صفحات گسترده را برعکس کن</string>
|
||||
<string name="no_location_set">مکان ذخیره سازی تنظیم نشده است</string>
|
||||
<string name="split_tall_images">عکس های بلند را تقسیم کن</string>
|
||||
<string name="track_activity_name">ورود به ناضر</string>
|
||||
<string name="pref_hide_in_library_items">اجرای داخل کتاب خانه را مخفی کن</string>
|
||||
<string name="pref_storage_location">محل زخیره سازی</string>
|
||||
<string name="onboarding_action_finish">شروع کنید</string>
|
||||
<string name="onboarding_action_skip">پرش</string>
|
||||
<string name="ext_permission_install_apps_warning">مجوز برای نصب افزونه ها لازم است. به اینجا ضربه بزنید تا اعطا کنید.</string>
|
||||
<string name="action_add_repo_message">به میهون مخازن اضافی اضافه کنید. این باید یک آدرس باشد که با \"index.min.json\" تمام شود.</string>
|
||||
<string name="information_empty_repos">شما هیچ مخزنی برای تنظیم ندارید.</string>
|
||||
<string name="action_open_repo">مخزن منبع باز</string>
|
||||
<string name="pref_flash_page_summ">مقدار رد باقی مانده در نمایش گر های E-ink کاهش میابد</string>
|
||||
</resources>
|
@ -72,7 +72,7 @@
|
||||
<string name="label_extensions">Laajennukset</string>
|
||||
<string name="label_extension_info">Laajennuksen tiedot</string>
|
||||
<string name="action_filter">Suodatus</string>
|
||||
<string name="action_filter_bookmarked">Kirjanmerkki</string>
|
||||
<string name="action_filter_bookmarked">Kirjanmerkityt</string>
|
||||
<string name="action_filter_unread">Lukemattomat</string>
|
||||
<string name="action_filter_empty">Poista suodattimet</string>
|
||||
<string name="action_sort_alpha">Aakkosjärjestyksessä</string>
|
||||
@ -103,7 +103,7 @@
|
||||
<string name="action_install">Asenna</string>
|
||||
<string name="action_share">Jaa</string>
|
||||
<string name="action_save">Tallenna</string>
|
||||
<string name="action_reset">Resetoi</string>
|
||||
<string name="action_reset">Nollaa</string>
|
||||
<string name="action_undo">Kumoa</string>
|
||||
<string name="action_open_log">Avaa loki</string>
|
||||
<string name="action_restore">Palauta</string>
|
||||
@ -143,7 +143,7 @@
|
||||
<string name="untrusted_extension">Luottamattomat laajennokset</string>
|
||||
<string name="untrusted_extension_message">Tämä laajennus on allekirjoitettu luottamattomalla sertifikaatilla ja sitä ei ole aktivoitu.
|
||||
\n
|
||||
\nHaitallinen laajennus voisi lukea mahdolliset kirjautumistiedot Tachiyomista tai suorittaa luvatonta koodia.
|
||||
\nHaitallinen laajennus voisi lukea mahdolliset kirjautumistiedot Mihonista tai suorittaa luvatonta koodia.
|
||||
\n
|
||||
\nLuottamalla tähän sertifikaattiin hyväksyt nämä riskit.</string>
|
||||
<string name="pref_fullscreen">Koko näyttö</string>
|
||||
@ -348,7 +348,7 @@
|
||||
<string name="action_pin">Kiinnitä</string>
|
||||
<string name="action_select_inverse">Valitse käänteinen</string>
|
||||
<string name="vertical_plus_viewer">Jatkuva pystysuora</string>
|
||||
<string name="information_webview_required">WebView on pakollinen Tachiyomissa</string>
|
||||
<string name="information_webview_required">WebView on pakollinen Mihonissa</string>
|
||||
<string name="restore_in_progress">Palautus on jo käynnissä</string>
|
||||
<string name="backup_in_progress">Varmuuskopiointi on jo käynnissä</string>
|
||||
<string name="pref_webtoon_side_padding">Sivuntäyttö</string>
|
||||
@ -447,7 +447,7 @@
|
||||
<string name="pref_dump_crash_logs_summary">Tallentaa virhelokit tiedostoon jaettavaksi kehittäjien kanssa</string>
|
||||
<string name="action_desc">Laskeva</string>
|
||||
<string name="action_asc">Nouseva</string>
|
||||
<string name="action_order_by_chapter_number">Luvunumeron mukaan</string>
|
||||
<string name="action_order_by_chapter_number">Lukunumeron mukaan</string>
|
||||
<string name="action_order_by_upload_date">Lisäyspäivämäärän mukaan</string>
|
||||
<string name="action_filter_tracked">Seuratut</string>
|
||||
<string name="action_display_show_number_of_items">Näytä kohteiden määrä</string>
|
||||
@ -495,7 +495,7 @@
|
||||
<string name="off">Pois päältä</string>
|
||||
<string name="on">Päällä</string>
|
||||
<string name="getting_started_guide">Aloitusopas</string>
|
||||
<string name="download_queue_size_warning">Varoitus: massalataukset voivat johtaa siihen, että lähteet muuttuvat hitaammiksi käyttää ja/tai ne estävät Tachiyomin käytön. Napauta saadaksesi lisätietoja.</string>
|
||||
<string name="download_queue_size_warning">Varoitus: massalataukset voivat johtaa siihen, että lähteet muuttuvat hitaammiksi käyttää ja/tai ne estävät Mihonin käytön. Napauta saadaksesi lisätietoja.</string>
|
||||
<string name="action_show_manga">Näytä manga</string>
|
||||
<string name="action_display_cover_only_grid">Kansikuva ruudukko</string>
|
||||
<string name="theme_monet">Dynaaminen</string>
|
||||
@ -615,4 +615,35 @@
|
||||
<string name="backup_info">Varmuuskopioita kannattaa säilyttää myös muissa paikoissa.</string>
|
||||
<string name="wish_list">Toivelista</string>
|
||||
<string name="cant_open_last_read_chapter">Viimeksi luettua lukua ei voitu avata</string>
|
||||
<string name="selected">Valitut</string>
|
||||
<string name="scanlator">Skanlaattori</string>
|
||||
<string name="label_data_storage">Data ja tallennustila</string>
|
||||
<string name="label_stats">Tilastotiedot</string>
|
||||
<string name="label_downloaded">Ladattu</string>
|
||||
<string name="action_sort_next_updated">Seuraava odotettu päivitys</string>
|
||||
<string name="action_sort_tracker_score">Seurannan pisteytys</string>
|
||||
<string name="action_update_category">Päivitä kategoria</string>
|
||||
<string name="action_sort_category">Järjestä kategoriat</string>
|
||||
<string name="action_display_show_continue_reading_button">Jatka lukemista painike</string>
|
||||
<string name="action_apply">Käytä</string>
|
||||
<string name="action_ok">OK</string>
|
||||
<string name="action_revert_to_default">Palauta oletus</string>
|
||||
<string name="crash_screen_description">%s kohtasi odottamattoman virheen. Ehdotamme että jaat kaatumisen lokitiedot tukikanavallemme Discordissa.</string>
|
||||
<string name="delete_downloaded">Poista ladatut</string>
|
||||
<string name="create_backup_file_error">Varmuuskopiotiedoston luonti epäonnistui</string>
|
||||
<string name="confirm_add_duplicate_manga">Kirjastossasi on jo samanniminen merkintä.
|
||||
\n
|
||||
\nHaluatko silti jatkaa?</string>
|
||||
<string name="crash_screen_title">Hupsista!</string>
|
||||
<string name="copied_to_clipboard_plain">Kopioitu leikepöydälle</string>
|
||||
<string name="crash_screen_restart_application">Uudelleenkäynnistä applikaatio</string>
|
||||
<string name="custom_cover">Mukautettu kansikuva</string>
|
||||
<string name="not_selected">Ei valitut</string>
|
||||
<string name="action_menu_overflow_description">Lisää asetuksia</string>
|
||||
<string name="label_local">Paikallinen</string>
|
||||
<string name="label_started">Aloitettu</string>
|
||||
<string name="action_open_random_manga">Avaa satunnainen merkintä</string>
|
||||
<string name="sort_category_confirmation">Haluatko järjestää kategoriat aakkosjärjestykseen?</string>
|
||||
<string name="action_copy_to_clipboard">Kopioi leikepöydälle</string>
|
||||
<string name="action_move_to_bottom_all_for_series">Siirrä sarja pohjimmaiseksi</string>
|
||||
</resources>
|
@ -38,7 +38,7 @@
|
||||
</plurals>
|
||||
<plurals name="num_trackers">
|
||||
<item quantity="one">%d tracker</item>
|
||||
<item quantity="other">%d mga tracker</item>
|
||||
<item quantity="other">%d na tracker</item>
|
||||
</plurals>
|
||||
<plurals name="missing_chapters_warning">
|
||||
<item quantity="one">Nilaktawan ang %d na kabanata, maaaring ito ay wala sa source o na-filter ang mga ito</item>
|
||||
@ -46,11 +46,11 @@
|
||||
</plurals>
|
||||
<plurals name="relative_time">
|
||||
<item quantity="one">Kahapon</item>
|
||||
<item quantity="other">%1$d araw na ang makalipas</item>
|
||||
<item quantity="other">%1$d araw na ang nakakalipas</item>
|
||||
</plurals>
|
||||
<plurals name="next_unread_chapters">
|
||||
<item quantity="one">Susunod na hindi pa nababasa na kabanata</item>
|
||||
<item quantity="other">Susunod na %d di pa nababasa na kabanata</item>
|
||||
<item quantity="one">Susunod na hindi pa nababasang kabanata</item>
|
||||
<item quantity="other">Susunod na %d hindi pa nababasang kabanata</item>
|
||||
</plurals>
|
||||
<plurals name="download_amount">
|
||||
<item quantity="one">Sunod na kabanata</item>
|
||||
@ -58,11 +58,11 @@
|
||||
</plurals>
|
||||
<plurals name="missing_chapters">
|
||||
<item quantity="one">Nawawalang %1$s na kabanata</item>
|
||||
<item quantity="other">Nawawalang %1$s mga kabanata</item>
|
||||
<item quantity="other">Nawawalang %1$s na mga kabanata</item>
|
||||
</plurals>
|
||||
<plurals name="day">
|
||||
<item quantity="one">1 araw</item>
|
||||
<item quantity="other">%d (mga) araw</item>
|
||||
<item quantity="other">%d (na) araw</item>
|
||||
</plurals>
|
||||
<plurals name="num_repos">
|
||||
<item quantity="one">%d na repo</item>
|
||||
|
@ -22,7 +22,7 @@
|
||||
<string name="action_disable">Isara</string>
|
||||
<string name="action_display_show_tabs">Ipakita ang mga tab ng kategorya</string>
|
||||
<string name="action_display_download_badge">Bilang ng kabanatang na-download</string>
|
||||
<string name="action_display_comfortable_grid">Maalwan na grid</string>
|
||||
<string name="action_display_comfortable_grid">Kumportableng grid</string>
|
||||
<string name="action_display_list">Listahan</string>
|
||||
<string name="action_display_grid">Siksik na grid</string>
|
||||
<string name="action_display">Pagpapakita</string>
|
||||
@ -114,7 +114,7 @@
|
||||
<string name="second_to_last">Pangalawa sa huling nabasa</string>
|
||||
<string name="last_read_chapter">Huling nabasang kabanata</string>
|
||||
<string name="disabled">Sarado</string>
|
||||
<string name="pref_remove_after_marked_as_read">Pagkamarkahang nabasa na</string>
|
||||
<string name="pref_remove_after_marked_as_read">Markahang nabasa na</string>
|
||||
<string name="pref_remove_after_read">Pagkatapos basahin, awtomatikong burahin</string>
|
||||
<string name="pref_webtoon_side_padding">Kapal ng gilid</string>
|
||||
<string name="pref_category_reading">Pagbabasa</string>
|
||||
@ -243,7 +243,7 @@
|
||||
<string name="channel_ext_updates">Mga update sa extension</string>
|
||||
<string name="tapping_inverted_none">Wala</string>
|
||||
<string name="channel_new_chapters">Mga update sa kabanata</string>
|
||||
<string name="channel_common">Komon</string>
|
||||
<string name="channel_common">Pangkaraniwan</string>
|
||||
<string name="download_notifier_download_paused">Nakahinto ang mga pag-download</string>
|
||||
<string name="download_notifier_no_network">Walang koneksyon sa Internet</string>
|
||||
<string name="download_notifier_text_only_wifi">Walang koneksyon sa Wifi</string>
|
||||
@ -291,7 +291,7 @@
|
||||
<string name="decode_image_error">Di mai-load ang larawan</string>
|
||||
<string name="no_next_chapter">Di makita ang susunod na kabanata</string>
|
||||
<string name="chapter_progress">Pahina: %1$d</string>
|
||||
<string name="cover_updated">Napalitan na ang cover</string>
|
||||
<string name="cover_updated">Napalitan ang cover</string>
|
||||
<string name="set_as_cover">Gawin itong cover</string>
|
||||
<string name="custom_filter">Pinili kong filter</string>
|
||||
<string name="picture_saved">Na-save na ang larawan</string>
|
||||
@ -556,7 +556,7 @@
|
||||
<string name="library_errors_help">Para sa tulong sa pag-aayos ng mga error sa pag-update ng aklatan, tingnan ang %1$s</string>
|
||||
<string name="pref_update_only_completely_read">Laktawan ang mga entry na hindi pa nababasang kabanata</string>
|
||||
<string name="save_chapter_as_cbz">I-save bilang CBZ archive</string>
|
||||
<string name="publishing_finished">Tapos na\'ng mailathala</string>
|
||||
<string name="publishing_finished">Tapos na ang paglalathala</string>
|
||||
<string name="on_hiatus">Naka-hiatus</string>
|
||||
<string name="cancelled">Kinansela</string>
|
||||
<string name="action_show_manga">Ipakita ang entry</string>
|
||||
@ -639,9 +639,9 @@
|
||||
<string name="pref_library_summary">Mga kategorya, panlahatang update, pag-swipe ng kabanata</string>
|
||||
<string name="pref_browse_summary">Mga source, extension, panlahatang paghanap</string>
|
||||
<string name="crash_screen_description">Nagkaroon ng hindi inaasahang error ang %s. Iminumungkahi naming ibahagi mo ang mga crash log sa aming support channel sa Discord.</string>
|
||||
<string name="crash_screen_title">Ay!</string>
|
||||
<string name="crash_screen_title">Ay Naku!</string>
|
||||
<string name="crash_screen_restart_application">Buksan muli ang app</string>
|
||||
<string name="invalid_location">Invalid na lugar: %s</string>
|
||||
<string name="invalid_location">Imbalidong lugar: %s</string>
|
||||
<string name="unknown_title">Di alam na pamagat</string>
|
||||
<string name="error_user_agent_string_invalid">Di-wastong string ng user agent</string>
|
||||
<string name="updates_last_update_info_just_now">Ngayon lang</string>
|
||||
@ -693,7 +693,7 @@
|
||||
<string name="pref_page_rotate">I-rotate ang malalawak na pahina upang magkasya</string>
|
||||
<string name="pref_page_rotate_invert">I-flip ang oryentasyon ng mga pinaikot na malalawak na pahina</string>
|
||||
<string name="overlay_header">Nakapatong (Overlay)</string>
|
||||
<string name="split_tall_images">Hatiin ang mga matatangkad na larawan</string>
|
||||
<string name="split_tall_images">Hatiin ang mga matataas na larawan</string>
|
||||
<string name="pref_debug_info">Impormasyon sa pag-debug</string>
|
||||
<string name="pref_chapter_swipe_start">Mag-swipe ng pakaliwang pagkilos</string>
|
||||
<string name="pref_chapter_swipe">Mag swipe ng kabanata</string>
|
||||
|
@ -412,7 +412,7 @@
|
||||
<string name="pref_category_delete_chapters">Suppression des chapitres</string>
|
||||
<string name="ext_nsfw_warning">Les sources de cette extension peuvent contenir du contenu NSFW (18+)</string>
|
||||
<string name="ext_nsfw_short">18+</string>
|
||||
<string name="parental_controls_info">Ceci n\'empêche pas les extensions non officielles ou potentiellement mal signalées de diffuser du contenu +18 dans l\'application.</string>
|
||||
<string name="parental_controls_info">Ceci n\'empêche pas les extensions de diffuser du contenu +18 dans l\'application.</string>
|
||||
<string name="no_chapters_error">Aucun chapitre trouvé</string>
|
||||
<string name="confirm_set_chapter_settings">Appliquer ce paramétrage par défaut \?</string>
|
||||
<string name="chapter_settings">Paramètres du chapitre</string>
|
||||
@ -534,7 +534,7 @@
|
||||
<string name="ext_installer_shizuku_unavailable_dialog">Installez et démarrez Shizuku pour utiliser Shizuku comme installateur d\'extensions.</string>
|
||||
<string name="ext_installer_shizuku_stopped">Shizuku n\'est pas en cours d\'exécution</string>
|
||||
<string name="ext_installer_legacy">Legacy</string>
|
||||
<string name="ext_installer_pref">installeur</string>
|
||||
<string name="ext_installer_pref">Installeur</string>
|
||||
<string name="ext_install_service_notif">Installation de l\'extension…</string>
|
||||
<string name="action_sort_count">Entrées totales</string>
|
||||
<string name="pref_verbose_logging">Rapports détaillés</string>
|
||||
|
@ -102,7 +102,7 @@
|
||||
<string name="pref_enable_acra">שלח דוחות קריסה</string>
|
||||
<string name="untrusted_extension_message">תוסף זה נחתם עם אישור לא מהימן ולא הופעל.
|
||||
\n
|
||||
\nתוסף זדוני יכול לקרוא את אישורי ההתחברות המאוחסנים ב-Tachiyomi או להריץ קוד שרירותי.
|
||||
\nתוסף זדוני יכול לקרוא את אישורי ההתחברות המאוחסנים ב-Mihon או להריץ קוד שרירותי.
|
||||
\n
|
||||
\nעל ידי אמון באישור זה אתה מקבל סיכונים אלה.</string>
|
||||
<string name="ext_uninstall">הסר התקנה</string>
|
||||
@ -524,7 +524,7 @@
|
||||
<string name="categorized_display_settings">הגדרות מיון לכל קטגוריה בנפרד</string>
|
||||
<string name="source_empty_screen">מקור לא נמצא</string>
|
||||
<string name="update_check_eol">גרסת האנדרואיד הזאת כבר לא נתמכת</string>
|
||||
<string name="information_webview_required">WebView נצרך ל-Tachiyomi</string>
|
||||
<string name="information_webview_required">WebView נצרך ל-Mihon</string>
|
||||
<string name="action_display_cover_only_grid">רשת רק של הכריכות</string>
|
||||
<string name="action_open_log">פתח את יומן האירועים</string>
|
||||
<string name="notification_update_error">%1$d עדכונים נכשלו</string>
|
||||
@ -543,7 +543,7 @@
|
||||
<string name="clear_history_confirmation">אתה בטוח\? כל ההיסטוריה תמחק.</string>
|
||||
<string name="migration_help_guide">מדריך נדידת מקורות</string>
|
||||
<string name="download_insufficient_space">לא ניתן להוריד פרקים כי אין מספיק מקום אחסון</string>
|
||||
<string name="download_queue_size_warning">אזהרה: הורדות גדולות עלולות לגרום להאטה במקורות ו/או לחסימת Tachiyomi. לחץ כדי ללמוד עוד.</string>
|
||||
<string name="download_queue_size_warning">אזהרה: הורדות גדולות עלולות לגרום להאטה במקורות ו/או לחסימת Mihon. לחץ כדי ללמוד עוד.</string>
|
||||
<string name="local_invalid_format">פורמט פרק לא תקין</string>
|
||||
<string name="on_hiatus">בהפסקה</string>
|
||||
<string name="delete_category_confirmation">האם תרצה למחוק את הקטגוריה %s\?</string>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user