mirror of
https://github.com/mihonapp/mihon.git
synced 2025-07-31 11:55:53 +02:00
Compare commits
472 Commits
Author | SHA1 | Date | |
---|---|---|---|
38d5fc9160 | |||
9454fe4482 | |||
6de06419f8 | |||
fc2f339ea1 | |||
264030d6ec | |||
140083ee39 | |||
2bf7ef5d18 | |||
df9fff60da | |||
aae0e3459c | |||
f7752a98b2 | |||
2ba7ed3280 | |||
c153ac01f5 | |||
47b0e9d7be | |||
d4bf19f957 | |||
01b44c0458 | |||
e1e3ca7a56 | |||
78d2cc75d5 | |||
c550a81598 | |||
0be36a10c3 | |||
e16c3953c7 | |||
f3a2f566c8 | |||
15e3f28aa3 | |||
3bf70b230f | |||
eb3bea8150 | |||
5612ae0149 | |||
dbf6ad2ca7 | |||
d2afbfe4ed | |||
337806d9e1 | |||
443f6e0ae5 | |||
572ee2f02a | |||
ba1343bed8 | |||
9f3d5d13d4 | |||
48166b9b52 | |||
2e2c8d36c1 | |||
788235feec | |||
afa5002988 | |||
9503082d44 | |||
de36357da8 | |||
eb6092bd0c | |||
32d2c2ac1b | |||
4051f180a2 | |||
3ed8a91c7b | |||
87db3f90de | |||
0a4ad89b99 | |||
a72db41bf1 | |||
6b2bba4e54 | |||
7c7af72f8c | |||
c8bb78d91a | |||
2ba3f0612c | |||
f84d9a08b4 | |||
37419cdc26 | |||
481cfedf08 | |||
9b8ab6acc2 | |||
3bddb55385 | |||
2beb89d531 | |||
016f627fb0 | |||
44aab7a243 | |||
a2dc88965b | |||
aa998071a1 | |||
8113b77f1e | |||
6adfa4fd0f | |||
76e0aba70c | |||
f7fbc93833 | |||
85ee9c6686 | |||
c72c07f355 | |||
6984e0465b | |||
3ca989eae8 | |||
cca33481dd | |||
f7c8f1801e | |||
112b68b782 | |||
2dd02b73d6 | |||
369df527b2 | |||
d04eeface9 | |||
dde942df4e | |||
380787a310 | |||
418ba30265 | |||
b3867dd63c | |||
6dd93d70cc | |||
2276abbb23 | |||
be671b42ce | |||
0042cb6582 | |||
1e570bc965 | |||
9cc7d42dd9 | |||
f5c6d2e1a6 | |||
339dc33f58 | |||
223af5508f | |||
d42f776c5c | |||
5c0dc3e05a | |||
bebf80dfae | |||
86dd809f4d | |||
1ff88dd927 | |||
be5d467955 | |||
fcb01b5bcf | |||
844dae1a4d | |||
83fd4746ed | |||
c8ad6cdf31 | |||
fbcc48fefc | |||
6f422745ba | |||
bec549cc44 | |||
c4f235ae07 | |||
8fd1239bea | |||
b56a97bb8e | |||
52036e5664 | |||
29a74509a4 | |||
0e956cbb51 | |||
2baffa62ca | |||
bd7b354198 | |||
70c1a842b2 | |||
001249a89d | |||
c4d2fffb12 | |||
f22767d863 | |||
02af9b1acf | |||
3c611b95fb | |||
fc1c804bfd | |||
abfb72c89c | |||
9c1905ede7 | |||
9f99f038f3 | |||
6c6ea84509 | |||
4ee31bfea5 | |||
03eb756ecb | |||
a45eb5e528 | |||
8f9a325895 | |||
f74071ab0a | |||
7fb3ef48e4 | |||
1837faa573 | |||
518abf032c | |||
7ca64a67c5 | |||
d26c010e57 | |||
607e56a4ec | |||
952a98c180 | |||
45628b14db | |||
5dc6569a68 | |||
fba9bacdc1 | |||
ca968f162e | |||
379d587826 | |||
034ec4cb12 | |||
2481767532 | |||
ab2b734d49 | |||
aac4d6e548 | |||
08ae51ea8c | |||
4387ae5ff3 | |||
d6252ab770 | |||
5ae8095ef1 | |||
ac41bffdc9 | |||
777ae2461e | |||
b2f1719c50 | |||
3f050a83dd | |||
6f4e3f776f | |||
1c47a6b9b3 | |||
f4348df870 | |||
9a34ace09c | |||
124a787cda | |||
b404a71e26 | |||
be124ebe86 | |||
fdb96179c6 | |||
c5994e057b | |||
3f1d28c383 | |||
84b2164787 | |||
200d39e023 | |||
b1b15a93ee | |||
97c81fadb4 | |||
9240eceedc | |||
14ae57d78b | |||
4828c54245 | |||
dca9bf1057 | |||
e8b7c3e24b | |||
af77083660 | |||
36b9caeea8 | |||
fdc1423f3d | |||
8e40146f96 | |||
1c16fc79c2 | |||
31263084ec | |||
2858ef835f | |||
edb8201f74 | |||
854474f85f | |||
04db46fe75 | |||
3f6bd5f010 | |||
a3dfd2efe6 | |||
de8ef6dad7 | |||
8160b47ff5 | |||
c201b341a7 | |||
56fb4f62a1 | |||
0af90999c8 | |||
913ff22132 | |||
04aa5b36a5 | |||
41e2dc7ae8 | |||
88efde8796 | |||
b7849d7146 | |||
602b58f364 | |||
e48dbdbf23 | |||
6ace423e18 | |||
51b68cd25f | |||
ca784cbe32 | |||
4f61b2e4e8 | |||
8c9d12a840 | |||
f63e950910 | |||
f3f2bd41c3 | |||
14d687c5cd | |||
e94c8dac94 | |||
7a2ca4bf4d | |||
4a7613d515 | |||
e65634cb42 | |||
daa47e0493 | |||
cbcd8bd668 | |||
2092c81bad | |||
5a61ca5535 | |||
ddba71df37 | |||
75b5d96601 | |||
77db8873f6 | |||
bff6183cf3 | |||
e620665dda | |||
c0f9de88e7 | |||
80cdebcdf4 | |||
9e2f97eeb8 | |||
e132cc405f | |||
2674b84974 | |||
f34702d4fc | |||
7823966ddf | |||
2d41bf5589 | |||
d8fe7d32ca | |||
2f86f25d5b | |||
239c38982c | |||
36e40c0997 | |||
40754659a9 | |||
a41ea8a61d | |||
5c249dd790 | |||
e17f70f722 | |||
e57638a49c | |||
0ce1cf22cd | |||
4ed2062cab | |||
f6ec53cdde | |||
b37357f909 | |||
f58a05e918 | |||
cf02119da5 | |||
5e2a3ee927 | |||
3b8ed3059a | |||
4182ae89a0 | |||
f3226fb278 | |||
30a6e3a6a1 | |||
2e78bceb30 | |||
a5838387b1 | |||
aa1714b2ac | |||
f696f209c6 | |||
9fa22f0b37 | |||
6d8cfd5f30 | |||
af57e124f2 | |||
8e8ee69bba | |||
e9d69a83fe | |||
6a80305d6c | |||
119bcbf8ed | |||
87fe64468c | |||
bdce3c39f1 | |||
15d999229f | |||
1edd55c981 | |||
777a071f4a | |||
71b558cb34 | |||
46003ec251 | |||
e8fdfaad64 | |||
1f7574bd4f | |||
da62c7a21a | |||
0870cffba1 | |||
8632ba85ee | |||
116579d38c | |||
098f925519 | |||
9f5db70572 | |||
1f286f1a35 | |||
7ab7f5ac37 | |||
e567250b17 | |||
095da924b9 | |||
b9da98b527 | |||
af8696cb90 | |||
de5a64aa73 | |||
9b944092c7 | |||
0cb1794a44 | |||
2f243fae11 | |||
d2e5c78074 | |||
5912d6b08f | |||
99b550ae0d | |||
8d187f7865 | |||
653d5d3e25 | |||
1dca9363a4 | |||
fffa6a462d | |||
ce497003e3 | |||
84ea5166de | |||
0392ef0d18 | |||
16392adcbb | |||
f603db3f3f | |||
ab546e0884 | |||
d3306e8cfe | |||
aebb86794a | |||
9a62e4fba3 | |||
5955c9c311 | |||
fb9423028e | |||
8e9396a9cf | |||
1df87eabf2 | |||
ca7391bbf3 | |||
550f1197e8 | |||
dbcc4a7d71 | |||
e473c7f09f | |||
263e467cde | |||
7ec2108812 | |||
e55e5f6f64 | |||
28dca3b7b8 | |||
70cd688ac2 | |||
21145144cd | |||
134e4648a9 | |||
fa6dba6cc7 | |||
8a51d56c59 | |||
47ee2b45a8 | |||
a2f7d47a0a | |||
4e5cbbc96b | |||
b720f34267 | |||
c6a1412f18 | |||
6290cf222d | |||
a3d438e2f5 | |||
80461d883f | |||
c3e7bb12f4 | |||
2ad98520aa | |||
f1ce205d00 | |||
5289830a84 | |||
dd932e1362 | |||
e85ce6456a | |||
9a3ffe2ea6 | |||
213effa169 | |||
25570147a1 | |||
e82a2f5f9f | |||
935c0c7e2e | |||
2ad462b4d8 | |||
7fd8f65352 | |||
b152e3881b | |||
f27ca3b1b2 | |||
843daa5304 | |||
f080a4937e | |||
8bba926891 | |||
5378c4c5d6 | |||
c94d212ef4 | |||
4c43a0ef66 | |||
ea0fe2414e | |||
015620711d | |||
6d4267b3bb | |||
2a01a2ac6b | |||
9a6559b013 | |||
a7509b3a3c | |||
2755d1f35e | |||
6a8a9c6bbf | |||
7862088b94 | |||
35f8eda8c5 | |||
fa6fa1f53a | |||
c348fac78f | |||
ab06720966 | |||
42ebf017e4 | |||
3910ffdd9e | |||
0bfacf5570 | |||
1e28999e13 | |||
1ee54d74a4 | |||
a1a52ae81a | |||
56e66e041d | |||
13656959ae | |||
20e4cb26d6 | |||
e448e40406 | |||
aed53d3bdc | |||
d77f2f429d | |||
c3fd2df6f5 | |||
f5a41e9693 | |||
34bf5c6f87 | |||
6abaa47f5b | |||
c9fddf9e38 | |||
555d2f834f | |||
6b3423a12b | |||
7c6fd026a3 | |||
86fbd20665 | |||
8c8d65d3c7 | |||
f1660beafc | |||
72222ad86d | |||
0265c16eb2 | |||
666d6aa117 | |||
6965e59a64 | |||
e020ae5ed5 | |||
05071b4205 | |||
da20d00481 | |||
8c437ceecf | |||
9672ea8b1b | |||
ba9cfd867c | |||
4b4e468510 | |||
e75488f5d9 | |||
3838dbcf08 | |||
b3ca097e5a | |||
70c2443e82 | |||
c0a888807b | |||
34930920a5 | |||
6682b5dd39 | |||
3c5f4a317a | |||
6a2bfd5e87 | |||
ef6cad58fe | |||
7e9340aa7f | |||
3f2c8e9ef6 | |||
a29870c01e | |||
583aa430ba | |||
0ea0138a73 | |||
59bedb33ff | |||
ebee275110 | |||
015d9b3bd0 | |||
f2ccfb0817 | |||
1b60c5f0f4 | |||
0a91b57f67 | |||
bcdf17fe27 | |||
a08e03f5cb | |||
f087135876 | |||
f66f52c244 | |||
0d6f426dbd | |||
edd7d0522c | |||
4ae9dbe524 | |||
402e579a69 | |||
d0e64d3a66 | |||
154f4d327c | |||
d8b9a9f593 | |||
b7e091d5d0 | |||
31e052ac15 | |||
60480686da | |||
d6ba3c8249 | |||
c56f4665ef | |||
b51a0a38bd | |||
f72b6e4d7c | |||
84984ef7e1 | |||
9f48def1e2 | |||
e83bfb0d35 | |||
0301362430 | |||
9d5978aca0 | |||
4bfc5e7b51 | |||
5859a8bbf6 | |||
802a2c5c1e | |||
c1c1746985 | |||
4fcbd80a8e | |||
16969193c7 | |||
55637ddfe1 | |||
e50358dc4b | |||
1e40199b7d | |||
410b918b77 | |||
a4f5dfab1a | |||
085147b15b | |||
085ad8d446 | |||
9254079957 | |||
7974a1fc0c | |||
1521c35941 | |||
2247f6004a | |||
c15f3f2fd5 | |||
21020e1797 | |||
7edecae57f | |||
07f963d5ae | |||
ab02568ac6 | |||
617bf491ee | |||
840b647b4b | |||
1b0bbb8440 | |||
95d4df9ca8 | |||
fb86c470f6 | |||
5aec8f8018 | |||
e183cbb231 | |||
6bdb37be65 | |||
7ff95e21ba | |||
96c236e5c3 | |||
72f3756a3b | |||
0780385d2e | |||
31e9273b1f | |||
088e37b2d8 | |||
5b88f1bd94 | |||
18beb20aac | |||
d5d7065e75 | |||
78271a54a4 | |||
9bff20cb1a | |||
2ccff8cdde | |||
0da7ad6f1a | |||
170daf9fb2 |
.github
.gitignore.idea
CHANGELOG.mdCONTRIBUTING.mdREADME.mdapp
.gitignorebuild.gradle.ktsproguard-rules.pro
build.gradle.ktssrc
dev
java
mihon
core
firebase
main
AndroidManifest.xml
java
eu
kanade
core
domain
DomainModule.kt
base
chapter
interactor
extension
interactor
manga
model
source
service
track
interactor
model
service
ui
presentation
browse
ExtensionDetailsScreen.ktExtensionFilterScreen.ktExtensionsScreen.ktGlobalSearchScreen.ktMigrateSourceScreen.ktSourcesFilterScreen.ktSourcesScreen.kt
components
category
components
crash
history
library
manga
more
NewUpdateScreen.kt
onboarding
settings
PreferenceItem.kt
screen
SettingsAdvancedScreen.ktSettingsAppearanceScreen.ktSettingsBrowseScreen.ktSettingsDataScreen.ktSettingsDownloadScreen.ktSettingsLibraryScreen.ktSettingsReaderScreen.ktSettingsSearchScreen.ktSettingsSecurityScreen.ktSettingsTrackingScreen.kt
about
browse
data
debug
widget
stats
components
reader
theme
TachiyomiTheme.kt
colorscheme
track
TrackInfoDialogHome.ktTrackInfoDialogHomePreviewProvider.ktTrackInfoDialogSelector.ktTrackerSearch.ktTrackerSearchPreviewProvider.kt
components
updates
util
webview
tachiyomi
App.ktMigrations.kt
crash
data
backup
coil
database
download
library
notification
saver
track
BaseTracker.ktTracker.ktTrackerManager.kt
anilist
bangumi
kitsu
mangaupdates
model
myanimelist
shikimori
di
extension
source
ui
base
browse
extension
migration
search
source
history
home
library
main
manga
more
reader
stats
updates
webview
util
test
mihon
core
designsystem
utils
migration
feature
res
drawable
values-night
values
xml
standard
test
java
mihon
core
migration
buildSrc
.gitignorebuild.gradle.ktssettings.gradle.kts
src
config/detekt
core-metadata
core
archive
common
.gitignorebuild.gradle.kts
src
main
data
.gitignorebuild.gradle.kts
src
main
domain
.gitignorebuild.gradle.kts
gradle.propertiessrc
main
AndroidManifest.xml
java
mihon
domain
chapter
interactor
extensionrepo
exception
interactor
CreateExtensionRepo.ktDeleteExtensionRepo.ktGetExtensionRepo.ktGetExtensionRepoCount.ktReplaceExtensionRepo.ktUpdateExtensionRepo.kt
model
repository
service
upcoming
interactor
tachiyomi
domain
category
interactor
chapter
download
service
library
manga
release
interactor
source
service
track
model
test
java
tachiyomi
domain
gradle
gradlewgradlew.bati18n
.gitignoreREADME.mdbuild.gradle.kts
src
androidMain
commonMain
moko-resources
am
ar
base
be
bg
bn
ca
ceb
cs
cv
da
de
el
eo
es
eu
fa
fi
fil
fr
gl
he
hi
hr
hu
in
it
ja
jv
ka-rGE
kk
km
kn
ko
lt
lv
ml
mr
ms
nb-rNO
ne
nl
nn
pl
pt-rBR
pt
ro
ru
sa
sah
sc
sdh
sk
sq
sr
sv
te
th
tr
uk
uz
vi
zh-rCN
zh-rTW
resources
MR
macrobenchmark
presentation-core
.gitignorebuild.gradle.kts
src
presentation-widget
settings.gradle.ktssource-api
source-local
.gitignorebuild.gradle.kts
src
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
4
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
@ -53,7 +53,7 @@ body:
|
|||||||
label: Mihon version
|
label: Mihon version
|
||||||
description: You can find your Mihon version in **More → About**.
|
description: You can find your Mihon version in **More → About**.
|
||||||
placeholder: |
|
placeholder: |
|
||||||
Example: "0.16.3"
|
Example: "0.17.0"
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
- label: I have gone through the [FAQ](https://mihon.app/docs/faq/general) and [troubleshooting guide](https://mihon.app/docs/guides/troubleshooting/).
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.16.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
- label: I have updated the app to version **[0.17.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated all installed extensions.
|
- label: I have updated all installed extensions.
|
||||||
required: true
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
2
.github/ISSUE_TEMPLATE/request_feature.yml
vendored
@ -31,7 +31,7 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I have written a short but informative title.
|
- label: I have written a short but informative title.
|
||||||
required: true
|
required: true
|
||||||
- label: I have updated the app to version **[0.16.3](https://github.com/mihonapp/mihon/releases/latest)**.
|
- label: I have updated the app to version **[0.17.0](https://github.com/mihonapp/mihon/releases/latest)**.
|
||||||
required: true
|
required: true
|
||||||
- label: I will fill out all of the requested information in this form.
|
- label: I will fill out all of the requested information in this form.
|
||||||
required: true
|
required: true
|
||||||
|
10
.github/mergify.yml
vendored
10
.github/mergify.yml
vendored
@ -1,10 +0,0 @@
|
|||||||
#pull_request_rules:
|
|
||||||
# - name: Automatically merge translations
|
|
||||||
# conditions:
|
|
||||||
# - "author = weblate"
|
|
||||||
# - "-conflict"
|
|
||||||
# - "current-day-of-week = Sat"
|
|
||||||
# - "created-at < 1 day ago"
|
|
||||||
# actions:
|
|
||||||
# merge:
|
|
||||||
# method: squash
|
|
16
.github/renovate.json5
vendored
16
.github/renovate.json5
vendored
@ -1,17 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": ["config:base"],
|
||||||
"config:base"
|
"labels": ["Dependencies"],
|
||||||
],
|
"semanticCommits": "disabled",
|
||||||
"schedule": ["every sunday"],
|
|
||||||
"packageRules": [
|
"packageRules": [
|
||||||
{
|
{
|
||||||
// Compiler plugins are tightly coupled to Kotlin version
|
"groupName": "GitHub Actions",
|
||||||
"groupName": "Kotlin",
|
"matchManagers": ["github-actions"],
|
||||||
"matchPackagePrefixes": [
|
"pinDigests": true,
|
||||||
"androidx.compose.compiler",
|
|
||||||
"org.jetbrains.kotlin",
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
36
.github/workflows/build_pull_request.yml
vendored
36
.github/workflows/build_pull_request.yml
vendored
@ -1,10 +1,13 @@
|
|||||||
name: PR build check
|
name: PR build check
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths:
|
||||||
- '**.md'
|
- '**'
|
||||||
- 'i18n/src/commonMain/resources/**/strings.xml'
|
- '!**.md'
|
||||||
- 'i18n/src/commonMain/resources/**/plurals.xml'
|
- '!i18n/src/commonMain/moko-resources/**/strings.xml'
|
||||||
|
- '!i18n/src/commonMain/moko-resources/**/plurals.xml'
|
||||||
|
- 'i18n/src/commonMain/moko-resources/base/strings.xml'
|
||||||
|
- 'i18n/src/commonMain/moko-resources/base/plurals.xml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||||
@ -20,21 +23,34 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
- name: Dependency Review
|
- name: Dependency Review
|
||||||
uses: actions/dependency-review-action@v3
|
uses: actions/dependency-review-action@a6993e2c61fd5dc440b409aa1d6904921c5e1894 # v4.3.5
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Set up gradle
|
||||||
|
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||||
|
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
arguments: detekt assembleStandardRelease testReleaseUnitTest
|
name: arm64-v8a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||||
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
|
with:
|
||||||
|
name: mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/standardRelease
|
||||||
|
27
.github/workflows/build_push.yml
vendored
27
.github/workflows/build_push.yml
vendored
@ -17,25 +17,38 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/actions/wrapper-validation@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
- name: Setup Android SDK
|
- name: Setup Android SDK
|
||||||
run: |
|
run: |
|
||||||
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager "build-tools;29.0.3"
|
||||||
|
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
|
||||||
with:
|
with:
|
||||||
java-version: 17
|
java-version: 17
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
|
|
||||||
|
- name: Set up gradle
|
||||||
|
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # v4.1.0
|
||||||
|
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
run: ./gradlew spotlessCheck assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest
|
||||||
|
|
||||||
|
- name: Upload APK
|
||||||
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
with:
|
with:
|
||||||
arguments: detekt assembleStandardRelease testReleaseUnitTest
|
name: arm64-v8a-${{ github.sha }}
|
||||||
|
path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk
|
||||||
|
|
||||||
|
- name: Upload mapping
|
||||||
|
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
|
||||||
|
with:
|
||||||
|
name: mapping-${{ github.sha }}
|
||||||
|
path: app/build/outputs/mapping/standardRelease
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
@ -47,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # v1
|
||||||
with:
|
with:
|
||||||
releaseDirectory: app/build/outputs/apk/standard/release
|
releaseDirectory: app/build/outputs/apk/standard/release
|
||||||
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
|
||||||
@ -82,7 +95,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'mihonapp/mihon'
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.VERSION_TAG }}
|
tag_name: ${{ env.VERSION_TAG }}
|
||||||
name: Mihon ${{ env.VERSION_TAG }}
|
name: Mihon ${{ env.VERSION_TAG }}
|
||||||
|
45
.github/workflows/issue_moderator.yml
vendored
45
.github/workflows/issue_moderator.yml
vendored
@ -1,45 +0,0 @@
|
|||||||
name: Issue moderator
|
|
||||||
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened, edited, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
moderate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Moderate issues
|
|
||||||
uses: tachiyomiorg/issue-moderator-action@v2
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
duplicate-label: Duplicate
|
|
||||||
|
|
||||||
auto-close-rules: |
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
|
|
||||||
"message": "The acknowledgment section was not removed."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "body",
|
|
||||||
"regex": ".*\\* (Tachiyomi version|Android version|Device): \\?.*",
|
|
||||||
"message": "Requested information in the template was not filled out."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "both",
|
|
||||||
"regex": "^(?!.*myanimelist.*).*(aniyomi|anime).*$",
|
|
||||||
"ignoreCase": true,
|
|
||||||
"message": "Tachiyomi does not support anime, and has no plans to support anime. In addition Tachiyomi is not affiliated with Aniyomi https://github.com/jmir1/aniyomi"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "both",
|
|
||||||
"regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(?<!n[o']?t )blocked by|error) (?:to )?(?:get past|by ?pass|penetrate)?.*cloud ?fl?are.*",
|
|
||||||
"ignoreCase": true,
|
|
||||||
"labels": ["Cloudflare protected"],
|
|
||||||
"message": "Refer to the **Solving Cloudflare issues** section at https://mihon.app/docs/guides/troubleshooting/#cloudflare. If it doesn't work, migrate to other sources or wait until they lower their protection."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
auto-close-ignore-label: do-not-autoclose
|
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -12,7 +12,7 @@ jobs:
|
|||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v5
|
- uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: '2'
|
issue-inactive-days: '2'
|
||||||
|
23
.gitignore
vendored
23
.gitignore
vendored
@ -1,17 +1,16 @@
|
|||||||
|
# Build files
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
.kotlin
|
||||||
/.idea/workspace.xml
|
build
|
||||||
.DS_Store
|
|
||||||
|
# IDE files
|
||||||
|
*.iml
|
||||||
.idea/*
|
.idea/*
|
||||||
!.idea/icon.png
|
!.idea/icon.png
|
||||||
*iml
|
/captures
|
||||||
*.iml
|
|
||||||
|
|
||||||
# Built files
|
# Configuration files
|
||||||
*/build
|
local.properties
|
||||||
/build
|
|
||||||
*.apk
|
|
||||||
app/**/output.json
|
|
||||||
|
|
||||||
# Unnecessary file
|
# macOS specific files
|
||||||
*.swp
|
.DS_Store
|
||||||
|
BIN
.idea/icon.png
generated
BIN
.idea/icon.png
generated
Binary file not shown.
Before ![]() (image error) Size: 26 KiB After ![]() (image error) Size: 62 KiB ![]() ![]() |
268
CHANGELOG.md
Normal file
268
CHANGELOG.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is a modified version of [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
- `Added` - for new features.
|
||||||
|
- `Changed ` - for changes in existing functionality.
|
||||||
|
- `Improved` - for enhancement or optimization in existing functionality.
|
||||||
|
- `Removed` - for now removed features.
|
||||||
|
- `Fixed` - for any bug fixes.
|
||||||
|
- `Other` - for technical stuff.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [v0.17.0] - 2024-10-26
|
||||||
|
### Added
|
||||||
|
- Option to disable reader zoom out ([@Splintorien](https://github.com/Splintorien)) ([#302](https://github.com/mihonapp/mihon/pull/302))
|
||||||
|
- Source name and tracker urls to app generated `ComicInfo.xml` file ([@Shamicen](https://github.com/Shamicen)) ([#459](https://github.com/mihonapp/mihon/pull/459))
|
||||||
|
- Option to migrate in Duplicate entry dialog ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
|
||||||
|
- Upcoming screen to visualize expected update dates ([@sirlag](https://github.com/sirlag)) ([#420](https://github.com/mihonapp/mihon/pull/420))
|
||||||
|
- Only show upcoming updates in the future ([@sirlag](https://github.com/sirlag)) ([#606](https://github.com/mihonapp/mihon/pull/606))
|
||||||
|
- Add Quantity Badge to Upcoming Screen ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1250](https://github.com/mihonapp/mihon/pull/1250))
|
||||||
|
- Crash screen error message to the top of the crash log generated from that screen ([@FooIbar](https://github.com/FooIbar)) ([#742](https://github.com/mihonapp/mihon/pull/742))
|
||||||
|
- Support for 7Zip and RAR5 archives ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949))
|
||||||
|
- Extra configuration options to e-ink page flashes ([@sirlag](https://github.com/sirlag)) ([#625](https://github.com/mihonapp/mihon/pull/625))
|
||||||
|
- 8-bit+ AVIF image support ([@WerctFourth](https://github.com/WerctFourth)) ([#971](https://github.com/mihonapp/mihon/pull/971))
|
||||||
|
- Smart update dialog message when no predicted released date exists ([@Animeboynz](https://github.com/Animeboynz)) ([#977](https://github.com/mihonapp/mihon/pull/977))
|
||||||
|
- Option to copy reader panel to clipboard ([@Animeboynz](https://github.com/Animeboynz)) ([#1003](https://github.com/mihonapp/mihon/pull/1003))
|
||||||
|
- Copy Tracker URL option to tracker sheet ([@mm12](https://github.com/mm12)) ([#1101](https://github.com/mihonapp/mihon/pull/1101))
|
||||||
|
- A button to exclude all scanlators in exclude scanlators dialog ([@AntsyLich](https://github.com/AntsyLich)) ([`84b2164`](https://github.com/mihonapp/mihon/commit/84b2164787a795f3fd757c325cbfb6ef660ac3a3))
|
||||||
|
- Open in browser option to reader menu ([@mm12](https://github.com/mm12)) ([#1110](https://github.com/mihonapp/mihon/pull/1110))
|
||||||
|
- Reorder reader menu overflow items ([@AntsyLich](https://github.com/AntsyLich)) ([`788235f`](https://github.com/mihonapp/mihon/commit/788235feeca241228eac0561339dd07b5ea0b77d))
|
||||||
|
- Option to skip downloading duplicate read chapters ([@shabnix](https://github.com/shabnix)) ([#1125](https://github.com/mihonapp/mihon/pull/1125))
|
||||||
|
- Add confirmation dialog when adding repo via URI ([@Animeboynz](https://github.com/Animeboynz)) ([#1158](https://github.com/mihonapp/mihon/pull/1158))
|
||||||
|
- Add "show entry" action to download notifications ([@mm12](https://github.com/mm12), [@AntsyLich](https://github.com/AntsyLich)) ([#1159](https://github.com/mihonapp/mihon/pull/1159))
|
||||||
|
- Option to update trackers when chapter marked as read ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1177](https://github.com/mihonapp/mihon/pull/1177), [#1365](https://github.com/mihonapp/mihon/pull/1365), [#1374](https://github.com/mihonapp/mihon/pull/1374))
|
||||||
|
- Toast to restart app when User-Agent is changed ([@NGB-Was-Taken](https://github.com/NGB-Was-Taken)) ([#1204](https://github.com/mihonapp/mihon/pull/1204))
|
||||||
|
- Added more profile compilation status (p) ([`c8bb78d`](https://github.com/mihonapp/mihon/commit/c8bb78d91afc2824baaca999f0095559c49d1306))
|
||||||
|
- Add option to opt out of Analytics and Crashlytics ([@Animeboynz](https://github.com/Animeboynz)) ([#1237](https://github.com/mihonapp/mihon/pull/1237))
|
||||||
|
- Rework Firebase setup ([@AntsyLich](https://github.com/AntsyLich)) ([`15e3f28`](https://github.com/mihonapp/mihon/commit/15e3f28aa36bec3c31f212c572ab57ce960cc862))
|
||||||
|
- Added random library sort ([@jackhamilton](https://github.com/jackhamilton)) ([#1317](https://github.com/mihonapp/mihon/pull/1317))
|
||||||
|
- Make sure random library sort is at the bottom ([@AntsyLich](https://github.com/AntsyLich)) ([`2e2c8d3`](https://github.com/mihonapp/mihon/commit/2e2c8d36c1e23bf274c7c19f1242e14b0c7afbc1))
|
||||||
|
- Confirmation dialog when removing privately installed extensions ([@Animeboynz](https://github.com/Animeboynz), [@AntsyLich](https://github.com/AntsyLich)) ([#1320](https://github.com/mihonapp/mihon/pull/1320))
|
||||||
|
- Option to backup non-library read entries ([@Animeboynz](https://github.com/Animeboynz), [@jobobby04](https://github.com/jobobby04), [@AntsyLich](https://github.com/AntsyLich)) ([#1324](https://github.com/mihonapp/mihon/pull/1324))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Read archive files from memory instead of temporarily extracting to internal storage ([@FooIbar](https://github.com/FooIbar)) ([#326](https://github.com/mihonapp/mihon/pull/326))
|
||||||
|
- Fix dual page split ([@FooIbar](https://github.com/FooIbar)) ([#485](https://github.com/mihonapp/mihon/pull/485))
|
||||||
|
- Bump default user agent ([@AntsyLich](https://github.com/AntsyLich)) ([`8160b47`](https://github.com/mihonapp/mihon/commit/8160b47ff5fbbd9b32caeb462b5be881fabd3449))
|
||||||
|
- Wait for sources to be initialized before performing source related tasks ([@jobobby04](https://github.com/jobobby04)) ([`a08e03f`](https://github.com/mihonapp/mihon/commit/a08e03f5cbf3f4e6be1de35f97ef8ebb26a1210e))
|
||||||
|
- Duplicate entry dialog UI ([@sirlag](https://github.com/sirlag)) ([#492](https://github.com/mihonapp/mihon/pull/492))
|
||||||
|
- Extension trust system
|
||||||
|
- Store extension repo details from `repo.json` in database ([@sirlag](https://github.com/sirlag)) ([#506](https://github.com/mihonapp/mihon/pull/506))
|
||||||
|
- Fix extension repo migration not triggering ([@AntsyLich](https://github.com/AntsyLich)) ([`9672ea8`](https://github.com/mihonapp/mihon/commit/9672ea8b1b06f464800e310c96e060ead182f7ca))
|
||||||
|
- Refactor the ExtensionRepoService to use DTOs ([@MajorTanya](https://github.com/MajorTanya)) ([#573](https://github.com/mihonapp/mihon/pull/573))
|
||||||
|
- Fix extension repo name is used to construct URL instead of baseUrl ([@MajorTanya](https://github.com/MajorTanya)) ([#572](https://github.com/mihonapp/mihon/pull/572))
|
||||||
|
- Fix crash with `TypeReference` issue when creating extension repo ([@AntsyLich](https://github.com/AntsyLich)) ([#574](https://github.com/mihonapp/mihon/pull/574), [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a))
|
||||||
|
- Fix mishap in [`e020ae5`](https://github.com/mihonapp/mihon/commit/e020ae5ed558e80742ef0ad8bfa0f69af0959d5a) ([@AntsyLich](https://github.com/AntsyLich)) ([`6965e59`](https://github.com/mihonapp/mihon/commit/6965e59a643c67a2bf81b3c69ec70268e5da5797))
|
||||||
|
- Backup and Restore ([@Animeboynz](https://github.com/Animeboynz)) ([#1057](https://github.com/mihonapp/mihon/pull/1057))
|
||||||
|
- Trust extension by repo ([@AntsyLich](https://github.com/AntsyLich)) ([#570](https://github.com/mihonapp/mihon/pull/570))-
|
||||||
|
- From M2 ripple to M3 ([@FooIbar](https://github.com/FooIbar)) ([#675](https://github.com/mihonapp/mihon/pull/675))
|
||||||
|
- Increased continue reading button size ([@AntsyLich](https://github.com/AntsyLich), [@Animeboynz](https://github.com/Animeboynz)) ([`e17f70f`](https://github.com/mihonapp/mihon/commit/e17f70f7226ea031fc1f962c9dfea3e404ba53ad))
|
||||||
|
- Global search "Has result" choice is now sticky ([@AntsyLich](https://github.com/AntsyLich)) ([`5a61ca5`](https://github.com/mihonapp/mihon/commit/5a61ca5535fe0d9e8e7bcb9e665ba2f9cb0cf649))
|
||||||
|
- Make category backup/restore not dependant on library backup ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
|
||||||
|
- Rename backup restore error log file ([@AntsyLich](https://github.com/AntsyLich)) ([`2858ef8`](https://github.com/mihonapp/mihon/commit/2858ef835fec8d7278b1d0cad1b5664104d1e4b0))
|
||||||
|
- Keyboard type in add extension repo dialog ([@xbjfk](https://github.com/xbjfk)) ([#764](https://github.com/mihonapp/mihon/pull/764))
|
||||||
|
- Adjust collapse/open animation on manga description ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`1c16fc7`](https://github.com/mihonapp/mihon/commit/1c16fc79c2ac4c4be30308fed84ffb371dab5902))
|
||||||
|
- Kitsu domain to `kitsu.app` ([@MajorTanya](https://github.com/MajorTanya)) ([#1106](https://github.com/mihonapp/mihon/pull/1106))
|
||||||
|
- Respect privacy settings in extension update notification ([@Animeboynz](https://github.com/Animeboynz)) ([#1156](https://github.com/mihonapp/mihon/pull/1156))
|
||||||
|
- Hide keyboard when a Tracker SearchResultItem is clicked ([@Animeboynz](https://github.com/Animeboynz)) ([#1168](https://github.com/mihonapp/mihon/pull/1168))
|
||||||
|
- Enable 'Split Tall Images' by default ([@Smol-Ame](https://github.com/Smol-Ame)) ([#1185](https://github.com/mihonapp/mihon/pull/1185))
|
||||||
|
- Ignore "intent://" urls on webview ([@bapeey](https://github.com/bapeey)) ([#1193](https://github.com/mihonapp/mihon/pull/1193))
|
||||||
|
- Make reader chapter navigator slightly wider on small screens (p) ([#1202](https://github.com/mihonapp/mihon/pull/1202))
|
||||||
|
- Re-enable fetching chapters list for entries with licenced status ([@Animeboynz](https://github.com/Animeboynz)) ([#1230](https://github.com/mihonapp/mihon/pull/1230))
|
||||||
|
- Change casing for Extention Repos String ([@Animeboynz](https://github.com/Animeboynz)) ([#1248](https://github.com/mihonapp/mihon/pull/1248))
|
||||||
|
- Retain remote last chapter read if it's higher than the local one for EnhancedTracker ([@brewkunz](https://github.com/brewkunz)) ([#1301](https://github.com/mihonapp/mihon/pull/1301))
|
||||||
|
- Adjust expandable fab animation (p) ([`eb6092b`](https://github.com/mihonapp/mihon/commit/eb6092bd0cfa09694985a8bafdd8bbf2815190a1))
|
||||||
|
- "Invalidate downloads index" to "Reindex downloads" ([@AntsyLich](https://github.com/AntsyLich)) ([`d2afbfe`](https://github.com/mihonapp/mihon/commit/d2afbfe4ede283076aae40633c79c3f90b4390e7))
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- Reader performance
|
||||||
|
- Avoid unnecessary copying when processing reader image ([@FooIbar](https://github.com/FooIbar)) ([#691](https://github.com/mihonapp/mihon/pull/691))
|
||||||
|
- Significantly improve performance when loading extremely long images in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#692](https://github.com/mihonapp/mihon/pull/692))
|
||||||
|
- Use `Bitmap.Config.HARDWARE` if possible to improve image loading speed ([@wwww-wwww](https://github.com/wwww-wwww)) ([#687](https://github.com/mihonapp/mihon/pull/687))
|
||||||
|
- Improve preloading in long strip mode ([@FooIbar](https://github.com/FooIbar)) ([#1076](https://github.com/mihonapp/mihon/pull/1076))
|
||||||
|
- Performance when looking up specific files ([@raxod502](https://github.com/raxod502)) ([#728](https://github.com/mihonapp/mihon/pull/728))
|
||||||
|
- Chapter number parsing ([@Naputt1](https://github.com/Naputt1)) ([`6a80305`](https://github.com/mihonapp/mihon/commit/6a80305d6c572da6c08c0c69f5c25ff26ecf7383))
|
||||||
|
- Error message on restoring if backup decoding fails ([@vetleledaal](https://github.com/vetleledaal)) ([#1056](https://github.com/mihonapp/mihon/pull/1056))
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Legacy download folder names no longer supported ([@AntsyLich](https://github.com/AntsyLich)) ([`e55e5f6`](https://github.com/mihonapp/mihon/commit/e55e5f6f64f872475d370d6ce0c186e2601776e4))
|
||||||
|
- Remove legacy broken source and history backup ([@AntsyLich](https://github.com/AntsyLich)) ([`518abf0`](https://github.com/mihonapp/mihon/commit/518abf032ccb9bb45d197927be2a5faca4167d29))
|
||||||
|
- Remove more unnecessary permissions from Firebase dependency ([@AntsyLich](https://github.com/AntsyLich)) ([`02af9b1`](https://github.com/mihonapp/mihon/commit/02af9b1acf9f590d29560bc3fc90d206e8e6e1af))
|
||||||
|
- Fix mishap in `02af9b1` ([@AntsyLich](https://github.com/AntsyLich)) ([`f22767d`](https://github.com/mihonapp/mihon/commit/f22767d863a0fa001f93f24092cd5ade87350502))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Extracting `ComicInfo.xml` from local source archives ([@FooIbar](https://github.com/FooIbar)) ([#325](https://github.com/mihonapp/mihon/pull/325))
|
||||||
|
- Chapter download indicator ([@ivaniskandar](https://github.com/ivaniskandar)) ([`d8b9a9f`](https://github.com/mihonapp/mihon/commit/d8b9a9f593911569ff2bceb49b4f020978d0d2e1))
|
||||||
|
- Issues with shizuku in a multi user setup ([@Redjard](https://github.com/Redjard)) ([#494](https://github.com/mihonapp/mihon/pull/494))
|
||||||
|
- Fix reader page image not being decoded until it's visible ([@FooIbar](https://github.com/FooIbar)) ([#563](https://github.com/mihonapp/mihon/pull/563))
|
||||||
|
- Reader chapter progress slider visuals ([@FooIbar](https://github.com/FooIbar)) ([#674](https://github.com/mihonapp/mihon/pull/674))
|
||||||
|
- Extension being marked as not installed instead of untrusted after updating with private installer ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- Extension update counter not updating due to extension being marked as untrusted ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- `Key "extension-XXX-YYY" was already used` crash ([@AntsyLich](https://github.com/AntsyLich)) ([`2114514`](https://github.com/mihonapp/mihon/commit/21145144cdf550aa775047603e06e261951ebc42))
|
||||||
|
- Navigation layout tap zones shifting after zooming out in webtoon readers ([@FooIbar](https://github.com/FooIbar)) ([#767](https://github.com/mihonapp/mihon/pull/767))
|
||||||
|
- Some extension not loading due to missing classes ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#783](https://github.com/mihonapp/mihon/pull/783))
|
||||||
|
- Theme colors in accordance to upstream changes ([@CrepeTF](https://github.com/CrepeTF), [@AntsyLich](https://github.com/AntsyLich)) ([#766](https://github.com/mihonapp/mihon/pull/766), [#963](https://github.com/mihonapp/mihon/pull/963), [#976](https://github.com/mihonapp/mihon/pull/976), [9a34ace](https://github.com/mihonapp/mihon/commit/9a34ace09c66274e6c2b3f9446058a0fa99d4bd0))
|
||||||
|
- Crash when requesting folder access on non-conforming devices ([@mainrs](https://github.com/mainrs)) ([#726](https://github.com/mihonapp/mihon/pull/726))
|
||||||
|
- Fix unexpected skips in strong skipping mode ([@FooIbar](https://github.com/FooIbar)) ([#940](https://github.com/mihonapp/mihon/pull/940))
|
||||||
|
- Bugged color for Date/Scanlator in chapter list for read chapters ([@ivaniskandar](https://github.com/ivaniskandar)) ([`15d9992`](https://github.com/mihonapp/mihon/commit/15d999229fcce865001d5fa77d0163e6e80e38db))
|
||||||
|
- Categories having same `order` after restoring backup ([@Cologler](https://github.com/Cologler)) ([`119bcbf`](https://github.com/mihonapp/mihon/commit/119bcbf8ed2415664922ea77fadf0da1165d1732))
|
||||||
|
- Filter by "Tracking" temporarily stuck after signing out of tracker ([@AntsyLich](https://github.com/AntsyLich)) ([#987](https://github.com/mihonapp/mihon/pull/987))
|
||||||
|
- Fix login prompts despite being logged in to trackers in Manga screen ([@AntsyLich](https://github.com/AntsyLich)) ([`cbcd8bd`](https://github.com/mihonapp/mihon/commit/cbcd8bd6682023f728568f2b44da26124618aed7))
|
||||||
|
- JXL image downloading and loading ([@FooIbar](https://github.com/FooIbar)) ([#993](https://github.com/mihonapp/mihon/pull/993))
|
||||||
|
- Crash when using `%` in category name ([@Animeboynz](https://github.com/Animeboynz), [@FooIbar](https://github.com/FooIbar)) ([#1030](https://github.com/mihonapp/mihon/pull/1030))
|
||||||
|
- Fix item disappearing when fast scrolling ([@cuong-tran](https://github.com/cuong-tran)) ([#1035](https://github.com/mihonapp/mihon/pull/1035))
|
||||||
|
- Library is backed up while being disabled ([@AntsyLich](https://github.com/AntsyLich)) ([`56fb4f6`](https://github.com/mihonapp/mihon/commit/56fb4f62a152e87a71892aa68c78cac51a2c8596))
|
||||||
|
- Crash on list with only sticky header ([@cuong-tran](https://github.com/cuong-tran)) ([#1083](https://github.com/mihonapp/mihon/pull/1083))
|
||||||
|
- Crash when trying to clear cookies of some source ([@FooIbar](https://github.com/FooIbar)) ([#1084](https://github.com/mihonapp/mihon/pull/1084))
|
||||||
|
- MAL search results not showing start dates ([@MajorTanya](https://github.com/MajorTanya)) ([#1098](https://github.com/mihonapp/mihon/pull/1098))
|
||||||
|
- Android SDK 35 API collision ([@AntsyLich](https://github.com/AntsyLich)) ([`fdb9617`](https://github.com/mihonapp/mihon/commit/fdb96179c6373eb0a8e7d6daea671a315d5ce5f0))
|
||||||
|
- Manga next update calculation when considering custom fetch interval ([@cuong-tran](https://github.com/cuong-tran)) ([#1206](https://github.com/mihonapp/mihon/pull/1206))
|
||||||
|
- WheelPicker Manual Input ([@Animeboynz](https://github.com/Animeboynz)) ([#1209](https://github.com/mihonapp/mihon/pull/1209))
|
||||||
|
- EnhancedTracker not auto binding when adding manga to library ([@brewkunz](https://github.com/brewkunz)) ([#1298](https://github.com/mihonapp/mihon/pull/1298))
|
||||||
|
- Step count in settings slider ([@abdurisaq](https://github.com/abdurisaq)) ([#1356](https://github.com/mihonapp/mihon/pull/1356))
|
||||||
|
- Freezing in some screens due to blocking call ([@cuong-tran](https://github.com/cuong-tran)) ([#1364](https://github.com/mihonapp/mihon/pull/1364))
|
||||||
|
- Crash when removing non-existent tracked entry from tracker ([@cuong-tran](https://github.com/cuong-tran)) ([#1380](https://github.com/mihonapp/mihon/pull/1380))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Code cleanup
|
||||||
|
- Minor refactor of theming when expressions ([@MajorTanya](https://github.com/MajorTanya)) ([#396](https://github.com/mihonapp/mihon/pull/396))
|
||||||
|
- Inside `WorkerInfoScreen` ([@AntsyLich](https://github.com/AntsyLich)) ([`5aec8f8`](https://github.com/mihonapp/mihon/commit/5aec8f8018236a38106483da08f9cbc28261ac9b))
|
||||||
|
- Inside `ChapterDownloadIndicator`, `MangaChapterListItem` ([@AntsyLich](https://github.com/AntsyLich)) ([`b7e091d`](https://github.com/mihonapp/mihon/commit/b7e091d5d039e00cababc7daf555280df6cf9c03))
|
||||||
|
- MangaCoverFetcher ([@ivaniskandar](https://github.com/ivaniskandar)) ([`1365695`](https://github.com/mihonapp/mihon/commit/13656959ae0606736f6ca9eb62699dc23e467c2f))
|
||||||
|
- Cleanup `LibraryScreenModel` `LibraryMap.applySort` and some more ([@AntsyLich](https://github.com/AntsyLich)) ([`2beb89d`](https://github.com/mihonapp/mihon/commit/2beb89d53163a6d288f8acdebe0f5d26fea8ab3e))
|
||||||
|
- Address `overridePendingTransition` deprecation ([@MajorTanya](https://github.com/MajorTanya)) ([#410](https://github.com/mihonapp/mihon/pull/410))
|
||||||
|
- Prioritize extension classes and files over app ([@beer-psi](https://github.com/beer-psi)) ([#433](https://github.com/mihonapp/mihon/pull/433))
|
||||||
|
- Use compose pager implementation ([@ivaniskandar](https://github.com/ivaniskandar)) ([`84984ef`](https://github.com/mihonapp/mihon/commit/84984ef7e1d7242924120cd2f171cb9dd75bc916))
|
||||||
|
- Switch to coil3 from coil2 ([@ivaniskandar](https://github.com/ivaniskandar)) ([`f72b6e4`](https://github.com/mihonapp/mihon/commit/f72b6e4d7c1f2f93d705402e4d80c94160bef54d))
|
||||||
|
- Fix GIF not playing ([@jobobby04](https://github.com/jobobby04)) ([`59bedb3`](https://github.com/mihonapp/mihon/commit/59bedb33ff59ad5db1df2e93567a2266fb63eacc))
|
||||||
|
- Accommodate db for sync support ([@kaiserbh](https://github.com/kaiserbh)) ([#450](https://github.com/mihonapp/mihon/pull/450))
|
||||||
|
- Fix webtoon last visible item position calculation ([@FooIbar](https://github.com/FooIbar)) ([#562](https://github.com/mihonapp/mihon/pull/562))
|
||||||
|
- Migrate from `com.google.accompanist:accompanist-webview` to `io.github.kevinnzou:compose-webview` ([@sirlag](https://github.com/sirlag)) ([#569](https://github.com/mihonapp/mihon/pull/569))
|
||||||
|
- Rewrite migrations ([@ghostbear](https://github.com/ghostbear)) ([#577](https://github.com/mihonapp/mihon/pull/577))
|
||||||
|
- Further improve migration ([@ghostbear](https://github.com/ghostbear)) ([#588](https://github.com/mihonapp/mihon/pull/588))
|
||||||
|
- Fix migrations not running ([@ghostbear](https://github.com/ghostbear)) ([#604](https://github.com/mihonapp/mihon/pull/604))
|
||||||
|
- Fix MigratorTest after updating to Kotlin 2 ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896))
|
||||||
|
- Add MigratorTest to build script ([@cuong-tran](https://github.com/cuong-tran)) ([#896](https://github.com/mihonapp/mihon/pull/896))
|
||||||
|
- Fix UI freeze after migration ([@AntsyLich](https://github.com/AntsyLich)) ([`3f1d28c`](https://github.com/mihonapp/mihon/commit/3f1d28c3833e6b868152149ed02b3fb8c54eccef))
|
||||||
|
- Fix some migrations never running ([@MajorTanya](https://github.com/MajorTanya), [@AntsyLich](https://github.com/AntsyLich)) ([#1030](https://github.com/mihonapp/mihon/pull/1030))
|
||||||
|
- Add ProGuard rule to keep `mihon` namespace classes ([@MajorTanya](https://github.com/MajorTanya)) ([#605](https://github.com/mihonapp/mihon/pull/605))
|
||||||
|
- Use gradle plugins to share build configuration instead of subprojects ([@AntsyLich](https://github.com/AntsyLich)) ([`e448e40`](https://github.com/mihonapp/mihon/commit/e448e40406e8d9916120a278e42829a6f1b25a7a))
|
||||||
|
- Remove dependency on compose material 2 components ([@AntsyLich](https://github.com/AntsyLich)) ([`fb94230`](https://github.com/mihonapp/mihon/commit/fb9423028eb017c110cb805f2d0601e5b02e50f9))
|
||||||
|
- Upload PR build artifacts to GitHub ([@FooIbar](https://github.com/FooIbar)) ([#941](https://github.com/mihonapp/mihon/pull/941))
|
||||||
|
- Refactor archive support with libarchive ([@FooIbar](https://github.com/FooIbar)) ([#949](https://github.com/mihonapp/mihon/pull/949))
|
||||||
|
- Add safeguard to prevent ArchiveInputStream from being closed twice ([@null2264](https://github.com/null2264)) ([#967](https://github.com/mihonapp/mihon/pull/967))
|
||||||
|
- Move archive related code to :core:archive ([@AntsyLich](https://github.com/AntsyLich)) ([`bd7b354`](https://github.com/mihonapp/mihon/commit/bd7b35419861df6d426d6ec0a188391910d0f615))
|
||||||
|
- Replace detekt with ktlint via spotless ([@AntsyLich](https://github.com/AntsyLich)) ([#1130](https://github.com/mihonapp/mihon/pull/1130), [#1136](https://github.com/mihonapp/mihon/pull/1136), [#1138](https://github.com/mihonapp/mihon/pull/1138))
|
||||||
|
- Refrain from running spotless on weblate files ([@AntsyLich](https://github.com/AntsyLich)) ([`32d2c2a`](https://github.com/mihonapp/mihon/commit/32d2c2ac1bc224cbda2f09a4023d7d120ea0e954))
|
||||||
|
- Use feature flags in compose compiler plugin ([@AntsyLich](https://github.com/AntsyLich)) ([`8f9a325`](https://github.com/mihonapp/mihon/commit/8f9a325895bb7b94c2ec92dd969094fc30b3b5e2))- PagerPageHolder: lazy init loading indicator ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`a45eb5e`](https://github.com/mihonapp/mihon/commit/a45eb5e5288159dbbbbb5f92140ce0dd32a8f3ab))
|
||||||
|
- Collect MangaScreen state with lifecycle ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b))
|
||||||
|
- Add stable marker to Manga data class ([@AntsyLich](https://github.com/AntsyLich), [@ivaniskandar](https://github.com/ivaniskandar)) ([`03eb756`](https://github.com/mihonapp/mihon/commit/03eb756ecba0692d88d3a76254afc4c157fa225b))
|
||||||
|
- Use DTOs to parse tracking API responses ([@MajorTanya](https://github.com/MajorTanya)) ([#1103](https://github.com/mihonapp/mihon/pull/1103))
|
||||||
|
- Fix Kitsu ratingTwenty being typed as String ([@MajorTanya](https://github.com/MajorTanya)) ([#1191](https://github.com/mihonapp/mihon/pull/1191))
|
||||||
|
- Fix Kitsu `synopsis` nullability ([@MajorTanya](https://github.com/MajorTanya)) ([#1233](https://github.com/mihonapp/mihon/pull/1233))
|
||||||
|
- Fix AniList `ALSearchItem.status` nullibility ([@Secozzi](https://github.com/Secozzi)) ([#1297](https://github.com/mihonapp/mihon/pull/1297))
|
||||||
|
- Migrate some classpaths to gradle plugins ([@AntsyLich](https://github.com/AntsyLich)) ([`fc1c804`](https://github.com/mihonapp/mihon/commit/fc1c804bfda1d76c0399bbb6214e75b3def951cc))
|
||||||
|
- Add crashlytics to standard builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c611b9`](https://github.com/mihonapp/mihon/commit/3c611b95fb79e5ac972019b76c7b24f46a3087fd))
|
||||||
|
- Switch to stable compose ([@AntsyLich](https://github.com/AntsyLich)) ([`2baffa6`](https://github.com/mihonapp/mihon/commit/2baffa62cade1abd978d5fd03151b47fc87fd31e))
|
||||||
|
- Switch from inorichi injekt to kohesive Injekt ([@AntsyLich](https://github.com/AntsyLich)) ([#1205](https://github.com/mihonapp/mihon/pull/1205))
|
||||||
|
- Use custom injekt register with inorichi patch ([@AntsyLich](https://github.com/AntsyLich)) ([`83fd474`](https://github.com/mihonapp/mihon/commit/83fd4746eda1b99f35292b0c2211e606a421b3eb))
|
||||||
|
- Use TextFieldState in BasicTextField where applicable (p) ([#1201](https://github.com/mihonapp/mihon/pull/1201))
|
||||||
|
- Bump NDK version ([@AntsyLich](https://github.com/AntsyLich)) ([#1203](https://github.com/mihonapp/mihon/pull/1203))
|
||||||
|
- Move firebase permission removal to standard flavor ([@AntsyLich](https://github.com/AntsyLich)) ([`be671b4`](https://github.com/mihonapp/mihon/commit/be671b42cefd70180644e01bb065a18cb7701bf9))
|
||||||
|
- Adjust distinct checker in WidgetManager and run on default dispatcher (p) ([`9b8ab6a`](https://github.com/mihonapp/mihon/commit/9b8ab6acc25a5f99c9c5eebf9cc250975931c57c))
|
||||||
|
- Update resources exclusion rules (p) ([`481cfed`](https://github.com/mihonapp/mihon/commit/481cfedf08576cecfbb35616837bd8f627d8f959))
|
||||||
|
- Bump compile sdk to 35 (p) ([`37419cd`](https://github.com/mihonapp/mihon/commit/37419cdc26c2b5c4f8583fc2ba439b08fab42856))
|
||||||
|
- ChapterNavigator: dispatch page change only when needed (p) ([`f84d9a0`](https://github.com/mihonapp/mihon/commit/f84d9a08b4af768b1e9920c43cc445c86f5427fc))
|
||||||
|
- Remove usage of deprecated accompanist SystemUiController ([@AntsyLich](https://github.com/AntsyLich)) ([`2ba3f06`](https://github.com/mihonapp/mihon/commit/2ba3f0612c08c7021fed2f6d96cd538da2f34a13))
|
||||||
|
- Run PR check when base strings are changed ([@AntsyLich](https://github.com/AntsyLich)) ([`4051f18`](https://github.com/mihonapp/mihon/commit/4051f180a2e36e8a2cde6c55f0bea7952fdc4704))
|
||||||
|
- Fix PR build check ([@AntsyLich](https://github.com/AntsyLich)) ([`9503082`](https://github.com/mihonapp/mihon/commit/9503082d44b5bd868ee1bfc42741dc978d1d9047))
|
||||||
|
- Cleanup .gitignore files ([@AntsyLich](https://github.com/AntsyLich)) ([`afa5002`](https://github.com/mihonapp/mihon/commit/afa50029882655af8d5eea40aed7644fce4564d8))
|
||||||
|
- Pass uncaught exception to default handler in GlobalExceptionHandler (so it's reported to crashlytics) ([@AntsyLich](https://github.com/AntsyLich)) ([`f3a2f56`](https://github.com/mihonapp/mihon/commit/f3a2f566c8a09ab862758ae69b43da2a2cd8f1db))
|
||||||
|
|
||||||
|
## [v0.16.5] - 2024-04-09
|
||||||
|
### Added
|
||||||
|
- Relative date for up to a week in the future ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415))
|
||||||
|
- Advance setting to install custom color profiles ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Permanently enable 32-bit color mode ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Wrong dates in Updates and History tab due to time zone issues ([@sirlag](https://github.com/sirlag)) ([#402](https://github.com/mihonapp/mihon/pull/402))
|
||||||
|
- Fix extra date header introduced by parent PR ([@sirlag](https://github.com/sirlag)) ([#415](https://github.com/mihonapp/mihon/pull/415))
|
||||||
|
- Fix build time in about screen displayed in UTC ([@AntsyLich](https://github.com/AntsyLich)) ([`aed53d3`](https://github.com/mihonapp/mihon/commit/aed53d3bdc85ce0e899fbb90b9f9cad0f1b86480))
|
||||||
|
- App infinitely retries tracker update instead of failing after 3 tries ([@MajorTanya](https://github.com/MajorTanya)) ([#411](https://github.com/mihonapp/mihon/pull/411))
|
||||||
|
- Crash on Pixel devices (was introduced due to compose update) ([`ab06720`](https://github.com/mihonapp/mihon/commit/ab067209661eceefc04c65f6bdbfcaa8a1264651))
|
||||||
|
- Crash when opening some heif/heic images ([@az4521](https://github.com/az4521)) ([#466](https://github.com/mihonapp/mihon/pull/466))
|
||||||
|
- Crash when putting app in background while track date selection dialog is open ([@ivaniskandar](https://github.com/ivaniskandar)) ([`c348fac`](https://github.com/mihonapp/mihon/commit/c348fac78fac479fb123bd617c01c78b9ca851d5))
|
||||||
|
- Dates for saved images not following the specification (fixes date issue mainly on Samsung devices) ([@MajorTanya](https://github.com/MajorTanya)) ([#552](https://github.com/mihonapp/mihon/pull/552))
|
||||||
|
- Colors getting distorted when opening CMYK jpeg images ([@wwww-wwww](https://github.com/wwww-wwww)) ([#523](https://github.com/mihonapp/mihon/pull/523))
|
||||||
|
|
||||||
|
## [v0.16.4] - 2024-02-27
|
||||||
|
### Changed
|
||||||
|
- Don't include custom user agent for MAL (circumvents MAL block) ([@AntsyLich](https://github.com/AntsyLich)) ([`085ad8d`](https://github.com/mihonapp/mihon/commit/085ad8d44637c375a8ed24aba3a6f75f5b0cc9ee))
|
||||||
|
|
||||||
|
## [v0.16.3] - 2024-01-30
|
||||||
|
### Added
|
||||||
|
- Copy extension debug info when clicking logo or name in the extension details screen ([@MajorTanya](https://github.com/MajorTanya)) ([#271](https://github.com/mihonapp/mihon/pull/271))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Hide display cutoff setting in reader settings sheet if fullscreen is disabled ([@Riztard](https://github.com/Riztard)) ([#241](https://github.com/mihonapp/mihon/pull/241))
|
||||||
|
- Library update error filename to `mihon_update_errors.txt` from `tachiyomi_update_errors.txt` ([@mjishnu](https://github.com/mjishnu)) ([#253](https://github.com/mihonapp/mihon/pull/253))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Bottom sheet UI issues on non-tablet devices ([@theolm](https://github.com/theolm)) ([#182](https://github.com/mihonapp/mihon/pull/182))
|
||||||
|
- Crash when switching screen while a list is scrolling ([@theolm](https://github.com/theolm)) ([#272](https://github.com/mihonapp/mihon/pull/272))
|
||||||
|
- Newly installed extensions not being recognized by Mihon ([@AwkwardPeak7](https://github.com/AwkwardPeak7)) ([#275](https://github.com/mihonapp/mihon/pull/275))
|
||||||
|
- Failing to refresh MAL token being inferred as token expiration ([@AntsyLich](https://github.com/AntsyLich)) ([`0f4de03`](https://github.com/mihonapp/mihon/commit/0f4de03d7a77b52490dc9a95e96a308b93b26e4f))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Add `detekt` (kotlin code analyzer) to the project ([@theolm](https://github.com/theolm)) ([#216](https://github.com/mihonapp/mihon/pull/216))
|
||||||
|
|
||||||
|
## [v0.16.2] - 2024-01-28
|
||||||
|
### Changed
|
||||||
|
- Backup now contains scanlator filter of a series ([@jobobby04](https://github.com/jobobby04)) ([#166](https://github.com/mihonapp/mihon/pull/166))
|
||||||
|
- App icon scaling ([@AntsyLich](https://github.com/AntsyLich)) ([`26815c7`](https://github.com/mihonapp/mihon/commit/26815c7356111394665467c1e81255ac9ee33c1a))
|
||||||
|
- Tracker OAuth client to Mihon's (fixes login issue for Shikimori tracker) ([@AntsyLich](https://github.com/AntsyLich)) ([`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5))
|
||||||
|
- Tracker user agents ([@AntsyLich](https://github.com/AntsyLich), [@kitsumed](https://github.com/kitsumed)) ([`e3f33e2`](https://github.com/mihonapp/mihon/commit/e3f33e24f5e928ac8a85d1f500fd42d4715fc6b5))
|
||||||
|
- Crash log filename to `mihon_crash_logs.txt` from `tachiyomi_crash_logs.txt` ([@MajorTanya](https://github.com/MajorTanya)) ([#234](https://github.com/mihonapp/mihon/pull/234))
|
||||||
|
- Don't try to refresh MAL token after refresh token expires ([@AntsyLich](https://github.com/AntsyLich)) ([`32188f9`](https://github.com/mihonapp/mihon/commit/32188f9f65009a18250674ef1bd6e57d351c1fba))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- "Flash screen on page change" making the screen full black ([@AntsyLich](https://github.com/AntsyLich)) ([`38d6ab8`](https://github.com/mihonapp/mihon/commit/38d6ab80ce868707829dbc81de4170afe3c2f2a5))
|
||||||
|
- Faulty MangaUpdates score in database ([@AntsyLich](https://github.com/AntsyLich) ([`a024218`](https://github.com/mihonapp/mihon/commit/a024218410953a389b8af4880fa7ae6cc30124a2)
|
||||||
|
- Updating extension not reflecting correctly ([@AntsyLich](https://github.com/AntsyLich)) ([`cb06898`](https://github.com/mihonapp/mihon/commit/cb068984303f811692531bf6f14902ae118d8ac7))
|
||||||
|
- Inconsistent button height in "Data and storage" for some languages ([@theolm](https://github.com/theolm)) ([#202](https://github.com/mihonapp/mihon/pull/202))
|
||||||
|
- Chapter not being marked as read locally when refreshing Enhanced Trackers ([@Secozzi](https://github.com/Secozzi)) ([#219](https://github.com/mihonapp/mihon/pull/219))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Make `last_modified_at` field in database be `0` on insert ([@kaiserbh](https://github.com/kaiserbh)) ([#113](https://github.com/mihonapp/mihon/pull/113))
|
||||||
|
- Remove usage of `.not()` where possible in code ([@AntsyLich](https://github.com/AntsyLich)) ([`3940740`](https://github.com/mihonapp/mihon/commit/39407407f282dbb7fa972b12053c26b3e3bd66d8))
|
||||||
|
- Use type-safe project accessors ([@theolm](https://github.com/theolm)) ([#194](https://github.com/mihonapp/mihon/pull/194))
|
||||||
|
- Legacy tracker model properties now has the same type as the domain ones ([@AntsyLich](https://github.com/AntsyLich)) ([#245](https://github.com/mihonapp/mihon/pull/245))
|
||||||
|
|
||||||
|
## [v0.16.1] - 2024-01-18
|
||||||
|
### Changed
|
||||||
|
- Branding to Mihon (for references we missed) ([@AntsyLich](https://github.com/AntsyLich)) ([`6539406`](https://github.com/mihonapp/mihon/commit/653940613d661eb371aab3b3c3a8181e4e308c43))
|
||||||
|
- Preview builds are now called Beta builds ([@AntsyLich](https://github.com/AntsyLich)) ([`3c3a1cd`](https://github.com/mihonapp/mihon/commit/3c3a1cd448ab1f653ddd12b2afe0cba38968d1b9))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- App icon not following the [specification](https://developer.android.com/develop/ui/views/launch/icon_design_adaptive) ([@AntsyLich](https://github.com/AntsyLich)) ([`1849715`](https://github.com/mihonapp/mihon/commit/18497154183356bb0d469b27827f9f7d6b7a3130))
|
||||||
|
- MangaUpdates default score being set to -1.0 ([@AntsyLich](https://github.com/AntsyLich)) ([`99fd273`](https://github.com/mihonapp/mihon/commit/99fd2731f5d9d374700e89fa67d4d5bf611bbafa))
|
||||||
|
|
||||||
|
## [v0.16.0] - 2024-01-16
|
||||||
|
### Changed
|
||||||
|
- Branding to Mihon ([@AntsyLich](https://github.com/AntsyLich))
|
||||||
|
- Minimum supported Android version to 8 ([@AntsyLich](https://github.com/AntsyLich)) ([`dfb3091`](https://github.com/mihonapp/mihon/commit/dfb3091e380dda3e9bfb64bf5c9a685cf3a03d0e))
|
||||||
|
|
||||||
|
[unreleased]: https://github.com/mihonapp/mihon/compare/v0.17.0...main
|
||||||
|
[v0.17.0]: https://github.com/mihonapp/mihon/compare/v0.16.5...v0.17.0
|
||||||
|
[v0.16.5]: https://github.com/mihonapp/mihon/compare/v0.16.4...v0.16.5
|
||||||
|
[v0.16.4]: https://github.com/mihonapp/mihon/compare/v0.16.3...v0.16.4
|
||||||
|
[v0.16.3]: https://github.com/mihonapp/mihon/compare/v0.16.2...v0.16.3
|
||||||
|
[v0.16.2]: https://github.com/mihonapp/mihon/compare/v0.16.1...v0.16.2
|
||||||
|
[v0.16.1]: https://github.com/mihonapp/mihon/compare/v0.16.0...v0.16.1
|
||||||
|
[v0.16.0]: https://github.com/mihonapp/mihon/compare/a9c7cbf...v0.16.0
|
@ -24,10 +24,6 @@ Before you start, please note that the ability to use following technologies is
|
|||||||
- [Android Studio](https://developer.android.com/studio)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
## Linting
|
|
||||||
|
|
||||||
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
- Join [the Discord server](https://discord.gg/mihon) for online help and to ask questions while developing.
|
||||||
|
45
README.md
45
README.md
@ -29,7 +29,7 @@ Discover and read manga, webtoons, comics, and more – easier than ever on your
|
|||||||
|
|
||||||
* Local reading of content.
|
* Local reading of content.
|
||||||
* A configurable reader with multiple viewers, reading directions and other settings.
|
* 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.
|
* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.app/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) support.
|
||||||
* Categories to organize your library.
|
* Categories to organize your library.
|
||||||
* Light and dark themes.
|
* Light and dark themes.
|
||||||
* Schedule updating your library for new chapters.
|
* Schedule updating your library for new chapters.
|
||||||
@ -44,40 +44,13 @@ Discover and read manga, webtoons, comics, and more – easier than ever on your
|
|||||||
|
|
||||||
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
||||||
|
|
||||||
If you got any questions, [join our Discord server](https://discord.gg/mihon).
|
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); if you got any questions, join our [Discord server](https://discord.gg/mihon).
|
||||||
|
|
||||||
<details align="center"><summary>Issues</summary><div align="left">
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
</div></details>
|
|
||||||
|
|
||||||
<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)
|
|
||||||
|
|
||||||
</div></details>
|
|
||||||
|
|
||||||
<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";
|
|
||||||
* Include screenshot (if needed)
|
|
||||||
* Source requests are not accepted.
|
|
||||||
|
|
||||||
</div></details>
|
|
||||||
|
|
||||||
### Repositories
|
### Repositories
|
||||||
|
|
||||||
[](https://github.com/mihonapp/website/)
|
[](https://github.com/mihonapp/website/)
|
||||||
|
[](https://github.com/mihonapp/bitmap.kt/)
|
||||||
|
|
||||||
### Credits
|
### Credits
|
||||||
|
|
||||||
@ -93,8 +66,10 @@ The developer(s) of this application does not have any affiliation with the cont
|
|||||||
|
|
||||||
### License
|
### License
|
||||||
|
|
||||||
```
|
<pre>
|
||||||
Copyright © 2015 Javier Tomás
|
Copyright © 2015 Javier Tomás
|
||||||
|
Copyright © 2024 The Mihon Open Source Project
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "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 not use this file except in compliance with the License.
|
||||||
You may obtain a copy of the License at
|
You may obtain a copy of the License at
|
||||||
@ -106,8 +81,6 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
|||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
</pre>
|
||||||
|
|
||||||
Modifications Copyright © 2024 The Mihon Open Source Project
|
</div>
|
||||||
```
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
/build
|
|
||||||
*iml
|
|
||||||
*.iml
|
|
@ -1,20 +1,26 @@
|
|||||||
|
import mihon.buildlogic.getBuildTime
|
||||||
|
import mihon.buildlogic.getCommitCount
|
||||||
|
import mihon.buildlogic.getGitSha
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application")
|
id("mihon.android.application")
|
||||||
id("com.mikepenz.aboutlibraries.plugin")
|
id("mihon.android.application.compose")
|
||||||
kotlin("android")
|
|
||||||
kotlin("plugin.serialization")
|
|
||||||
id("com.github.zellius.shortcut-helper")
|
id("com.github.zellius.shortcut-helper")
|
||||||
|
kotlin("plugin.serialization")
|
||||||
|
alias(libs.plugins.aboutLibraries)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
if (gradle.startParameter.taskRequests.toString().contains("Standard")) {
|
||||||
apply<com.google.gms.googleservices.GoogleServicesPlugin>()
|
pluginManager.apply {
|
||||||
|
apply(libs.plugins.google.services.get().pluginId)
|
||||||
|
apply(libs.plugins.firebase.crashlytics.get().pluginId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcutHelper.setFilePath("./shortcuts.xml")
|
shortcutHelper.setFilePath("./shortcuts.xml")
|
||||||
|
|
||||||
val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
val supportedAbis = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.kanade.tachiyomi"
|
namespace = "eu.kanade.tachiyomi"
|
||||||
@ -22,8 +28,8 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "app.mihon"
|
applicationId = "app.mihon"
|
||||||
|
|
||||||
versionCode = 4
|
versionCode = 8
|
||||||
versionName = "0.16.3"
|
versionName = "0.17.0"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
@ -32,7 +38,7 @@ android {
|
|||||||
buildConfigField("boolean", "PREVIEW", "false")
|
buildConfigField("boolean", "PREVIEW", "false")
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += SUPPORTED_ABIS
|
abiFilters += supportedAbis
|
||||||
}
|
}
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@ -42,7 +48,7 @@ android {
|
|||||||
abi {
|
abi {
|
||||||
isEnable = true
|
isEnable = true
|
||||||
reset()
|
reset()
|
||||||
include(*SUPPORTED_ABIS.toTypedArray())
|
include(*supportedAbis.toTypedArray())
|
||||||
isUniversalApk = true
|
isUniversalApk = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -102,13 +108,16 @@ android {
|
|||||||
packaging {
|
packaging {
|
||||||
resources.excludes.addAll(
|
resources.excludes.addAll(
|
||||||
listOf(
|
listOf(
|
||||||
|
"kotlin-tooling-metadata.json",
|
||||||
"META-INF/DEPENDENCIES",
|
"META-INF/DEPENDENCIES",
|
||||||
"LICENSE.txt",
|
"LICENSE.txt",
|
||||||
"META-INF/LICENSE",
|
"META-INF/LICENSE",
|
||||||
"META-INF/LICENSE.txt",
|
"META-INF/**/LICENSE.txt",
|
||||||
|
"META-INF/*.properties",
|
||||||
|
"META-INF/**/*.properties",
|
||||||
"META-INF/README.md",
|
"META-INF/README.md",
|
||||||
"META-INF/NOTICE",
|
"META-INF/NOTICE",
|
||||||
"META-INF/*.kotlin_module",
|
"META-INF/*.version",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -119,7 +128,6 @@ android {
|
|||||||
|
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding = true
|
viewBinding = true
|
||||||
compose = true
|
|
||||||
buildConfig = true
|
buildConfig = true
|
||||||
|
|
||||||
// Disable some unused things
|
// Disable some unused things
|
||||||
@ -132,14 +140,11 @@ android {
|
|||||||
abortOnError = false
|
abortOnError = false
|
||||||
checkReleaseBuilds = false
|
checkReleaseBuilds = false
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion = compose.versions.compiler.get()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.i18n)
|
implementation(projects.i18n)
|
||||||
|
implementation(projects.core.archive)
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
implementation(projects.coreMetadata)
|
implementation(projects.coreMetadata)
|
||||||
implementation(projects.sourceApi)
|
implementation(projects.sourceApi)
|
||||||
@ -150,19 +155,17 @@ dependencies {
|
|||||||
implementation(projects.presentationWidget)
|
implementation(projects.presentationWidget)
|
||||||
|
|
||||||
// Compose
|
// Compose
|
||||||
implementation(platform(compose.bom))
|
|
||||||
implementation(compose.activity)
|
implementation(compose.activity)
|
||||||
implementation(compose.foundation)
|
implementation(compose.foundation)
|
||||||
implementation(compose.material3.core)
|
implementation(compose.material3.core)
|
||||||
implementation(compose.material.core)
|
|
||||||
implementation(compose.material.icons)
|
implementation(compose.material.icons)
|
||||||
implementation(compose.animation)
|
implementation(compose.animation)
|
||||||
implementation(compose.animation.graphics)
|
implementation(compose.animation.graphics)
|
||||||
debugImplementation(compose.ui.tooling)
|
debugImplementation(compose.ui.tooling)
|
||||||
implementation(compose.ui.tooling.preview)
|
implementation(compose.ui.tooling.preview)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
implementation(compose.accompanist.webview)
|
|
||||||
implementation(compose.accompanist.systemuicontroller)
|
implementation(androidx.interpolator)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
@ -208,13 +211,12 @@ dependencies {
|
|||||||
// Disk
|
// Disk
|
||||||
implementation(libs.disklrucache)
|
implementation(libs.disklrucache)
|
||||||
implementation(libs.unifile)
|
implementation(libs.unifile)
|
||||||
implementation(libs.junrar)
|
|
||||||
|
|
||||||
// Preferences
|
// Preferences
|
||||||
implementation(libs.preferencektx)
|
implementation(libs.preferencektx)
|
||||||
|
|
||||||
// Dependency injection
|
// Dependency injection
|
||||||
implementation(libs.injekt.core)
|
implementation(libs.injekt)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
implementation(platform(libs.coil.bom))
|
implementation(platform(libs.coil.bom))
|
||||||
@ -237,12 +239,16 @@ dependencies {
|
|||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.compose.materialmotion)
|
implementation(libs.compose.materialmotion)
|
||||||
implementation(libs.swipe)
|
implementation(libs.swipe)
|
||||||
|
implementation(libs.compose.webview)
|
||||||
|
implementation(libs.compose.grid)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
|
||||||
// Crash reports/analytics
|
// Crash reports/analytics
|
||||||
|
"standardImplementation"(platform(libs.firebase.bom))
|
||||||
"standardImplementation"(libs.firebase.analytics)
|
"standardImplementation"(libs.firebase.analytics)
|
||||||
|
"standardImplementation"(libs.firebase.crashlytics)
|
||||||
|
|
||||||
// Shizuku
|
// Shizuku
|
||||||
implementation(libs.bundles.shizuku)
|
implementation(libs.bundles.shizuku)
|
||||||
@ -253,6 +259,8 @@ dependencies {
|
|||||||
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
// For detecting memory leaks; see https://square.github.io/leakcanary/
|
||||||
// debugImplementation(libs.leakcanary.android)
|
// debugImplementation(libs.leakcanary.android)
|
||||||
implementation(libs.leakcanary.plumber)
|
implementation(libs.leakcanary.plumber)
|
||||||
|
|
||||||
|
testImplementation(kotlinx.coroutines.test)
|
||||||
}
|
}
|
||||||
|
|
||||||
androidComponents {
|
androidComponents {
|
||||||
@ -274,7 +282,7 @@ androidComponents {
|
|||||||
tasks {
|
tasks {
|
||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
compilerOptions.freeCompilerArgs.addAll(
|
||||||
"-Xcontext-receivers",
|
"-Xcontext-receivers",
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
@ -284,26 +292,12 @@ tasks {
|
|||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil3.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
)
|
)
|
||||||
|
|
||||||
if (project.findProperty("tachiyomi.enableComposeCompilerMetrics") == "true") {
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
|
||||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
|
||||||
)
|
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
|
||||||
"-P",
|
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
|
||||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
-keep,allowoptimization class eu.kanade.**
|
-keep,allowoptimization class eu.kanade.**
|
||||||
-keep,allowoptimization class tachiyomi.**
|
-keep,allowoptimization class tachiyomi.**
|
||||||
|
-keep,allowoptimization class mihon.**
|
||||||
|
|
||||||
# Keep common dependencies used in extensions
|
# Keep common dependencies used in extensions
|
||||||
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
-keep,allowoptimization class androidx.preference.** { public protected *; }
|
||||||
@ -43,6 +44,10 @@
|
|||||||
-dontnote rx.internal.util.PlatformDependent
|
-dontnote rx.internal.util.PlatformDependent
|
||||||
##---------------End: proguard configuration for RxJava 1.x ----------
|
##---------------End: proguard configuration for RxJava 1.x ----------
|
||||||
|
|
||||||
|
##---------------Begin: proguard configuration for okhttp ----------
|
||||||
|
-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; }
|
||||||
|
##---------------End: proguard configuration for okhttp ----------
|
||||||
|
|
||||||
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
##---------------Begin: proguard configuration for kotlinx.serialization ----------
|
||||||
-keepattributes *Annotation*, InnerClasses
|
-keepattributes *Annotation*, InnerClasses
|
||||||
-dontnote kotlinx.serialization.** # core serialization annotations
|
-dontnote kotlinx.serialization.** # core serialization annotations
|
||||||
|
11
app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
Normal file
11
app/src/dev/java/mihon/core/firebase/FirebaseConfig.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package mihon.core.firebase
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
object FirebaseConfig {
|
||||||
|
fun init(context: Context) = Unit
|
||||||
|
|
||||||
|
fun setAnalyticsEnabled(enabled: Boolean) = Unit
|
||||||
|
|
||||||
|
fun setCrashlyticsEnabled(enabled: Boolean) = Unit
|
||||||
|
}
|
@ -33,11 +33,6 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||||
|
|
||||||
<!-- Remove permission from Firebase dependency -->
|
|
||||||
<uses-permission
|
|
||||||
android:name="com.google.android.gms.permission.AD_ID"
|
|
||||||
tools:node="remove" />
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
@ -236,11 +231,6 @@
|
|||||||
android:name="android.webkit.WebView.MetricsOptOut"
|
android:name="android.webkit.WebView.MetricsOptOut"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
|
||||||
<!-- Disable advertising ID collection for Firebase -->
|
|
||||||
<meta-data
|
|
||||||
android:name="google_analytics_adid_collection_enabled"
|
|
||||||
android:value="false" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -5,7 +5,7 @@ import kotlin.contracts.ExperimentalContracts
|
|||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
generator: (T?, T?) -> R?,
|
generator: (before: T?, after: T?) -> R?,
|
||||||
): List<R> {
|
): List<R> {
|
||||||
if (isEmpty()) return emptyList()
|
if (isEmpty()) return emptyList()
|
||||||
val newList = mutableListOf<R>()
|
val newList = mutableListOf<R>()
|
||||||
@ -19,6 +19,24 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||||||
return newList
|
return newList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to [eu.kanade.core.util.insertSeparators] but iterates from last to first element
|
||||||
|
*/
|
||||||
|
fun <T : R, R : Any> List<T>.insertSeparatorsReversed(
|
||||||
|
generator: (before: T?, after: T?) -> R?,
|
||||||
|
): List<R> {
|
||||||
|
if (isEmpty()) return emptyList()
|
||||||
|
val newList = mutableListOf<R>()
|
||||||
|
for (i in size downTo 0) {
|
||||||
|
val after = getOrNull(i)
|
||||||
|
after?.let(newList::add)
|
||||||
|
val before = getOrNull(i - 1)
|
||||||
|
val separator = generator.invoke(before, after)
|
||||||
|
separator?.let(newList::add)
|
||||||
|
}
|
||||||
|
return newList.asReversed()
|
||||||
|
}
|
||||||
|
|
||||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
|
13
app/src/main/java/eu/kanade/core/util/SourceUtil.kt
Normal file
13
app/src/main/java/eu/kanade/core/util/SourceUtil.kt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ifSourcesLoaded(): Boolean {
|
||||||
|
return remember { Injekt.get<SourceManager>().isInitialized }.collectAsState().value
|
||||||
|
}
|
@ -4,10 +4,7 @@ import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
|||||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||||
import eu.kanade.domain.download.interactor.DeleteDownload
|
import eu.kanade.domain.download.interactor.DeleteDownload
|
||||||
import eu.kanade.domain.extension.interactor.CreateExtensionRepo
|
|
||||||
import eu.kanade.domain.extension.interactor.DeleteExtensionRepo
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
import eu.kanade.domain.extension.interactor.GetExtensionLanguages
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
import eu.kanade.domain.extension.interactor.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
import eu.kanade.domain.extension.interactor.GetExtensionsByType
|
||||||
import eu.kanade.domain.extension.interactor.TrustExtension
|
import eu.kanade.domain.extension.interactor.TrustExtension
|
||||||
@ -26,6 +23,17 @@ import eu.kanade.domain.track.interactor.AddTracks
|
|||||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||||
import eu.kanade.domain.track.interactor.TrackChapter
|
import eu.kanade.domain.track.interactor.TrackChapter
|
||||||
|
import mihon.data.repository.ExtensionRepoRepositoryImpl
|
||||||
|
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||||
|
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||||
|
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
|
import mihon.domain.extensionrepo.service.ExtensionRepoService
|
||||||
|
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||||
import tachiyomi.data.history.HistoryRepositoryImpl
|
import tachiyomi.data.history.HistoryRepositoryImpl
|
||||||
@ -111,6 +119,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { GetMangaByUrlAndSourceId(get()) }
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
|
addFactory { GetUpcomingManga(get()) }
|
||||||
addFactory { ResetViewerFlags(get()) }
|
addFactory { ResetViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
addFactory { FetchInterval(get()) }
|
addFactory { FetchInterval(get()) }
|
||||||
@ -144,6 +153,7 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { GetAvailableScanlators(get()) }
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
addFactory { FilterChaptersForDownload(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetHistory(get()) }
|
addFactory { GetHistory(get()) }
|
||||||
@ -171,10 +181,15 @@ class DomainModule : InjektModule {
|
|||||||
addFactory { ToggleLanguage(get()) }
|
addFactory { ToggleLanguage(get()) }
|
||||||
addFactory { ToggleSource(get()) }
|
addFactory { ToggleSource(get()) }
|
||||||
addFactory { ToggleSourcePin(get()) }
|
addFactory { ToggleSourcePin(get()) }
|
||||||
addFactory { TrustExtension(get()) }
|
addFactory { TrustExtension(get(), get()) }
|
||||||
|
|
||||||
addFactory { CreateExtensionRepo(get()) }
|
addSingletonFactory<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
|
||||||
|
addFactory { ExtensionRepoService(get(), get()) }
|
||||||
|
addFactory { GetExtensionRepo(get()) }
|
||||||
|
addFactory { GetExtensionRepoCount(get()) }
|
||||||
|
addFactory { CreateExtensionRepo(get(), get()) }
|
||||||
addFactory { DeleteExtensionRepo(get()) }
|
addFactory { DeleteExtensionRepo(get()) }
|
||||||
addFactory { GetExtensionRepos(get()) }
|
addFactory { ReplaceExtensionRepo(get()) }
|
||||||
|
addFactory { UpdateExtensionRepo(get(), get()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,4 +28,6 @@ class BasePreferences(
|
|||||||
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
SHIZUKU(MR.strings.ext_installer_shizuku, false),
|
||||||
PRIVATE(MR.strings.ext_installer_private, false),
|
PRIVATE(MR.strings.ext_installer_private, false),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun displayProfile() = preferenceStore.getString("pref_display_profile_key", "")
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,10 @@ class SyncChaptersWithSource(
|
|||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||||
downloadManager.isChapterDownloaded(
|
downloadManager.isChapterDownloaded(
|
||||||
dbChapter.name, dbChapter.scanlator, manga.title, manga.source,
|
dbChapter.name,
|
||||||
|
dbChapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
)
|
)
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import tachiyomi.core.common.preference.plusAssign
|
|
||||||
|
|
||||||
class CreateExtensionRepo(private val preferences: SourcePreferences) {
|
|
||||||
|
|
||||||
fun await(name: String): Result {
|
|
||||||
// Do not allow invalid formats
|
|
||||||
if (!name.matches(repoRegex)) {
|
|
||||||
return Result.InvalidUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
preferences.extensionRepos() += name.removeSuffix("/index.min.json")
|
|
||||||
|
|
||||||
return Result.Success
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface Result {
|
|
||||||
data object InvalidUrl : Result
|
|
||||||
data object Success : Result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val repoRegex = """^https://.*/index\.min\.json$""".toRegex()
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import tachiyomi.core.common.preference.minusAssign
|
|
||||||
|
|
||||||
class DeleteExtensionRepo(private val preferences: SourcePreferences) {
|
|
||||||
|
|
||||||
fun await(repo: String) {
|
|
||||||
preferences.extensionRepos() -= repo
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.kanade.domain.extension.interactor
|
|
||||||
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
class GetExtensionRepos(private val preferences: SourcePreferences) {
|
|
||||||
|
|
||||||
fun subscribe(): Flow<Set<String>> {
|
|
||||||
return preferences.extensionRepos().changes()
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,7 +20,7 @@ class GetExtensionsByType(
|
|||||||
extensionManager.installedExtensionsFlow,
|
extensionManager.installedExtensionsFlow,
|
||||||
extensionManager.untrustedExtensionsFlow,
|
extensionManager.untrustedExtensionsFlow,
|
||||||
extensionManager.availableExtensionsFlow,
|
extensionManager.availableExtensionsFlow,
|
||||||
) { _activeLanguages, _installed, _untrusted, _available ->
|
) { enabledLanguages, _installed, _untrusted, _available ->
|
||||||
val (updates, installed) = _installed
|
val (updates, installed) = _installed
|
||||||
.filter { (showNsfwSources || !it.isNsfw) }
|
.filter { (showNsfwSources || !it.isNsfw) }
|
||||||
.sortedWith(
|
.sortedWith(
|
||||||
@ -40,9 +40,9 @@ class GetExtensionsByType(
|
|||||||
}
|
}
|
||||||
.flatMap { ext ->
|
.flatMap { ext ->
|
||||||
if (ext.sources.isEmpty()) {
|
if (ext.sources.isEmpty()) {
|
||||||
return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList()
|
return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList()
|
||||||
}
|
}
|
||||||
ext.sources.filter { it.lang in _activeLanguages }
|
ext.sources.filter { it.lang in enabledLanguages }
|
||||||
.map {
|
.map {
|
||||||
ext.copy(
|
ext.copy(
|
||||||
name = it.name,
|
name = it.name,
|
||||||
|
@ -3,15 +3,18 @@ package eu.kanade.domain.extension.interactor
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
|
import mihon.domain.extensionrepo.repository.ExtensionRepoRepository
|
||||||
import tachiyomi.core.common.preference.getAndSet
|
import tachiyomi.core.common.preference.getAndSet
|
||||||
|
|
||||||
class TrustExtension(
|
class TrustExtension(
|
||||||
|
private val extensionRepoRepository: ExtensionRepoRepository,
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean {
|
suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List<String>): Boolean {
|
||||||
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash"
|
val trustedFingerprints = extensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet()
|
||||||
return key in preferences.trustedExtensions().get()
|
val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}"
|
||||||
|
return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
|
||||||
@ -19,9 +22,7 @@ class TrustExtension(
|
|||||||
// Remove previously trusted versions
|
// Remove previously trusted versions
|
||||||
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
|
||||||
|
|
||||||
removed.also {
|
removed.also { it += "$pkgName:$versionCode:$signatureHash" }
|
||||||
it += "$pkgName:$versionCode:$signatureHash"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +95,13 @@ fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
|||||||
/**
|
/**
|
||||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||||
*/
|
*/
|
||||||
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories: List<String>?) = ComicInfo(
|
fun getComicInfo(
|
||||||
|
manga: Manga,
|
||||||
|
chapter: Chapter,
|
||||||
|
urls: List<String>,
|
||||||
|
categories: List<String>?,
|
||||||
|
sourceName: String,
|
||||||
|
) = ComicInfo(
|
||||||
title = ComicInfo.Title(chapter.name),
|
title = ComicInfo.Title(chapter.name),
|
||||||
series = ComicInfo.Series(manga.title),
|
series = ComicInfo.Series(manga.title),
|
||||||
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
||||||
@ -105,7 +111,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories:
|
|||||||
ComicInfo.Number(it.toString())
|
ComicInfo.Number(it.toString())
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
web = ComicInfo.Web(chapterUrl),
|
web = ComicInfo.Web(urls.joinToString(" ")),
|
||||||
summary = manga.description?.let { ComicInfo.Summary(it) },
|
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||||
writer = manga.author?.let { ComicInfo.Writer(it) },
|
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||||
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
penciller = manga.artist?.let { ComicInfo.Penciller(it) },
|
||||||
@ -115,6 +121,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories:
|
|||||||
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||||
),
|
),
|
||||||
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||||
|
source = ComicInfo.SourceMihon(sourceName),
|
||||||
inker = null,
|
inker = null,
|
||||||
colorist = null,
|
colorist = null,
|
||||||
letterer = null,
|
letterer = null,
|
||||||
|
@ -48,4 +48,9 @@ class SourcePreferences(
|
|||||||
Preference.appStateKey("trusted_extensions"),
|
Preference.appStateKey("trusted_extensions"),
|
||||||
emptySet(),
|
emptySet(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun globalSearchFilterState() = preferenceStore.getBoolean(
|
||||||
|
Preference.appStateKey("has_filters_toggle_state"),
|
||||||
|
false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import eu.kanade.domain.track.model.toDomainTrack
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
|
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
@ -14,17 +15,16 @@ import tachiyomi.core.common.util.system.logcat
|
|||||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||||
import tachiyomi.domain.history.interactor.GetHistory
|
import tachiyomi.domain.history.interactor.GetHistory
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.domain.track.interactor.GetTracks
|
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
|
||||||
class AddTracks(
|
class AddTracks(
|
||||||
private val getTracks: GetTracks,
|
|
||||||
private val insertTrack: InsertTrack,
|
private val insertTrack: InsertTrack,
|
||||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val trackerManager: TrackerManager,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// TODO: update all trackers based on common data
|
// TODO: update all trackers based on common data
|
||||||
@ -79,7 +79,7 @@ class AddTracks(
|
|||||||
|
|
||||||
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
suspend fun bindEnhancedTrackers(manga: Manga, source: Source) = withNonCancellableContext {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
getTracks.await(manga.id)
|
trackerManager.loggedInTrackers()
|
||||||
.filterIsInstance<EnhancedTracker>()
|
.filterIsInstance<EnhancedTracker>()
|
||||||
.filter { it.accept(source) }
|
.filter { it.accept(source) }
|
||||||
.forEach { service ->
|
.forEach { service ->
|
||||||
@ -87,11 +87,11 @@ class AddTracks(
|
|||||||
service.match(manga)?.let { track ->
|
service.match(manga)?.let { track ->
|
||||||
track.manga_id = manga.id
|
track.manga_id = manga.id
|
||||||
(service as Tracker).bind(track)
|
(service as Tracker).bind(track)
|
||||||
insertTrack.await(track.toDomainTrack()!!)
|
insertTrack.await(track.toDomainTrack(idRequired = false)!!)
|
||||||
|
|
||||||
syncChapterProgressWithTrack.await(
|
syncChapterProgressWithTrack.await(
|
||||||
manga.id,
|
manga.id,
|
||||||
track.toDomainTrack()!!,
|
track.toDomainTrack(idRequired = false)!!,
|
||||||
service,
|
service,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import tachiyomi.domain.chapter.interactor.UpdateChapter
|
|||||||
import tachiyomi.domain.chapter.model.toChapterUpdate
|
import tachiyomi.domain.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.interactor.InsertTrack
|
import tachiyomi.domain.track.interactor.InsertTrack
|
||||||
import tachiyomi.domain.track.model.Track
|
import tachiyomi.domain.track.model.Track
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
class SyncChapterProgressWithTrack(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter,
|
private val updateChapter: UpdateChapter,
|
||||||
@ -36,7 +37,8 @@ class SyncChapterProgressWithTrack(
|
|||||||
|
|
||||||
// only take into account continuous reading
|
// only take into account continuous reading
|
||||||
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
val localLastRead = sortedChapters.takeWhile { it.read }.lastOrNull()?.chapterNumber ?: 0F
|
||||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
val lastRead = max(remoteTrack.lastChapterRead, localLastRead.toDouble())
|
||||||
|
val updatedTrack = remoteTrack.copy(lastChapterRead = lastRead)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
tracker.update(updatedTrack.toDbTrack())
|
tracker.update(updatedTrack.toDbTrack())
|
||||||
|
@ -21,7 +21,7 @@ class TrackChapter(
|
|||||||
private val delayedTrackingStore: DelayedTrackingStore,
|
private val delayedTrackingStore: DelayedTrackingStore,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
|
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double, setupJobOnFailure: Boolean = true) {
|
||||||
withNonCancellableContext {
|
withNonCancellableContext {
|
||||||
val tracks = getTracks.await(mangaId)
|
val tracks = getTracks.await(mangaId)
|
||||||
if (tracks.isEmpty()) return@withNonCancellableContext
|
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||||
@ -43,7 +43,9 @@ class TrackChapter(
|
|||||||
delayedTrackingStore.remove(track.id)
|
delayedTrackingStore.remove(track.id)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
delayedTrackingStore.add(track.id, chapterNumber)
|
delayedTrackingStore.add(track.id, chapterNumber)
|
||||||
DelayedTrackingUpdateJob.setupTask(context)
|
if (setupJobOnFailure) {
|
||||||
|
DelayedTrackingUpdateJob.setupTask(context)
|
||||||
|
}
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.domain.track.model
|
||||||
|
|
||||||
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import tachiyomi.i18n.MR
|
||||||
|
|
||||||
|
enum class AutoTrackState(val titleRes: StringResource) {
|
||||||
|
ALWAYS(MR.strings.lock_always),
|
||||||
|
ASK(MR.strings.default_category_summary),
|
||||||
|
NEVER(MR.strings.lock_never),
|
||||||
|
}
|
@ -45,7 +45,7 @@ class DelayedTrackingUpdateJob(private val context: Context, workerParams: Worke
|
|||||||
logcat(LogPriority.DEBUG) {
|
logcat(LogPriority.DEBUG) {
|
||||||
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
}
|
}
|
||||||
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
trackChapter.await(context, track.mangaId, track.lastChapterRead, setupJobOnFailure = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package eu.kanade.domain.track.service
|
package eu.kanade.domain.track.service
|
||||||
|
|
||||||
|
import eu.kanade.domain.track.model.AutoTrackState
|
||||||
import eu.kanade.tachiyomi.data.track.Tracker
|
import eu.kanade.tachiyomi.data.track.Tracker
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import tachiyomi.core.common.preference.Preference
|
import tachiyomi.core.common.preference.Preference
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
|
import tachiyomi.core.common.preference.getEnum
|
||||||
|
|
||||||
class TrackPreferences(
|
class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
private val preferenceStore: PreferenceStore,
|
||||||
@ -35,4 +37,9 @@ class TrackPreferences(
|
|||||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||||
|
|
||||||
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
fun autoUpdateTrack() = preferenceStore.getBoolean("pref_auto_update_manga_sync_key", true)
|
||||||
|
|
||||||
|
fun autoUpdateTrackOnMarkRead() = preferenceStore.getEnum(
|
||||||
|
"pref_auto_update_manga_on_mark_read",
|
||||||
|
AutoTrackState.ALWAYS,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,8 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
||||||
import tachiyomi.core.common.preference.PreferenceStore
|
import tachiyomi.core.common.preference.PreferenceStore
|
||||||
import tachiyomi.core.common.preference.getEnum
|
import tachiyomi.core.common.preference.getEnum
|
||||||
import java.text.DateFormat
|
import java.time.format.DateTimeFormatter
|
||||||
import java.text.SimpleDateFormat
|
import java.time.format.FormatStyle
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class UiPreferences(
|
class UiPreferences(
|
||||||
@ -19,7 +19,11 @@ class UiPreferences(
|
|||||||
|
|
||||||
fun appTheme() = preferenceStore.getEnum(
|
fun appTheme() = preferenceStore.getEnum(
|
||||||
"pref_app_theme",
|
"pref_app_theme",
|
||||||
if (DeviceUtil.isDynamicColorAvailable) { AppTheme.MONET } else { AppTheme.DEFAULT },
|
if (DeviceUtil.isDynamicColorAvailable) {
|
||||||
|
AppTheme.MONET
|
||||||
|
} else {
|
||||||
|
AppTheme.DEFAULT
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||||
@ -31,9 +35,9 @@ class UiPreferences(
|
|||||||
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
fun tabletUiMode() = preferenceStore.getEnum("tablet_ui_mode", TabletUiMode.AUTOMATIC)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun dateFormat(format: String): DateFormat = when (format) {
|
fun dateFormat(format: String): DateTimeFormatter = when (format) {
|
||||||
"" -> DateFormat.getDateInstance(DateFormat.SHORT)
|
"" -> DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
|
||||||
else -> SimpleDateFormat(format, Locale.getDefault())
|
else -> DateTimeFormatter.ofPattern(format, Locale.getDefault())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import android.net.Uri
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@ -191,7 +190,7 @@ private fun ExtensionDetails(
|
|||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
@ -233,7 +232,7 @@ private fun DetailsHeader(
|
|||||||
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
Extension name: ${extension.name} (lang: ${extension.lang}; package: ${extension.pkgName})
|
||||||
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
Extension version: ${extension.versionName} (lib: ${extension.libVersion}; version code: ${extension.versionCode})
|
||||||
NSFW: ${extension.isNsfw}
|
NSFW: ${extension.isNsfw}
|
||||||
""".trimIndent()
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (extension is Extension.Installed) {
|
if (extension is Extension.Installed) {
|
||||||
@ -243,8 +242,8 @@ private fun DetailsHeader(
|
|||||||
Update available: ${extension.hasUpdate}
|
Update available: ${extension.hasUpdate}
|
||||||
Obsolete: ${extension.isObsolete}
|
Obsolete: ${extension.isObsolete}
|
||||||
Shared: ${extension.isShared}
|
Shared: ${extension.isShared}
|
||||||
Repository: ${extension.repoUrl}
|
Repository: ${extension.repoUrl}
|
||||||
""".trimIndent()
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -354,10 +353,8 @@ private fun InfoText(
|
|||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
|
|
||||||
val clickableModifier = if (onClick != null) {
|
val clickableModifier = if (onClick != null) {
|
||||||
Modifier.clickable(interactionSource, indication = null) { onClick() }
|
Modifier.clickable(interactionSource = null, indication = null, onClick = onClick)
|
||||||
} else {
|
} else {
|
||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ private fun ExtensionFilterContent(
|
|||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
@ -48,6 +48,7 @@ import eu.kanade.presentation.browse.components.ExtensionIcon
|
|||||||
import eu.kanade.presentation.components.WarningBanner
|
import eu.kanade.presentation.components.WarningBanner
|
||||||
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
import eu.kanade.presentation.manga.components.DotSeparatorNoSpaceText
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
@ -90,7 +91,7 @@ fun ExtensionScreen(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = { !state.isLoading },
|
enabled = !state.isLoading,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
@ -187,14 +188,14 @@ private fun ExtensionContent(
|
|||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ExtensionUiModel.Header.Text -> {
|
is ExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,13 +213,15 @@ private fun ExtensionContent(
|
|||||||
},
|
},
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Extension.Available -> onInstallExtension(it)
|
is Extension.Available -> onInstallExtension(it)
|
||||||
is Extension.Installed -> onOpenExtension(it)
|
is Extension.Installed -> onOpenExtension(it)
|
||||||
is Extension.Untrusted -> { trustState = it }
|
is Extension.Untrusted -> {
|
||||||
|
trustState = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
@ -240,7 +243,9 @@ private fun ExtensionContent(
|
|||||||
onOpenExtension(it)
|
onOpenExtension(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Extension.Untrusted -> { trustState = it }
|
is Extension.Untrusted -> {
|
||||||
|
trustState = it
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
import eu.kanade.presentation.browse.components.GlobalSearchCardRow
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||||
@ -79,6 +80,7 @@ internal fun GlobalSearchContent(
|
|||||||
} ?: source.name,
|
} ?: source.name,
|
||||||
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
subtitle = LocaleHelper.getLocalizedDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
|
modifier = Modifier.animateItem(),
|
||||||
) {
|
) {
|
||||||
when (result) {
|
when (result) {
|
||||||
SearchItemResult.Loading -> {
|
SearchItemResult.Loading -> {
|
||||||
|
@ -133,7 +133,7 @@ private fun MigrateSourceList(
|
|||||||
key = { (source, _) -> "migrate-${source.id}" },
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
) { (source, count) ->
|
) { (source, count) ->
|
||||||
MigrateSourceItem(
|
MigrateSourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = source,
|
source = source,
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
|
@ -10,6 +10,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import eu.kanade.presentation.browse.components.BaseSourceItem
|
import eu.kanade.presentation.browse.components.BaseSourceItem
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
import eu.kanade.tachiyomi.ui.browse.source.SourcesFilterScreenModel
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
@ -68,7 +69,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
SourcesFilterHeader(
|
SourcesFilterHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
language = language,
|
language = language,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
@ -81,7 +82,7 @@ private fun SourcesFilterContent(
|
|||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourcesFilterItem(
|
SourcesFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
source = source,
|
source = source,
|
||||||
enabled = "${source.id}" !in state.disabledSources,
|
enabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
|
@ -28,7 +28,7 @@ import tachiyomi.domain.source.model.Pin
|
|||||||
import tachiyomi.domain.source.model.Source
|
import tachiyomi.domain.source.model.Source
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
import tachiyomi.presentation.core.components.material.topSmallPaddingValues
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -74,12 +74,12 @@ fun SourcesScreen(
|
|||||||
when (model) {
|
when (model) {
|
||||||
is SourceUiModel.Header -> {
|
is SourceUiModel.Header -> {
|
||||||
SourceHeader(
|
SourceHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
language = model.language,
|
language = model.language,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is SourceUiModel.Item -> SourceItem(
|
is SourceUiModel.Item -> SourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
source = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
@ -148,7 +148,7 @@ private fun SourcePinButton(
|
|||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
MaterialTheme.colorScheme.onBackground.copy(
|
MaterialTheme.colorScheme.onBackground.copy(
|
||||||
alpha = SecondaryItemAlpha,
|
alpha = SECONDARY_ALPHA,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
val description = if (isPinned) MR.strings.action_unpin else MR.strings.action_pin
|
||||||
|
@ -25,7 +25,7 @@ import androidx.compose.ui.res.imageResource
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import eu.kanade.domain.source.model.icon
|
import eu.kanade.domain.source.model.icon
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
@ -127,7 +127,7 @@ private fun Extension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT): St
|
|||||||
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
value = try {
|
value = try {
|
||||||
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo
|
val appInfo = ExtensionLoader.getExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo!!
|
||||||
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||||
Result.Success(
|
Result.Success(
|
||||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||||
|
@ -32,9 +32,10 @@ fun GlobalSearchResultItem(
|
|||||||
title: String,
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Column {
|
Column(modifier = modifier) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
|
@ -107,7 +107,7 @@ private fun CategoryContent(
|
|||||||
key = { _, category -> "category-${category.id}" },
|
key = { _, category -> "category-${category.id}" },
|
||||||
) { index, category ->
|
) { index, category ->
|
||||||
CategoryListItem(
|
CategoryListItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
category = category,
|
category = category,
|
||||||
canMoveUp = index != 0,
|
canMoveUp = index != 0,
|
||||||
canMoveDown = index != categories.lastIndex,
|
canMoveDown = index != categories.lastIndex,
|
||||||
|
@ -10,8 +10,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryFloatingActionButton(
|
fun CategoryFloatingActionButton(
|
||||||
@ -23,7 +22,7 @@ fun CategoryFloatingActionButton(
|
|||||||
text = { Text(text = stringResource(MR.strings.action_add)) },
|
text = { Text(text = stringResource(MR.strings.action_add)) },
|
||||||
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) },
|
||||||
onClick = onCreate,
|
onClick = onCreate,
|
||||||
expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(),
|
expanded = lazyListState.shouldExpandFAB(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
import cafe.adriel.voyager.core.annotation.InternalVoyagerApi
|
||||||
@ -23,7 +21,6 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl
|
|||||||
@Composable
|
@Composable
|
||||||
fun NavigatorAdaptiveSheet(
|
fun NavigatorAdaptiveSheet(
|
||||||
screen: Screen,
|
screen: Screen,
|
||||||
tonalElevation: Dp = 1.dp,
|
|
||||||
enableSwipeDismiss: (Navigator) -> Boolean = { true },
|
enableSwipeDismiss: (Navigator) -> Boolean = { true },
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -31,7 +28,6 @@ fun NavigatorAdaptiveSheet(
|
|||||||
screen = screen,
|
screen = screen,
|
||||||
content = { sheetNavigator ->
|
content = { sheetNavigator ->
|
||||||
AdaptiveSheet(
|
AdaptiveSheet(
|
||||||
tonalElevation = tonalElevation,
|
|
||||||
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
enableSwipeDismiss = enableSwipeDismiss(sheetNavigator),
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
@ -73,7 +69,6 @@ fun NavigatorAdaptiveSheet(
|
|||||||
fun AdaptiveSheet(
|
fun AdaptiveSheet(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
tonalElevation: Dp = 1.dp,
|
|
||||||
enableSwipeDismiss: Boolean = true,
|
enableSwipeDismiss: Boolean = true,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -86,7 +81,6 @@ fun AdaptiveSheet(
|
|||||||
AdaptiveSheetImpl(
|
AdaptiveSheetImpl(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
isTabletUi = isTabletUi,
|
isTabletUi = isTabletUi,
|
||||||
tonalElevation = tonalElevation,
|
|
||||||
enableSwipeDismiss = enableSwipeDismiss,
|
enableSwipeDismiss = enableSwipeDismiss,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
) {
|
) {
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.TextFieldDefaults
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
@ -21,6 +20,7 @@ import androidx.compose.material3.LocalContentColor
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PlainTooltip
|
import androidx.compose.material3.PlainTooltip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextFieldDefaults
|
||||||
import androidx.compose.material3.TooltipBox
|
import androidx.compose.material3.TooltipBox
|
||||||
import androidx.compose.material3.TooltipDefaults
|
import androidx.compose.material3.TooltipDefaults
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
@ -179,7 +179,7 @@ fun AppBarTitle(
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.basicMarquee(
|
modifier = Modifier.basicMarquee(
|
||||||
delayMillis = 2_000,
|
repeatDelayMillis = 2_000,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -312,7 +312,7 @@ fun SearchToolbar(
|
|||||||
visualTransformation = visualTransformation,
|
visualTransformation = visualTransformation,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
TextFieldDefaults.TextFieldDecorationBox(
|
TextFieldDefaults.DecorationBox(
|
||||||
value = searchQuery,
|
value = searchQuery,
|
||||||
innerTextField = innerTextField,
|
innerTextField = innerTextField,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
@ -331,6 +331,7 @@ fun SearchToolbar(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
container = {},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -9,20 +9,26 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun relativeDateText(
|
fun relativeDateText(
|
||||||
dateEpochMillis: Long,
|
dateEpochMillis: Long,
|
||||||
): String {
|
): String {
|
||||||
return relativeDateText(
|
return relativeDateText(
|
||||||
date = Date(dateEpochMillis).takeIf { dateEpochMillis > 0L },
|
localDate = LocalDate.ofInstant(
|
||||||
|
Instant.ofEpochMilli(dateEpochMillis),
|
||||||
|
ZoneId.systemDefault(),
|
||||||
|
)
|
||||||
|
.takeIf { dateEpochMillis > 0L },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun relativeDateText(
|
fun relativeDateText(
|
||||||
date: Date?,
|
localDate: LocalDate?,
|
||||||
): String {
|
): String {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@ -30,11 +36,10 @@ fun relativeDateText(
|
|||||||
val relativeTime = remember { preferences.relativeTime().get() }
|
val relativeTime = remember { preferences.relativeTime().get() }
|
||||||
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
val dateFormat = remember { UiPreferences.dateFormat(preferences.dateFormat().get()) }
|
||||||
|
|
||||||
return date
|
return localDate?.toRelativeString(
|
||||||
?.toRelativeString(
|
context = context,
|
||||||
context = context,
|
relative = relativeTime,
|
||||||
relative = relativeTime,
|
dateFormat = dateFormat,
|
||||||
dateFormat = dateFormat,
|
)
|
||||||
)
|
|
||||||
?: stringResource(MR.strings.not_applicable)
|
?: stringResource(MR.strings.not_applicable)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.ColumnScope
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -29,7 +30,6 @@ import androidx.compose.ui.util.fastForEachIndexed
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@ -58,6 +58,7 @@ fun TabbedDialog(
|
|||||||
PrimaryTabRow(
|
PrimaryTabRow(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
selectedTabIndex = pagerState.currentPage,
|
selectedTabIndex = pagerState.currentPage,
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
divider = {},
|
divider = {},
|
||||||
) {
|
) {
|
||||||
tabTitles.fastForEachIndexed { index, tab ->
|
tabTitles.fastForEachIndexed { index, tab ->
|
||||||
@ -78,9 +79,8 @@ fun TabbedDialog(
|
|||||||
modifier = Modifier.animateContentSize(),
|
modifier = Modifier.animateContentSize(),
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
verticalAlignment = Alignment.Top,
|
verticalAlignment = Alignment.Top,
|
||||||
) { page ->
|
pageContent = { page -> content(page) },
|
||||||
content(page)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.calculateEndPadding
|
|||||||
import androidx.compose.foundation.layout.calculateStartPadding
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.PrimaryTabRow
|
import androidx.compose.material3.PrimaryTabRow
|
||||||
@ -24,7 +25,6 @@ import dev.icerock.moko.resources.StringResource
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.TabText
|
import tachiyomi.presentation.core.components.material.TabText
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
@ -37,7 +37,7 @@ fun CrashScreen(
|
|||||||
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
acceptText = stringResource(MR.strings.pref_dump_crash_logs),
|
||||||
onAcceptClick = {
|
onAcceptClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
CrashLogUtil(context).dumpLogs()
|
CrashLogUtil(context).dumpLogs(exception)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
rejectText = stringResource(MR.strings.crash_screen_restart_application),
|
||||||
|
@ -18,6 +18,7 @@ import eu.kanade.presentation.components.SearchToolbar
|
|||||||
import eu.kanade.presentation.components.relativeDateText
|
import eu.kanade.presentation.components.relativeDateText
|
||||||
import eu.kanade.presentation.history.components.HistoryItem
|
import eu.kanade.presentation.history.components.HistoryItem
|
||||||
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
import eu.kanade.presentation.theme.TachiyomiPreviewTheme
|
||||||
|
import eu.kanade.presentation.util.animateItemFastScroll
|
||||||
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
@ -28,7 +29,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
|
|||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
import java.util.Date
|
import java.time.LocalDate
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HistoryScreen(
|
fun HistoryScreen(
|
||||||
@ -113,14 +114,14 @@ private fun HistoryScreenContent(
|
|||||||
when (item) {
|
when (item) {
|
||||||
is HistoryUiModel.Header -> {
|
is HistoryUiModel.Header -> {
|
||||||
ListGroupHeader(
|
ListGroupHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
text = relativeDateText(item.date),
|
text = relativeDateText(item.date),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is HistoryUiModel.Item -> {
|
is HistoryUiModel.Item -> {
|
||||||
val value = item.item
|
val value = item.item
|
||||||
HistoryItem(
|
HistoryItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItemFastScroll(),
|
||||||
history = value,
|
history = value,
|
||||||
onClickCover = { onClickCover(value) },
|
onClickCover = { onClickCover(value) },
|
||||||
onClickResume = { onClickResume(value) },
|
onClickResume = { onClickResume(value) },
|
||||||
@ -133,7 +134,7 @@ private fun HistoryScreenContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sealed interface HistoryUiModel {
|
sealed interface HistoryUiModel {
|
||||||
data class Header(val date: Date) : HistoryUiModel
|
data class Header(val date: LocalDate) : HistoryUiModel
|
||||||
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
data class Item(val item: HistoryWithRelations) : HistoryUiModel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.ui.history.HistoryScreenModel
|
|||||||
import tachiyomi.domain.history.model.HistoryWithRelations
|
import tachiyomi.domain.history.model.HistoryWithRelations
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.time.LocalDate
|
||||||
import java.time.temporal.ChronoUnit
|
import java.time.temporal.ChronoUnit
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@ -71,10 +72,10 @@ class HistoryScreenModelStateProvider : PreviewParameterProvider<HistoryScreenMo
|
|||||||
private object HistoryUiModelExamples {
|
private object HistoryUiModelExamples {
|
||||||
val headerToday = header()
|
val headerToday = header()
|
||||||
val headerTomorrow =
|
val headerTomorrow =
|
||||||
HistoryUiModel.Header(Date.from(Instant.now().plus(1, ChronoUnit.DAYS)))
|
HistoryUiModel.Header(LocalDate.now().plusDays(1))
|
||||||
|
|
||||||
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
fun header(instantBuilder: (Instant) -> Instant = { it }) =
|
||||||
HistoryUiModel.Header(Date.from(instantBuilder(Instant.now())))
|
HistoryUiModel.Header(LocalDate.from(instantBuilder(Instant.now())))
|
||||||
|
|
||||||
fun items() = sequence {
|
fun items() = sequence {
|
||||||
var count = 1
|
var count = 1
|
||||||
|
@ -6,9 +6,12 @@ import androidx.compose.foundation.layout.ColumnScope
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.FilterChip
|
import androidx.compose.material3.FilterChip
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -26,6 +29,7 @@ import tachiyomi.domain.library.model.LibrarySort
|
|||||||
import tachiyomi.domain.library.model.sort
|
import tachiyomi.domain.library.model.sort
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.BaseSortItem
|
||||||
import tachiyomi.presentation.core.components.CheckboxItem
|
import tachiyomi.presentation.core.components.CheckboxItem
|
||||||
import tachiyomi.presentation.core.components.HeadingItem
|
import tachiyomi.presentation.core.components.HeadingItem
|
||||||
import tachiyomi.presentation.core.components.SettingsChipRow
|
import tachiyomi.presentation.core.components.SettingsChipRow
|
||||||
@ -125,7 +129,7 @@ private fun ColumnScope.FilterPage(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val trackers = remember { screenModel.trackers }
|
val trackers by screenModel.trackersFlow.collectAsState()
|
||||||
when (trackers.size) {
|
when (trackers.size) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// No trackers
|
// No trackers
|
||||||
@ -158,26 +162,42 @@ private fun ColumnScope.SortPage(
|
|||||||
category: Category?,
|
category: Category?,
|
||||||
screenModel: LibrarySettingsScreenModel,
|
screenModel: LibrarySettingsScreenModel,
|
||||||
) {
|
) {
|
||||||
|
val trackers by screenModel.trackersFlow.collectAsState()
|
||||||
val sortingMode = category.sort.type
|
val sortingMode = category.sort.type
|
||||||
val sortDescending = !category.sort.isAscending
|
val sortDescending = !category.sort.isAscending
|
||||||
|
|
||||||
val trackerSortOption =
|
val options = remember(trackers.isEmpty()) {
|
||||||
if (screenModel.trackers.isEmpty()) {
|
val trackerMeanPair = if (trackers.isNotEmpty()) {
|
||||||
emptyList()
|
MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean
|
||||||
} else {
|
} else {
|
||||||
listOf(MR.strings.action_sort_tracker_score to LibrarySort.Type.TrackerMean)
|
null
|
||||||
}
|
}
|
||||||
|
listOfNotNull(
|
||||||
|
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
||||||
|
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
|
||||||
|
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
|
||||||
|
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
||||||
|
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
||||||
|
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
||||||
|
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
||||||
|
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
|
||||||
|
trackerMeanPair,
|
||||||
|
MR.strings.action_sort_random to LibrarySort.Type.Random,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
listOf(
|
options.map { (titleRes, mode) ->
|
||||||
MR.strings.action_sort_alpha to LibrarySort.Type.Alphabetical,
|
if (mode == LibrarySort.Type.Random) {
|
||||||
MR.strings.action_sort_total to LibrarySort.Type.TotalChapters,
|
BaseSortItem(
|
||||||
MR.strings.action_sort_last_read to LibrarySort.Type.LastRead,
|
label = stringResource(titleRes),
|
||||||
MR.strings.action_sort_last_manga_update to LibrarySort.Type.LastUpdate,
|
icon = Icons.Default.Refresh
|
||||||
MR.strings.action_sort_unread_count to LibrarySort.Type.UnreadCount,
|
.takeIf { sortingMode == LibrarySort.Type.Random },
|
||||||
MR.strings.action_sort_latest_chapter to LibrarySort.Type.LatestChapter,
|
onClick = {
|
||||||
MR.strings.action_sort_chapter_fetch_date to LibrarySort.Type.ChapterFetchDate,
|
screenModel.setSort(category, mode, LibrarySort.Direction.Ascending)
|
||||||
MR.strings.action_sort_date_added to LibrarySort.Type.DateAdded,
|
},
|
||||||
).plus(trackerSortOption).map { (titleRes, mode) ->
|
)
|
||||||
|
return@map
|
||||||
|
}
|
||||||
SortItem(
|
SortItem(
|
||||||
label = stringResource(titleRes),
|
label = stringResource(titleRes),
|
||||||
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
sortDescending = sortDescending.takeIf { sortingMode == mode },
|
||||||
|
@ -35,6 +35,7 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.Shadow
|
import androidx.compose.ui.graphics.Shadow
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import eu.kanade.presentation.manga.components.MangaCover
|
import eu.kanade.presentation.manga.components.MangaCover
|
||||||
@ -42,19 +43,26 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.components.BadgeGroup
|
import tachiyomi.presentation.core.components.BadgeGroup
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
|
import tachiyomi.domain.manga.model.MangaCover as MangaCoverModel
|
||||||
|
|
||||||
object CommonMangaItemDefaults {
|
object CommonMangaItemDefaults {
|
||||||
val GridHorizontalSpacer = 4.dp
|
val GridHorizontalSpacer = 4.dp
|
||||||
val GridVerticalSpacer = 4.dp
|
val GridVerticalSpacer = 4.dp
|
||||||
|
|
||||||
|
@Suppress("ConstPropertyName")
|
||||||
const val BrowseFavoriteCoverAlpha = 0.34f
|
const val BrowseFavoriteCoverAlpha = 0.34f
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ContinueReadingButtonSize = 28.dp
|
private val ContinueReadingButtonSizeSmall = 28.dp
|
||||||
|
private val ContinueReadingButtonSizeLarge = 32.dp
|
||||||
|
|
||||||
|
private val ContinueReadingButtonIconSizeSmall = 16.dp
|
||||||
|
private val ContinueReadingButtonIconSizeLarge = 20.dp
|
||||||
|
|
||||||
private val ContinueReadingButtonGridPadding = 6.dp
|
private val ContinueReadingButtonGridPadding = 6.dp
|
||||||
private val ContinueReadingButtonListSpacing = 8.dp
|
private val ContinueReadingButtonListSpacing = 8.dp
|
||||||
|
|
||||||
private const val GridSelectedCoverAlpha = 0.76f
|
private const val GRID_SELECTED_COVER_ALPHA = 0.76f
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of grid list item with title overlaying the cover.
|
* Layout of grid list item with title overlaying the cover.
|
||||||
@ -62,7 +70,7 @@ private const val GridSelectedCoverAlpha = 0.76f
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCompactGridItem(
|
fun MangaCompactGridItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: MangaCoverModel,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
isSelected: Boolean = false,
|
isSelected: Boolean = false,
|
||||||
@ -82,7 +90,7 @@ fun MangaCompactGridItem(
|
|||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
||||||
data = coverData,
|
data = coverData,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -96,10 +104,12 @@ fun MangaCompactGridItem(
|
|||||||
)
|
)
|
||||||
} else if (onClickContinueReading != null) {
|
} else if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
|
size = ContinueReadingButtonSizeLarge,
|
||||||
|
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||||
|
onClick = onClickContinueReading,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(ContinueReadingButtonGridPadding)
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
.align(Alignment.BottomEnd),
|
.align(Alignment.BottomEnd),
|
||||||
onClickContinueReading = onClickContinueReading,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -148,11 +158,13 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
)
|
)
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
|
size = ContinueReadingButtonSizeSmall,
|
||||||
|
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||||
|
onClick = onClickContinueReading,
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
end = ContinueReadingButtonGridPadding,
|
end = ContinueReadingButtonGridPadding,
|
||||||
bottom = ContinueReadingButtonGridPadding,
|
bottom = ContinueReadingButtonGridPadding,
|
||||||
),
|
),
|
||||||
onClickContinueReading = onClickContinueReading,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +175,7 @@ private fun BoxScope.CoverTextOverlay(
|
|||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaComfortableGridItem(
|
fun MangaComfortableGridItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: MangaCoverModel,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
@ -185,7 +197,7 @@ fun MangaComfortableGridItem(
|
|||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.alpha(if (isSelected) GridSelectedCoverAlpha else coverAlpha),
|
.alpha(if (isSelected) GRID_SELECTED_COVER_ALPHA else coverAlpha),
|
||||||
data = coverData,
|
data = coverData,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -194,10 +206,12 @@ fun MangaComfortableGridItem(
|
|||||||
content = {
|
content = {
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
|
size = ContinueReadingButtonSizeLarge,
|
||||||
|
iconSize = ContinueReadingButtonIconSizeLarge,
|
||||||
|
onClick = onClickContinueReading,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(ContinueReadingButtonGridPadding)
|
.padding(ContinueReadingButtonGridPadding)
|
||||||
.align(Alignment.BottomEnd),
|
.align(Alignment.BottomEnd),
|
||||||
onClickContinueReading = onClickContinueReading,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -309,14 +323,14 @@ private fun GridItemSelectable(
|
|||||||
private fun Modifier.selectedOutline(
|
private fun Modifier.selectedOutline(
|
||||||
isSelected: Boolean,
|
isSelected: Boolean,
|
||||||
color: Color,
|
color: Color,
|
||||||
) = this then drawBehind { if (isSelected) drawRect(color = color) }
|
) = drawBehind { if (isSelected) drawRect(color = color) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout of list item.
|
* Layout of list item.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaListItem(
|
fun MangaListItem(
|
||||||
coverData: tachiyomi.domain.manga.model.MangaCover,
|
coverData: MangaCoverModel,
|
||||||
title: String,
|
title: String,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
@ -354,8 +368,10 @@ fun MangaListItem(
|
|||||||
BadgeGroup(content = badge)
|
BadgeGroup(content = badge)
|
||||||
if (onClickContinueReading != null) {
|
if (onClickContinueReading != null) {
|
||||||
ContinueReadingButton(
|
ContinueReadingButton(
|
||||||
|
size = ContinueReadingButtonSizeSmall,
|
||||||
|
iconSize = ContinueReadingButtonIconSizeSmall,
|
||||||
|
onClick = onClickContinueReading,
|
||||||
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
modifier = Modifier.padding(start = ContinueReadingButtonListSpacing),
|
||||||
onClickContinueReading = onClickContinueReading,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,23 +379,25 @@ fun MangaListItem(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ContinueReadingButton(
|
private fun ContinueReadingButton(
|
||||||
|
size: Dp,
|
||||||
|
iconSize: Dp,
|
||||||
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClickContinueReading: () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
FilledIconButton(
|
FilledIconButton(
|
||||||
onClick = onClickContinueReading,
|
onClick = onClick,
|
||||||
modifier = Modifier.size(ContinueReadingButtonSize),
|
|
||||||
shape = MaterialTheme.shapes.small,
|
shape = MaterialTheme.shapes.small,
|
||||||
colors = IconButtonDefaults.filledIconButtonColors(
|
colors = IconButtonDefaults.filledIconButtonColors(
|
||||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
||||||
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
|
||||||
),
|
),
|
||||||
|
modifier = Modifier.size(size),
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Filled.PlayArrow,
|
imageVector = Icons.Filled.PlayArrow,
|
||||||
contentDescription = stringResource(MR.strings.action_resume),
|
contentDescription = stringResource(MR.strings.action_resume),
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(iconSize),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,7 +93,7 @@ fun LibraryContent(
|
|||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = { notSelectionMode },
|
enabled = notSelectionMode,
|
||||||
) {
|
) {
|
||||||
LibraryPager(
|
LibraryPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
@ -22,7 +23,6 @@ import eu.kanade.tachiyomi.ui.library.LibraryItem
|
|||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.domain.library.model.LibraryManga
|
import tachiyomi.domain.library.model.LibraryManga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.HorizontalPager
|
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
|
@ -1,16 +1,33 @@
|
|||||||
package eu.kanade.presentation.manga
|
package eu.kanade.presentation.manga
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Add
|
||||||
|
import androidx.compose.material.icons.outlined.Book
|
||||||
|
import androidx.compose.material.icons.outlined.SwapVert
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import eu.kanade.presentation.components.AdaptiveSheet
|
||||||
|
import eu.kanade.presentation.components.TabbedDialogPaddings
|
||||||
|
import eu.kanade.presentation.more.settings.LocalPreferenceMinHeight
|
||||||
|
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -18,42 +35,92 @@ fun DuplicateMangaDialog(
|
|||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
onOpenManga: () -> Unit,
|
onOpenManga: () -> Unit,
|
||||||
|
onMigrate: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
AlertDialog(
|
val minHeight = LocalPreferenceMinHeight.current
|
||||||
|
|
||||||
|
AdaptiveSheet(
|
||||||
|
modifier = modifier,
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
title = {
|
) {
|
||||||
Text(text = stringResource(MR.strings.are_you_sure))
|
Column(
|
||||||
},
|
modifier = Modifier
|
||||||
text = {
|
.padding(
|
||||||
Text(text = stringResource(MR.strings.confirm_add_duplicate_manga))
|
vertical = TabbedDialogPaddings.Vertical,
|
||||||
},
|
horizontal = TabbedDialogPaddings.Horizontal,
|
||||||
confirmButton = {
|
)
|
||||||
FlowRow(
|
.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(TitlePadding),
|
||||||
|
text = stringResource(MR.strings.are_you_sure),
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringResource(MR.strings.confirm_add_duplicate_manga),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(PaddingSize))
|
||||||
|
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.action_show_manga),
|
||||||
|
icon = Icons.Outlined.Book,
|
||||||
|
onPreferenceClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onOpenManga()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.action_migrate_duplicate),
|
||||||
|
icon = Icons.Outlined.SwapVert,
|
||||||
|
onPreferenceClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onMigrate()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
HorizontalDivider()
|
||||||
|
|
||||||
|
TextPreferenceWidget(
|
||||||
|
title = stringResource(MR.strings.action_add_anyway),
|
||||||
|
icon = Icons.Outlined.Add,
|
||||||
|
onPreferenceClick = {
|
||||||
|
onDismissRequest()
|
||||||
|
onConfirm()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.sizeIn(minHeight = minHeight)
|
||||||
|
.clickable { onDismissRequest.invoke() }
|
||||||
|
.padding(ButtonPadding)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
) {
|
) {
|
||||||
TextButton(
|
OutlinedButton(onClick = onDismissRequest, modifier = Modifier.fillMaxWidth()) {
|
||||||
onClick = {
|
Text(
|
||||||
onDismissRequest()
|
modifier = Modifier
|
||||||
onOpenManga()
|
.padding(vertical = 8.dp),
|
||||||
},
|
text = stringResource(MR.strings.action_cancel),
|
||||||
) {
|
color = MaterialTheme.colorScheme.primary,
|
||||||
Text(text = stringResource(MR.strings.action_show_manga))
|
style = MaterialTheme.typography.titleLarge,
|
||||||
}
|
fontSize = 16.sp,
|
||||||
|
)
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(text = stringResource(MR.strings.action_cancel))
|
|
||||||
}
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
onDismissRequest()
|
|
||||||
onConfirm()
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(MR.strings.action_add))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val PaddingSize = 16.dp
|
||||||
|
|
||||||
|
private val ButtonPadding = PaddingValues(top = 16.dp, bottom = 16.dp)
|
||||||
|
private val TitlePadding = PaddingValues(bottom = 16.dp, top = 8.dp)
|
||||||
|
@ -75,8 +75,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut
|
|||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
import tachiyomi.presentation.core.util.shouldExpandFAB
|
||||||
import tachiyomi.presentation.core.util.isScrollingUp
|
|
||||||
import tachiyomi.source.local.isLocal
|
import tachiyomi.source.local.isLocal
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@ -346,7 +345,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -356,7 +355,7 @@ private fun MangaScreenSmallImpl(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = { !isAnySelected },
|
enabled = !isAnySelected,
|
||||||
indicatorPadding = PaddingValues(top = topPadding),
|
indicatorPadding = PaddingValues(top = topPadding),
|
||||||
) {
|
) {
|
||||||
val layoutDirection = LocalLayoutDirection.current
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
@ -381,13 +380,9 @@ private fun MangaScreenSmallImpl(
|
|||||||
MangaInfoBox(
|
MangaInfoBox(
|
||||||
isTabletUi = false,
|
isTabletUi = false,
|
||||||
appBarPadding = topPadding,
|
appBarPadding = topPadding,
|
||||||
title = state.manga.title,
|
manga = state.manga,
|
||||||
author = state.manga.author,
|
|
||||||
artist = state.manga.artist,
|
|
||||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||||
isStubSource = remember { state.source is StubSource },
|
isStubSource = remember { state.source is StubSource },
|
||||||
coverDataProvider = { state.manga },
|
|
||||||
status = state.manga.status,
|
|
||||||
onCoverClick = onCoverClicked,
|
onCoverClick = onCoverClicked,
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
)
|
)
|
||||||
@ -594,7 +589,7 @@ fun MangaScreenLargeImpl(
|
|||||||
},
|
},
|
||||||
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
|
||||||
onClick = onContinueReading,
|
onClick = onContinueReading,
|
||||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
expanded = chapterListState.shouldExpandFAB(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -602,7 +597,7 @@ fun MangaScreenLargeImpl(
|
|||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshingData,
|
refreshing = state.isRefreshingData,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = { !isAnySelected },
|
enabled = !isAnySelected,
|
||||||
indicatorPadding = PaddingValues(
|
indicatorPadding = PaddingValues(
|
||||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||||
top = with(density) { topBarHeight.toDp() },
|
top = with(density) { topBarHeight.toDp() },
|
||||||
@ -623,13 +618,9 @@ fun MangaScreenLargeImpl(
|
|||||||
MangaInfoBox(
|
MangaInfoBox(
|
||||||
isTabletUi = true,
|
isTabletUi = true,
|
||||||
appBarPadding = contentPadding.calculateTopPadding(),
|
appBarPadding = contentPadding.calculateTopPadding(),
|
||||||
title = state.manga.title,
|
manga = state.manga,
|
||||||
author = state.manga.author,
|
|
||||||
artist = state.manga.artist,
|
|
||||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||||
isStubSource = remember { state.source is StubSource },
|
isStubSource = remember { state.source is StubSource },
|
||||||
coverDataProvider = { state.manga },
|
|
||||||
status = state.manga.status,
|
|
||||||
onCoverClick = onCoverClicked,
|
onCoverClick = onCoverClicked,
|
||||||
doSearch = onSearch,
|
doSearch = onSearch,
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,6 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
@ -10,13 +9,13 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.outlined.ArrowDownward
|
import androidx.compose.material.icons.outlined.ArrowDownward
|
||||||
import androidx.compose.material.icons.outlined.ErrorOutline
|
import androidx.compose.material.icons.outlined.ErrorOutline
|
||||||
import androidx.compose.material.ripple
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProgressIndicatorDefaults
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -24,8 +23,9 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.composed
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
|
import androidx.compose.ui.hapticfeedback.HapticFeedback
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@ -91,6 +91,7 @@ private fun NotDownloadedIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
onLongClick = { onClick(ChapterDownloadAction.START_NOW) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
)
|
)
|
||||||
@ -120,6 +121,7 @@ private fun DownloadingIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
onLongClick = { onClick(ChapterDownloadAction.CANCEL) },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
@ -136,6 +138,8 @@ private fun DownloadingIndicator(
|
|||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorStrokeWidth,
|
strokeWidth = IndicatorStrokeWidth,
|
||||||
|
trackColor = Color.Transparent,
|
||||||
|
strokeCap = StrokeCap.Butt,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val animatedProgress by animateFloatAsState(
|
val animatedProgress by animateFloatAsState(
|
||||||
@ -153,6 +157,9 @@ private fun DownloadingIndicator(
|
|||||||
modifier = IndicatorModifier,
|
modifier = IndicatorModifier,
|
||||||
color = strokeColor,
|
color = strokeColor,
|
||||||
strokeWidth = IndicatorSize / 2,
|
strokeWidth = IndicatorSize / 2,
|
||||||
|
trackColor = Color.Transparent,
|
||||||
|
strokeCap = StrokeCap.Butt,
|
||||||
|
gapSize = 0.dp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
|
||||||
@ -192,6 +199,7 @@ private fun DownloadedIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
onLongClick = { isMenuExpanded = true },
|
onLongClick = { isMenuExpanded = true },
|
||||||
onClick = { isMenuExpanded = true },
|
onClick = { isMenuExpanded = true },
|
||||||
),
|
),
|
||||||
@ -226,6 +234,7 @@ private fun ErrorIndicator(
|
|||||||
.size(IconButtonTokens.StateLayerSize)
|
.size(IconButtonTokens.StateLayerSize)
|
||||||
.commonClickable(
|
.commonClickable(
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
|
hapticFeedback = LocalHapticFeedback.current,
|
||||||
onLongClick = { onClick(ChapterDownloadAction.START) },
|
onLongClick = { onClick(ChapterDownloadAction.START) },
|
||||||
onClick = { onClick(ChapterDownloadAction.START) },
|
onClick = { onClick(ChapterDownloadAction.START) },
|
||||||
),
|
),
|
||||||
@ -242,26 +251,23 @@ private fun ErrorIndicator(
|
|||||||
|
|
||||||
private fun Modifier.commonClickable(
|
private fun Modifier.commonClickable(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
|
hapticFeedback: HapticFeedback,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) = composed {
|
) = this.combinedClickable(
|
||||||
val haptic = LocalHapticFeedback.current
|
enabled = enabled,
|
||||||
|
onLongClick = {
|
||||||
Modifier.combinedClickable(
|
onLongClick()
|
||||||
enabled = enabled,
|
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||||
onLongClick = {
|
},
|
||||||
onLongClick()
|
onClick = onClick,
|
||||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
role = Role.Button,
|
||||||
},
|
interactionSource = null,
|
||||||
onClick = onClick,
|
indication = ripple(
|
||||||
role = Role.Button,
|
bounded = false,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
radius = IconButtonTokens.StateLayerSize / 2,
|
||||||
indication = ripple(
|
),
|
||||||
bounded = false,
|
)
|
||||||
radius = IconButtonTokens.StateLayerSize / 2,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val IndicatorSize = 26.dp
|
private val IndicatorSize = 26.dp
|
||||||
private val IndicatorPadding = 2.dp
|
private val IndicatorPadding = 2.dp
|
||||||
|
@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
@ -60,6 +60,6 @@ private fun MissingChaptersWarning(count: Int) {
|
|||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.error.copy(alpha = SecondaryItemAlpha),
|
color = MaterialTheme.colorScheme.error.copy(alpha = SECONDARY_ALPHA),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import androidx.compose.animation.fadeIn
|
|||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@ -30,11 +29,11 @@ import androidx.compose.material.icons.outlined.Delete
|
|||||||
import androidx.compose.material.icons.outlined.DoneAll
|
import androidx.compose.material.icons.outlined.DoneAll
|
||||||
import androidx.compose.material.icons.outlined.Download
|
import androidx.compose.material.icons.outlined.Download
|
||||||
import androidx.compose.material.icons.outlined.RemoveDone
|
import androidx.compose.material.icons.outlined.RemoveDone
|
||||||
import androidx.compose.material.ripple
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.ripple
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
@ -82,7 +81,7 @@ fun MangaBottomActionMenu(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||||
tonalElevation = 3.dp,
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false) }
|
||||||
@ -191,7 +190,7 @@ private fun RowScope.Button(
|
|||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.weight(animatedWeight)
|
.weight(animatedWeight)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = null,
|
||||||
indication = ripple(bounded = false),
|
indication = ripple(bounded = false),
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
@ -238,7 +237,7 @@ fun LibraryBottomActionMenu(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
shape = MaterialTheme.shapes.large.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize),
|
||||||
tonalElevation = 3.dp,
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val haptic = LocalHapticFeedback.current
|
||||||
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
val confirm = remember { mutableStateListOf(false, false, false, false, false) }
|
||||||
|
@ -24,36 +24,26 @@ import androidx.compose.material3.ProvideTextStyle
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.contentColorFor
|
import androidx.compose.material3.contentColorFor
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshotFlow
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clipToBounds
|
import androidx.compose.ui.draw.clipToBounds
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
|
||||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
|
||||||
import androidx.compose.ui.platform.ViewConfiguration
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import me.saket.swipe.SwipeableActionsBox
|
import me.saket.swipe.SwipeableActionsBox
|
||||||
import me.saket.swipe.rememberSwipeableActionsState
|
|
||||||
import tachiyomi.domain.library.service.LibraryPreferences
|
import tachiyomi.domain.library.service.LibraryPreferences
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.ReadItemAlpha
|
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.selectedBackground
|
import tachiyomi.presentation.core.util.selectedBackground
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaChapterListItem(
|
fun MangaChapterListItem(
|
||||||
@ -75,142 +65,119 @@ fun MangaChapterListItem(
|
|||||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val haptic = LocalHapticFeedback.current
|
val start = getSwipeAction(
|
||||||
val density = LocalDensity.current
|
action = chapterSwipeStartAction,
|
||||||
|
read = read,
|
||||||
|
bookmark = bookmark,
|
||||||
|
downloadState = downloadStateProvider(),
|
||||||
|
background = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
|
||||||
|
)
|
||||||
|
val end = getSwipeAction(
|
||||||
|
action = chapterSwipeEndAction,
|
||||||
|
read = read,
|
||||||
|
bookmark = bookmark,
|
||||||
|
downloadState = downloadStateProvider(),
|
||||||
|
background = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
|
||||||
|
)
|
||||||
|
|
||||||
val textAlpha = if (read) ReadItemAlpha else 1f
|
SwipeableActionsBox(
|
||||||
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
modifier = Modifier.clipToBounds(),
|
||||||
|
startActions = listOfNotNull(start),
|
||||||
// Increase touch slop of swipe action to reduce accidental trigger
|
endActions = listOfNotNull(end),
|
||||||
val configuration = LocalViewConfiguration.current
|
swipeThreshold = swipeActionThreshold,
|
||||||
CompositionLocalProvider(
|
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
||||||
LocalViewConfiguration provides object : ViewConfiguration by configuration {
|
|
||||||
override val touchSlop: Float = configuration.touchSlop * 3f
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
val start = getSwipeAction(
|
Row(
|
||||||
action = chapterSwipeStartAction,
|
modifier = modifier
|
||||||
read = read,
|
.selectedBackground(selected)
|
||||||
bookmark = bookmark,
|
.combinedClickable(
|
||||||
downloadState = downloadStateProvider(),
|
onClick = onClick,
|
||||||
background = MaterialTheme.colorScheme.primaryContainer,
|
onLongClick = onLongClick,
|
||||||
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
|
)
|
||||||
)
|
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
|
||||||
val end = getSwipeAction(
|
|
||||||
action = chapterSwipeEndAction,
|
|
||||||
read = read,
|
|
||||||
bookmark = bookmark,
|
|
||||||
downloadState = downloadStateProvider(),
|
|
||||||
background = MaterialTheme.colorScheme.primaryContainer,
|
|
||||||
onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
|
|
||||||
)
|
|
||||||
|
|
||||||
val swipeableActionsState = rememberSwipeableActionsState()
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
// Haptic effect when swipe over threshold
|
|
||||||
val swipeActionThresholdPx = with(density) { swipeActionThreshold.toPx() }
|
|
||||||
snapshotFlow { swipeableActionsState.offset.value.absoluteValue > swipeActionThresholdPx }
|
|
||||||
.collect { if (it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }
|
|
||||||
}
|
|
||||||
|
|
||||||
SwipeableActionsBox(
|
|
||||||
modifier = Modifier.clipToBounds(),
|
|
||||||
state = swipeableActionsState,
|
|
||||||
startActions = listOfNotNull(start),
|
|
||||||
endActions = listOfNotNull(end),
|
|
||||||
swipeThreshold = swipeActionThreshold,
|
|
||||||
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
modifier = modifier
|
modifier = Modifier.weight(1f),
|
||||||
.selectedBackground(selected)
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
.combinedClickable(
|
|
||||||
onClick = onClick,
|
|
||||||
onLongClick = onLongClick,
|
|
||||||
)
|
|
||||||
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
|
|
||||||
) {
|
) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Row(
|
var textHeight by remember { mutableIntStateOf(0) }
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
if (!read) {
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Icon(
|
||||||
) {
|
imageVector = Icons.Filled.Circle,
|
||||||
var textHeight by remember { mutableIntStateOf(0) }
|
contentDescription = stringResource(MR.strings.unread),
|
||||||
if (!read) {
|
modifier = Modifier
|
||||||
Icon(
|
.height(8.dp)
|
||||||
imageVector = Icons.Filled.Circle,
|
.padding(end = 4.dp),
|
||||||
contentDescription = stringResource(MR.strings.unread),
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier
|
|
||||||
.height(8.dp)
|
|
||||||
.padding(end = 4.dp),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (bookmark) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Bookmark,
|
|
||||||
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
|
||||||
modifier = Modifier
|
|
||||||
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
|
||||||
tint = MaterialTheme.colorScheme.primary,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = LocalContentColor.current.copy(alpha = textAlpha),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
onTextLayout = { textHeight = it.size.height },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (bookmark) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Bookmark,
|
||||||
|
contentDescription = stringResource(MR.strings.action_filter_bookmarked),
|
||||||
|
modifier = Modifier
|
||||||
|
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
onTextLayout = { textHeight = it.size.height },
|
||||||
|
color = LocalContentColor.current.copy(alpha = if (read) DISABLED_ALPHA else 1f),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
ProvideTextStyle(
|
val subtitleStyle = MaterialTheme.typography.bodySmall
|
||||||
value = MaterialTheme.typography.bodyMedium.copy(
|
.merge(
|
||||||
fontSize = 12.sp,
|
color = LocalContentColor.current
|
||||||
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
|
.copy(alpha = if (read) DISABLED_ALPHA else SECONDARY_ALPHA),
|
||||||
),
|
)
|
||||||
) {
|
ProvideTextStyle(value = subtitleStyle) {
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
Text(
|
Text(
|
||||||
text = date,
|
text = date,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
if (readProgress != null || scanlator != null) DotSeparatorText()
|
if (readProgress != null || scanlator != null) DotSeparatorText()
|
||||||
}
|
}
|
||||||
if (readProgress != null) {
|
if (readProgress != null) {
|
||||||
Text(
|
Text(
|
||||||
text = readProgress,
|
text = readProgress,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
color = LocalContentColor.current.copy(alpha = ReadItemAlpha),
|
color = LocalContentColor.current.copy(alpha = DISABLED_ALPHA),
|
||||||
)
|
)
|
||||||
if (scanlator != null) DotSeparatorText()
|
if (scanlator != null) DotSeparatorText()
|
||||||
}
|
}
|
||||||
if (scanlator != null) {
|
if (scanlator != null) {
|
||||||
Text(
|
Text(
|
||||||
text = scanlator,
|
text = scanlator,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ChapterDownloadIndicator(
|
|
||||||
enabled = downloadIndicatorEnabled,
|
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
|
||||||
downloadStateProvider = downloadStateProvider,
|
|
||||||
downloadProgressProvider = downloadProgressProvider,
|
|
||||||
onClick = { onDownloadClick?.invoke(it) },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChapterDownloadIndicator(
|
||||||
|
enabled = downloadIndicatorEnabled,
|
||||||
|
modifier = Modifier.padding(start = 4.dp),
|
||||||
|
downloadStateProvider = downloadStateProvider,
|
||||||
|
downloadProgressProvider = downloadProgressProvider,
|
||||||
|
onClick = { onDownloadClick?.invoke(it) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import androidx.compose.ui.graphics.Shape
|
|||||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
|
||||||
|
@ -37,10 +37,11 @@ import androidx.compose.ui.viewinterop.AndroidView
|
|||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import coil.imageLoader
|
import coil3.asDrawable
|
||||||
import coil.request.CachePolicy
|
import coil3.imageLoader
|
||||||
import coil.request.ImageRequest
|
import coil3.request.CachePolicy
|
||||||
import coil.size.Size
|
import coil3.request.ImageRequest
|
||||||
|
import coil3.size.Size
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
import eu.kanade.presentation.components.AppBarActions
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
@ -55,7 +56,7 @@ import tachiyomi.presentation.core.util.clickableNoIndication
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaCoverDialog(
|
fun MangaCoverDialog(
|
||||||
coverDataProvider: () -> Manga,
|
manga: Manga,
|
||||||
isCustomCover: Boolean,
|
isCustomCover: Boolean,
|
||||||
snackbarHostState: SnackbarHostState,
|
snackbarHostState: SnackbarHostState,
|
||||||
onShareClick: () -> Unit,
|
onShareClick: () -> Unit,
|
||||||
@ -165,10 +166,12 @@ fun MangaCoverDialog(
|
|||||||
},
|
},
|
||||||
update = { view ->
|
update = { view ->
|
||||||
val request = ImageRequest.Builder(view.context)
|
val request = ImageRequest.Builder(view.context)
|
||||||
.data(coverDataProvider())
|
.data(manga)
|
||||||
.size(Size.ORIGINAL)
|
.size(Size.ORIGINAL)
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.target { drawable ->
|
.target { image ->
|
||||||
|
val drawable = image.asDrawable(view.context.resources)
|
||||||
|
|
||||||
// Copy bitmap in case it came from memory cache
|
// Copy bitmap in case it came from memory cache
|
||||||
// Because SSIV needs to thoroughly read the image
|
// Because SSIV needs to thoroughly read the image
|
||||||
val copy = (drawable as? BitmapDrawable)?.let {
|
val copy = (drawable as? BitmapDrawable)?.let {
|
||||||
|
@ -102,9 +102,12 @@ fun SetIntervalDialog(
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
Spacer(Modifier.height(MaterialTheme.padding.small))
|
Text(
|
||||||
|
stringResource(MR.strings.manga_interval_expected_update_null),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.height(MaterialTheme.padding.small))
|
||||||
|
|
||||||
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
|
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
|
||||||
Text(stringResource(MR.strings.manga_interval_custom_amount))
|
Text(stringResource(MR.strings.manga_interval_custom_amount))
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.manga.components
|
|||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
import androidx.compose.animation.graphics.res.animatedVectorResource
|
import androidx.compose.animation.graphics.res.animatedVectorResource
|
||||||
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
|
||||||
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
import androidx.compose.animation.graphics.vector.AnimatedImageVector
|
||||||
@ -42,7 +43,7 @@ import androidx.compose.material.icons.outlined.Sync
|
|||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement
|
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
|
||||||
import androidx.compose.material3.LocalTextStyle
|
import androidx.compose.material3.LocalTextStyle
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.ProvideTextStyle
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
@ -73,13 +74,16 @@ import androidx.compose.ui.unit.Constraints
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import coil3.request.ImageRequest
|
||||||
|
import coil3.request.crossfade
|
||||||
import eu.kanade.presentation.components.DropdownMenu
|
import eu.kanade.presentation.components.DropdownMenu
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.components.material.DISABLED_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.TextButton
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
@ -96,13 +100,9 @@ private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTIL
|
|||||||
fun MangaInfoBox(
|
fun MangaInfoBox(
|
||||||
isTabletUi: Boolean,
|
isTabletUi: Boolean,
|
||||||
appBarPadding: Dp,
|
appBarPadding: Dp,
|
||||||
title: String,
|
manga: Manga,
|
||||||
author: String?,
|
|
||||||
artist: String?,
|
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
isStubSource: Boolean,
|
isStubSource: Boolean,
|
||||||
coverDataProvider: () -> Manga,
|
|
||||||
status: Long,
|
|
||||||
onCoverClick: () -> Unit,
|
onCoverClick: () -> Unit,
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@ -114,7 +114,10 @@ fun MangaInfoBox(
|
|||||||
MaterialTheme.colorScheme.background,
|
MaterialTheme.colorScheme.background,
|
||||||
)
|
)
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = coverDataProvider(),
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(manga)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -134,28 +137,20 @@ fun MangaInfoBox(
|
|||||||
if (!isTabletUi) {
|
if (!isTabletUi) {
|
||||||
MangaAndSourceTitlesSmall(
|
MangaAndSourceTitlesSmall(
|
||||||
appBarPadding = appBarPadding,
|
appBarPadding = appBarPadding,
|
||||||
coverDataProvider = coverDataProvider,
|
manga = manga,
|
||||||
onCoverClick = onCoverClick,
|
|
||||||
title = title,
|
|
||||||
doSearch = doSearch,
|
|
||||||
author = author,
|
|
||||||
artist = artist,
|
|
||||||
status = status,
|
|
||||||
sourceName = sourceName,
|
sourceName = sourceName,
|
||||||
isStubSource = isStubSource,
|
isStubSource = isStubSource,
|
||||||
|
onCoverClick = onCoverClick,
|
||||||
|
doSearch = doSearch,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
MangaAndSourceTitlesLarge(
|
MangaAndSourceTitlesLarge(
|
||||||
appBarPadding = appBarPadding,
|
appBarPadding = appBarPadding,
|
||||||
coverDataProvider = coverDataProvider,
|
manga = manga,
|
||||||
onCoverClick = onCoverClick,
|
|
||||||
title = title,
|
|
||||||
doSearch = doSearch,
|
|
||||||
author = author,
|
|
||||||
artist = artist,
|
|
||||||
status = status,
|
|
||||||
sourceName = sourceName,
|
sourceName = sourceName,
|
||||||
isStubSource = isStubSource,
|
isStubSource = isStubSource,
|
||||||
|
onCoverClick = onCoverClick,
|
||||||
|
doSearch = doSearch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -176,7 +171,7 @@ fun MangaActionRow(
|
|||||||
onEditCategory: (() -> Unit)?,
|
onEditCategory: (() -> Unit)?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = DISABLED_ALPHA)
|
||||||
|
|
||||||
// TODO: show something better when using custom interval
|
// TODO: show something better when using custom interval
|
||||||
val nextUpdateDays = remember(nextUpdate) {
|
val nextUpdateDays = remember(nextUpdate) {
|
||||||
@ -271,7 +266,8 @@ fun ExpandableMangaDescription(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(top = 8.dp)
|
.padding(top = 8.dp)
|
||||||
.padding(vertical = 12.dp)
|
.padding(vertical = 12.dp)
|
||||||
.animateContentSize(),
|
.animateContentSize(animationSpec = spring())
|
||||||
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
var tagSelected by remember { mutableStateOf("") }
|
var tagSelected by remember { mutableStateOf("") }
|
||||||
@ -335,15 +331,11 @@ fun ExpandableMangaDescription(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MangaAndSourceTitlesLarge(
|
private fun MangaAndSourceTitlesLarge(
|
||||||
appBarPadding: Dp,
|
appBarPadding: Dp,
|
||||||
coverDataProvider: () -> Manga,
|
manga: Manga,
|
||||||
onCoverClick: () -> Unit,
|
|
||||||
title: String,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
|
||||||
author: String?,
|
|
||||||
artist: String?,
|
|
||||||
status: Long,
|
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
isStubSource: Boolean,
|
isStubSource: Boolean,
|
||||||
|
onCoverClick: () -> Unit,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -353,19 +345,22 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
) {
|
) {
|
||||||
MangaCover.Book(
|
MangaCover.Book(
|
||||||
modifier = Modifier.fillMaxWidth(0.65f),
|
modifier = Modifier.fillMaxWidth(0.65f),
|
||||||
data = coverDataProvider(),
|
data = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(manga)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
contentDescription = stringResource(MR.strings.manga_cover),
|
contentDescription = stringResource(MR.strings.manga_cover),
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
MangaContentInfo(
|
MangaContentInfo(
|
||||||
title = title,
|
title = manga.title,
|
||||||
doSearch = doSearch,
|
author = manga.author,
|
||||||
author = author,
|
artist = manga.artist,
|
||||||
artist = artist,
|
status = manga.status,
|
||||||
status = status,
|
|
||||||
sourceName = sourceName,
|
sourceName = sourceName,
|
||||||
isStubSource = isStubSource,
|
isStubSource = isStubSource,
|
||||||
|
doSearch = doSearch,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -374,15 +369,11 @@ private fun MangaAndSourceTitlesLarge(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun MangaAndSourceTitlesSmall(
|
private fun MangaAndSourceTitlesSmall(
|
||||||
appBarPadding: Dp,
|
appBarPadding: Dp,
|
||||||
coverDataProvider: () -> Manga,
|
manga: Manga,
|
||||||
onCoverClick: () -> Unit,
|
|
||||||
title: String,
|
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
|
||||||
author: String?,
|
|
||||||
artist: String?,
|
|
||||||
status: Long,
|
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
isStubSource: Boolean,
|
isStubSource: Boolean,
|
||||||
|
onCoverClick: () -> Unit,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@ -395,7 +386,10 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.sizeIn(maxWidth = 100.dp)
|
.sizeIn(maxWidth = 100.dp)
|
||||||
.align(Alignment.Top),
|
.align(Alignment.Top),
|
||||||
data = coverDataProvider(),
|
data = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(manga)
|
||||||
|
.crossfade(true)
|
||||||
|
.build(),
|
||||||
contentDescription = stringResource(MR.strings.manga_cover),
|
contentDescription = stringResource(MR.strings.manga_cover),
|
||||||
onClick = onCoverClick,
|
onClick = onCoverClick,
|
||||||
)
|
)
|
||||||
@ -403,13 +397,13 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
MangaContentInfo(
|
MangaContentInfo(
|
||||||
title = title,
|
title = manga.title,
|
||||||
doSearch = doSearch,
|
author = manga.author,
|
||||||
author = author,
|
artist = manga.artist,
|
||||||
artist = artist,
|
status = manga.status,
|
||||||
status = status,
|
|
||||||
sourceName = sourceName,
|
sourceName = sourceName,
|
||||||
isStubSource = isStubSource,
|
isStubSource = isStubSource,
|
||||||
|
doSearch = doSearch,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -418,12 +412,12 @@ private fun MangaAndSourceTitlesSmall(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.MangaContentInfo(
|
private fun ColumnScope.MangaContentInfo(
|
||||||
title: String,
|
title: String,
|
||||||
doSearch: (query: String, global: Boolean) -> Unit,
|
|
||||||
author: String?,
|
author: String?,
|
||||||
artist: String?,
|
artist: String?,
|
||||||
status: Long,
|
status: Long,
|
||||||
sourceName: String,
|
sourceName: String,
|
||||||
isStubSource: Boolean,
|
isStubSource: Boolean,
|
||||||
|
doSearch: (query: String, global: Boolean) -> Unit,
|
||||||
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
textAlign: TextAlign? = LocalTextStyle.current.textAlign,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -649,7 +643,7 @@ private fun TagsChip(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
|
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
|
||||||
SuggestionChip(
|
SuggestionChip(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
@ -31,8 +31,6 @@ import tachiyomi.i18n.MR
|
|||||||
import tachiyomi.presentation.core.components.material.TextButton
|
import tachiyomi.presentation.core.components.material.TextButton
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ScanlatorFilterDialog(
|
fun ScanlatorFilterDialog(
|
||||||
@ -96,8 +94,8 @@ fun ScanlatorFilterDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
properties = DialogProperties(
|
properties = DialogProperties(
|
||||||
@ -110,8 +108,14 @@ fun ScanlatorFilterDialog(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FlowRow {
|
FlowRow {
|
||||||
TextButton(onClick = mutableExcludedScanlators::clear) {
|
if (mutableExcludedScanlators.isEmpty()) {
|
||||||
Text(text = stringResource(MR.strings.action_reset))
|
TextButton(onClick = { mutableExcludedScanlators.addAll(availableScanlators) }) {
|
||||||
|
Text(text = stringResource(MR.strings.action_select_all))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TextButton(onClick = mutableExcludedScanlators::clear) {
|
||||||
|
Text(text = stringResource(MR.strings.action_reset))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
TextButton(onClick = onDismissRequest) {
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
@ -75,7 +75,7 @@ private fun NewUpdateScreenPreview() {
|
|||||||
changelogInfo = """
|
changelogInfo = """
|
||||||
## Yay
|
## Yay
|
||||||
Foobar
|
Foobar
|
||||||
|
|
||||||
### More info
|
### More info
|
||||||
- Hello
|
- Hello
|
||||||
- World
|
- World
|
||||||
|
@ -14,11 +14,13 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.ListItemDefaults
|
import androidx.compose.material3.ListItemDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@ -28,19 +30,24 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState
|
||||||
|
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
internal class PermissionStep : OnboardingStep {
|
internal class PermissionStep : OnboardingStep {
|
||||||
|
|
||||||
|
private val privacyPreferences: PrivacyPreferences by injectLazy()
|
||||||
|
|
||||||
private var notificationGranted by mutableStateOf(false)
|
private var notificationGranted by mutableStateOf(false)
|
||||||
private var batteryGranted by mutableStateOf(false)
|
private var batteryGranted by mutableStateOf(false)
|
||||||
|
|
||||||
@ -73,7 +80,7 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
PermissionItem(
|
PermissionCheckbox(
|
||||||
title = stringResource(MR.strings.onboarding_permission_install_apps),
|
title = stringResource(MR.strings.onboarding_permission_install_apps),
|
||||||
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
|
subtitle = stringResource(MR.strings.onboarding_permission_install_apps_description),
|
||||||
granted = installGranted,
|
granted = installGranted,
|
||||||
@ -89,7 +96,7 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
// no-op. resulting checks is being done on resume
|
// no-op. resulting checks is being done on resume
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
PermissionItem(
|
PermissionCheckbox(
|
||||||
title = stringResource(MR.strings.onboarding_permission_notifications),
|
title = stringResource(MR.strings.onboarding_permission_notifications),
|
||||||
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
|
subtitle = stringResource(MR.strings.onboarding_permission_notifications_description),
|
||||||
granted = notificationGranted,
|
granted = notificationGranted,
|
||||||
@ -97,7 +104,7 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PermissionItem(
|
PermissionCheckbox(
|
||||||
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
|
title = stringResource(MR.strings.onboarding_permission_ignore_battery_opts),
|
||||||
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
|
subtitle = stringResource(MR.strings.onboarding_permission_ignore_battery_opts_description),
|
||||||
granted = batteryGranted,
|
granted = batteryGranted,
|
||||||
@ -109,6 +116,29 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
|
color = MaterialTheme.colorScheme.onPrimaryContainer,
|
||||||
|
)
|
||||||
|
|
||||||
|
val crashlyticsPref = privacyPreferences.crashlytics()
|
||||||
|
val crashlytics by crashlyticsPref.collectAsState()
|
||||||
|
PermissionSwitch(
|
||||||
|
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||||
|
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
||||||
|
granted = crashlytics,
|
||||||
|
onToggleChange = crashlyticsPref::set,
|
||||||
|
)
|
||||||
|
|
||||||
|
val analyticsPref = privacyPreferences.analytics()
|
||||||
|
val analytics by analyticsPref.collectAsState()
|
||||||
|
PermissionSwitch(
|
||||||
|
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||||
|
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
||||||
|
granted = analytics,
|
||||||
|
onToggleChange = analyticsPref::set,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +157,7 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PermissionItem(
|
private fun PermissionCheckbox(
|
||||||
title: String,
|
title: String,
|
||||||
subtitle: String,
|
subtitle: String,
|
||||||
granted: Boolean,
|
granted: Boolean,
|
||||||
@ -157,4 +187,26 @@ internal class PermissionStep : OnboardingStep {
|
|||||||
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun PermissionSwitch(
|
||||||
|
title: String,
|
||||||
|
subtitle: String,
|
||||||
|
granted: Boolean,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onToggleChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
ListItem(
|
||||||
|
modifier = modifier,
|
||||||
|
headlineContent = { Text(text = title) },
|
||||||
|
supportingContent = { Text(text = subtitle) },
|
||||||
|
trailingContent = {
|
||||||
|
Switch(
|
||||||
|
checked = granted,
|
||||||
|
onCheckedChange = onToggleChange,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = ListItemDefaults.colors(containerColor = Color.Transparent),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.animation.shrinkVertically
|
import androidx.compose.animation.shrinkVertically
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.structuralEqualityPolicy
|
import androidx.compose.runtime.structuralEqualityPolicy
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
|
||||||
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
import eu.kanade.presentation.more.settings.widget.InfoWidget
|
||||||
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget
|
||||||
@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import tachiyomi.presentation.core.components.SliderItem
|
import tachiyomi.presentation.core.components.SliderItem
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false }
|
||||||
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
|
val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp }
|
||||||
@ -156,16 +154,14 @@ internal fun PreferenceItem(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.TrackerPreference -> {
|
is Preference.PreferenceItem.TrackerPreference -> {
|
||||||
val uName by Injekt.get<TrackPreferences>()
|
val isLoggedIn by item.tracker.let { tracker ->
|
||||||
.trackUsername(item.tracker)
|
tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn)
|
||||||
.collectAsState()
|
|
||||||
item.tracker.run {
|
|
||||||
TrackingPreferenceWidget(
|
|
||||||
tracker = this,
|
|
||||||
checked = uName.isNotEmpty(),
|
|
||||||
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
TrackingPreferenceWidget(
|
||||||
|
tracker = item.tracker,
|
||||||
|
checked = isLoggedIn,
|
||||||
|
onClick = { if (isLoggedIn) item.logout() else item.login() },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is Preference.PreferenceItem.InfoPreference -> {
|
is Preference.PreferenceItem.InfoPreference -> {
|
||||||
InfoWidget(text = item.title)
|
InfoWidget(text = item.title)
|
||||||
|
@ -6,6 +6,8 @@ import android.content.Intent
|
|||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.webkit.WebStorage
|
import android.webkit.WebStorage
|
||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -123,6 +125,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
getDataGroup(),
|
getDataGroup(),
|
||||||
getNetworkGroup(networkPreferences = networkPreferences),
|
getNetworkGroup(networkPreferences = networkPreferences),
|
||||||
getLibraryGroup(),
|
getLibraryGroup(),
|
||||||
|
getReaderGroup(basePreferences = basePreferences),
|
||||||
getExtensionsGroup(basePreferences = basePreferences),
|
getExtensionsGroup(basePreferences = basePreferences),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -261,6 +264,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
try {
|
try {
|
||||||
// OkHttp checks for valid values internally
|
// OkHttp checks for valid values internally
|
||||||
Headers.Builder().add("User-Agent", it)
|
Headers.Builder().add("User-Agent", it)
|
||||||
|
context.toast(MR.strings.requires_app_restart)
|
||||||
} catch (_: IllegalArgumentException) {
|
} catch (_: IllegalArgumentException) {
|
||||||
context.toast(MR.strings.error_user_agent_string_invalid)
|
context.toast(MR.strings.error_user_agent_string_invalid)
|
||||||
return@EditTextPreference false
|
return@EditTextPreference false
|
||||||
@ -313,6 +317,34 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getReaderGroup(
|
||||||
|
basePreferences: BasePreferences,
|
||||||
|
): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val chooseColorProfile = rememberLauncherForActivityResult(
|
||||||
|
contract = ActivityResultContracts.OpenDocument(),
|
||||||
|
) { uri ->
|
||||||
|
uri?.let {
|
||||||
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
basePreferences.displayProfile().set(uri.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_category_reader),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.TextPreference(
|
||||||
|
title = stringResource(MR.strings.pref_display_profile),
|
||||||
|
subtitle = basePreferences.displayProfile().get(),
|
||||||
|
onClick = {
|
||||||
|
chooseColorProfile.launch(arrayOf("*/*"))
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getExtensionsGroup(
|
private fun getExtensionsGroup(
|
||||||
basePreferences: BasePreferences,
|
basePreferences: BasePreferences,
|
||||||
|
@ -26,7 +26,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.time.Instant
|
import java.time.LocalDate
|
||||||
|
|
||||||
object SettingsAppearanceScreen : SearchableSettings {
|
object SettingsAppearanceScreen : SearchableSettings {
|
||||||
|
|
||||||
@ -101,7 +101,7 @@ object SettingsAppearanceScreen : SearchableSettings {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val now = remember { Instant.now().toEpochMilli() }
|
val now = remember { LocalDate.now() }
|
||||||
|
|
||||||
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
val dateFormat by uiPreferences.dateFormat().collectAsState()
|
||||||
val formattedNow = remember(dateFormat) {
|
val formattedNow = remember(dateFormat) {
|
||||||
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.more.settings.screen
|
|||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.ReadOnlyComposable
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -13,11 +14,11 @@ import eu.kanade.presentation.more.settings.Preference
|
|||||||
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
import eu.kanade.presentation.more.settings.screen.browse.ExtensionReposScreen
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepoCount
|
||||||
import tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.pluralStringResource
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
@ -33,7 +34,9 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
val navigator = LocalNavigator.currentOrThrow
|
val navigator = LocalNavigator.currentOrThrow
|
||||||
|
|
||||||
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
|
||||||
val reposCount by sourcePreferences.extensionRepos().collectAsState()
|
val getExtensionRepoCount = remember { Injekt.get<GetExtensionRepoCount>() }
|
||||||
|
|
||||||
|
val reposCount by getExtensionRepoCount.subscribe().collectAsState(0)
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
@ -45,7 +48,7 @@ object SettingsBrowseScreen : SearchableSettings {
|
|||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.label_extension_repos),
|
title = stringResource(MR.strings.label_extension_repos),
|
||||||
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount.size, reposCount.size),
|
subtitle = pluralStringResource(MR.plurals.num_repos, reposCount, reposCount),
|
||||||
onClick = {
|
onClick = {
|
||||||
navigator.push(ExtensionReposScreen())
|
navigator.push(ExtensionReposScreen())
|
||||||
},
|
},
|
||||||
|
@ -111,7 +111,17 @@ object SettingsDataScreen : SearchableSettings {
|
|||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
// For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not
|
||||||
|
// work. However, simply retrieving the URI and using it works fine for these devices. Access is not
|
||||||
|
// revoked after the app is closed or the device is restarted.
|
||||||
|
// This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and
|
||||||
|
// ignore the exception if it is thrown.
|
||||||
|
try {
|
||||||
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
logcat(LogPriority.ERROR, e)
|
||||||
|
context.toast(MR.strings.file_picker_uri_permission_unsupported)
|
||||||
|
}
|
||||||
|
|
||||||
UniFile.fromUri(context, uri)?.let {
|
UniFile.fromUri(context, uri)?.let {
|
||||||
storageDirPref.set(it.uri.toString())
|
storageDirPref.set(it.uri.toString())
|
||||||
|
@ -15,7 +15,6 @@ import eu.kanade.presentation.more.settings.widget.TriStateListDialog
|
|||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
import tachiyomi.domain.download.service.DownloadPreferences
|
import tachiyomi.domain.download.service.DownloadPreferences
|
||||||
@ -35,7 +34,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||||
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
|
|
||||||
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
|
||||||
return listOf(
|
return listOf(
|
||||||
@ -120,6 +119,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
allCategories: List<Category>,
|
allCategories: List<Category>,
|
||||||
): Preference.PreferenceGroup {
|
): Preference.PreferenceGroup {
|
||||||
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
|
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
|
||||||
|
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
|
||||||
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
|
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
|
||||||
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
|
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
|
||||||
|
|
||||||
@ -152,6 +152,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||||||
pref = downloadNewChaptersPref,
|
pref = downloadNewChaptersPref,
|
||||||
title = stringResource(MR.strings.pref_download_new),
|
title = stringResource(MR.strings.pref_download_new),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = downloadNewUnreadChaptersOnlyPref,
|
||||||
|
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
|
||||||
|
enabled = downloadNewChapters,
|
||||||
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(MR.strings.categories),
|
title = stringResource(MR.strings.categories),
|
||||||
subtitle = getCategoriesLabel(
|
subtitle = getCategoriesLabel(
|
||||||
|
@ -24,7 +24,6 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import tachiyomi.domain.category.interactor.GetCategories
|
import tachiyomi.domain.category.interactor.GetCategories
|
||||||
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
import tachiyomi.domain.category.interactor.ResetCategoryFlags
|
||||||
import tachiyomi.domain.category.model.Category
|
import tachiyomi.domain.category.model.Category
|
||||||
@ -53,7 +52,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val getCategories = remember { Injekt.get<GetCategories>() }
|
val getCategories = remember { Injekt.get<GetCategories>() }
|
||||||
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
|
||||||
val allCategories by getCategories.subscribe().collectAsState(initial = runBlocking { getCategories.await() })
|
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
||||||
@ -71,9 +70,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
|
val userCategoriesCount = allCategories.filterNot(Category::isSystemCategory).size
|
||||||
|
|
||||||
val defaultCategory by libraryPreferences.defaultCategory().collectAsState()
|
|
||||||
val selectedCategory = allCategories.find { it.id == defaultCategory.toLong() }
|
|
||||||
|
|
||||||
// For default category
|
// For default category
|
||||||
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
|
val ids = listOf(libraryPreferences.defaultCategory().defaultValue()) +
|
||||||
allCategories.fastMap { it.id.toInt() }
|
allCategories.fastMap { it.id.toInt() }
|
||||||
@ -95,7 +91,6 @@ object SettingsLibraryScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = libraryPreferences.defaultCategory(),
|
pref = libraryPreferences.defaultCategory(),
|
||||||
title = stringResource(MR.strings.default_category),
|
title = stringResource(MR.strings.default_category),
|
||||||
subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary),
|
|
||||||
entries = ids.zip(labels).toMap().toImmutableMap(),
|
entries = ids.zip(labels).toMap().toImmutableMap(),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import kotlinx.collections.immutable.toImmutableMap
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
|
import tachiyomi.presentation.core.i18n.pluralStringResource
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.collectAsState
|
import tachiyomi.presentation.core.util.collectAsState
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
@ -29,6 +30,7 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val readerPref = remember { Injekt.get<ReaderPreferences>() }
|
val readerPref = remember { Injekt.get<ReaderPreferences>() }
|
||||||
|
|
||||||
return listOf(
|
return listOf(
|
||||||
Preference.PreferenceItem.ListPreference(
|
Preference.PreferenceItem.ListPreference(
|
||||||
pref = readerPref.defaultReadingMode(),
|
pref = readerPref.defaultReadingMode(),
|
||||||
@ -56,21 +58,12 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
title = stringResource(MR.strings.pref_show_navigation_mode),
|
title = stringResource(MR.strings.pref_show_navigation_mode),
|
||||||
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
|
subtitle = stringResource(MR.strings.pref_show_navigation_mode_summary),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = readerPref.trueColor(),
|
|
||||||
title = stringResource(MR.strings.pref_true_color),
|
|
||||||
subtitle = stringResource(MR.strings.pref_true_color_summary),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPref.pageTransitions(),
|
pref = readerPref.pageTransitions(),
|
||||||
title = stringResource(MR.strings.pref_page_transitions),
|
title = stringResource(MR.strings.pref_page_transitions),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
|
||||||
pref = readerPref.flashOnPageChange(),
|
|
||||||
title = stringResource(MR.strings.pref_flash_page),
|
|
||||||
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
|
||||||
),
|
|
||||||
getDisplayGroup(readerPreferences = readerPref),
|
getDisplayGroup(readerPreferences = readerPref),
|
||||||
|
getEInkGroup(readerPreferences = readerPref),
|
||||||
getReadingGroup(readerPreferences = readerPref),
|
getReadingGroup(readerPreferences = readerPref),
|
||||||
getPagedGroup(readerPreferences = readerPref),
|
getPagedGroup(readerPreferences = readerPref),
|
||||||
getWebtoonGroup(readerPreferences = readerPref),
|
getWebtoonGroup(readerPreferences = readerPref),
|
||||||
@ -126,6 +119,65 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||||
|
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
|
||||||
|
|
||||||
|
val flashMillisPref = readerPreferences.flashDurationMillis()
|
||||||
|
val flashMillis by flashMillisPref.collectAsState()
|
||||||
|
|
||||||
|
val flashIntervalPref = readerPreferences.flashPageInterval()
|
||||||
|
val flashInterval by flashIntervalPref.collectAsState()
|
||||||
|
|
||||||
|
val flashColorPref = readerPreferences.flashColor()
|
||||||
|
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = "E-Ink",
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPreferences.flashOnPageChange(),
|
||||||
|
title = stringResource(MR.strings.pref_flash_page),
|
||||||
|
subtitle = stringResource(MR.strings.pref_flash_page_summ),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SliderPreference(
|
||||||
|
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
|
||||||
|
min = 1,
|
||||||
|
max = 15,
|
||||||
|
title = stringResource(MR.strings.pref_flash_duration),
|
||||||
|
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
|
||||||
|
onValueChanged = {
|
||||||
|
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
enabled = flashPageState,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SliderPreference(
|
||||||
|
value = flashInterval,
|
||||||
|
min = 1,
|
||||||
|
max = 10,
|
||||||
|
title = stringResource(MR.strings.pref_flash_page_interval),
|
||||||
|
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
|
||||||
|
onValueChanged = {
|
||||||
|
flashIntervalPref.set(it)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
enabled = flashPageState,
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = flashColorPref,
|
||||||
|
title = stringResource(MR.strings.pref_flash_with),
|
||||||
|
entries = persistentMapOf(
|
||||||
|
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
|
||||||
|
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
|
||||||
|
ReaderPreferences.FlashColor.WHITE_BLACK
|
||||||
|
to stringResource(MR.strings.pref_flash_style_white_black),
|
||||||
|
),
|
||||||
|
enabled = flashPageState,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
|
||||||
return Preference.PreferenceGroup(
|
return Preference.PreferenceGroup(
|
||||||
@ -341,7 +393,10 @@ object SettingsReaderScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.SwitchPreference(
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
pref = readerPreferences.webtoonDoubleTapZoomEnabled(),
|
pref = readerPreferences.webtoonDoubleTapZoomEnabled(),
|
||||||
title = stringResource(MR.strings.pref_double_tap_zoom),
|
title = stringResource(MR.strings.pref_double_tap_zoom),
|
||||||
enabled = true,
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = readerPreferences.webtoonDisableZoomOut(),
|
||||||
|
title = stringResource(MR.strings.pref_webtoon_disable_zoom_out),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -13,8 +13,10 @@ import androidx.compose.foundation.lazy.LazyListState
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||||
|
import androidx.compose.foundation.text.input.clearText
|
||||||
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.Close
|
import androidx.compose.material.icons.outlined.Close
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
@ -28,11 +30,8 @@ import androidx.compose.runtime.DisposableEffect
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.NonRestartableComposable
|
import androidx.compose.runtime.NonRestartableComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
@ -43,7 +42,6 @@ import androidx.compose.ui.platform.LocalLayoutDirection
|
|||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -88,7 +86,7 @@ class SettingsSearchScreen : Screen() {
|
|||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
var textFieldValue by rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }
|
val textFieldState = rememberTextFieldState()
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
Column {
|
Column {
|
||||||
@ -103,20 +101,19 @@ class SettingsSearchScreen : Screen() {
|
|||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = textFieldValue,
|
state = textFieldState,
|
||||||
onValueChange = { textFieldValue = it },
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.focusRequester(focusRequester)
|
.focusRequester(focusRequester)
|
||||||
.runOnEnterKeyPressed(action = focusManager::clearFocus),
|
.runOnEnterKeyPressed(action = focusManager::clearFocus),
|
||||||
textStyle = MaterialTheme.typography.bodyLarge
|
textStyle = MaterialTheme.typography.bodyLarge
|
||||||
.copy(color = MaterialTheme.colorScheme.onSurface),
|
.copy(color = MaterialTheme.colorScheme.onSurface),
|
||||||
singleLine = true,
|
lineLimits = TextFieldLineLimits.SingleLine,
|
||||||
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search),
|
||||||
keyboardActions = KeyboardActions(onSearch = { focusManager.clearFocus() }),
|
onKeyboardAction = { focusManager.clearFocus() },
|
||||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||||
decorationBox = {
|
decorator = {
|
||||||
if (textFieldValue.text.isEmpty()) {
|
if (textFieldState.text.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(MR.strings.action_search_settings),
|
text = stringResource(MR.strings.action_search_settings),
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@ -128,8 +125,8 @@ class SettingsSearchScreen : Screen() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
if (textFieldValue.text.isNotEmpty()) {
|
if (textFieldState.text.isNotEmpty()) {
|
||||||
IconButton(onClick = { textFieldValue = TextFieldValue() }) {
|
IconButton(onClick = { textFieldState.clearText() }) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Close,
|
imageVector = Icons.Outlined.Close,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
@ -144,7 +141,7 @@ class SettingsSearchScreen : Screen() {
|
|||||||
},
|
},
|
||||||
) { contentPadding ->
|
) { contentPadding ->
|
||||||
SearchResult(
|
SearchResult(
|
||||||
searchKey = textFieldValue.text,
|
searchKey = textFieldState.text.toString(),
|
||||||
listState = listState,
|
listState = listState,
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) { result ->
|
) { result ->
|
||||||
|
@ -7,6 +7,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
|
import eu.kanade.tachiyomi.core.security.PrivacyPreferences
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
||||||
@ -28,55 +29,91 @@ object SettingsSecurityScreen : SearchableSettings {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun getPreferences(): List<Preference> {
|
override fun getPreferences(): List<Preference> {
|
||||||
val context = LocalContext.current
|
|
||||||
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
|
val securityPreferences = remember { Injekt.get<SecurityPreferences>() }
|
||||||
val authSupported = remember { context.isAuthenticationSupported() }
|
val privacyPreferences = remember { Injekt.get<PrivacyPreferences>() }
|
||||||
|
return listOf(
|
||||||
|
getSecurityGroup(securityPreferences),
|
||||||
|
getFirebaseGroup(privacyPreferences),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun getSecurityGroup(
|
||||||
|
securityPreferences: SecurityPreferences,
|
||||||
|
): Preference.PreferenceGroup {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val authSupported = remember { context.isAuthenticationSupported() }
|
||||||
val useAuthPref = securityPreferences.useAuthenticator()
|
val useAuthPref = securityPreferences.useAuthenticator()
|
||||||
val useAuth by useAuthPref.collectAsState()
|
val useAuth by useAuthPref.collectAsState()
|
||||||
|
|
||||||
return listOf(
|
return Preference.PreferenceGroup(
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
title = stringResource(MR.strings.pref_security),
|
||||||
pref = useAuthPref,
|
preferenceItems = persistentListOf(
|
||||||
title = stringResource(MR.strings.lock_with_biometrics),
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
enabled = authSupported,
|
pref = useAuthPref,
|
||||||
onValueChanged = {
|
title = stringResource(MR.strings.lock_with_biometrics),
|
||||||
(context as FragmentActivity).authenticate(
|
enabled = authSupported,
|
||||||
title = context.stringResource(MR.strings.lock_with_biometrics),
|
onValueChanged = {
|
||||||
)
|
(context as FragmentActivity).authenticate(
|
||||||
},
|
title = context.stringResource(MR.strings.lock_with_biometrics),
|
||||||
),
|
)
|
||||||
Preference.PreferenceItem.ListPreference(
|
},
|
||||||
pref = securityPreferences.lockAppAfter(),
|
),
|
||||||
title = stringResource(MR.strings.lock_when_idle),
|
Preference.PreferenceItem.ListPreference(
|
||||||
enabled = authSupported && useAuth,
|
pref = securityPreferences.lockAppAfter(),
|
||||||
entries = LockAfterValues
|
title = stringResource(MR.strings.lock_when_idle),
|
||||||
.associateWith {
|
enabled = authSupported && useAuth,
|
||||||
when (it) {
|
entries = LockAfterValues
|
||||||
-1 -> stringResource(MR.strings.lock_never)
|
.associateWith {
|
||||||
0 -> stringResource(MR.strings.lock_always)
|
when (it) {
|
||||||
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
|
-1 -> stringResource(MR.strings.lock_never)
|
||||||
|
0 -> stringResource(MR.strings.lock_always)
|
||||||
|
else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.toImmutableMap(),
|
||||||
.toImmutableMap(),
|
onValueChanged = {
|
||||||
onValueChanged = {
|
(context as FragmentActivity).authenticate(
|
||||||
(context as FragmentActivity).authenticate(
|
title = context.stringResource(MR.strings.lock_when_idle),
|
||||||
title = context.stringResource(MR.strings.lock_when_idle),
|
)
|
||||||
)
|
},
|
||||||
},
|
),
|
||||||
|
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = securityPreferences.hideNotificationContent(),
|
||||||
|
title = stringResource(MR.strings.hide_notification_content),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = securityPreferences.secureScreen(),
|
||||||
|
title = stringResource(MR.strings.secure_screen),
|
||||||
|
entries = SecurityPreferences.SecureScreenMode.entries
|
||||||
|
.associateWith { stringResource(it.titleRes) }
|
||||||
|
.toImmutableMap(),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.SwitchPreference(
|
)
|
||||||
pref = securityPreferences.hideNotificationContent(),
|
}
|
||||||
title = stringResource(MR.strings.hide_notification_content),
|
|
||||||
|
@Composable
|
||||||
|
private fun getFirebaseGroup(
|
||||||
|
privacyPreferences: PrivacyPreferences,
|
||||||
|
): Preference.PreferenceGroup {
|
||||||
|
return Preference.PreferenceGroup(
|
||||||
|
title = stringResource(MR.strings.pref_firebase),
|
||||||
|
preferenceItems = persistentListOf(
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = privacyPreferences.crashlytics(),
|
||||||
|
title = stringResource(MR.strings.onboarding_permission_crashlytics),
|
||||||
|
subtitle = stringResource(MR.strings.onboarding_permission_crashlytics_description),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.SwitchPreference(
|
||||||
|
pref = privacyPreferences.analytics(),
|
||||||
|
title = stringResource(MR.strings.onboarding_permission_analytics),
|
||||||
|
subtitle = stringResource(MR.strings.onboarding_permission_analytics_description),
|
||||||
|
),
|
||||||
|
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.firebase_summary)),
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.ListPreference(
|
|
||||||
pref = securityPreferences.secureScreen(),
|
|
||||||
title = stringResource(MR.strings.secure_screen),
|
|
||||||
entries = SecurityPreferences.SecureScreenMode.entries
|
|
||||||
.associateWith { stringResource(it.titleRes) }
|
|
||||||
.toImmutableMap(),
|
|
||||||
),
|
|
||||||
Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ import androidx.compose.ui.text.input.VisualTransformation
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
|
import eu.kanade.domain.track.model.AutoTrackState
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
import eu.kanade.domain.track.service.TrackPreferences
|
||||||
import eu.kanade.presentation.more.settings.Preference
|
import eu.kanade.presentation.more.settings.Preference
|
||||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||||
@ -53,6 +54,7 @@ import eu.kanade.tachiyomi.util.system.openInBrowser
|
|||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toPersistentMap
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.core.common.util.lang.withUIContext
|
import tachiyomi.core.common.util.lang.withUIContext
|
||||||
import tachiyomi.domain.source.service.SourceManager
|
import tachiyomi.domain.source.service.SourceManager
|
||||||
@ -85,6 +87,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
val trackPreferences = remember { Injekt.get<TrackPreferences>() }
|
||||||
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
val trackerManager = remember { Injekt.get<TrackerManager>() }
|
||||||
val sourceManager = remember { Injekt.get<SourceManager>() }
|
val sourceManager = remember { Injekt.get<SourceManager>() }
|
||||||
|
val autoTrackStatePref = trackPreferences.autoUpdateTrackOnMarkRead()
|
||||||
|
|
||||||
var dialog by remember { mutableStateOf<Any?>(null) }
|
var dialog by remember { mutableStateOf<Any?>(null) }
|
||||||
dialog?.run {
|
dialog?.run {
|
||||||
@ -125,6 +128,13 @@ object SettingsTrackingScreen : SearchableSettings {
|
|||||||
pref = trackPreferences.autoUpdateTrack(),
|
pref = trackPreferences.autoUpdateTrack(),
|
||||||
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
title = stringResource(MR.strings.pref_auto_update_manga_sync),
|
||||||
),
|
),
|
||||||
|
Preference.PreferenceItem.ListPreference(
|
||||||
|
pref = trackPreferences.autoUpdateTrackOnMarkRead(),
|
||||||
|
title = stringResource(MR.strings.pref_auto_update_manga_on_mark_read),
|
||||||
|
entries = AutoTrackState.entries
|
||||||
|
.associateWith { stringResource(it.titleRes) }
|
||||||
|
.toPersistentMap(),
|
||||||
|
),
|
||||||
Preference.PreferenceGroup(
|
Preference.PreferenceGroup(
|
||||||
title = stringResource(MR.strings.services),
|
title = stringResource(MR.strings.services),
|
||||||
preferenceItems = persistentListOf(
|
preferenceItems = persistentListOf(
|
||||||
|
@ -55,10 +55,9 @@ import tachiyomi.presentation.core.icons.Reddit
|
|||||||
import tachiyomi.presentation.core.icons.X
|
import tachiyomi.presentation.core.icons.X
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.text.DateFormat
|
import java.time.Instant
|
||||||
import java.text.SimpleDateFormat
|
import java.time.LocalDateTime
|
||||||
import java.util.Locale
|
import java.time.ZoneId
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
object AboutScreen : Screen() {
|
object AboutScreen : Screen() {
|
||||||
|
|
||||||
@ -269,18 +268,15 @@ object AboutScreen : Screen() {
|
|||||||
|
|
||||||
internal fun getFormattedBuildTime(): String {
|
internal fun getFormattedBuildTime(): String {
|
||||||
return try {
|
return try {
|
||||||
val inputDf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'", Locale.US)
|
LocalDateTime.ofInstant(
|
||||||
inputDf.timeZone = TimeZone.getTimeZone("UTC")
|
Instant.parse(BuildConfig.BUILD_TIME),
|
||||||
val buildTime = inputDf.parse(BuildConfig.BUILD_TIME)
|
ZoneId.systemDefault(),
|
||||||
|
|
||||||
val outputDf = DateFormat.getDateTimeInstance(
|
|
||||||
DateFormat.MEDIUM,
|
|
||||||
DateFormat.SHORT,
|
|
||||||
Locale.getDefault(),
|
|
||||||
)
|
)
|
||||||
outputDf.timeZone = TimeZone.getDefault()
|
.toDateTimestampString(
|
||||||
|
UiPreferences.dateFormat(
|
||||||
buildTime!!.toDateTimestampString(UiPreferences.dateFormat(Injekt.get<UiPreferences>().dateFormat().get()))
|
Injekt.get<UiPreferences>().dateFormat().get(),
|
||||||
|
),
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
BuildConfig.BUILD_TIME
|
BuildConfig.BUILD_TIME
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,13 @@ class OpenSourceLicensesScreen : Screen() {
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
onLibraryClick = {
|
onLibraryClick = {
|
||||||
val libraryLicenseScreen = OpenSourceLibraryLicenseScreen(
|
navigator.push(
|
||||||
name = it.library.name,
|
OpenSourceLibraryLicenseScreen(
|
||||||
website = it.library.website,
|
name = it.name,
|
||||||
license = it.library.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
website = it.website,
|
||||||
|
license = it.licenses.firstOrNull()?.htmlReadyLicenseContent.orEmpty(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
navigator.push(libraryLicenseScreen)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,15 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import cafe.adriel.voyager.core.model.rememberScreenModel
|
import cafe.adriel.voyager.core.model.rememberScreenModel
|
||||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
import cafe.adriel.voyager.navigator.currentOrThrow
|
import cafe.adriel.voyager.navigator.currentOrThrow
|
||||||
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConfirmDialog
|
||||||
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoConflictDialog
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoCreateDialog
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionRepoDeleteDialog
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionReposScreen
|
import eu.kanade.presentation.more.settings.screen.browse.components.ExtensionReposScreen
|
||||||
import eu.kanade.presentation.util.Screen
|
import eu.kanade.presentation.util.Screen
|
||||||
|
import eu.kanade.tachiyomi.util.system.openInBrowser
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import tachiyomi.presentation.core.screens.LoadingScreen
|
import tachiyomi.presentation.core.screens.LoadingScreen
|
||||||
|
|
||||||
@ -29,7 +33,7 @@ class ExtensionReposScreen(
|
|||||||
val state by screenModel.state.collectAsState()
|
val state by screenModel.state.collectAsState()
|
||||||
|
|
||||||
LaunchedEffect(url) {
|
LaunchedEffect(url) {
|
||||||
url?.let { screenModel.createRepo(it) }
|
url?.let { screenModel.showDialog(RepoDialog.Confirm(it)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is RepoScreenState.Loading) {
|
if (state is RepoScreenState.Loading) {
|
||||||
@ -42,17 +46,19 @@ class ExtensionReposScreen(
|
|||||||
ExtensionReposScreen(
|
ExtensionReposScreen(
|
||||||
state = successState,
|
state = successState,
|
||||||
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
onClickCreate = { screenModel.showDialog(RepoDialog.Create) },
|
||||||
|
onOpenWebsite = { context.openInBrowser(it.website) },
|
||||||
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
onClickDelete = { screenModel.showDialog(RepoDialog.Delete(it)) },
|
||||||
|
onClickRefresh = { screenModel.refreshRepos() },
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val dialog = successState.dialog) {
|
when (val dialog = successState.dialog) {
|
||||||
null -> {}
|
null -> {}
|
||||||
RepoDialog.Create -> {
|
is RepoDialog.Create -> {
|
||||||
ExtensionRepoCreateDialog(
|
ExtensionRepoCreateDialog(
|
||||||
onDismissRequest = screenModel::dismissDialog,
|
onDismissRequest = screenModel::dismissDialog,
|
||||||
onCreate = { screenModel.createRepo(it) },
|
onCreate = { screenModel.createRepo(it) },
|
||||||
repos = successState.repos,
|
repoUrls = successState.repos.map { it.baseUrl }.toImmutableSet(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is RepoDialog.Delete -> {
|
is RepoDialog.Delete -> {
|
||||||
@ -62,6 +68,21 @@ class ExtensionReposScreen(
|
|||||||
repo = dialog.repo,
|
repo = dialog.repo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
is RepoDialog.Conflict -> {
|
||||||
|
ExtensionRepoConflictDialog(
|
||||||
|
onDismissRequest = screenModel::dismissDialog,
|
||||||
|
onMigrate = { screenModel.replaceRepo(dialog.newRepo) },
|
||||||
|
oldRepo = dialog.oldRepo,
|
||||||
|
newRepo = dialog.newRepo,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is RepoDialog.Confirm -> {
|
||||||
|
ExtensionRepoConfirmDialog(
|
||||||
|
onDismissRequest = screenModel::dismissDialog,
|
||||||
|
onCreate = { screenModel.createRepo(dialog.url) },
|
||||||
|
repo = dialog.url,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
63
app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt
63
app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/ExtensionReposScreenModel.kt
@ -4,24 +4,29 @@ import androidx.compose.runtime.Immutable
|
|||||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||||
import cafe.adriel.voyager.core.model.screenModelScope
|
import cafe.adriel.voyager.core.model.screenModelScope
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import eu.kanade.domain.extension.interactor.CreateExtensionRepo
|
|
||||||
import eu.kanade.domain.extension.interactor.DeleteExtensionRepo
|
|
||||||
import eu.kanade.domain.extension.interactor.GetExtensionRepos
|
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.ReplaceExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.interactor.UpdateExtensionRepo
|
||||||
|
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||||
import tachiyomi.core.common.util.lang.launchIO
|
import tachiyomi.core.common.util.lang.launchIO
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
class ExtensionReposScreenModel(
|
class ExtensionReposScreenModel(
|
||||||
private val getExtensionRepos: GetExtensionRepos = Injekt.get(),
|
private val getExtensionRepo: GetExtensionRepo = Injekt.get(),
|
||||||
private val createExtensionRepo: CreateExtensionRepo = Injekt.get(),
|
private val createExtensionRepo: CreateExtensionRepo = Injekt.get(),
|
||||||
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
|
private val deleteExtensionRepo: DeleteExtensionRepo = Injekt.get(),
|
||||||
|
private val replaceExtensionRepo: ReplaceExtensionRepo = Injekt.get(),
|
||||||
|
private val updateExtensionRepo: UpdateExtensionRepo = Injekt.get(),
|
||||||
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
) : StateScreenModel<RepoScreenState>(RepoScreenState.Loading) {
|
||||||
|
|
||||||
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
private val _events: Channel<RepoEvent> = Channel(Int.MAX_VALUE)
|
||||||
@ -29,7 +34,7 @@ class ExtensionReposScreenModel(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
getExtensionRepos.subscribe()
|
getExtensionRepo.subscribeAll()
|
||||||
.collectLatest { repos ->
|
.collectLatest { repos ->
|
||||||
mutableState.update {
|
mutableState.update {
|
||||||
RepoScreenState.Success(
|
RepoScreenState.Success(
|
||||||
@ -43,25 +48,51 @@ class ExtensionReposScreenModel(
|
|||||||
/**
|
/**
|
||||||
* Creates and adds a new repo to the database.
|
* Creates and adds a new repo to the database.
|
||||||
*
|
*
|
||||||
* @param name The name of the repo to create.
|
* @param baseUrl The baseUrl of the repo to create.
|
||||||
*/
|
*/
|
||||||
fun createRepo(name: String) {
|
fun createRepo(baseUrl: String) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
when (createExtensionRepo.await(name)) {
|
when (val result = createExtensionRepo.await(baseUrl)) {
|
||||||
is CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
CreateExtensionRepo.Result.InvalidUrl -> _events.send(RepoEvent.InvalidUrl)
|
||||||
|
CreateExtensionRepo.Result.RepoAlreadyExists -> _events.send(RepoEvent.RepoAlreadyExists)
|
||||||
|
is CreateExtensionRepo.Result.DuplicateFingerprint -> {
|
||||||
|
showDialog(RepoDialog.Conflict(result.oldRepo, result.newRepo))
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the given repo from the database.
|
* Inserts a repo to the database, replace a matching repo with the same signing key fingerprint if found.
|
||||||
*
|
*
|
||||||
* @param repo The repo to delete.
|
* @param newRepo The repo to insert
|
||||||
*/
|
*/
|
||||||
fun deleteRepo(repo: String) {
|
fun replaceRepo(newRepo: ExtensionRepo) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
deleteExtensionRepo.await(repo)
|
replaceExtensionRepo.await(newRepo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refreshes information for each repository.
|
||||||
|
*/
|
||||||
|
fun refreshRepos() {
|
||||||
|
val status = state.value
|
||||||
|
|
||||||
|
if (status is RepoScreenState.Success) {
|
||||||
|
screenModelScope.launchIO {
|
||||||
|
updateExtensionRepo.awaitAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the given repo from the database
|
||||||
|
*/
|
||||||
|
fun deleteRepo(baseUrl: String) {
|
||||||
|
screenModelScope.launchIO {
|
||||||
|
deleteExtensionRepo.await(baseUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,11 +118,14 @@ class ExtensionReposScreenModel(
|
|||||||
sealed class RepoEvent {
|
sealed class RepoEvent {
|
||||||
sealed class LocalizedMessage(val stringRes: StringResource) : RepoEvent()
|
sealed class LocalizedMessage(val stringRes: StringResource) : RepoEvent()
|
||||||
data object InvalidUrl : LocalizedMessage(MR.strings.invalid_repo_name)
|
data object InvalidUrl : LocalizedMessage(MR.strings.invalid_repo_name)
|
||||||
|
data object RepoAlreadyExists : LocalizedMessage(MR.strings.error_repo_exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class RepoDialog {
|
sealed class RepoDialog {
|
||||||
data object Create : RepoDialog()
|
data object Create : RepoDialog()
|
||||||
data class Delete(val repo: String) : RepoDialog()
|
data class Delete(val repo: String) : RepoDialog()
|
||||||
|
data class Conflict(val oldRepo: ExtensionRepo, val newRepo: ExtensionRepo) : RepoDialog()
|
||||||
|
data class Confirm(val url: String) : RepoDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class RepoScreenState {
|
sealed class RepoScreenState {
|
||||||
@ -101,7 +135,8 @@ sealed class RepoScreenState {
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Success(
|
data class Success(
|
||||||
val repos: ImmutableSet<String>,
|
val repos: ImmutableSet<ExtensionRepo>,
|
||||||
|
val oldRepos: ImmutableSet<String>? = null,
|
||||||
val dialog: RepoDialog? = null,
|
val dialog: RepoDialog? = null,
|
||||||
) : RepoScreenState() {
|
) : RepoScreenState() {
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Label
|
import androidx.compose.material.icons.automirrored.outlined.Label
|
||||||
|
import androidx.compose.material.icons.automirrored.outlined.OpenInNew
|
||||||
import androidx.compose.material.icons.outlined.ContentCopy
|
import androidx.compose.material.icons.outlined.ContentCopy
|
||||||
import androidx.compose.material.icons.outlined.Delete
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.ElevatedCard
|
import androidx.compose.material3.ElevatedCard
|
||||||
@ -22,15 +23,17 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
|
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionReposContent(
|
fun ExtensionReposContent(
|
||||||
repos: ImmutableSet<String>,
|
repos: ImmutableSet<ExtensionRepo>,
|
||||||
lazyListState: LazyListState,
|
lazyListState: LazyListState,
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
|
onOpenWebsite: (ExtensionRepo) -> Unit,
|
||||||
onClickDelete: (String) -> Unit,
|
onClickDelete: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@ -43,9 +46,10 @@ fun ExtensionReposContent(
|
|||||||
repos.forEach {
|
repos.forEach {
|
||||||
item {
|
item {
|
||||||
ExtensionRepoListItem(
|
ExtensionRepoListItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
modifier = Modifier.animateItem(),
|
||||||
repo = it,
|
repo = it,
|
||||||
onDelete = { onClickDelete(it) },
|
onOpenWebsite = { onOpenWebsite(it) },
|
||||||
|
onDelete = { onClickDelete(it.baseUrl) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -54,7 +58,8 @@ fun ExtensionReposContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionRepoListItem(
|
private fun ExtensionRepoListItem(
|
||||||
repo: String,
|
repo: ExtensionRepo,
|
||||||
|
onOpenWebsite: () -> Unit,
|
||||||
onDelete: () -> Unit,
|
onDelete: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@ -74,16 +79,27 @@ private fun ExtensionRepoListItem(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
Icon(imageVector = Icons.AutoMirrored.Outlined.Label, contentDescription = null)
|
||||||
Text(text = repo, modifier = Modifier.padding(start = MaterialTheme.padding.medium))
|
Text(
|
||||||
|
text = repo.name,
|
||||||
|
modifier = Modifier.padding(start = MaterialTheme.padding.medium),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
) {
|
) {
|
||||||
|
IconButton(onClick = onOpenWebsite) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Outlined.OpenInNew,
|
||||||
|
contentDescription = stringResource(MR.strings.action_open_in_browser),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
val url = "$repo/index.min.json"
|
val url = "${repo.baseUrl}/index.min.json"
|
||||||
context.copyToClipboard(url, url)
|
context.copyToClipboard(url, url)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package eu.kanade.presentation.more.settings.screen.browse.components
|
package eu.kanade.presentation.more.settings.screen.browse.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@ -14,8 +15,10 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import kotlinx.collections.immutable.ImmutableSet
|
import kotlinx.collections.immutable.ImmutableSet
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@ -24,12 +27,12 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
fun ExtensionRepoCreateDialog(
|
fun ExtensionRepoCreateDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
onCreate: (String) -> Unit,
|
onCreate: (String) -> Unit,
|
||||||
repos: ImmutableSet<String>,
|
repoUrls: ImmutableSet<String>,
|
||||||
) {
|
) {
|
||||||
var name by remember { mutableStateOf("") }
|
var name by remember { mutableStateOf("") }
|
||||||
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val nameAlreadyExists = remember(name) { repos.contains(name) }
|
val nameAlreadyExists = remember(name) { repoUrls.contains(name) }
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismissRequest,
|
onDismissRequest = onDismissRequest,
|
||||||
@ -73,6 +76,7 @@ fun ExtensionRepoCreateDialog(
|
|||||||
Text(text = stringResource(msgRes))
|
Text(text = stringResource(msgRes))
|
||||||
},
|
},
|
||||||
isError = name.isNotEmpty() && nameAlreadyExists,
|
isError = name.isNotEmpty() && nameAlreadyExists,
|
||||||
|
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -115,3 +119,68 @@ fun ExtensionRepoDeleteDialog(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExtensionRepoConflictDialog(
|
||||||
|
oldRepo: ExtensionRepo,
|
||||||
|
newRepo: ExtensionRepo,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onMigrate: () -> Unit,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onMigrate()
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_replace_repo))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.action_replace_repo_title))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.action_replace_repo_message, newRepo.name, oldRepo.name))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExtensionRepoConfirmDialog(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onCreate: () -> Unit,
|
||||||
|
repo: String,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(MR.strings.action_add_repo))
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(MR.strings.add_repo_confirmation, repo))
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
onCreate()
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(MR.strings.action_add))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(text = stringResource(MR.strings.action_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -5,12 +5,17 @@ package eu.kanade.presentation.more.settings.screen.browse.components
|
|||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Refresh
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
import eu.kanade.presentation.category.components.CategoryFloatingActionButton
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState
|
import eu.kanade.presentation.more.settings.screen.browse.RepoScreenState
|
||||||
|
import mihon.domain.extensionrepo.model.ExtensionRepo
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
@ -23,7 +28,9 @@ import tachiyomi.presentation.core.util.plus
|
|||||||
fun ExtensionReposScreen(
|
fun ExtensionReposScreen(
|
||||||
state: RepoScreenState.Success,
|
state: RepoScreenState.Success,
|
||||||
onClickCreate: () -> Unit,
|
onClickCreate: () -> Unit,
|
||||||
|
onOpenWebsite: (ExtensionRepo) -> Unit,
|
||||||
onClickDelete: (String) -> Unit,
|
onClickDelete: (String) -> Unit,
|
||||||
|
onClickRefresh: () -> Unit,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
@ -33,6 +40,14 @@ fun ExtensionReposScreen(
|
|||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
title = stringResource(MR.strings.label_extension_repos),
|
title = stringResource(MR.strings.label_extension_repos),
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onClickRefresh) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Refresh,
|
||||||
|
contentDescription = stringResource(resource = MR.strings.action_webview_refresh),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
@ -55,6 +70,7 @@ fun ExtensionReposScreen(
|
|||||||
lazyListState = lazyListState,
|
lazyListState = lazyListState,
|
||||||
paddingValues = paddingValues + topSmallPaddingValues +
|
paddingValues = paddingValues + topSmallPaddingValues +
|
||||||
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
PaddingValues(horizontal = MaterialTheme.padding.medium),
|
||||||
|
onOpenWebsite = onOpenWebsite,
|
||||||
onClickDelete = onClickDelete,
|
onClickDelete = onClickDelete,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import android.content.Intent
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@ -68,7 +67,7 @@ class CreateBackupScreen : Screen() {
|
|||||||
LazyColumnWithAction(
|
LazyColumnWithAction(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
actionLabel = stringResource(MR.strings.action_create),
|
actionLabel = stringResource(MR.strings.action_create),
|
||||||
actionEnabled = state.options.anyEnabled(),
|
actionEnabled = state.options.canCreate(),
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
if (!BackupCreateJob.isManualJobRunning(context)) {
|
if (!BackupCreateJob.isManualJobRunning(context)) {
|
||||||
try {
|
try {
|
||||||
@ -103,7 +102,7 @@ class CreateBackupScreen : Screen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ColumnScope.Options(
|
private fun Options(
|
||||||
options: ImmutableList<BackupOptions.Entry>,
|
options: ImmutableList<BackupOptions.Entry>,
|
||||||
state: CreateBackupScreenModel.State,
|
state: CreateBackupScreenModel.State,
|
||||||
model: CreateBackupScreenModel,
|
model: CreateBackupScreenModel,
|
||||||
|
@ -63,7 +63,7 @@ class RestoreBackupScreen(
|
|||||||
LazyColumnWithAction(
|
LazyColumnWithAction(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
actionLabel = stringResource(MR.strings.action_restore),
|
actionLabel = stringResource(MR.strings.action_restore),
|
||||||
actionEnabled = state.canRestore && state.options.anyEnabled(),
|
actionEnabled = state.canRestore && state.options.canRestore(),
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
model.startRestore()
|
model.startRestore()
|
||||||
navigator.pop()
|
navigator.pop()
|
||||||
|
@ -28,7 +28,7 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
class BackupSchemaScreen : Screen() {
|
class BackupSchemaScreen : Screen() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val title = "Backup file schema"
|
const val TITLE = "Backup file schema"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -41,7 +41,7 @@ class BackupSchemaScreen : Screen() {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppBar(
|
AppBar(
|
||||||
title = title,
|
title = TITLE,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
@ -50,7 +50,7 @@ class BackupSchemaScreen : Screen() {
|
|||||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||||
icon = Icons.Default.ContentCopy,
|
icon = Icons.Default.ContentCopy,
|
||||||
onClick = {
|
onClick = {
|
||||||
context.copyToClipboard(title, schema)
|
context.copyToClipboard(TITLE, schema)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -31,11 +31,11 @@ class DebugInfoScreen : Screen() {
|
|||||||
itemsProvider = {
|
itemsProvider = {
|
||||||
listOf(
|
listOf(
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = WorkerInfoScreen.title,
|
title = WorkerInfoScreen.TITLE,
|
||||||
onClick = { navigator.push(WorkerInfoScreen()) },
|
onClick = { navigator.push(WorkerInfoScreen()) },
|
||||||
),
|
),
|
||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = BackupSchemaScreen.title,
|
title = BackupSchemaScreen.TITLE,
|
||||||
onClick = { navigator.push(BackupSchemaScreen()) },
|
onClick = { navigator.push(BackupSchemaScreen()) },
|
||||||
),
|
),
|
||||||
getAppInfoGroup(),
|
getAppInfoGroup(),
|
||||||
@ -78,7 +78,7 @@ class DebugInfoScreen : Screen() {
|
|||||||
val status by produceState(initialValue = "-") {
|
val status by produceState(initialValue = "-") {
|
||||||
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
|
val result = ProfileVerifier.getCompilationStatusAsync().await().profileInstallResultCode
|
||||||
value = when (result) {
|
value = when (result) {
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE -> "No profile installed"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_NO_PROFILE_INSTALLED -> "No profile installed"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE -> "Compiled"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
|
ProfileVerifier.CompilationStatus.RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING ->
|
||||||
"Compiled non-matching"
|
"Compiled non-matching"
|
||||||
@ -88,6 +88,7 @@ class DebugInfoScreen : Screen() {
|
|||||||
-> "Error $result"
|
-> "Error $result"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION -> "Not supported"
|
||||||
ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation"
|
ProfileVerifier.CompilationStatus.RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION -> "Pending compilation"
|
||||||
|
ProfileVerifier.CompilationStatus.RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED -> "No profile embedded"
|
||||||
else -> "Unknown code $result"
|
else -> "Unknown code $result"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,14 @@ import tachiyomi.presentation.core.i18n.stringResource
|
|||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Date
|
import java.time.Instant
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
import java.time.ZoneId
|
||||||
|
|
||||||
class WorkerInfoScreen : Screen() {
|
class WorkerInfoScreen : Screen() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val title = "Worker info"
|
const val TITLE = "Worker info"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -63,7 +65,7 @@ class WorkerInfoScreen : Screen() {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppBar(
|
AppBar(
|
||||||
title = title,
|
title = TITLE,
|
||||||
navigateUp = navigator::pop,
|
navigateUp = navigator::pop,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
@ -72,7 +74,7 @@ class WorkerInfoScreen : Screen() {
|
|||||||
title = stringResource(MR.strings.action_copy_to_clipboard),
|
title = stringResource(MR.strings.action_copy_to_clipboard),
|
||||||
icon = Icons.Default.ContentCopy,
|
icon = Icons.Default.ContentCopy,
|
||||||
onClick = {
|
onClick = {
|
||||||
context.copyToClipboard(title, enqueued + finished + running)
|
context.copyToClipboard(TITLE, enqueued + finished + running)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -148,13 +150,16 @@ class WorkerInfoScreen : Screen() {
|
|||||||
}
|
}
|
||||||
appendLine("State: ${workInfo.state}")
|
appendLine("State: ${workInfo.state}")
|
||||||
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
if (workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||||
appendLine(
|
val timestamp = LocalDateTime.ofInstant(
|
||||||
"Next scheduled run: ${Date(workInfo.nextScheduleTimeMillis).toDateTimestampString(
|
Instant.ofEpochMilli(workInfo.nextScheduleTimeMillis),
|
||||||
|
ZoneId.systemDefault(),
|
||||||
|
)
|
||||||
|
.toDateTimestampString(
|
||||||
UiPreferences.dateFormat(
|
UiPreferences.dateFormat(
|
||||||
Injekt.get<UiPreferences>().dateFormat().get(),
|
Injekt.get<UiPreferences>().dateFormat().get(),
|
||||||
),
|
),
|
||||||
)}",
|
)
|
||||||
)
|
appendLine("Next scheduled run: $timestamp")
|
||||||
appendLine("Attempt #${workInfo.runAttemptCount + 1}")
|
appendLine("Attempt #${workInfo.runAttemptCount + 1}")
|
||||||
}
|
}
|
||||||
appendLine()
|
appendLine()
|
||||||
|
@ -223,13 +223,12 @@ fun AppThemePreviewItem(
|
|||||||
contentAlignment = Alignment.BottomCenter,
|
contentAlignment = Alignment.BottomCenter,
|
||||||
) {
|
) {
|
||||||
Surface(
|
Surface(
|
||||||
tonalElevation = 3.dp,
|
color = MaterialTheme.colorScheme.surfaceContainer,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(32.dp)
|
.height(32.dp)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
@ -26,8 +26,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun <T> ListPreferenceWidget(
|
fun <T> ListPreferenceWidget(
|
||||||
@ -69,8 +67,8 @@ fun <T> ListPreferenceWidget(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
|
@ -30,11 +30,11 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import tachiyomi.i18n.MR
|
import tachiyomi.i18n.MR
|
||||||
import tachiyomi.presentation.core.i18n.stringResource
|
import tachiyomi.presentation.core.i18n.stringResource
|
||||||
import tachiyomi.presentation.core.util.isScrolledToEnd
|
|
||||||
import tachiyomi.presentation.core.util.isScrolledToStart
|
|
||||||
|
|
||||||
private enum class State {
|
private enum class State {
|
||||||
CHECKED, INVERSED, UNCHECKED
|
CHECKED,
|
||||||
|
INVERSED,
|
||||||
|
UNCHECKED,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -115,16 +115,8 @@ fun <T> TriStateListDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!listState.isScrolledToStart()) {
|
if (listState.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter))
|
||||||
HorizontalDivider(
|
if (listState.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter))
|
||||||
modifier = Modifier.align(Alignment.TopCenter),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (!listState.isScrolledToEnd()) {
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.align(Alignment.BottomCenter),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
|
import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -73,7 +73,7 @@ private fun RowScope.BaseStatsItem(
|
|||||||
style = subtitleStyle
|
style = subtitleStyle
|
||||||
.copy(
|
.copy(
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
.copy(alpha = SecondaryItemAlpha),
|
.copy(alpha = SECONDARY_ALPHA),
|
||||||
),
|
),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user