mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-21 12:17:12 +03:00
Merge upstream up to d4dfa9a2c2
Squashed commit of the following: commit49c30b336d
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 19:28:32 2023 +0100 fuck lint commit06f3f7e6b4
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 19:26:41 2023 +0100 cleanup commit807ea3d1a5
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 18:51:04 2023 +0100 merge41 Last commit merged:d4dfa9a2c2
commit2b9fe5c389
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 15:18:03 2023 +0100 fix anime tracker & airing sort commiteafc64aed0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 14:40:45 2023 +0100 fuck lint commiteef4ef7ef2
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 14:20:10 2023 +0100 fix weird "explore tabs" behaviour commit4baf786d57
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 13:43:13 2023 +0100 use formatTime function commitdf6a85a944
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 13:38:39 2023 +0100 fix AnimeScreen crash commit91de0ed82e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 26 01:44:41 2023 +0100 merge40 Last commit merged:69aa13bc56
commit375a252a69
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 21:09:34 2023 +0100 merge39 Last commit merged:634ceeec50
commitd7aee03688
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 19:42:37 2023 +0100 merge38 Last commit merged:1d144e6767
commit89777e98b0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 17:54:17 2023 +0100 merge37 Last commit merged:aca36f9625
commit3fba4cbc2b
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 15:09:18 2023 +0100 merge36 Last commit merged:64ad25d1b5
commita60268e61c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 13:39:08 2023 +0100 lint cleanup commitc6f81b7fda
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 13:38:22 2023 +0100 merge35 Last commit merged:eed57b80be
commit5f782440c6
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 25 00:22:45 2023 +0100 merge34.5 Last commit merged:8e4cedf173
commite4283fe416
Merge:e4d31057f
adb571fcb
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 24 23:21:58 2023 +0100 Merge branch 'master' into merge_upstream commite4d31057fd
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 24 23:07:20 2023 +0100 cleanup commit150d43e325
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 24 23:06:00 2023 +0100 merge34 Last commit merged:489d22720a
commit76df725cab
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 24 20:16:18 2023 +0100 merge33 Last commit merged:b7d282235d
commitc07cc8dc21
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 24 16:59:50 2023 +0100 merge32 Last commit merged:8a8afa46e9
commita654a54b74
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 23 23:01:52 2023 +0100 Update build_push.yml commit325e625aef
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 23 22:44:56 2023 +0100 merge31 Last commit merged:86edce0d87
commitb8cdb9d55e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Nov 20 21:47:46 2023 +0100 small Episode Tracker change commitf6d430df6c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Nov 20 21:18:01 2023 +0100 fuck lint commit99eea595c0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Nov 20 21:15:15 2023 +0100 crash fix & add forgotten commit No idea how I didn't add it yet. Commit:efabe801be
commit18e04b75df
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 19:38:42 2023 +0100 merge30 Last commit merged:1668be8587
commit3a3115304e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 16:45:34 2023 +0100 ktlint formt commitea2b7fe7c0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 16:05:25 2023 +0100 ktlint format again commit05f91245ac
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 15:47:42 2023 +0100 klint format again commit6215528c9d
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 15:37:56 2023 +0100 ktlint format commitb1f728e54a
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 15:24:35 2023 +0100 fix ConcurrentHashMap crash commita34bc764b4
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 15:04:24 2023 +0100 renaming & ktlintCheck commitf1cd8f69d3
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 14:39:59 2023 +0100 merge29 Last commit merged:772db51593
commit2c4230376c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 13:18:41 2023 +0100 merge28 Last commit merged:6922792ad1
commitfa7b8427a2
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 19 00:36:58 2023 +0100 merge27 Last commit merged:7146913c71
commite29dc62837
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 18 17:28:12 2023 +0100 lint cleanup commit85d0e49fd4
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 18 17:26:35 2023 +0100 little fix commiteeddf17691
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 18 17:20:36 2023 +0100 merge26 Last commit merged:c9a1bd86b5
commita2a445fdc5
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 18 16:01:50 2023 +0100 merge25 Last commit merged:6a558ad119
commit1b6301cc95
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 18 00:21:17 2023 +0100 merge24 Also a little test to see how this whole github team works. Last commit merged:fe90546821
commit4cbf9a813e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 17 20:12:49 2023 +0100 merge23.5 Last commit merged:6d69caf59e
commit72a54cbb6e
Merge:a43904531
00fb0a740
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 17 19:52:08 2023 +0100 Merge branch 'master' into MR commita439045319
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 17 19:47:18 2023 +0100 merge23 Last commit merged:8ff0c9d61a
commit370212c677
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 17 17:00:32 2023 +0100 merge22 Last commit merged:2556e9f08c
commit717479961c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Nov 15 21:25:52 2023 +0100 Update StreamsCatalogSheet.kt commit563efef3c9
Merge:645746aa4
2b8000b81
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Nov 15 21:11:51 2023 +0100 Merge branch 'master' into MR commit645746aa43
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 12 15:57:35 2023 +0100 merge21 Last commit merged:7a4680603d
commit4709cbedba
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Nov 12 14:40:35 2023 +0100 merge20 Last commit merged:ef7b285151
commit096276cd68
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 19:09:44 2023 +0100 merge19.5 Last commit merged:34f7caa0fc
commit82eb36b628
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 18:55:20 2023 +0100 fuck lint commit02717061ba
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 18:49:20 2023 +0100 merge19 Last commit merged:ec08ba05fc
commite7b1066e3c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 17:58:02 2023 +0100 merge18 Last commit merged:12e7ee9d0c
commit61256a22fd
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 15:55:09 2023 +0100 again repositioning commitb9d42c97d6
Merge:4732c3893
236849b6e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 15:44:21 2023 +0100 Merge branch 'master' into MR commit4732c3893c
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 15:43:57 2023 +0100 repositioning commit83dcd5ea31
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 11 15:42:22 2023 +0100 merge17 Last commit merged:9a817e49be
commit338b54a39c
Merge:748d6101c
8b1934fc3
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 9 19:28:19 2023 +0100 Merge branch 'master' into MR commit748d6101c0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 9 19:24:17 2023 +0100 small changes commit872fc749e9
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 9 18:49:35 2023 +0100 fix downloader pause commitbb96174520
Merge:59b4404c1
fd83a7e14
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Nov 7 19:08:04 2023 +0100 Merge branch 'master' into MR commit59b4404c11
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Nov 4 22:10:05 2023 +0100 merge16 Last commit merged:16cbcecd99
commit184804f62e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 3 23:19:24 2023 +0100 merge15.5 Last commit merged:d32409bd6e
commit6bab100f78
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 3 22:42:04 2023 +0100 Update ReaderActivity.kt commit264b0e6127
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 3 22:30:37 2023 +0100 merge15 Last commit merged:cf3f2d0380
commitdd69ce5a12
Merge:d2ccb75c6
5fd00d4a1
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Nov 3 20:14:10 2023 +0100 Merge branch 'master' into MR commitd2ccb75c60
Merge:8f08a246a
5567be64f
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 2 22:04:53 2023 +0100 Merge branch 'master' into MR commit8f08a246a2
Merge:9509d098a
082d9e339
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Nov 2 20:58:42 2023 +0100 Merge branch 'master' into MR commit9509d098a0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Nov 1 18:51:57 2023 +0100 Update AnimeDownloader.kt commit5415cbbdef
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Nov 1 18:47:21 2023 +0100 Update AnimeDownloader.kt commitf0180e0d15
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Nov 1 15:48:14 2023 +0100 rework anime downloader commitf3ec9da2da
Merge:fd1c6437a
67a5bccec
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Oct 31 18:37:41 2023 +0100 Merge branch 'master' into MR commitfd1c6437a6
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 29 15:12:39 2023 +0100 merge14 Last commit merged:f344831d58
commit9bb1a5da02
Merge:03ee4bc5f
d8327e872
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 29 13:58:41 2023 +0100 Merge branch 'master' into MR commit03ee4bc5f4
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Oct 28 23:05:13 2023 +0200 merge13 Last commit merged:7f0ed58b54
commit6dd16ca07d
Merge:f0783f534
509ecedb3
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Oct 28 12:03:04 2023 +0200 Merge branch 'master' into MR commitf0783f534d
Author: Quickdev <devesh.ratra@gmail.com> Date: Fri Oct 27 15:03:11 2023 -0400 feat(player): Subtitle settings + refactor + crash fixes (#1152) Co-authored-by: jmir1 <jhmiramon@gmail.com> Co-authored-by: Abdallah <54363735+abdallahmehiz@users.noreply.github.com> commit2431d8a858
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Oct 28 00:04:20 2023 +0200 merge12 Last commit merged:0d9f8e8743
commitc6317e9589
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 23:50:37 2023 +0200 Update AdaptiveSheet.kt commit692b566f77
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 23:26:45 2023 +0200 revert subs commit commit5b1099da9d
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 21:39:46 2023 +0200 Update AdaptiveSheet.kt commit7e14b6d2ab
Merge:f51b36a75
7f9255b51
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 21:36:43 2023 +0200 Merge remote-tracking branch 'upstream/master' into MR commitf51b36a759
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 20:27:23 2023 +0200 merge11 Last commit merged:34b9c82cd0
commit564a959bab
Merge:b9333bedd
928a62c5a
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 27 18:50:18 2023 +0200 Merge remote-tracking branch 'origin/master' into MR commitb9333bedd7
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Oct 26 19:39:29 2023 +0200 fix download button not shown commit5d575f1e90
Merge:e282528e6
afb921a5a
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Oct 26 18:56:43 2023 +0200 Merge remote-tracking branch 'upstream/master' into MR commite282528e6e
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Oct 23 18:27:42 2023 +0200 Revert "Translations update from Hosted Weblate (#9531)" This reverts commit3a8e7d04fc
. commit3a8e7d04fc
Author: Weblate (bot) <hosted@weblate.org> Date: Sat Jun 3 19:10:13 2023 +0200 Translations update from Hosted Weblate (#9531) Weblate translations Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/jv/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ko/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ne/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/th/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uk/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/vi/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/ Translation: Tachiyomi/Tachiyomi 0.x Co-authored-by: Alessandro Jean <alessandrojean@gmail.com> Co-authored-by: Ali Aljishi <ahj696@hotmail.com> Co-authored-by: AntonP <tony.pug.stark@gmail.com> Co-authored-by: Christian Elbrianno <crse@protonmail.ch> Co-authored-by: Clxff H3r4ld0 <123844876+clxf12@users.noreply.github.com> Co-authored-by: Dan <denqwerta@gmail.com> Co-authored-by: Danel Dave Barbuco <barbucodanel@gmail.com> Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com> Co-authored-by: Dexroneum <Rozhenkov69@gmail.com> Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat> Co-authored-by: FateXBlood <zecrofelix@gmail.com> Co-authored-by: Ferran <ferrancette@gmail.com> Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com> Co-authored-by: ID-86 <id86dev@gmail.com> Co-authored-by: Igor <zerrxs@gmail.com> Co-authored-by: Izxmi <heltherrivas05@gmail.com> Co-authored-by: Leonardo Falcoski <leonardo.falcoski@gmail.com> Co-authored-by: Lyfja <yassinelaoud@gmail.com> Co-authored-by: Milo Ivir <mail@milotype.de> Co-authored-by: Pitpe11 <giorgos2550@gmail.com> Co-authored-by: Rostyslav Haitkulov <info@ubilling.net.ua> Co-authored-by: Swyter <swyterzone@gmail.com> Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com> Co-authored-by: Uzuki Shimamura <hzy980512@126.com> Co-authored-by: altinat <altinat@duck.com> Co-authored-by: stevenlele <stevenlele@outlook.com> commit974d3b56f7
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Oct 23 17:51:27 2023 +0200 Revert "Translations update from Hosted Weblate (#9531)" This reverts commitd78159bda8
. commitd78159bda8
Author: Weblate (bot) <hosted@weblate.org> Date: Sat Jun 3 19:10:13 2023 +0200 Translations update from Hosted Weblate (#9531) Weblate translations Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ar/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ca/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/cs/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/de/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/el/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/es/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/fil/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hi/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/hr/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/id/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/it/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ja/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/jv/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ko/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ne/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pl/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/ru/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/th/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/uk/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/vi/ Translate-URL: https://hosted.weblate.org/projects/tachiyomi/strings/zh_Hans/ Translation: Tachiyomi/Tachiyomi 0.x Co-authored-by: Alessandro Jean <alessandrojean@gmail.com> Co-authored-by: Ali Aljishi <ahj696@hotmail.com> Co-authored-by: AntonP <tony.pug.stark@gmail.com> Co-authored-by: Christian Elbrianno <crse@protonmail.ch> Co-authored-by: Clxff H3r4ld0 <123844876+clxf12@users.noreply.github.com> Co-authored-by: Dan <denqwerta@gmail.com> Co-authored-by: Danel Dave Barbuco <barbucodanel@gmail.com> Co-authored-by: DatTran MLL <tranthanhdat1142003@gmail.com> Co-authored-by: Dexroneum <Rozhenkov69@gmail.com> Co-authored-by: Eduard Ereza Martínez <eduard@ereza.cat> Co-authored-by: FateXBlood <zecrofelix@gmail.com> Co-authored-by: Ferran <ferrancette@gmail.com> Co-authored-by: Giorgio Sanna <sannagiorgio1997@gmail.com> Co-authored-by: ID-86 <id86dev@gmail.com> Co-authored-by: Igor <zerrxs@gmail.com> Co-authored-by: Izxmi <heltherrivas05@gmail.com> Co-authored-by: Leonardo Falcoski <leonardo.falcoski@gmail.com> Co-authored-by: Lyfja <yassinelaoud@gmail.com> Co-authored-by: Milo Ivir <mail@milotype.de> Co-authored-by: Pitpe11 <giorgos2550@gmail.com> Co-authored-by: Rostyslav Haitkulov <info@ubilling.net.ua> Co-authored-by: Swyter <swyterzone@gmail.com> Co-authored-by: TheKingTermux <achmadmaulana0233@gmail.com> Co-authored-by: Uzuki Shimamura <hzy980512@126.com> Co-authored-by: altinat <altinat@duck.com> Co-authored-by: stevenlele <stevenlele@outlook.com> commitdfb5ca4f8e
Merge:c19b4b5cf
92f9ab276
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Mon Oct 23 17:11:37 2023 +0200 Merge remote-tracking branch 'upstream/master' into MR commitc19b4b5cff
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 22 18:25:03 2023 +0200 fuck lint (again) commitfebf0460c0
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 22 18:16:03 2023 +0200 fuck lint commitda3265fb7a
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 22 18:08:27 2023 +0200 merge10 Last Commit Merged:531e1c62bb
commitc85576e06b
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sun Oct 22 14:11:21 2023 +0200 merge9 Last Commit Merged:4c65c2311e
commit67d3043533
Merge:c62f86f6c
63e95d9cb
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Oct 21 14:54:03 2023 +0200 Merge branch 'aniyomiorg:master' into MR commitc62f86f6cc
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Sat Oct 21 12:45:41 2023 +0200 merge8 Last Commit Merged:ed5a56be60
commite99e4c2f41
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 6 18:47:44 2023 +0200 merge7 Last Commit Merged:152fdec855
commitb3c911ea28
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Fri Oct 6 16:51:34 2023 +0200 merge6 Last Commit Merged:22a4372583
commitf705e19182
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Thu Oct 5 14:16:41 2023 +0200 merge5 Last Commit Merged:b4bb855675
commit2ebf477bd1
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Oct 4 21:44:23 2023 +0200 merge4 Last Commit Merged:44383ff950
commit14a8aebc60
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Oct 4 21:33:37 2023 +0200 merge3 I hate Weblate commits! Last Commit Merged:e15b945e16
commit5ceae3116b
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Oct 4 20:24:51 2023 +0200 merge2 Last Commit Merged:9a10656bf0
commit5a2a3fd080
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Oct 4 13:19:33 2023 +0200 fuck lint commitc3062d2ed7
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Wed Oct 4 13:08:36 2023 +0200 merge1 Last Commit Merged:f63573f25f
commit928a62c5a3
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Jul 25 00:25:30 2023 +0200 Update README.md commita883c90cd6
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Jul 25 00:24:31 2023 +0200 Delete ic_launcher.png commitc1b7b89c63
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Jul 25 00:23:44 2023 +0200 Update README.md commitc18830714b
Author: LuftVerbot <97435834+LuftVerbot@users.noreply.github.com> Date: Tue Jul 25 00:22:16 2023 +0200 Add files via upload
This commit is contained in:
parent
adb571fcbd
commit
fd830ea2d0
1062 changed files with 66171 additions and 40416 deletions
|
@ -1,7 +1,8 @@
|
|||
[*.{kt,kts}]
|
||||
indent_size=4
|
||||
insert_final_newline=true
|
||||
ij_kotlin_allow_trailing_comma=true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site=true
|
||||
max_line_length = 120
|
||||
indent_size = 4
|
||||
insert_final_newline = true
|
||||
ij_kotlin_allow_trailing_comma = true
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = true
|
||||
ij_kotlin_name_count_to_use_star_import = 2147483647
|
||||
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
|
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
|
@ -5,7 +5,7 @@ I acknowledge that:
|
|||
- I have updated:
|
||||
- To the latest version of the app (stable is v0.12.3.10)
|
||||
- All extensions
|
||||
- I have gone through the FAQ (https://aniyomi.org/help/faq/) and troubleshooting guide (https://aniyomi.org/help/guides/troubleshooting/)
|
||||
- I have gone through the FAQ (https://aniyomi.org/docs/faq/general) and troubleshooting guide (https://aniyomi.org/docs/guides/troubleshooting/)
|
||||
- If this is an issue with an anime extension, that I should be opening an issue in https://github.com/aniyomiorg/aniyomi-extensions
|
||||
- I have searched the existing issues and this is new ticket **NOT** a duplicate or related to another open or closed issue
|
||||
- I will fill out the title and the information in this template
|
||||
|
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -4,7 +4,7 @@ contact_links:
|
|||
url: https://github.com/aniyomiorg/aniyomi-extensions/issues/new/choose
|
||||
about: Issues and requests for extensions and sources should be opened in the aniyomi-extensions repository instead
|
||||
- name: 📦 Aniyomi extensions
|
||||
url: https://aniyomi.org/extensions
|
||||
url: https://aniyomi.org/extensions/
|
||||
about: Anime extensions and sources
|
||||
- name: 🧑💻 Aniyomi help discord
|
||||
url: https://discord.gg/F32UjdJZrR
|
||||
|
|
2
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
2
.github/ISSUE_TEMPLATE/report_issue.yml
vendored
|
@ -95,7 +95,7 @@ body:
|
|||
required: true
|
||||
- label: If this is an issue with an extension, I should be opening an issue in the [extensions repository](https://github.com/aniyomiorg/aniyomi-extensions/issues/new/choose).
|
||||
required: true
|
||||
- label: I have gone through the [FAQ](https://aniyomi.org/help/faq/) and [troubleshooting guide](https://aniyomi.org/help/guides/troubleshooting/).
|
||||
- label: I have gone through the [FAQ](https://aniyomi.org/docs/faq/general) and [troubleshooting guide](https://aniyomi.org/docs/guides/troubleshooting/).
|
||||
required: true
|
||||
- label: I have updated the app to version **[0.12.3.10](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
|
||||
required: true
|
||||
|
|
11
.github/renovate.json
vendored
11
.github/renovate.json
vendored
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"ignoreDeps": [
|
||||
"androidx.core:core-splashscreen",
|
||||
"com.android.tools:r8",
|
||||
"com.google.guava:guava"
|
||||
]
|
||||
}
|
17
.github/renovate.json5
vendored
Normal file
17
.github/renovate.json5
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"schedule": ["every sunday"],
|
||||
"packageRules": [
|
||||
{
|
||||
// Compiler plugins are tightly coupled to Kotlin version
|
||||
"groupName": "Kotlin",
|
||||
"matchPackagePrefixes": [
|
||||
"androidx.compose.compiler",
|
||||
"org.jetbrains.kotlin",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
4
.github/workflows/build_pull_request.yml
vendored
4
.github/workflows/build_pull_request.yml
vendored
|
@ -20,7 +20,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
@ -37,4 +37,4 @@ jobs:
|
|||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
||||
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
15
.github/workflows/build_push.yml
vendored
15
.github/workflows/build_push.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Clone repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
@ -46,7 +46,7 @@ jobs:
|
|||
- name: Build app and run unit tests
|
||||
uses: gradle/gradle-command-action@v2
|
||||
with:
|
||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
||||
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
||||
|
||||
# Sign APK and create release for tags
|
||||
|
||||
|
@ -120,3 +120,14 @@ jobs:
|
|||
prerelease: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
update-website:
|
||||
needs: [ build ]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/') && github.repository == 'aniyomiorg/aniyomi'
|
||||
steps:
|
||||
- name: Trigger Netlify build hook
|
||||
run: curl -s -X POST -d {} "https://api.netlify.com/build_hooks/${TOKEN}"
|
||||
env:
|
||||
TOKEN: ${{ secrets.NETLIFY_HOOK_RELEASE }}
|
||||
|
||||
|
|
7
.github/workflows/issue_moderator.yml
vendored
7
.github/workflows/issue_moderator.yml
vendored
|
@ -27,6 +27,13 @@ jobs:
|
|||
"type": "body",
|
||||
"regex": ".*\\* (Aniyomi version|Android version|Device): \\?.*",
|
||||
"message": "Requested information in the template was not filled out."
|
||||
},
|
||||
{
|
||||
"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://aniyomi.org/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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v4
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: '2'
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,7 +3,8 @@
|
|||
/acra.properties
|
||||
/.idea/workspace.xml
|
||||
.DS_Store
|
||||
.idea/
|
||||
.idea/*
|
||||
!.idea/icon.png
|
||||
*iml
|
||||
*.iml
|
||||
|
||||
|
|
BIN
.idea/icon.png
Normal file
BIN
.idea/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -24,13 +24,17 @@ Before you start, please note that the ability to use following technologies is
|
|||
- [Android Studio](https://developer.android.com/studio)
|
||||
- Emulator or phone with developer options enabled to test changes.
|
||||
|
||||
## Linting
|
||||
|
||||
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||
|
||||
## Getting help
|
||||
|
||||
- Join [the Discord server](https://discord.gg/F32UjdJZrR) for online help and to ask questions while developing.
|
||||
|
||||
# Translations
|
||||
|
||||
Translations are done externally via Weblate. See [our website](https://aniyomi.org/help/contribution/#translation) for more details.
|
||||
Translations are done externally via Weblate. See [our website](https://aniyomi.org/docs/contribute#translation) for more details.
|
||||
|
||||
|
||||
# Forks
|
||||
|
|
|
@ -31,7 +31,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||
|
||||
<details><summary>Issues</summary>
|
||||
|
||||
1. **Before reporting a new issue, take a look at the already opened [issues](https://github.com/aniyomiorg/aniyomi/issues).**
|
||||
1. **Before reporting a new issue, take a look at the already opened [issues](https://aniyomi.org/changelogs/).**
|
||||
2. If you are unsure, ask here: [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR)
|
||||
|
||||
</details>
|
||||
|
@ -40,7 +40,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
|||
|
||||
* Include version (More → About → Version)
|
||||
* If not latest, try updating, it may have already been solved
|
||||
* Preview version is equal to the number of commits as seen in the main page
|
||||
* Preview version is equal to the number of commits as seen on the main page
|
||||
* Include steps to reproduce (if not obvious from description)
|
||||
* Include screenshot (if needed)
|
||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||
|
|
1
app/.gitignore
vendored
1
app/.gitignore
vendored
|
@ -1,4 +1,3 @@
|
|||
/build
|
||||
*iml
|
||||
*.iml
|
||||
custom.gradle
|
||||
|
|
3
app/.idea/.gitignore
vendored
Normal file
3
app/.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
1
app/.idea/.name
Normal file
1
app/.idea/.name
Normal file
|
@ -0,0 +1 @@
|
|||
MangaDownloader.kt
|
7
app/.idea/discord.xml
Normal file
7
app/.idea/discord.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
18
app/.idea/gradle.xml
Normal file
18
app/.idea/gradle.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="jbr-17" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
3
app/.idea/misc.xml
Normal file
3
app/.idea/misc.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
</project>
|
6
app/.idea/vcs.xml
Normal file
6
app/.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -1,5 +1,4 @@
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
|
@ -21,8 +20,8 @@ android {
|
|||
defaultConfig {
|
||||
applicationId = "xyz.jmir.tachiyomi.mi"
|
||||
|
||||
versionCode = 106
|
||||
versionName = "0.14.6"
|
||||
versionCode = 110
|
||||
versionName = "0.14.7"
|
||||
|
||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||
|
@ -73,11 +72,11 @@ android {
|
|||
initWith(getByName("release"))
|
||||
buildConfigField("boolean", "PREVIEW", "true")
|
||||
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
val debugType = getByName("debug")
|
||||
signingConfig = debugType.signingConfig
|
||||
versionNameSuffix = debugType.versionNameSuffix
|
||||
applicationIdSuffix = debugType.applicationIdSuffix
|
||||
matchingFallbacks.add("release")
|
||||
}
|
||||
create("benchmark") {
|
||||
initWith(getByName("release"))
|
||||
|
@ -85,6 +84,7 @@ android {
|
|||
signingConfig = signingConfigs.getByName("debug")
|
||||
matchingFallbacks.add("release")
|
||||
isDebuggable = false
|
||||
isProfileable = true
|
||||
versionNameSuffix = "-benchmark"
|
||||
applicationIdSuffix = ".benchmark"
|
||||
}
|
||||
|
@ -110,7 +110,8 @@ android {
|
|||
}
|
||||
|
||||
packaging {
|
||||
resources.excludes.addAll(listOf(
|
||||
resources.excludes.addAll(
|
||||
listOf(
|
||||
"META-INF/DEPENDENCIES",
|
||||
"LICENSE.txt",
|
||||
"META-INF/LICENSE",
|
||||
|
@ -118,7 +119,8 @@ android {
|
|||
"META-INF/README.md",
|
||||
"META-INF/NOTICE",
|
||||
"META-INF/*.kotlin_module",
|
||||
))
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
|
@ -165,12 +167,13 @@ dependencies {
|
|||
implementation(compose.material.icons)
|
||||
implementation(compose.animation)
|
||||
implementation(compose.animation.graphics)
|
||||
implementation(compose.ui.tooling)
|
||||
debugImplementation(compose.ui.tooling)
|
||||
implementation(compose.ui.tooling.preview)
|
||||
implementation(compose.ui.util)
|
||||
implementation(compose.accompanist.webview)
|
||||
implementation(compose.accompanist.permissions)
|
||||
implementation(compose.accompanist.themeadapter)
|
||||
implementation(compose.accompanist.systemuicontroller)
|
||||
lintChecks(compose.lintchecks)
|
||||
|
||||
implementation(androidx.paging.runtime)
|
||||
implementation(androidx.paging.compose)
|
||||
|
@ -178,6 +181,7 @@ dependencies {
|
|||
implementation(libs.bundles.sqlite)
|
||||
|
||||
implementation(kotlinx.reflect)
|
||||
implementation(kotlinx.immutables)
|
||||
|
||||
implementation(platform(kotlinx.coroutines.bom))
|
||||
implementation(kotlinx.bundles.coroutines)
|
||||
|
@ -187,7 +191,6 @@ dependencies {
|
|||
implementation(androidx.appcompat)
|
||||
implementation(androidx.biometricktx)
|
||||
implementation(androidx.constraintlayout)
|
||||
implementation(androidx.coordinatorlayout)
|
||||
implementation(androidx.corektx)
|
||||
implementation(androidx.splashscreen)
|
||||
implementation(androidx.recyclerview)
|
||||
|
@ -198,10 +201,10 @@ dependencies {
|
|||
implementation(androidx.bundles.lifecycle)
|
||||
|
||||
// Job scheduling
|
||||
implementation(androidx.bundles.workmanager)
|
||||
implementation(androidx.workmanager)
|
||||
|
||||
// RxJava
|
||||
implementation(libs.bundles.reactivex)
|
||||
implementation(libs.rxjava)
|
||||
implementation(libs.flowreactivenetwork)
|
||||
|
||||
// Networking
|
||||
|
@ -227,6 +230,7 @@ dependencies {
|
|||
implementation(libs.injekt.core)
|
||||
|
||||
// Image loading
|
||||
implementation(platform(libs.coil.bom))
|
||||
implementation(libs.bundles.coil)
|
||||
implementation(libs.subsamplingscaleimageview) {
|
||||
exclude(module = "image-decoder")
|
||||
|
@ -236,7 +240,6 @@ dependencies {
|
|||
// UI libraries
|
||||
implementation(libs.material)
|
||||
implementation(libs.flexible.adapter.core)
|
||||
implementation(libs.flexible.adapter.ui)
|
||||
implementation(libs.photoview)
|
||||
implementation(libs.directionalviewpager) {
|
||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||
|
@ -245,9 +248,8 @@ dependencies {
|
|||
implementation(libs.bundles.richtext)
|
||||
implementation(libs.aboutLibraries.compose)
|
||||
implementation(libs.bundles.voyager)
|
||||
implementation(libs.compose.cascade)
|
||||
implementation(libs.compose.materialmotion)
|
||||
implementation(libs.compose.simpleicons)
|
||||
implementation(libs.swipe)
|
||||
|
||||
// Logging
|
||||
implementation(libs.logcat)
|
||||
|
@ -281,7 +283,9 @@ androidComponents {
|
|||
beforeVariants { variantBuilder ->
|
||||
// Disables standardBenchmark
|
||||
if (variantBuilder.buildType == "benchmark") {
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(listOf("default" to "dev"))
|
||||
variantBuilder.enable = variantBuilder.productFlavors.containsAll(
|
||||
listOf("default" to "dev"),
|
||||
)
|
||||
}
|
||||
}
|
||||
onVariants(selector().withFlavor("default" to "standard")) {
|
||||
|
@ -292,16 +296,10 @@ androidComponents {
|
|||
}
|
||||
|
||||
tasks {
|
||||
withType<LintTask>().configureEach {
|
||||
exclude { it.file.path.contains("generated[\\\\/]".toRegex()) }
|
||||
}
|
||||
|
||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||
withType<KotlinCompile> {
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-Xcontext-receivers",
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||
|
@ -310,6 +308,8 @@ tasks {
|
|||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||
|
@ -320,12 +320,12 @@ tasks {
|
|||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||
)
|
||||
kotlinOptions.freeCompilerArgs += listOf(
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||
project.buildDir.absolutePath + "/compose_metrics"
|
||||
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
3
app/proguard-rules.pro
vendored
3
app/proguard-rules.pro
vendored
|
@ -11,10 +11,11 @@
|
|||
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||
-keep,allowoptimization class kotlinx.serialization.** { public protected *; }
|
||||
-keep,allowoptimization class kotlin.time.** { public protected *; }
|
||||
-keep,allowoptimization class okhttp3.** { public protected *; }
|
||||
-keep,allowoptimization class okio.** { public protected *; }
|
||||
-keep,allowoptimization class rx.** { public protected *; }
|
||||
-keep,allowoptimization class org.jsoup.** { public protected *; }
|
||||
-keep,allowoptimization class rx.** { public protected *; }
|
||||
-keep,allowoptimization class app.cash.quickjs.** { public protected *; }
|
||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||
-keep,allowoptimization class is.xyz.mpv.** { public protected *; }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<shortcut
|
||||
android:enabled="true"
|
||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||
|
|
|
@ -29,21 +29,17 @@
|
|||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="false"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/Theme.Tachiyomi"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<!-- enable profiling by macrobenchmark -->
|
||||
<profileable
|
||||
android:shell="true"
|
||||
tools:targetApi="q" />
|
||||
android:theme="@style/Theme.Tachiyomi">
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.MainActivity"
|
||||
|
@ -67,10 +63,10 @@
|
|||
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkAnimeActivity"
|
||||
android:name=".ui.deeplink.anime.DeepLinkAnimeActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/action_global_anime_search"
|
||||
android:label="@string/action_search"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
|
@ -94,10 +90,10 @@
|
|||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.main.DeepLinkMangaActivity"
|
||||
android:name=".ui.deeplink.manga.DeepLinkMangaActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@android:style/Theme.NoDisplay"
|
||||
android:label="@string/action_global_manga_search"
|
||||
android:label="@string/action_search"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
|
@ -172,8 +168,8 @@
|
|||
android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
||||
android:label="Anilist"
|
||||
android:name=".ui.setting.track.TrackLoginActivity"
|
||||
android:label="@string/track_activity_name"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -181,69 +177,21 @@
|
|||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="anilist-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
<data android:host="anilist-auth"/>
|
||||
<data android:host="bangumi-auth"/>
|
||||
<data android:host="myanimelist-auth"/>
|
||||
<data android:host="shikimori-auth"/>
|
||||
|
||||
<data android:scheme="tachiyomi"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
||||
android:label="MyAnimeList"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="myanimelist-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.ShikimoriLoginActivity"
|
||||
android:label="Shikimori"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="shikimori-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.BangumiLoginActivity"
|
||||
android:label="Bangumi"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="bangumi-auth"
|
||||
android:scheme="tachiyomi" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".ui.setting.track.SimklLoginActivity"
|
||||
android:label="Simkl"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="simkl-auth"
|
||||
android:scheme="aniyomi" />
|
||||
<data android:host="simkl-auth"/>
|
||||
<data android:scheme="aniyomi"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
|
@ -251,34 +199,6 @@
|
|||
android:name=".data.notification.NotificationReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.manga.MangaUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="tachiyomi.presentation.widget.entries.anime.AnimeUpdatesGridGlanceReceiver"
|
||||
android:enabled="@bool/glance_appwidget_available"
|
||||
android:exported="false"
|
||||
android:label="@string/label_recent_anime_updates">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/updates_grid_glance_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<service
|
||||
android:name=".data.download.manga.MangaDownloadService"
|
||||
android:exported="false" />
|
||||
|
@ -288,13 +208,11 @@
|
|||
android:exported="false" />
|
||||
|
||||
<service
|
||||
android:name=".data.updater.AppUpdateService"
|
||||
android:name=".extension.manga.util.MangaExtensionInstallService"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name=".extension.manga.util.MangaExtensionInstallService"
|
||||
android:exported="false" />
|
||||
|
||||
<service android:name=".extension.anime.util.AnimeExtensionInstallService"
|
||||
<service
|
||||
android:name=".extension.anime.util.AnimeExtensionInstallService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.source.model.Page
|
|||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import tachiyomi.core.preference.Preference
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
|
@ -27,15 +26,6 @@ interface DataSaver {
|
|||
}
|
||||
}
|
||||
|
||||
fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable<Response> {
|
||||
val imageUrl = page.imageUrl ?: return fetchImage(page)
|
||||
page.imageUrl = dataSaver.compress(imageUrl)
|
||||
return fetchImage(page)
|
||||
.doOnNext {
|
||||
page.imageUrl = imageUrl
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response {
|
||||
val imageUrl = page.imageUrl ?: return getImage(page)
|
||||
page.imageUrl = dataSaver.compress(imageUrl)
|
||||
|
@ -74,7 +64,13 @@ private class BandwidthHeroDataSaver(preferences: SourcePreferences) : DataSaver
|
|||
override fun compress(imageUrl: String): String {
|
||||
return if (dataSavedServer.isNotBlank() && !imageUrl.contains(dataSavedServer)) {
|
||||
when {
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) {
|
||||
imageUrl
|
||||
} else {
|
||||
getUrl(
|
||||
imageUrl,
|
||||
)
|
||||
}
|
||||
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||
else -> getUrl(imageUrl)
|
||||
}
|
||||
|
@ -100,7 +96,13 @@ private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver {
|
|||
|
||||
override fun compress(imageUrl: String): String {
|
||||
return when {
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) {
|
||||
imageUrl
|
||||
} else {
|
||||
getUrl(
|
||||
imageUrl,
|
||||
)
|
||||
}
|
||||
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||
else -> getUrl(imageUrl)
|
||||
}
|
||||
|
@ -108,7 +110,11 @@ private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver {
|
|||
|
||||
private fun getUrl(imageUrl: String): String {
|
||||
// Network Request sent to wsrv
|
||||
return "https://wsrv.nl/?url=$imageUrl" + if (imageUrl.contains(".webp", true) || imageUrl.contains(".gif", true)) {
|
||||
return "https://wsrv.nl/?url=$imageUrl" + if (imageUrl.contains(".webp", true) || imageUrl.contains(
|
||||
".gif",
|
||||
true,
|
||||
)
|
||||
) {
|
||||
if (!format) {
|
||||
// Preserve output image extension for animated images(.webp and .gif)
|
||||
"&q=$quality&n=-1"
|
||||
|
@ -140,7 +146,13 @@ private class ReSmushItDataSaver(preferences: SourcePreferences) : DataSaver {
|
|||
|
||||
override fun compress(imageUrl: String): String {
|
||||
return when {
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
|
||||
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) {
|
||||
imageUrl
|
||||
} else {
|
||||
getUrl(
|
||||
imageUrl,
|
||||
)
|
||||
}
|
||||
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||
else -> getUrl(imageUrl)
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
|
|||
}
|
||||
|
||||
override fun component2(): (T) -> Unit {
|
||||
return { preference.set(it) }
|
||||
return preference::set
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package eu.kanade.core.util
|
||||
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.contract
|
||||
|
||||
|
@ -20,15 +19,6 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
|||
return newList
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new map containing only the key entries of [transform] that are not null.
|
||||
*/
|
||||
inline fun <K, V, R> Map<out K, V>.mapNotNullKeys(transform: (Map.Entry<K?, V>) -> R?): ConcurrentHashMap<R, V> {
|
||||
val mutableMap = ConcurrentHashMap<R, V>()
|
||||
forEach { element -> transform(element)?.let { mutableMap[it] = element.value } }
|
||||
return mutableMap
|
||||
}
|
||||
|
||||
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||
if (shouldAdd) {
|
||||
add(value)
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package eu.kanade.domain
|
||||
|
||||
import eu.kanade.domain.download.anime.interactor.DeleteAnimeDownload
|
||||
import eu.kanade.domain.download.anime.interactor.DeleteEpisodeDownload
|
||||
import eu.kanade.domain.download.manga.interactor.DeleteChapterDownload
|
||||
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
||||
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
||||
import eu.kanade.domain.entries.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.entries.manga.interactor.SetExcludedScanlators
|
||||
import eu.kanade.domain.entries.manga.interactor.SetMangaViewerFlags
|
||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionLanguages
|
||||
|
@ -12,12 +14,11 @@ import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionsByType
|
|||
import eu.kanade.domain.extension.manga.interactor.GetExtensionSources
|
||||
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages
|
||||
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionsByType
|
||||
import eu.kanade.domain.items.chapter.interactor.GetAvailableScanlators
|
||||
import eu.kanade.domain.items.chapter.interactor.SetReadStatus
|
||||
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.items.episode.interactor.SetSeenStatus
|
||||
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource
|
||||
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithTrackServiceTwoWay
|
||||
import eu.kanade.domain.source.anime.interactor.GetAnimeSourcesWithFavoriteCount
|
||||
import eu.kanade.domain.source.anime.interactor.GetEnabledAnimeSources
|
||||
import eu.kanade.domain.source.anime.interactor.GetLanguagesWithAnimeSources
|
||||
|
@ -30,6 +31,14 @@ import eu.kanade.domain.source.manga.interactor.ToggleMangaSource
|
|||
import eu.kanade.domain.source.manga.interactor.ToggleMangaSourcePin
|
||||
import eu.kanade.domain.source.service.SetMigrateSorting
|
||||
import eu.kanade.domain.source.service.ToggleLanguage
|
||||
import eu.kanade.domain.track.anime.interactor.AddAnimeTracks
|
||||
import eu.kanade.domain.track.anime.interactor.RefreshAnimeTracks
|
||||
import eu.kanade.domain.track.anime.interactor.SyncEpisodeProgressWithTrack
|
||||
import eu.kanade.domain.track.anime.interactor.TrackEpisode
|
||||
import eu.kanade.domain.track.manga.interactor.AddMangaTracks
|
||||
import eu.kanade.domain.track.manga.interactor.RefreshMangaTracks
|
||||
import eu.kanade.domain.track.manga.interactor.SyncChapterProgressWithTrack
|
||||
import eu.kanade.domain.track.manga.interactor.TrackChapter
|
||||
import tachiyomi.data.category.anime.AnimeCategoryRepositoryImpl
|
||||
import tachiyomi.data.category.manga.MangaCategoryRepositoryImpl
|
||||
import tachiyomi.data.entries.anime.AnimeRepositoryImpl
|
||||
|
@ -38,10 +47,11 @@ import tachiyomi.data.history.anime.AnimeHistoryRepositoryImpl
|
|||
import tachiyomi.data.history.manga.MangaHistoryRepositoryImpl
|
||||
import tachiyomi.data.items.chapter.ChapterRepositoryImpl
|
||||
import tachiyomi.data.items.episode.EpisodeRepositoryImpl
|
||||
import tachiyomi.data.source.anime.AnimeSourceDataRepositoryImpl
|
||||
import tachiyomi.data.release.ReleaseServiceImpl
|
||||
import tachiyomi.data.source.anime.AnimeSourceRepositoryImpl
|
||||
import tachiyomi.data.source.manga.MangaSourceDataRepositoryImpl
|
||||
import tachiyomi.data.source.anime.AnimeStubSourceRepositoryImpl
|
||||
import tachiyomi.data.source.manga.MangaSourceRepositoryImpl
|
||||
import tachiyomi.data.source.manga.MangaStubSourceRepositoryImpl
|
||||
import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl
|
||||
import tachiyomi.data.track.manga.MangaTrackRepositoryImpl
|
||||
import tachiyomi.data.updates.anime.AnimeUpdatesRepositoryImpl
|
||||
|
@ -55,7 +65,7 @@ import tachiyomi.domain.category.anime.interactor.RenameAnimeCategory
|
|||
import tachiyomi.domain.category.anime.interactor.ReorderAnimeCategory
|
||||
import tachiyomi.domain.category.anime.interactor.ResetAnimeCategoryFlags
|
||||
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
||||
import tachiyomi.domain.category.anime.interactor.SetDisplayModeForAnimeCategory
|
||||
import tachiyomi.domain.category.anime.interactor.SetAnimeDisplayMode
|
||||
import tachiyomi.domain.category.anime.interactor.SetSortModeForAnimeCategory
|
||||
import tachiyomi.domain.category.anime.interactor.UpdateAnimeCategory
|
||||
import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
|
||||
|
@ -67,25 +77,29 @@ import tachiyomi.domain.category.manga.interactor.HideMangaCategory
|
|||
import tachiyomi.domain.category.manga.interactor.RenameMangaCategory
|
||||
import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory
|
||||
import tachiyomi.domain.category.manga.interactor.ResetMangaCategoryFlags
|
||||
import tachiyomi.domain.category.manga.interactor.SetDisplayModeForMangaCategory
|
||||
import tachiyomi.domain.category.manga.interactor.SetMangaCategories
|
||||
import tachiyomi.domain.category.manga.interactor.SetMangaDisplayMode
|
||||
import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory
|
||||
import tachiyomi.domain.category.manga.interactor.UpdateMangaCategory
|
||||
import tachiyomi.domain.category.manga.repository.MangaCategoryRepository
|
||||
import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites
|
||||
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
|
||||
import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.GetMangaByUrlAndSourceId
|
||||
import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime
|
||||
import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags
|
||||
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
|
||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||
import tachiyomi.domain.entries.manga.interactor.GetAnimeByUrlAndSourceId
|
||||
import tachiyomi.domain.entries.manga.interactor.GetDuplicateLibraryManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||
import tachiyomi.domain.entries.manga.interactor.GetMangaFavorites
|
||||
import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters
|
||||
import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval
|
||||
import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga
|
||||
import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags
|
||||
import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags
|
||||
|
@ -102,25 +116,29 @@ import tachiyomi.domain.history.manga.interactor.RemoveMangaHistory
|
|||
import tachiyomi.domain.history.manga.interactor.UpsertMangaHistory
|
||||
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapter
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.SetMangaDefaultChapterFlags
|
||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisode
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByUrlAndAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.SetAnimeDefaultEpisodeFlags
|
||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
||||
import tachiyomi.domain.release.interactor.GetApplicationRelease
|
||||
import tachiyomi.domain.release.service.ReleaseService
|
||||
import tachiyomi.domain.source.anime.interactor.GetAnimeSourcesWithNonLibraryAnime
|
||||
import tachiyomi.domain.source.anime.interactor.GetRemoteAnime
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourceDataRepository
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||
import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository
|
||||
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
||||
import tachiyomi.domain.source.manga.interactor.GetRemoteManga
|
||||
import tachiyomi.domain.source.manga.repository.MangaSourceDataRepository
|
||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||
import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository
|
||||
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
|
||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime
|
||||
|
@ -148,7 +166,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetAnimeCategories(get()) }
|
||||
addFactory { GetVisibleAnimeCategories(get()) }
|
||||
addFactory { ResetAnimeCategoryFlags(get(), get()) }
|
||||
addFactory { SetDisplayModeForAnimeCategory(get(), get()) }
|
||||
addFactory { SetAnimeDisplayMode(get()) }
|
||||
addFactory { SetSortModeForAnimeCategory(get(), get()) }
|
||||
addFactory { CreateAnimeCategoryWithName(get(), get()) }
|
||||
addFactory { RenameAnimeCategory(get()) }
|
||||
|
@ -161,7 +179,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetMangaCategories(get()) }
|
||||
addFactory { GetVisibleMangaCategories(get()) }
|
||||
addFactory { ResetMangaCategoryFlags(get(), get()) }
|
||||
addFactory { SetDisplayModeForMangaCategory(get(), get()) }
|
||||
addFactory { SetMangaDisplayMode(get()) }
|
||||
addFactory { SetSortModeForMangaCategory(get(), get()) }
|
||||
addFactory { CreateMangaCategoryWithName(get(), get()) }
|
||||
addFactory { RenameMangaCategory(get()) }
|
||||
|
@ -175,14 +193,16 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetAnimeFavorites(get()) }
|
||||
addFactory { GetLibraryAnime(get()) }
|
||||
addFactory { GetAnimeWithEpisodes(get(), get()) }
|
||||
addFactory { GetAnimeByUrlAndSourceId(get()) }
|
||||
addFactory { GetAnime(get()) }
|
||||
addFactory { GetNextEpisodes(get(), get(), get()) }
|
||||
addFactory { ResetAnimeViewerFlags(get()) }
|
||||
addFactory { SetAnimeEpisodeFlags(get()) }
|
||||
addFactory { AnimeFetchInterval(get()) }
|
||||
addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) }
|
||||
addFactory { SetAnimeViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalAnime(get()) }
|
||||
addFactory { UpdateAnime(get()) }
|
||||
addFactory { UpdateAnime(get(), get()) }
|
||||
addFactory { SetAnimeCategories(get()) }
|
||||
|
||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||
|
@ -190,10 +210,12 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetMangaFavorites(get()) }
|
||||
addFactory { GetLibraryManga(get()) }
|
||||
addFactory { GetMangaWithChapters(get(), get()) }
|
||||
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||
addFactory { GetManga(get()) }
|
||||
addFactory { GetNextChapters(get(), get(), get()) }
|
||||
addFactory { ResetMangaViewerFlags(get()) }
|
||||
addFactory { SetMangaChapterFlags(get()) }
|
||||
addFactory { MangaFetchInterval(get()) }
|
||||
addFactory {
|
||||
SetMangaDefaultChapterFlags(
|
||||
get(),
|
||||
|
@ -203,45 +225,59 @@ class DomainModule : InjektModule {
|
|||
}
|
||||
addFactory { SetMangaViewerFlags(get()) }
|
||||
addFactory { NetworkToLocalManga(get()) }
|
||||
addFactory { UpdateManga(get()) }
|
||||
addFactory { UpdateManga(get(), get()) }
|
||||
addFactory { SetMangaCategories(get()) }
|
||||
addFactory { GetExcludedScanlators(get()) }
|
||||
addFactory { SetExcludedScanlators(get()) }
|
||||
|
||||
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||
addFactory { GetApplicationRelease(get(), get()) }
|
||||
|
||||
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(get()) }
|
||||
addFactory { TrackEpisode(get(), get(), get(), get()) }
|
||||
addFactory { AddAnimeTracks(get(), get(), get(), get()) }
|
||||
addFactory { RefreshAnimeTracks(get(), get(), get(), get()) }
|
||||
addFactory { DeleteAnimeTrack(get()) }
|
||||
addFactory { GetTracksPerAnime(get()) }
|
||||
addFactory { GetAnimeTracks(get()) }
|
||||
addFactory { InsertAnimeTrack(get()) }
|
||||
addFactory { SyncEpisodeProgressWithTrack(get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<MangaTrackRepository> { MangaTrackRepositoryImpl(get()) }
|
||||
addFactory { TrackChapter(get(), get(), get(), get()) }
|
||||
addFactory { AddMangaTracks(get(), get(), get(), get()) }
|
||||
addFactory { RefreshMangaTracks(get(), get(), get(), get()) }
|
||||
addFactory { DeleteMangaTrack(get()) }
|
||||
addFactory { GetTracksPerManga(get()) }
|
||||
addFactory { GetMangaTracks(get()) }
|
||||
addFactory { InsertMangaTrack(get()) }
|
||||
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
||||
addFactory { GetEpisode(get()) }
|
||||
addFactory { GetEpisodeByAnimeId(get()) }
|
||||
addFactory { GetEpisodesByAnimeId(get()) }
|
||||
addFactory { GetEpisodeByUrlAndAnimeId(get()) }
|
||||
addFactory { UpdateEpisode(get()) }
|
||||
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbEpisode() }
|
||||
addFactory { SyncEpisodesWithSource(get(), get(), get(), get()) }
|
||||
addFactory { SyncEpisodesWithTrackServiceTwoWay(get(), get()) }
|
||||
addFactory { SyncEpisodesWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||
addFactory { GetChapter(get()) }
|
||||
addFactory { GetChapterByMangaId(get()) }
|
||||
addFactory { GetChaptersByMangaId(get()) }
|
||||
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||
addFactory { UpdateChapter(get()) }
|
||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
addFactory { GetAvailableScanlators(get()) }
|
||||
|
||||
addSingletonFactory<AnimeHistoryRepository> { AnimeHistoryRepositoryImpl(get()) }
|
||||
addFactory { GetAnimeHistory(get()) }
|
||||
addFactory { UpsertAnimeHistory(get()) }
|
||||
addFactory { RemoveAnimeHistory(get()) }
|
||||
|
||||
addFactory { DeleteAnimeDownload(get(), get()) }
|
||||
addFactory { DeleteEpisodeDownload(get(), get()) }
|
||||
|
||||
addFactory { GetAnimeExtensionsByType(get(), get()) }
|
||||
addFactory { GetAnimeExtensionSources(get()) }
|
||||
|
@ -266,7 +302,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { GetMangaUpdates(get()) }
|
||||
|
||||
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<AnimeSourceDataRepository> { AnimeSourceDataRepositoryImpl(get()) }
|
||||
addSingletonFactory<AnimeStubSourceRepository> { AnimeStubSourceRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledAnimeSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
||||
addFactory { GetRemoteAnime(get()) }
|
||||
|
@ -276,7 +312,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { ToggleAnimeSourcePin(get()) }
|
||||
|
||||
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
||||
addSingletonFactory<MangaSourceDataRepository> { MangaSourceDataRepositoryImpl(get()) }
|
||||
addSingletonFactory<MangaStubSourceRepository> { MangaStubSourceRepositoryImpl(get()) }
|
||||
addFactory { GetEnabledMangaSources(get(), get()) }
|
||||
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
||||
addFactory { GetRemoteManga(get()) }
|
||||
|
|
|
@ -3,9 +3,11 @@ package eu.kanade.domain.base
|
|||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.StringRes
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
class BasePreferences(
|
||||
|
@ -13,21 +15,28 @@ class BasePreferences(
|
|||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun confirmExit() = preferenceStore.getBoolean("pref_confirm_exit", false)
|
||||
fun downloadedOnly() = preferenceStore.getBoolean(
|
||||
Preference.appStateKey("pref_downloaded_only"),
|
||||
false,
|
||||
)
|
||||
|
||||
fun downloadedOnly() = preferenceStore.getBoolean("pref_downloaded_only", false)
|
||||
|
||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
||||
fun incognitoMode() = preferenceStore.getBoolean(Preference.appStateKey("incognito_mode"), false)
|
||||
|
||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
||||
|
||||
fun acraEnabled() = preferenceStore.getBoolean("acra.enable", isPreviewBuildType || isReleaseBuildType)
|
||||
fun acraEnabled() = preferenceStore.getBoolean(
|
||||
"acra.enable",
|
||||
isPreviewBuildType || isReleaseBuildType,
|
||||
)
|
||||
|
||||
fun deviceHasPip() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
||||
fun deviceHasPip() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && context.packageManager.hasSystemFeature(
|
||||
PackageManager.FEATURE_PICTURE_IN_PICTURE,
|
||||
)
|
||||
|
||||
enum class ExtensionInstaller(val titleResId: Int) {
|
||||
enum class ExtensionInstaller(@StringRes val titleResId: Int) {
|
||||
LEGACY(R.string.ext_installer_legacy),
|
||||
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
||||
SHIZUKU(R.string.ext_installer_shizuku),
|
||||
PRIVATE(R.string.ext_installer_private),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
|
|||
|
||||
override fun key() = "extension_installer"
|
||||
|
||||
val entries get() = ExtensionInstaller.values().run {
|
||||
val entries get() = ExtensionInstaller.entries.run {
|
||||
if (context.hasMiuiPackageInstaller) {
|
||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||
} else {
|
||||
|
|
|
@ -6,7 +6,7 @@ import tachiyomi.domain.entries.anime.model.Anime
|
|||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||
|
||||
class DeleteAnimeDownload(
|
||||
class DeleteEpisodeDownload(
|
||||
private val sourceManager: AnimeSourceManager,
|
||||
private val downloadManager: AnimeDownloadManager,
|
||||
) {
|
||||
|
|
|
@ -3,16 +3,19 @@ package eu.kanade.domain.entries.anime.interactor
|
|||
import eu.kanade.domain.entries.anime.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
||||
import tachiyomi.domain.entries.anime.interactor.AnimeFetchInterval
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||
import tachiyomi.source.local.entries.anime.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateAnime(
|
||||
private val animeRepository: AnimeRepository,
|
||||
private val animeFetchInterval: AnimeFetchInterval,
|
||||
) {
|
||||
|
||||
suspend fun await(animeUpdate: AnimeUpdate): Boolean {
|
||||
|
@ -73,12 +76,24 @@ class UpdateAnime(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
anime: Anime,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = animeFetchInterval.getWindow(dateTime),
|
||||
): Boolean {
|
||||
return animeFetchInterval.toAnimeUpdateOrNull(anime, dateTime, window)
|
||||
?.let { animeRepository.updateAnime(it) }
|
||||
?: false
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(animeId: Long): Boolean {
|
||||
return animeRepository.updateAnime(AnimeUpdate(id = animeId, lastUpdate = Date().time))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||
return animeRepository.updateAnime(AnimeUpdate(id = mangaId, coverLastModified = Date().time))
|
||||
return animeRepository.updateAnime(
|
||||
AnimeUpdate(id = mangaId, coverLastModified = Date().time),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFavorite(animeId: Long, favorite: Boolean): Boolean {
|
||||
|
|
|
@ -3,24 +3,24 @@ package eu.kanade.domain.entries.anime.model
|
|||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
val Anime.downloadedFilter: TriStateFilter
|
||||
val Anime.downloadedFilter: TriState
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Anime.EPISODE_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
Anime.EPISODE_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
}
|
||||
fun Anime.episodesFiltered(): Boolean {
|
||||
return unseenFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
return unseenFilter != TriState.DISABLED ||
|
||||
downloadedFilter != TriState.DISABLED ||
|
||||
bookmarkedFilter != TriState.DISABLED
|
||||
}
|
||||
fun Anime.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package eu.kanade.domain.entries.manga.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||
|
||||
class GetExcludedScanlators(
|
||||
private val handler: MangaDatabaseHandler,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
.toSet()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return handler.subscribeToList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}
|
||||
.map { it.toSet() }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package eu.kanade.domain.entries.manga.interactor
|
||||
|
||||
import tachiyomi.data.handlers.manga.MangaDatabaseHandler
|
||||
|
||||
class SetExcludedScanlators(
|
||||
private val handler: MangaDatabaseHandler,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaId: Long, excludedScanlators: Set<String>) {
|
||||
handler.await(inTransaction = true) {
|
||||
val currentExcluded = handler.awaitList {
|
||||
excluded_scanlatorsQueries.getExcludedScanlatorsByMangaId(mangaId)
|
||||
}.toSet()
|
||||
val toAdd = excludedScanlators.minus(currentExcluded)
|
||||
for (scanlator in toAdd) {
|
||||
excluded_scanlatorsQueries.insert(mangaId, scanlator)
|
||||
}
|
||||
val toRemove = currentExcluded.minus(excludedScanlators)
|
||||
excluded_scanlatorsQueries.remove(mangaId, toRemove)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package eu.kanade.domain.entries.manga.interactor
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
||||
|
||||
|
@ -9,22 +9,22 @@ class SetMangaViewerFlags(
|
|||
private val mangaRepository: MangaRepository,
|
||||
) {
|
||||
|
||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
||||
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.updateManga(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingModeType.MASK.toLong()),
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReadingMode.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
|
||||
suspend fun awaitSetOrientation(id: Long, flag: Long) {
|
||||
val manga = mangaRepository.getMangaById(id)
|
||||
mangaRepository.updateManga(
|
||||
MangaUpdate(
|
||||
id = id,
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, OrientationType.MASK.toLong()),
|
||||
viewerFlags = manga.viewerFlags.setFlag(flag, ReaderOrientation.MASK.toLong()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,16 +3,19 @@ package eu.kanade.domain.entries.manga.interactor
|
|||
import eu.kanade.domain.entries.manga.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.entries.manga.interactor.MangaFetchInterval
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
||||
import tachiyomi.source.local.entries.manga.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
|
||||
class UpdateManga(
|
||||
private val mangaRepository: MangaRepository,
|
||||
private val mangaFetchInterval: MangaFetchInterval,
|
||||
) {
|
||||
|
||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
||||
|
@ -73,12 +76,24 @@ class UpdateManga(
|
|||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
window: Pair<Long, Long> = mangaFetchInterval.getWindow(dateTime),
|
||||
): Boolean {
|
||||
return mangaFetchInterval.toMangaUpdateOrNull(manga, dateTime, window)
|
||||
?.let { mangaRepository.updateManga(it) }
|
||||
?: false
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||
return mangaRepository.updateManga(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
||||
return mangaRepository.updateManga(MangaUpdate(id = mangaId, coverLastModified = Date().time))
|
||||
return mangaRepository.updateManga(
|
||||
MangaUpdate(id = mangaId, coverLastModified = Date().time),
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean {
|
||||
|
|
|
@ -3,36 +3,36 @@ package eu.kanade.domain.entries.manga.model
|
|||
import eu.kanade.domain.base.BasePreferences
|
||||
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
||||
import tachiyomi.domain.entries.TriStateFilter
|
||||
import tachiyomi.core.preference.TriState
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
// TODO: move these into the domain model
|
||||
val Manga.readingModeType: Long
|
||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
||||
val Manga.readingMode: Long
|
||||
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||
|
||||
val Manga.orientationType: Long
|
||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
||||
val Manga.readerOrientation: Long
|
||||
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||
|
||||
val Manga.downloadedFilter: TriStateFilter
|
||||
val Manga.downloadedFilter: TriState
|
||||
get() {
|
||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
||||
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||
return when (downloadedFilterRaw) {
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
||||
else -> TriStateFilter.DISABLED
|
||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||
else -> TriState.DISABLED
|
||||
}
|
||||
}
|
||||
fun Manga.chaptersFiltered(): Boolean {
|
||||
return unreadFilter != TriStateFilter.DISABLED ||
|
||||
downloadedFilter != TriStateFilter.DISABLED ||
|
||||
bookmarkedFilter != TriStateFilter.DISABLED
|
||||
return unreadFilter != TriState.DISABLED ||
|
||||
downloadedFilter != TriState.DISABLED ||
|
||||
bookmarkedFilter != TriState.DISABLED
|
||||
}
|
||||
fun Manga.forceDownloaded(): Boolean {
|
||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
||||
|
@ -95,9 +95,16 @@ fun Manga.hasCustomCover(coverCache: MangaCoverCache = Injekt.get()): Boolean {
|
|||
/**
|
||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||
*/
|
||||
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo(
|
||||
fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String, categories: List<String>?) = ComicInfo(
|
||||
title = ComicInfo.Title(chapter.name),
|
||||
series = ComicInfo.Series(manga.title),
|
||||
number = chapter.chapterNumber.takeIf { it >= 0 }?.let {
|
||||
if ((it.rem(1) == 0.0)) {
|
||||
ComicInfo.Number(it.toInt().toString())
|
||||
} else {
|
||||
ComicInfo.Number(it.toString())
|
||||
}
|
||||
},
|
||||
web = ComicInfo.Web(chapterUrl),
|
||||
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||
|
@ -107,6 +114,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo
|
|||
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||
),
|
||||
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||
inker = null,
|
||||
colorist = null,
|
||||
letterer = null,
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package eu.kanade.domain.items.chapter.interactor
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||
|
||||
class GetAvailableScanlators(
|
||||
private val repository: ChapterRepository,
|
||||
) {
|
||||
|
||||
private fun List<String>.cleanupAvailableScanlators(): Set<String> {
|
||||
return mapNotNull { it.ifBlank { null } }.toSet()
|
||||
}
|
||||
|
||||
suspend fun await(mangaId: Long): Set<String> {
|
||||
return repository.getScanlatorsByMangaId(mangaId)
|
||||
.cleanupAvailableScanlators()
|
||||
}
|
||||
|
||||
fun subscribe(mangaId: Long): Flow<Set<String>> {
|
||||
return repository.getScanlatorsByMangaIdAsFlow(mangaId)
|
||||
.map { it.cleanupAvailableScanlators() }
|
||||
}
|
||||
}
|
|
@ -72,9 +72,9 @@ class SetReadStatus(
|
|||
suspend fun await(manga: Manga, read: Boolean) =
|
||||
await(manga.id, read)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoChapters : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
sealed interface Result {
|
||||
data object Success : Result
|
||||
data object NoChapters : Result
|
||||
data class InternalError(val error: Throwable) : Result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package eu.kanade.domain.items.chapter.interactor
|
||||
|
||||
import eu.kanade.domain.entries.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.entries.manga.model.toSManga
|
||||
import eu.kanade.domain.items.chapter.model.copyFromSChapter
|
||||
|
@ -11,7 +12,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import tachiyomi.data.items.chapter.ChapterSanitizer
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChapterByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
|
@ -20,20 +21,20 @@ import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
|||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||
import tachiyomi.domain.items.chapter.service.ChapterRecognition
|
||||
import tachiyomi.source.local.entries.manga.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Long.max
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
class SyncChaptersWithSource(
|
||||
private val downloadManager: MangaDownloadManager = Injekt.get(),
|
||||
private val downloadProvider: MangaDownloadProvider = Injekt.get(),
|
||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
||||
private val downloadManager: MangaDownloadManager,
|
||||
private val downloadProvider: MangaDownloadProvider,
|
||||
private val chapterRepository: ChapterRepository,
|
||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||
private val updateManga: UpdateManga,
|
||||
private val updateChapter: UpdateChapter,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val getExcludedScanlators: GetExcludedScanlators,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
@ -48,11 +49,15 @@ class SyncChaptersWithSource(
|
|||
rawSourceChapters: List<SChapter>,
|
||||
manga: Manga,
|
||||
source: MangaSource,
|
||||
manualFetch: Boolean = false,
|
||||
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||
): List<Chapter> {
|
||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||
throw NoChaptersException()
|
||||
}
|
||||
|
||||
val now = ZonedDateTime.now()
|
||||
|
||||
val sourceChapters = rawSourceChapters
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sChapter ->
|
||||
|
@ -63,7 +68,7 @@ class SyncChaptersWithSource(
|
|||
}
|
||||
|
||||
// Chapters from db.
|
||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
||||
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||
|
||||
// Chapters from the source not in db.
|
||||
val toAdd = mutableListOf<Chapter>()
|
||||
|
@ -96,7 +101,11 @@ class SyncChaptersWithSource(
|
|||
}
|
||||
|
||||
// Recognize chapter number for the chapter.
|
||||
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapterNumber)
|
||||
val chapterNumber = ChapterRecognition.parseChapterNumber(
|
||||
manga.title,
|
||||
chapter.name,
|
||||
chapter.chapterNumber,
|
||||
)
|
||||
chapter = chapter.copy(chapterNumber = chapterNumber)
|
||||
|
||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||
|
@ -112,8 +121,16 @@ class SyncChaptersWithSource(
|
|||
toAdd.add(toAddChapter)
|
||||
} else {
|
||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(
|
||||
dbChapter,
|
||||
chapter,
|
||||
) &&
|
||||
downloadManager.isChapterDownloaded(
|
||||
dbChapter.name,
|
||||
dbChapter.scanlator,
|
||||
manga.title,
|
||||
manga.source,
|
||||
)
|
||||
|
||||
if (shouldRenameChapter) {
|
||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
||||
|
@ -134,14 +151,21 @@ class SyncChaptersWithSource(
|
|||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||
updateManga.awaitUpdateFetchInterval(
|
||||
manga,
|
||||
now,
|
||||
fetchWindow,
|
||||
)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val reAdded = mutableListOf<Chapter>()
|
||||
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||
val deletedChapterNumbers = TreeSet<Double>()
|
||||
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||
|
||||
toDelete.forEach { chapter ->
|
||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||
|
@ -188,6 +212,7 @@ class SyncChaptersWithSource(
|
|||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
}
|
||||
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||
|
||||
// Set this manga as updated since chapters were changed
|
||||
// Note that last_update actually represents last time the chapter list changed at all
|
||||
|
@ -195,6 +220,10 @@ class SyncChaptersWithSource(
|
|||
|
||||
val reAddedUrls = reAdded.map { it.url }.toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot { it.url in reAddedUrls }
|
||||
val excludedScanlators = getExcludedScanlators.await(manga.id).toHashSet()
|
||||
|
||||
return updatedToAdd.filterNot {
|
||||
it.url in reAddedUrls || it.scanlator in excludedScanlators
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package eu.kanade.domain.items.chapter.model
|
||||
|
||||
import data.Chapters
|
||||
import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
|
@ -12,7 +11,7 @@ fun Chapter.toSChapter(): SChapter {
|
|||
it.url = url
|
||||
it.name = name
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
|
@ -22,18 +21,8 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
|||
name = sChapter.name,
|
||||
url = sChapter.url,
|
||||
dateUpload = sChapter.date_upload,
|
||||
chapterNumber = sChapter.chapter_number,
|
||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
fun Chapter.copyFrom(other: Chapters): Chapter {
|
||||
return copy(
|
||||
name = other.name,
|
||||
url = other.url,
|
||||
dateUpload = other.date_upload,
|
||||
chapterNumber = other.chapter_number,
|
||||
scanlator = other.scanlator?.ifBlank { null },
|
||||
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -48,6 +37,6 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
|||
it.last_page_read = lastPageRead.toInt()
|
||||
it.date_fetch = dateFetch
|
||||
it.date_upload = dateUpload
|
||||
it.chapter_number = chapterNumber
|
||||
it.chapter_number = chapterNumber.toFloat()
|
||||
it.source_order = sourceOrder.toInt()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.domain.items.chapter.model
|
|||
|
||||
import eu.kanade.domain.entries.manga.model.downloadedFilter
|
||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.ChapterItem
|
||||
import eu.kanade.tachiyomi.ui.entries.manga.ChapterList
|
||||
import tachiyomi.domain.entries.applyFilter
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
|
@ -23,7 +23,12 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: MangaDownloadManag
|
|||
.filter { chapter -> applyFilter(bookmarkedFilter) { chapter.bookmark } }
|
||||
.filter { chapter ->
|
||||
applyFilter(downloadedFilter) {
|
||||
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
|
||||
val downloaded = downloadManager.isChapterDownloaded(
|
||||
chapter.name,
|
||||
chapter.scanlator,
|
||||
manga.title,
|
||||
manga.source,
|
||||
)
|
||||
downloaded || isLocalManga
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +39,7 @@ fun List<Chapter>.applyFilters(manga: Manga, downloadManager: MangaDownloadManag
|
|||
* Applies the view filters to the list of chapters obtained from the database.
|
||||
* @return an observable of the list of chapters filtered and sorted.
|
||||
*/
|
||||
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
|
||||
fun List<ChapterList.Item>.applyFilters(manga: Manga): Sequence<ChapterList.Item> {
|
||||
val isLocalManga = manga.isLocal()
|
||||
val unreadFilter = manga.unreadFilter
|
||||
val downloadedFilter = manga.downloadedFilter
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package eu.kanade.domain.items.episode.interactor
|
||||
|
||||
import eu.kanade.domain.download.anime.interactor.DeleteAnimeDownload
|
||||
import eu.kanade.domain.download.anime.interactor.DeleteEpisodeDownload
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
|
@ -13,7 +13,7 @@ import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
|||
|
||||
class SetSeenStatus(
|
||||
private val downloadPreferences: DownloadPreferences,
|
||||
private val deleteDownload: DeleteAnimeDownload,
|
||||
private val deleteDownload: DeleteEpisodeDownload,
|
||||
private val animeRepository: AnimeRepository,
|
||||
private val episodeRepository: EpisodeRepository,
|
||||
) {
|
||||
|
@ -72,9 +72,9 @@ class SetSeenStatus(
|
|||
suspend fun await(anime: Anime, seen: Boolean) =
|
||||
await(anime.id, seen)
|
||||
|
||||
sealed class Result {
|
||||
object Success : Result()
|
||||
object NoEpisodes : Result()
|
||||
data class InternalError(val error: Throwable) : Result()
|
||||
sealed interface Result {
|
||||
data object Success : Result
|
||||
data object NoEpisodes : Result
|
||||
data class InternalError(val error: Throwable) : Result
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
|||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider
|
||||
import tachiyomi.data.items.episode.EpisodeSanitizer
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodeByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
|
@ -20,20 +20,19 @@ import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
|||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
||||
import tachiyomi.domain.items.episode.service.EpisodeRecognition
|
||||
import tachiyomi.source.local.entries.anime.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.lang.Long.max
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Date
|
||||
import java.util.TreeSet
|
||||
|
||||
class SyncEpisodesWithSource(
|
||||
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
||||
private val downloadProvider: AnimeDownloadProvider = Injekt.get(),
|
||||
private val episodeRepository: EpisodeRepository = Injekt.get(),
|
||||
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode = Injekt.get(),
|
||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
||||
private val downloadManager: AnimeDownloadManager,
|
||||
private val downloadProvider: AnimeDownloadProvider,
|
||||
private val episodeRepository: EpisodeRepository,
|
||||
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode,
|
||||
private val updateAnime: UpdateAnime,
|
||||
private val updateEpisode: UpdateEpisode,
|
||||
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
@ -48,11 +47,15 @@ class SyncEpisodesWithSource(
|
|||
rawSourceEpisodes: List<SEpisode>,
|
||||
anime: Anime,
|
||||
source: AnimeSource,
|
||||
manualFetch: Boolean = false,
|
||||
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||
): List<Episode> {
|
||||
if (rawSourceEpisodes.isEmpty() && !source.isLocal()) {
|
||||
throw NoEpisodesException()
|
||||
}
|
||||
|
||||
val now = ZonedDateTime.now()
|
||||
|
||||
val sourceEpisodes = rawSourceEpisodes
|
||||
.distinctBy { it.url }
|
||||
.mapIndexed { i, sEpisode ->
|
||||
|
@ -63,7 +66,7 @@ class SyncEpisodesWithSource(
|
|||
}
|
||||
|
||||
// Episodes from db.
|
||||
val dbEpisodes = getEpisodeByAnimeId.await(anime.id)
|
||||
val dbEpisodes = getEpisodesByAnimeId.await(anime.id)
|
||||
|
||||
// Episodes from the source not in db.
|
||||
val toAdd = mutableListOf<Episode>()
|
||||
|
@ -96,7 +99,11 @@ class SyncEpisodesWithSource(
|
|||
}
|
||||
|
||||
// Recognize episode number for the episode.
|
||||
val episodeNumber = EpisodeRecognition.parseEpisodeNumber(anime.title, episode.name, episode.episodeNumber)
|
||||
val episodeNumber = EpisodeRecognition.parseEpisodeNumber(
|
||||
anime.title,
|
||||
episode.name,
|
||||
episode.episodeNumber,
|
||||
)
|
||||
episode = episode.copy(episodeNumber = episodeNumber)
|
||||
|
||||
val dbEpisode = dbEpisodes.find { it.url == episode.url }
|
||||
|
@ -112,8 +119,16 @@ class SyncEpisodesWithSource(
|
|||
toAdd.add(toAddEpisode)
|
||||
} else {
|
||||
if (shouldUpdateDbEpisode.await(dbEpisode, episode)) {
|
||||
val shouldRenameEpisode = downloadProvider.isEpisodeDirNameChanged(dbEpisode, episode) &&
|
||||
downloadManager.isEpisodeDownloaded(dbEpisode.name, dbEpisode.scanlator, anime.title, anime.source)
|
||||
val shouldRenameEpisode = downloadProvider.isEpisodeDirNameChanged(
|
||||
dbEpisode,
|
||||
episode,
|
||||
) &&
|
||||
downloadManager.isEpisodeDownloaded(
|
||||
dbEpisode.name,
|
||||
dbEpisode.scanlator,
|
||||
anime.title,
|
||||
anime.source,
|
||||
)
|
||||
|
||||
if (shouldRenameEpisode) {
|
||||
downloadManager.renameEpisode(source, anime, dbEpisode, episode)
|
||||
|
@ -125,7 +140,9 @@ class SyncEpisodesWithSource(
|
|||
sourceOrder = episode.sourceOrder,
|
||||
)
|
||||
if (episode.dateUpload != 0L) {
|
||||
toChangeEpisode = toChangeEpisode.copy(dateUpload = sourceEpisode.dateUpload)
|
||||
toChangeEpisode = toChangeEpisode.copy(
|
||||
dateUpload = sourceEpisode.dateUpload,
|
||||
)
|
||||
}
|
||||
toChange.add(toChangeEpisode)
|
||||
}
|
||||
|
@ -134,14 +151,21 @@ class SyncEpisodesWithSource(
|
|||
|
||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||
if (manualFetch || anime.fetchInterval == 0 || anime.nextUpdate < fetchWindow.first) {
|
||||
updateAnime.awaitUpdateFetchInterval(
|
||||
anime,
|
||||
now,
|
||||
fetchWindow,
|
||||
)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val reAdded = mutableListOf<Episode>()
|
||||
|
||||
val deletedEpisodeNumbers = TreeSet<Float>()
|
||||
val deletedSeenEpisodeNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedEpisodeNumbers = TreeSet<Float>()
|
||||
val deletedEpisodeNumbers = TreeSet<Double>()
|
||||
val deletedSeenEpisodeNumbers = TreeSet<Double>()
|
||||
val deletedBookmarkedEpisodeNumbers = TreeSet<Double>()
|
||||
|
||||
toDelete.forEach { episode ->
|
||||
if (episode.seen) deletedSeenEpisodeNumbers.add(episode.episodeNumber)
|
||||
|
@ -188,6 +212,7 @@ class SyncEpisodesWithSource(
|
|||
val episodeUpdates = toChange.map { it.toEpisodeUpdate() }
|
||||
updateEpisode.awaitAll(episodeUpdates)
|
||||
}
|
||||
updateAnime.awaitUpdateFetchInterval(anime, now, fetchWindow)
|
||||
|
||||
// Set this anime as updated since episodes were changed
|
||||
// Note that last_update actually represents last time the episode list changed at all
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package eu.kanade.domain.items.episode.model
|
||||
|
||||
import dataanime.Episodes
|
||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||
import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
|
@ -12,7 +11,7 @@ fun Episode.toSEpisode(): SEpisode {
|
|||
it.url = url
|
||||
it.name = name
|
||||
it.date_upload = dateUpload
|
||||
it.episode_number = episodeNumber
|
||||
it.episode_number = episodeNumber.toFloat()
|
||||
it.scanlator = scanlator
|
||||
}
|
||||
}
|
||||
|
@ -22,21 +21,11 @@ fun Episode.copyFromSEpisode(sEpisode: SEpisode): Episode {
|
|||
name = sEpisode.name,
|
||||
url = sEpisode.url,
|
||||
dateUpload = sEpisode.date_upload,
|
||||
episodeNumber = sEpisode.episode_number,
|
||||
episodeNumber = sEpisode.episode_number.toDouble(),
|
||||
scanlator = sEpisode.scanlator?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
fun Episode.copyFrom(other: Episodes): Episode {
|
||||
return copy(
|
||||
name = other.name,
|
||||
url = other.url,
|
||||
dateUpload = other.date_upload,
|
||||
episodeNumber = other.episode_number,
|
||||
scanlator = other.scanlator?.ifBlank { null },
|
||||
)
|
||||
}
|
||||
|
||||
fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
||||
it.id = id
|
||||
it.anime_id = animeId
|
||||
|
@ -49,6 +38,6 @@ fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
|||
it.total_seconds = totalSeconds
|
||||
it.date_fetch = dateFetch
|
||||
it.date_upload = dateUpload
|
||||
it.episode_number = episodeNumber
|
||||
it.episode_number = episodeNumber.toFloat()
|
||||
it.source_order = sourceOrder.toInt()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.domain.items.episode.model
|
|||
|
||||
import eu.kanade.domain.entries.anime.model.downloadedFilter
|
||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeItem
|
||||
import eu.kanade.tachiyomi.ui.entries.anime.EpisodeList
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.applyFilter
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
|
@ -23,7 +23,12 @@ fun List<Episode>.applyFilters(anime: Anime, downloadManager: AnimeDownloadManag
|
|||
.filter { episode -> applyFilter(bookmarkedFilter) { episode.bookmark } }
|
||||
.filter { episode ->
|
||||
applyFilter(downloadedFilter) {
|
||||
val downloaded = downloadManager.isEpisodeDownloaded(episode.name, episode.scanlator, anime.title, anime.source)
|
||||
val downloaded = downloadManager.isEpisodeDownloaded(
|
||||
episode.name,
|
||||
episode.scanlator,
|
||||
anime.title,
|
||||
anime.source,
|
||||
)
|
||||
downloaded || isLocalAnime
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +39,7 @@ fun List<Episode>.applyFilters(anime: Anime, downloadManager: AnimeDownloadManag
|
|||
* Applies the view filters to the list of episodes obtained from the database.
|
||||
* @return an observable of the list of episodes filtered and sorted.
|
||||
*/
|
||||
fun List<EpisodeItem>.applyFilters(anime: Anime): Sequence<EpisodeItem> {
|
||||
fun List<EpisodeList.Item>.applyFilters(anime: Anime): Sequence<EpisodeList.Item> {
|
||||
val isLocalAnime = anime.isLocal()
|
||||
val unseenFilter = anime.unseenFilter
|
||||
val downloadedFilter = anime.downloadedFilter
|
||||
|
|
|
@ -4,12 +4,11 @@ import eu.kanade.domain.source.service.SetMigrateSorting
|
|||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.core.util.lang.compareToWithCollator
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||
import java.text.Collator
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
class GetAnimeSourcesWithFavoriteCount(
|
||||
private val repository: AnimeSourceRepository,
|
||||
|
@ -32,17 +31,13 @@ class GetAnimeSourcesWithFavoriteCount(
|
|||
direction: SetMigrateSorting.Direction,
|
||||
sorting: SetMigrateSorting.Mode,
|
||||
): java.util.Comparator<Pair<AnimeSource, Long>> {
|
||||
val locale = Locale.getDefault()
|
||||
val collator = Collator.getInstance(locale).apply {
|
||||
strength = Collator.PRIMARY
|
||||
}
|
||||
val sortFn: (Pair<AnimeSource, Long>, Pair<AnimeSource, Long>) -> Int = { a, b ->
|
||||
when (sorting) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
||||
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||
}
|
||||
}
|
||||
SetMigrateSorting.Mode.TOTAL -> {
|
||||
|
|
|
@ -6,13 +6,14 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||
import java.util.SortedMap
|
||||
|
||||
class GetLanguagesWithAnimeSources(
|
||||
private val repository: AnimeSourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Map<String, List<AnimeSource>>> {
|
||||
fun subscribe(): Flow<SortedMap<String, List<AnimeSource>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledAnimeSources().changes(),
|
||||
|
@ -23,7 +24,8 @@ class GetLanguagesWithAnimeSources(
|
|||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
|
||||
sortedSources.groupBy { it.lang }
|
||||
sortedSources
|
||||
.groupBy { it.lang }
|
||||
.toSortedMap(
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
|
|
|
@ -21,7 +21,13 @@ class ToggleAnimeSource(
|
|||
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||
preferences.disabledAnimeSources().getAndSet { disabled ->
|
||||
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
|
||||
if (enable) {
|
||||
disabled.minus(transformedSourceIds)
|
||||
} else {
|
||||
disabled.plus(
|
||||
transformedSourceIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,13 +6,14 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||
import java.util.SortedMap
|
||||
|
||||
class GetLanguagesWithMangaSources(
|
||||
private val repository: MangaSourceRepository,
|
||||
private val preferences: SourcePreferences,
|
||||
) {
|
||||
|
||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
||||
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||
return combine(
|
||||
preferences.enabledLanguages().changes(),
|
||||
preferences.disabledMangaSources().changes(),
|
||||
|
@ -23,7 +24,8 @@ class GetLanguagesWithMangaSources(
|
|||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||
)
|
||||
|
||||
sortedSources.groupBy { it.lang }
|
||||
sortedSources
|
||||
.groupBy { it.lang }
|
||||
.toSortedMap(
|
||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||
)
|
||||
|
|
|
@ -4,12 +4,11 @@ import eu.kanade.domain.source.service.SetMigrateSorting
|
|||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import tachiyomi.core.util.lang.compareToWithCollator
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||
import tachiyomi.source.local.entries.manga.LocalMangaSource
|
||||
import java.text.Collator
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
class GetMangaSourcesWithFavoriteCount(
|
||||
private val repository: MangaSourceRepository,
|
||||
|
@ -32,17 +31,13 @@ class GetMangaSourcesWithFavoriteCount(
|
|||
direction: SetMigrateSorting.Direction,
|
||||
sorting: SetMigrateSorting.Mode,
|
||||
): java.util.Comparator<Pair<Source, Long>> {
|
||||
val locale = Locale.getDefault()
|
||||
val collator = Collator.getInstance(locale).apply {
|
||||
strength = Collator.PRIMARY
|
||||
}
|
||||
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||
when (sorting) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||
when {
|
||||
a.first.isStub && b.first.isStub.not() -> -1
|
||||
b.first.isStub && a.first.isStub.not() -> 1
|
||||
else -> collator.compare(a.first.name.lowercase(locale), b.first.name.lowercase(locale))
|
||||
else -> a.first.name.lowercase().compareToWithCollator(b.first.name.lowercase())
|
||||
}
|
||||
}
|
||||
SetMigrateSorting.Mode.TOTAL -> {
|
||||
|
|
|
@ -21,7 +21,13 @@ class ToggleMangaSource(
|
|||
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||
preferences.disabledMangaSources().getAndSet { disabled ->
|
||||
if (enable) disabled.minus(transformedSourceIds) else disabled.plus(transformedSourceIds)
|
||||
if (enable) {
|
||||
disabled.minus(transformedSourceIds)
|
||||
} else {
|
||||
disabled.plus(
|
||||
transformedSourceIds,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.domain.source.service
|
||||
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.core.preference.Preference
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
import tachiyomi.core.preference.getEnum
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
@ -11,17 +12,31 @@ class SourcePreferences(
|
|||
|
||||
// Common options
|
||||
|
||||
fun sourceDisplayMode() = preferenceStore.getObject("pref_display_mode_catalogue", LibraryDisplayMode.default, LibraryDisplayMode.Serializer::serialize, LibraryDisplayMode.Serializer::deserialize)
|
||||
fun sourceDisplayMode() = preferenceStore.getObject(
|
||||
"pref_display_mode_catalogue",
|
||||
LibraryDisplayMode.default,
|
||||
LibraryDisplayMode.Serializer::serialize,
|
||||
LibraryDisplayMode.Serializer::deserialize,
|
||||
)
|
||||
|
||||
fun enabledLanguages() = preferenceStore.getStringSet("source_languages", LocaleHelper.getDefaultEnabledLanguages())
|
||||
fun enabledLanguages() = preferenceStore.getStringSet(
|
||||
"source_languages",
|
||||
LocaleHelper.getDefaultEnabledLanguages(),
|
||||
)
|
||||
|
||||
fun showNsfwSource() = preferenceStore.getBoolean("show_nsfw_source", true)
|
||||
|
||||
fun migrationSortingMode() = preferenceStore.getEnum("pref_migration_sorting", SetMigrateSorting.Mode.ALPHABETICAL)
|
||||
fun migrationSortingMode() = preferenceStore.getEnum(
|
||||
"pref_migration_sorting",
|
||||
SetMigrateSorting.Mode.ALPHABETICAL,
|
||||
)
|
||||
|
||||
fun migrationSortingDirection() = preferenceStore.getEnum("pref_migration_direction", SetMigrateSorting.Direction.ASCENDING)
|
||||
fun migrationSortingDirection() = preferenceStore.getEnum(
|
||||
"pref_migration_direction",
|
||||
SetMigrateSorting.Direction.ASCENDING,
|
||||
)
|
||||
|
||||
fun trustedSignatures() = preferenceStore.getStringSet("trusted_signatures", emptySet())
|
||||
fun trustedSignatures() = preferenceStore.getStringSet(Preference.appStateKey("trusted_signatures"), emptySet())
|
||||
|
||||
// Mixture Sources
|
||||
|
||||
|
@ -31,18 +46,27 @@ class SourcePreferences(
|
|||
fun pinnedAnimeSources() = preferenceStore.getStringSet("pinned_anime_catalogues", emptySet())
|
||||
fun pinnedMangaSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||
|
||||
fun lastUsedAnimeSource() = preferenceStore.getLong("last_anime_catalogue_source", -1)
|
||||
fun lastUsedMangaSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
||||
fun lastUsedAnimeSource() = preferenceStore.getLong(
|
||||
Preference.appStateKey("last_anime_catalogue_source"),
|
||||
-1,
|
||||
)
|
||||
fun lastUsedMangaSource() = preferenceStore.getLong(
|
||||
Preference.appStateKey("last_catalogue_source"),
|
||||
-1,
|
||||
)
|
||||
|
||||
fun animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
||||
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||
|
||||
fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
|
||||
fun searchPinnedMangaSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
||||
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean(
|
||||
"browse_hide_in_anime_library_items",
|
||||
false,
|
||||
)
|
||||
|
||||
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false)
|
||||
|
||||
fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
||||
fun hideInMangaLibraryItems() = preferenceStore.getBoolean(
|
||||
"browse_hide_in_library_items",
|
||||
false,
|
||||
)
|
||||
|
||||
// SY -->
|
||||
|
||||
|
@ -62,7 +86,10 @@ class SourcePreferences(
|
|||
|
||||
fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80)
|
||||
|
||||
fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false)
|
||||
fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean(
|
||||
"data_saver_image_format_jpeg",
|
||||
false,
|
||||
)
|
||||
|
||||
fun dataSaverServer() = preferenceStore.getString("data_saver_server", "")
|
||||
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package eu.kanade.domain.track.anime.interactor
|
||||
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.domain.track.anime.model.toDomainTrack
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTracker
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.history.anime.interactor.GetAnimeHistory
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class AddAnimeTracks(
|
||||
private val getTracks: GetAnimeTracks,
|
||||
private val insertTrack: InsertAnimeTrack,
|
||||
private val syncChapterProgressWithTrack: SyncEpisodeProgressWithTrack,
|
||||
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||
) {
|
||||
|
||||
// TODO: update all trackers based on common data
|
||||
suspend fun bind(tracker: AnimeTracker, item: AnimeTrack, animeId: Long) = withNonCancellableContext {
|
||||
withIOContext {
|
||||
val allChapters = getEpisodesByAnimeId.await(animeId)
|
||||
val hasSeenEpisodes = allChapters.any { it.seen }
|
||||
tracker.bind(item, hasSeenEpisodes)
|
||||
|
||||
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
||||
|
||||
insertTrack.await(track)
|
||||
|
||||
// TODO: merge into [SyncChapterProgressWithTrack]?
|
||||
// Update chapter progress if newer chapters marked read locally
|
||||
if (hasSeenEpisodes) {
|
||||
val latestLocalReadChapterNumber = allChapters
|
||||
.sortedBy { it.episodeNumber }
|
||||
.takeWhile { it.seen }
|
||||
.lastOrNull()
|
||||
?.episodeNumber ?: -1.0
|
||||
|
||||
if (latestLocalReadChapterNumber > track.lastEpisodeSeen) {
|
||||
track = track.copy(
|
||||
lastEpisodeSeen = latestLocalReadChapterNumber,
|
||||
)
|
||||
tracker.setRemoteLastEpisodeSeen(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||
}
|
||||
|
||||
if (track.startDate <= 0) {
|
||||
val firstReadChapterDate = Injekt.get<GetAnimeHistory>().await(animeId)
|
||||
.sortedBy { it.seenAt }
|
||||
.firstOrNull()
|
||||
?.seenAt
|
||||
|
||||
firstReadChapterDate?.let {
|
||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||
ZoneOffset.systemDefault(),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
track = track.copy(
|
||||
startDate = startDate,
|
||||
)
|
||||
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
syncChapterProgressWithTrack.await(animeId, track, tracker)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun bindEnhancedTrackers(anime: Anime, source: AnimeSource) = withNonCancellableContext {
|
||||
withIOContext {
|
||||
getTracks.await(anime.id)
|
||||
.filterIsInstance<EnhancedAnimeTracker>()
|
||||
.filter { it.accept(source) }
|
||||
.forEach { service ->
|
||||
try {
|
||||
service.match(anime)?.let { track ->
|
||||
track.anime_id = anime.id
|
||||
(service as Tracker).animeService.bind(track)
|
||||
insertTrack.await(track.toDomainTrack()!!)
|
||||
|
||||
syncChapterProgressWithTrack.await(
|
||||
anime.id,
|
||||
track.toDomainTrack()!!,
|
||||
service.animeService,
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(
|
||||
LogPriority.WARN,
|
||||
e,
|
||||
) { "Could not match anime: ${anime.title} with service $service" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package eu.kanade.domain.track.anime.interactor
|
||||
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.domain.track.anime.model.toDomainTrack
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||
|
||||
class RefreshAnimeTracks(
|
||||
private val getTracks: GetAnimeTracks,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val insertTrack: InsertAnimeTrack,
|
||||
private val syncEpisodeProgressWithTrack: SyncEpisodeProgressWithTrack,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Fetches updated tracking data from all logged in trackers.
|
||||
*
|
||||
* @return Failed updates.
|
||||
*/
|
||||
suspend fun await(animeId: Long): List<Pair<Tracker?, Throwable>> {
|
||||
return supervisorScope {
|
||||
return@supervisorScope getTracks.await(animeId)
|
||||
.map { it to trackerManager.get(it.syncId) }
|
||||
.filter { (_, service) -> service?.isLoggedIn == true }
|
||||
.map { (track, service) ->
|
||||
async {
|
||||
return@async try {
|
||||
val updatedTrack = service!!.animeService.refresh(track.toDbTrack())
|
||||
insertTrack.await(updatedTrack.toDomainTrack()!!)
|
||||
syncEpisodeProgressWithTrack.await(animeId, track, service.animeService)
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
service to e
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +1,35 @@
|
|||
package eu.kanade.domain.items.episode.interactor
|
||||
package eu.kanade.domain.track.anime.interactor
|
||||
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTrackService
|
||||
import eu.kanade.tachiyomi.data.track.AnimeTracker
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.items.episode.interactor.GetEpisodesByAnimeId
|
||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||
import tachiyomi.domain.items.episode.model.Episode
|
||||
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SyncEpisodesWithTrackServiceTwoWay(
|
||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
||||
private val insertTrack: InsertAnimeTrack = Injekt.get(),
|
||||
class SyncEpisodeProgressWithTrack(
|
||||
private val updateEpisode: UpdateEpisode,
|
||||
private val insertTrack: InsertAnimeTrack,
|
||||
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||
) {
|
||||
|
||||
suspend fun await(
|
||||
episodes: List<Episode>,
|
||||
animeId: Long,
|
||||
remoteTrack: AnimeTrack,
|
||||
service: AnimeTrackService,
|
||||
service: AnimeTracker,
|
||||
) {
|
||||
val sortedEpisodes = episodes.sortedBy { it.episodeNumber }
|
||||
if (service !is EnhancedAnimeTracker) {
|
||||
return
|
||||
}
|
||||
|
||||
val sortedEpisodes = getEpisodesByAnimeId.await(animeId)
|
||||
.sortedBy { it.episodeNumber }
|
||||
.filter { it.isRecognizedNumber }
|
||||
|
||||
val episodeUpdates = sortedEpisodes
|
||||
.filter { episode -> episode.episodeNumber <= remoteTrack.lastEpisodeSeen && !episode.seen }
|
||||
.map { it.copy(seen = true).toEpisodeUpdate() }
|
|
@ -0,0 +1,59 @@
|
|||
package eu.kanade.domain.track.anime.interactor
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.domain.track.anime.model.toDomainTrack
|
||||
import eu.kanade.domain.track.anime.service.DelayedAnimeTrackingUpdateJob
|
||||
import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.util.system.isOnline
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||
|
||||
class TrackEpisode(
|
||||
private val getTracks: GetAnimeTracks,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val insertTrack: InsertAnimeTrack,
|
||||
private val delayedTrackingStore: DelayedAnimeTrackingStore,
|
||||
) {
|
||||
|
||||
suspend fun await(context: Context, animeId: Long, episodeNumber: Double) = coroutineScope {
|
||||
launchNonCancellable {
|
||||
val tracks = getTracks.await(animeId)
|
||||
|
||||
if (tracks.isEmpty()) return@launchNonCancellable
|
||||
|
||||
tracks.mapNotNull { track ->
|
||||
val service = trackerManager.get(track.syncId)
|
||||
if (service == null || !service.isLoggedIn || episodeNumber <= track.lastEpisodeSeen) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
async {
|
||||
runCatching {
|
||||
if (context.isOnline()) {
|
||||
val updatedTrack = service.animeService.refresh(track.toDbTrack())
|
||||
.toDomainTrack(idRequired = true)!!
|
||||
.copy(lastEpisodeSeen = episodeNumber)
|
||||
service.animeService.update(updatedTrack.toDbTrack(), true)
|
||||
insertTrack.await(updatedTrack)
|
||||
delayedTrackingStore.removeAnimeItem(track.id)
|
||||
} else {
|
||||
delayedTrackingStore.addAnime(track.id, episodeNumber)
|
||||
DelayedAnimeTrackingUpdateJob.setupTask(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.mapNotNull { it.exceptionOrNull() }
|
||||
.forEach { logcat(LogPriority.INFO, it) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,9 @@ fun AnimeTrack.copyPersonalFrom(other: AnimeTrack): AnimeTrack {
|
|||
)
|
||||
}
|
||||
|
||||
fun AnimeTrack.toDbTrack(): DbAnimeTrack = eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack.create(syncId).also {
|
||||
fun AnimeTrack.toDbTrack(): DbAnimeTrack = eu.kanade.tachiyomi.data.database.models.anime.AnimeTrack.create(
|
||||
syncId,
|
||||
).also {
|
||||
it.id = id
|
||||
it.anime_id = animeId
|
||||
it.media_id = remoteId
|
||||
|
@ -22,7 +24,7 @@ fun AnimeTrack.toDbTrack(): DbAnimeTrack = eu.kanade.tachiyomi.data.database.mod
|
|||
it.last_episode_seen = lastEpisodeSeen.toFloat()
|
||||
it.total_episodes = totalEpisodes.toInt()
|
||||
it.status = status.toInt()
|
||||
it.score = score
|
||||
it.score = score.toFloat()
|
||||
it.tracking_url = remoteUrl
|
||||
it.started_watching_date = startDate
|
||||
it.finished_watching_date = finishDate
|
||||
|
@ -40,7 +42,7 @@ fun DbAnimeTrack.toDomainTrack(idRequired: Boolean = true): AnimeTrack? {
|
|||
lastEpisodeSeen = last_episode_seen.toDouble(),
|
||||
totalEpisodes = total_episodes.toLong(),
|
||||
status = status.toLong(),
|
||||
score = score,
|
||||
score = score.toDouble(),
|
||||
remoteUrl = tracking_url,
|
||||
startDate = started_watching_date,
|
||||
finishDate = finished_watching_date,
|
||||
|
|
|
@ -8,30 +8,32 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.domain.track.anime.model.toDbTrack
|
||||
import eu.kanade.domain.track.anime.interactor.TrackEpisode
|
||||
import eu.kanade.domain.track.anime.store.DelayedAnimeTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
|
||||
class DelayedAnimeTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val getTracks = Injekt.get<GetAnimeTracks>()
|
||||
val insertTrack = Injekt.get<InsertAnimeTrack>()
|
||||
if (runAttemptCount > 3) {
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val getTracks = Injekt.get<GetAnimeTracks>()
|
||||
val trackEpisode = Injekt.get<TrackEpisode>()
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val delayedTrackingStore = Injekt.get<DelayedAnimeTrackingStore>()
|
||||
|
||||
val results = withIOContext {
|
||||
withIOContext {
|
||||
delayedTrackingStore.getAnimeItems()
|
||||
.mapNotNull {
|
||||
val track = getTracks.awaitOne(it.trackId)
|
||||
|
@ -40,24 +42,16 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
|||
}
|
||||
track?.copy(lastEpisodeSeen = it.lastEpisodeSeen.toDouble())
|
||||
}
|
||||
.mapNotNull { animeTrack ->
|
||||
try {
|
||||
val service = trackManager.getService(animeTrack.syncId)
|
||||
if (service != null && service.isLogged) {
|
||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${animeTrack.id}, last episode seen: ${animeTrack.lastEpisodeSeen}" }
|
||||
service.animeService.update(animeTrack.toDbTrack(), true)
|
||||
insertTrack.await(animeTrack)
|
||||
}
|
||||
delayedTrackingStore.removeAnimeItem(animeTrack.id)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
.forEach { animeTrack ->
|
||||
logcat(LogPriority.DEBUG) {
|
||||
"Updating delayed track item: ${animeTrack.animeId}" +
|
||||
", last chapter read: ${animeTrack.lastEpisodeSeen}"
|
||||
}
|
||||
trackEpisode.await(context, animeTrack.animeId, animeTrack.lastEpisodeSeen)
|
||||
}
|
||||
}
|
||||
|
||||
return if (results.isNotEmpty()) Result.failure() else Result.success()
|
||||
return if (delayedTrackingStore.getAnimeItems().isEmpty()) Result.success() else Result.retry()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -70,7 +64,7 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
|||
|
||||
val request = OneTimeWorkRequestBuilder<DelayedAnimeTrackingUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||
import androidx.core.content.edit
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||
|
||||
class DelayedAnimeTrackingStore(context: Context) {
|
||||
|
||||
|
@ -13,13 +12,12 @@ class DelayedAnimeTrackingStore(context: Context) {
|
|||
*/
|
||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||
|
||||
fun addAnimeItem(track: AnimeTrack) {
|
||||
val trackId = track.id.toString()
|
||||
val lastEpisodeSeen = preferences.getFloat(trackId, 0f)
|
||||
if (track.lastEpisodeSeen > lastEpisodeSeen) {
|
||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last episode seen: ${track.lastEpisodeSeen}" }
|
||||
fun addAnime(trackId: Long, lastEpisodeSeen: Double) {
|
||||
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||
if (lastEpisodeSeen > previousLastChapterRead) {
|
||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last episode seen: $lastEpisodeSeen" }
|
||||
preferences.edit {
|
||||
putFloat(trackId, track.lastEpisodeSeen.toFloat())
|
||||
putFloat(trackId.toString(), lastEpisodeSeen.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
package eu.kanade.domain.track.manga.interactor
|
||||
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.domain.track.manga.model.toDomainTrack
|
||||
import eu.kanade.tachiyomi.data.database.models.manga.MangaTrack
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker
|
||||
import eu.kanade.tachiyomi.data.track.MangaTracker
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import eu.kanade.tachiyomi.util.lang.convertEpochMillisZone
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.history.manga.interactor.GetMangaHistory
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class AddMangaTracks(
|
||||
private val getTracks: GetMangaTracks,
|
||||
private val insertTrack: InsertMangaTrack,
|
||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
) {
|
||||
|
||||
// TODO: update all trackers based on common data
|
||||
suspend fun bind(tracker: MangaTracker, item: MangaTrack, mangaId: Long) = withNonCancellableContext {
|
||||
withIOContext {
|
||||
val allChapters = getChaptersByMangaId.await(mangaId)
|
||||
val hasReadChapters = allChapters.any { it.read }
|
||||
tracker.bind(item, hasReadChapters)
|
||||
|
||||
var track = item.toDomainTrack(idRequired = false) ?: return@withIOContext
|
||||
|
||||
insertTrack.await(track)
|
||||
|
||||
// TODO: merge into [SyncChapterProgressWithTrack]?
|
||||
// Update chapter progress if newer chapters marked read locally
|
||||
if (hasReadChapters) {
|
||||
val latestLocalReadChapterNumber = allChapters
|
||||
.sortedBy { it.chapterNumber }
|
||||
.takeWhile { it.read }
|
||||
.lastOrNull()
|
||||
?.chapterNumber ?: -1.0
|
||||
|
||||
if (latestLocalReadChapterNumber > track.lastChapterRead) {
|
||||
track = track.copy(
|
||||
lastChapterRead = latestLocalReadChapterNumber,
|
||||
)
|
||||
tracker.setRemoteLastChapterRead(track.toDbTrack(), latestLocalReadChapterNumber.toInt())
|
||||
}
|
||||
|
||||
if (track.startDate <= 0) {
|
||||
val firstReadChapterDate = Injekt.get<GetMangaHistory>().await(mangaId)
|
||||
.sortedBy { it.readAt }
|
||||
.firstOrNull()
|
||||
?.readAt
|
||||
|
||||
firstReadChapterDate?.let {
|
||||
val startDate = firstReadChapterDate.time.convertEpochMillisZone(
|
||||
ZoneOffset.systemDefault(),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
track = track.copy(
|
||||
startDate = startDate,
|
||||
)
|
||||
tracker.setRemoteStartDate(track.toDbTrack(), startDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
syncChapterProgressWithTrack.await(mangaId, track, tracker)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun bindEnhancedTrackers(manga: Manga, source: MangaSource) = withNonCancellableContext {
|
||||
withIOContext {
|
||||
getTracks.await(manga.id)
|
||||
.filterIsInstance<EnhancedMangaTracker>()
|
||||
.filter { it.accept(source) }
|
||||
.forEach { service ->
|
||||
try {
|
||||
service.match(manga)?.let { track ->
|
||||
track.manga_id = manga.id
|
||||
(service as Tracker).mangaService.bind(track)
|
||||
insertTrack.await(track.toDomainTrack()!!)
|
||||
|
||||
syncChapterProgressWithTrack.await(
|
||||
manga.id,
|
||||
track.toDomainTrack()!!,
|
||||
service.mangaService,
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logcat(
|
||||
LogPriority.WARN,
|
||||
e,
|
||||
) { "Could not match manga: ${manga.title} with service $service" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package eu.kanade.domain.track.manga.interactor
|
||||
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.domain.track.manga.model.toDomainTrack
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||
|
||||
class RefreshMangaTracks(
|
||||
private val getTracks: GetMangaTracks,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val insertTrack: InsertMangaTrack,
|
||||
private val syncChapterProgressWithTrack: SyncChapterProgressWithTrack,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Fetches updated tracking data from all logged in trackers.
|
||||
*
|
||||
* @return Failed updates.
|
||||
*/
|
||||
suspend fun await(mangaId: Long): List<Pair<Tracker?, Throwable>> {
|
||||
return supervisorScope {
|
||||
return@supervisorScope getTracks.await(mangaId)
|
||||
.map { it to trackerManager.get(it.syncId) }
|
||||
.filter { (_, service) -> service?.isLoggedIn == true }
|
||||
.map { (track, service) ->
|
||||
async {
|
||||
return@async try {
|
||||
val updatedTrack = service!!.mangaService.refresh(track.toDbTrack())
|
||||
insertTrack.await(updatedTrack.toDomainTrack()!!)
|
||||
syncChapterProgressWithTrack.await(mangaId, track, service.mangaService)
|
||||
null
|
||||
} catch (e: Throwable) {
|
||||
service to e
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.filterNotNull()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +1,35 @@
|
|||
package eu.kanade.domain.items.chapter.interactor
|
||||
package eu.kanade.domain.track.manga.interactor
|
||||
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.tachiyomi.data.track.MangaTrackService
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker
|
||||
import eu.kanade.tachiyomi.data.track.MangaTracker
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.items.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||
import tachiyomi.domain.items.chapter.model.Chapter
|
||||
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class SyncChaptersWithTrackServiceTwoWay(
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val insertTrack: InsertMangaTrack = Injekt.get(),
|
||||
class SyncChapterProgressWithTrack(
|
||||
private val updateChapter: UpdateChapter,
|
||||
private val insertTrack: InsertMangaTrack,
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
) {
|
||||
|
||||
suspend fun await(
|
||||
chapters: List<Chapter>,
|
||||
mangaId: Long,
|
||||
remoteTrack: MangaTrack,
|
||||
service: MangaTrackService,
|
||||
tracker: MangaTracker,
|
||||
) {
|
||||
val sortedChapters = chapters.sortedBy { it.chapterNumber }
|
||||
if (tracker !is EnhancedMangaTracker) {
|
||||
return
|
||||
}
|
||||
|
||||
val sortedChapters = getChaptersByMangaId.await(mangaId)
|
||||
.sortedBy { it.chapterNumber }
|
||||
.filter { it.isRecognizedNumber }
|
||||
|
||||
val chapterUpdates = sortedChapters
|
||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||
.map { it.copy(read = true).toChapterUpdate() }
|
||||
|
@ -32,7 +39,7 @@ class SyncChaptersWithTrackServiceTwoWay(
|
|||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||
|
||||
try {
|
||||
service.update(updatedTrack.toDbTrack())
|
||||
tracker.update(updatedTrack.toDbTrack())
|
||||
updateChapter.awaitAll(chapterUpdates)
|
||||
insertTrack.await(updatedTrack)
|
||||
} catch (e: Throwable) {
|
|
@ -0,0 +1,60 @@
|
|||
package eu.kanade.domain.track.manga.interactor
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.domain.track.manga.model.toDomainTrack
|
||||
import eu.kanade.domain.track.manga.service.DelayedMangaTrackingUpdateJob
|
||||
import eu.kanade.domain.track.manga.store.DelayedMangaTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||
|
||||
class TrackChapter(
|
||||
private val getTracks: GetMangaTracks,
|
||||
private val trackerManager: TrackerManager,
|
||||
private val insertTrack: InsertMangaTrack,
|
||||
private val delayedTrackingStore: DelayedMangaTrackingStore,
|
||||
) {
|
||||
|
||||
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) {
|
||||
withNonCancellableContext {
|
||||
val tracks = getTracks.await(mangaId)
|
||||
|
||||
if (tracks.isEmpty()) return@withNonCancellableContext
|
||||
|
||||
tracks.mapNotNull { track ->
|
||||
val service = trackerManager.get(track.syncId)
|
||||
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
}
|
||||
|
||||
async {
|
||||
runCatching {
|
||||
try {
|
||||
val updatedTrack = service.mangaService.refresh(track.toDbTrack())
|
||||
.toDomainTrack(idRequired = true)!!
|
||||
.copy(lastChapterRead = chapterNumber)
|
||||
service.mangaService.update(updatedTrack.toDbTrack(), true)
|
||||
insertTrack.await(updatedTrack)
|
||||
delayedTrackingStore.removeMangaItem(track.id)
|
||||
} catch (e: Exception) {
|
||||
delayedTrackingStore.addManga(track.id, chapterNumber)
|
||||
DelayedMangaTrackingUpdateJob.setupTask(context)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
.mapNotNull { it.exceptionOrNull() }
|
||||
.forEach { logcat(LogPriority.INFO, it) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,9 @@ fun MangaTrack.copyPersonalFrom(other: MangaTrack): MangaTrack {
|
|||
)
|
||||
}
|
||||
|
||||
fun MangaTrack.toDbTrack(): DbMangaTrack = eu.kanade.tachiyomi.data.database.models.manga.MangaTrack.create(syncId).also {
|
||||
fun MangaTrack.toDbTrack(): DbMangaTrack = eu.kanade.tachiyomi.data.database.models.manga.MangaTrack.create(
|
||||
syncId,
|
||||
).also {
|
||||
it.id = id
|
||||
it.manga_id = mangaId
|
||||
it.media_id = remoteId
|
||||
|
@ -22,7 +24,7 @@ fun MangaTrack.toDbTrack(): DbMangaTrack = eu.kanade.tachiyomi.data.database.mod
|
|||
it.last_chapter_read = lastChapterRead.toFloat()
|
||||
it.total_chapters = totalChapters.toInt()
|
||||
it.status = status.toInt()
|
||||
it.score = score
|
||||
it.score = score.toFloat()
|
||||
it.tracking_url = remoteUrl
|
||||
it.started_reading_date = startDate
|
||||
it.finished_reading_date = finishDate
|
||||
|
@ -40,7 +42,7 @@ fun DbMangaTrack.toDomainTrack(idRequired: Boolean = true): MangaTrack? {
|
|||
lastChapterRead = last_chapter_read.toDouble(),
|
||||
totalChapters = total_chapters.toLong(),
|
||||
status = status.toLong(),
|
||||
score = score,
|
||||
score = score.toDouble(),
|
||||
remoteUrl = tracking_url,
|
||||
startDate = started_reading_date,
|
||||
finishDate = finished_reading_date,
|
||||
|
|
|
@ -8,30 +8,32 @@ import androidx.work.ExistingWorkPolicy
|
|||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.domain.track.manga.model.toDbTrack
|
||||
import eu.kanade.domain.track.manga.interactor.TrackChapter
|
||||
import eu.kanade.domain.track.manga.store.DelayedMangaTrackingStore
|
||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.toJavaDuration
|
||||
|
||||
class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParameters) :
|
||||
class DelayedMangaTrackingUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
val getTracks = Injekt.get<GetMangaTracks>()
|
||||
val insertTrack = Injekt.get<InsertMangaTrack>()
|
||||
if (runAttemptCount > 3) {
|
||||
return Result.failure()
|
||||
}
|
||||
|
||||
val getTracks = Injekt.get<GetMangaTracks>()
|
||||
val trackChapter = Injekt.get<TrackChapter>()
|
||||
|
||||
val trackManager = Injekt.get<TrackManager>()
|
||||
val delayedTrackingStore = Injekt.get<DelayedMangaTrackingStore>()
|
||||
|
||||
val results = withIOContext {
|
||||
withIOContext {
|
||||
delayedTrackingStore.getMangaItems()
|
||||
.mapNotNull {
|
||||
val track = getTracks.awaitOne(it.trackId)
|
||||
|
@ -40,24 +42,15 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
|||
}
|
||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||
}
|
||||
.mapNotNull { track ->
|
||||
try {
|
||||
val service = trackManager.getService(track.syncId)
|
||||
if (service != null && service.isLogged) {
|
||||
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
|
||||
service.mangaService.update(track.toDbTrack(), true)
|
||||
insertTrack.await(track)
|
||||
}
|
||||
delayedTrackingStore.removeMangaItem(track.id)
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
false
|
||||
.forEach { track ->
|
||||
logcat(LogPriority.DEBUG) {
|
||||
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||
}
|
||||
trackChapter.await(context, track.mangaId, track.lastChapterRead)
|
||||
}
|
||||
}
|
||||
|
||||
return if (results.isNotEmpty()) Result.failure() else Result.success()
|
||||
return if (delayedTrackingStore.getMangaItems().isEmpty()) Result.success() else Result.retry()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -70,7 +63,7 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
|||
|
||||
val request = OneTimeWorkRequestBuilder<DelayedMangaTrackingUpdateJob>()
|
||||
.setConstraints(constraints)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||
.addTag(TAG)
|
||||
.build()
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||
import androidx.core.content.edit
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.system.logcat
|
||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||
|
||||
class DelayedMangaTrackingStore(context: Context) {
|
||||
|
||||
|
@ -13,13 +12,12 @@ class DelayedMangaTrackingStore(context: Context) {
|
|||
*/
|
||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||
|
||||
fun addMangaItem(track: MangaTrack) {
|
||||
val trackId = track.id.toString()
|
||||
val lastChapterRead = preferences.getFloat(trackId, 0f)
|
||||
if (track.lastChapterRead > lastChapterRead) {
|
||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: ${track.lastChapterRead}" }
|
||||
fun addManga(trackId: Long, lastChapterRead: Double) {
|
||||
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||
if (lastChapterRead > previousLastChapterRead) {
|
||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: $lastChapterRead" }
|
||||
preferences.edit {
|
||||
putFloat(trackId, track.lastChapterRead.toFloat())
|
||||
putFloat(trackId.toString(), lastChapterRead.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package eu.kanade.domain.track.service
|
||||
|
||||
import eu.kanade.tachiyomi.data.track.TrackService
|
||||
import eu.kanade.tachiyomi.data.track.Tracker
|
||||
import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import tachiyomi.core.preference.PreferenceStore
|
||||
|
||||
|
@ -8,16 +8,16 @@ class TrackPreferences(
|
|||
private val preferenceStore: PreferenceStore,
|
||||
) {
|
||||
|
||||
fun trackUsername(sync: TrackService) = preferenceStore.getString(trackUsername(sync.id), "")
|
||||
fun trackUsername(sync: Tracker) = preferenceStore.getString(trackUsername(sync.id), "")
|
||||
|
||||
fun trackPassword(sync: TrackService) = preferenceStore.getString(trackPassword(sync.id), "")
|
||||
fun trackPassword(sync: Tracker) = preferenceStore.getString(trackPassword(sync.id), "")
|
||||
|
||||
fun setTrackCredentials(sync: TrackService, username: String, password: String) {
|
||||
fun setCredentials(sync: Tracker, username: String, password: String) {
|
||||
trackUsername(sync).set(username)
|
||||
trackPassword(sync).set(password)
|
||||
}
|
||||
|
||||
fun trackToken(sync: TrackService) = preferenceStore.getString(trackToken(sync.id), "")
|
||||
fun trackToken(sync: Tracker) = preferenceStore.getString(trackToken(sync.id), "")
|
||||
|
||||
fun anilistScoreType() = preferenceStore.getString("anilist_score_type", Anilist.POINT_10)
|
||||
|
||||
|
@ -25,7 +25,10 @@ class TrackPreferences(
|
|||
|
||||
fun trackOnAddingToLibrary() = preferenceStore.getBoolean("track_on_adding_to_library", true)
|
||||
|
||||
fun showNextEpisodeAiringTime() = preferenceStore.getBoolean("show_next_episode_airing_time", true)
|
||||
fun showNextEpisodeAiringTime() = preferenceStore.getBoolean(
|
||||
"show_next_episode_airing_time",
|
||||
true,
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"
|
||||
|
|
|
@ -28,7 +28,7 @@ class UiPreferences(
|
|||
|
||||
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
|
||||
|
||||
fun relativeTime() = preferenceStore.getInt("relative_time", 7)
|
||||
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
|
||||
|
||||
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||
|
||||
|
|
|
@ -5,21 +5,21 @@ import eu.kanade.tachiyomi.R
|
|||
enum class AppTheme(val titleResId: Int?) {
|
||||
DEFAULT(R.string.label_default),
|
||||
MONET(R.string.theme_monet),
|
||||
CLOUDFLARE(R.string.theme_cloudflare),
|
||||
COTTONCANDY(R.string.theme_cottoncandy),
|
||||
DOOM(R.string.theme_doom),
|
||||
GREEN_APPLE(R.string.theme_greenapple),
|
||||
LAVENDER(R.string.theme_lavender),
|
||||
MATRIX(R.string.theme_matrix),
|
||||
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
||||
MOCHA(R.string.theme_mocha),
|
||||
SAPPHIRE(R.string.theme_sapphire),
|
||||
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
||||
TAKO(R.string.theme_tako),
|
||||
TEALTURQUOISE(R.string.theme_tealturquoise),
|
||||
TIDAL_WAVE(R.string.theme_tidalwave),
|
||||
YINYANG(R.string.theme_yinyang),
|
||||
YOTSUBA(R.string.theme_yotsuba),
|
||||
CLOUDFLARE(R.string.theme_cloudflare),
|
||||
SAPPHIRE(R.string.theme_sapphire),
|
||||
DOOM(R.string.theme_doom),
|
||||
MATRIX(R.string.theme_matrix),
|
||||
|
||||
// Deprecated
|
||||
DARK_BLUE(null),
|
||||
|
|
|
@ -25,7 +25,10 @@ fun BaseBrowseItem(
|
|||
onClick = onClickItem,
|
||||
onLongClick = onLongClickItem,
|
||||
)
|
||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
icon()
|
||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowForward
|
||||
import androidx.compose.material.icons.outlined.ArrowForward
|
||||
import androidx.compose.material.icons.outlined.Error
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
|
@ -54,25 +55,13 @@ fun GlobalSearchResultItem(
|
|||
Text(text = subtitle)
|
||||
}
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
||||
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchEmptyResultItem() {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results_found),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchLoadingResultItem() {
|
||||
Box(
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
package eu.kanade.presentation.browse
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
|
||||
@Composable
|
||||
fun GlobalSearchToolbar(
|
||||
searchQuery: String?,
|
||||
progress: Int,
|
||||
total: Int,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
Box {
|
||||
SearchToolbar(
|
||||
searchQuery = searchQuery,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
onClickCloseSearch = navigateUp,
|
||||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
if (progress in 1 until total) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress / total.toFloat(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,24 +10,25 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -52,11 +53,10 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.details.AnimeExtensionDetailsState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.details.AnimeExtensionDetailsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
@ -64,7 +64,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
@Composable
|
||||
fun AnimeExtensionDetailsScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: AnimeExtensionDetailsState,
|
||||
state: AnimeExtensionDetailsScreenModel.State,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickWhatsNew: () -> Unit,
|
||||
onClickReadme: () -> Unit,
|
||||
|
@ -81,7 +81,8 @@ fun AnimeExtensionDetailsScreen(
|
|||
navigateUp = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = buildList {
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
if (state.extension?.isUnofficial == false) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
|
@ -93,7 +94,7 @@ fun AnimeExtensionDetailsScreen(
|
|||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_faq_and_guides),
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onClickReadme,
|
||||
),
|
||||
)
|
||||
|
@ -114,7 +115,8 @@ fun AnimeExtensionDetailsScreen(
|
|||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
|
@ -175,7 +177,8 @@ private fun AnimeExtensionDetails(
|
|||
data = Uri.fromParts("package", extension.pkgName, null)
|
||||
context.startActivity(this)
|
||||
}
|
||||
},
|
||||
Unit
|
||||
}.takeIf { extension.isShared },
|
||||
onClickAgeRating = {
|
||||
showNsfwWarning = true
|
||||
},
|
||||
|
@ -187,7 +190,6 @@ private fun AnimeExtensionDetails(
|
|||
key = { it.source.id },
|
||||
) { source ->
|
||||
SourceSwitchPreference(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
source = source,
|
||||
onClickSourcePreferences = onClickSourcePreferences,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -208,7 +210,7 @@ private fun DetailsHeader(
|
|||
extension: AnimeExtension,
|
||||
onClickAgeRating: () -> Unit,
|
||||
onClickUninstall: () -> Unit,
|
||||
onClickAppInfo: () -> Unit,
|
||||
onClickAppInfo: (() -> Unit)?,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
@ -237,7 +239,9 @@ private fun DetailsHeader(
|
|||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.animeextension.")
|
||||
val strippedPkgName = extension.pkgName.substringAfter(
|
||||
"eu.kanade.tachiyomi.animeextension.",
|
||||
)
|
||||
|
||||
Text(
|
||||
text = strippedPkgName,
|
||||
|
@ -292,6 +296,7 @@ private fun DetailsHeader(
|
|||
top = MaterialTheme.padding.small,
|
||||
bottom = MaterialTheme.padding.medium,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
|
@ -300,8 +305,7 @@ private fun DetailsHeader(
|
|||
Text(stringResource(R.string.ext_uninstall))
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(16.dp))
|
||||
|
||||
if (onClickAppInfo != null) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = onClickAppInfo,
|
||||
|
@ -312,17 +316,18 @@ private fun DetailsHeader(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoText(
|
||||
modifier: Modifier,
|
||||
primaryText: String,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
secondaryText: String,
|
||||
modifier: Modifier = Modifier,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
@ -355,20 +360,17 @@ private fun InfoText(
|
|||
|
||||
@Composable
|
||||
private fun InfoDivider() {
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.height(20.dp)
|
||||
.width(1.dp),
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
||||
VerticalDivider(
|
||||
modifier = Modifier.height(20.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SourceSwitchPreference(
|
||||
modifier: Modifier = Modifier,
|
||||
source: AnimeExtensionSourceItem,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickSource: (sourceId: Long) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.anime
|
|||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
|
@ -53,12 +53,11 @@ private fun AnimeExtensionFilterContent(
|
|||
onClickLang: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(state.languages) { language ->
|
||||
SwitchPreferenceWidget(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||
checked = language in state.enabledLanguages,
|
||||
onCheckedChanged = { onClickLang(language) },
|
||||
|
|
|
@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
|
|||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionsState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
|
@ -56,7 +56,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||
|
||||
@Composable
|
||||
fun AnimeExtensionScreen(
|
||||
state: AnimeExtensionsState,
|
||||
state: AnimeExtensionsScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
searchQuery: String?,
|
||||
onLongClickItem: (AnimeExtension) -> Unit,
|
||||
|
@ -72,10 +72,10 @@ fun AnimeExtensionScreen(
|
|||
PullRefresh(
|
||||
refreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
enabled = !state.isLoading,
|
||||
enabled = { !state.isLoading },
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> {
|
||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||
R.string.no_results_found
|
||||
|
@ -107,7 +107,7 @@ fun AnimeExtensionScreen(
|
|||
|
||||
@Composable
|
||||
private fun AnimeExtensionContent(
|
||||
state: AnimeExtensionsState,
|
||||
state: AnimeExtensionsScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onLongClickItem: (AnimeExtension) -> Unit,
|
||||
onClickItemCancel: (AnimeExtension) -> Unit,
|
||||
|
@ -147,14 +147,13 @@ private fun AnimeExtensionContent(
|
|||
}
|
||||
ExtensionHeader(
|
||||
textRes = header.textRes,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
is AnimeExtensionUiModel.Header.Text -> {
|
||||
ExtensionHeader(
|
||||
text = header.text,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +165,7 @@ private fun AnimeExtensionContent(
|
|||
key = { "extension-${it.hashCode()}" },
|
||||
) { item ->
|
||||
AnimeExtensionItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
item = item,
|
||||
onClickItem = {
|
||||
when (it) {
|
||||
|
@ -216,12 +215,12 @@ private fun AnimeExtensionContent(
|
|||
|
||||
@Composable
|
||||
private fun AnimeExtensionItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: AnimeExtensionUiModel.Item,
|
||||
onClickItem: (AnimeExtension) -> Unit,
|
||||
onLongClickItem: (AnimeExtension) -> Unit,
|
||||
onClickItemCancel: (AnimeExtension) -> Unit,
|
||||
onClickItemAction: (AnimeExtension) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (extension, installStep) = item
|
||||
BaseBrowseItem(
|
||||
|
@ -246,7 +245,10 @@ private fun AnimeExtensionItem(
|
|||
)
|
||||
}
|
||||
|
||||
val padding by animateDpAsState(targetValue = if (idle) 0.dp else 8.dp)
|
||||
val padding by animateDpAsState(
|
||||
targetValue = if (idle) 0.dp else 8.dp,
|
||||
label = "iconPadding",
|
||||
)
|
||||
AnimeExtensionIcon(
|
||||
extension = extension,
|
||||
modifier = Modifier
|
||||
|
@ -295,7 +297,10 @@ private fun AnimeExtensionItemContent(
|
|||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
if (extension is AnimeExtension.Installed && extension.lang.isNotEmpty()) {
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||
text = LocaleHelper.getSourceDisplayName(
|
||||
extension.lang,
|
||||
LocalContext.current,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
|||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.AnimeSourcesFilterState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.AnimeSourcesFilterScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
|
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
@Composable
|
||||
fun AnimeSourcesFilterScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: AnimeSourcesFilterState.Success,
|
||||
state: AnimeSourcesFilterScreenModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (AnimeSource) -> Unit,
|
||||
) {
|
||||
|
@ -54,7 +54,7 @@ fun AnimeSourcesFilterScreen(
|
|||
@Composable
|
||||
private fun AnimeSourcesFilterContent(
|
||||
contentPadding: PaddingValues,
|
||||
state: AnimeSourcesFilterState.Success,
|
||||
state: AnimeSourcesFilterScreenModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (AnimeSource) -> Unit,
|
||||
) {
|
||||
|
@ -64,24 +64,24 @@ private fun AnimeSourcesFilterContent(
|
|||
state.items.forEach { (language, sources) ->
|
||||
val enabled = language in state.enabledLanguages
|
||||
item(
|
||||
key = language.hashCode(),
|
||||
key = language,
|
||||
contentType = "source-filter-header",
|
||||
) {
|
||||
AnimeSourcesFilterHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
language = language,
|
||||
enabled = enabled,
|
||||
onClickItem = onClickLanguage,
|
||||
)
|
||||
}
|
||||
if (!enabled) return@forEach
|
||||
if (enabled) {
|
||||
items(
|
||||
items = sources,
|
||||
key = { "source-filter-${it.key()}" },
|
||||
contentType = { "source-filter-item" },
|
||||
) { source ->
|
||||
AnimeSourcesFilterItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
source = source,
|
||||
isEnabled = "${source.id}" !in state.disabledSources,
|
||||
onClickItem = onClickSource,
|
||||
|
@ -89,14 +89,15 @@ private fun AnimeSourcesFilterContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimeSourcesFilterHeader(
|
||||
modifier: Modifier,
|
||||
language: String,
|
||||
enabled: Boolean,
|
||||
onClickItem: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SwitchPreferenceWidget(
|
||||
modifier = modifier,
|
||||
|
@ -108,10 +109,10 @@ fun AnimeSourcesFilterHeader(
|
|||
|
||||
@Composable
|
||||
private fun AnimeSourcesFilterItem(
|
||||
modifier: Modifier,
|
||||
source: AnimeSource,
|
||||
isEnabled: Boolean,
|
||||
onClickItem: (AnimeSource) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseAnimeSourceItem(
|
||||
modifier = modifier,
|
||||
|
|
|
@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.AnimeSourcesState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.AnimeSourcesScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.browse.BrowseAnimeSourceScreenModel.Listing
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
|
@ -40,14 +40,14 @@ import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
|||
|
||||
@Composable
|
||||
fun AnimeSourcesScreen(
|
||||
state: AnimeSourcesState,
|
||||
state: AnimeSourcesScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (AnimeSource, Listing) -> Unit,
|
||||
onClickPin: (AnimeSource) -> Unit,
|
||||
onLongClickItem: (AnimeSource) -> Unit,
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> EmptyScreen(
|
||||
textResource = R.string.source_empty_screen,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
|
@ -74,12 +74,12 @@ fun AnimeSourcesScreen(
|
|||
when (model) {
|
||||
is AnimeSourceUiModel.Header -> {
|
||||
AnimeSourceHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
language = model.language,
|
||||
)
|
||||
}
|
||||
is AnimeSourceUiModel.Item -> AnimeSourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
source = model.source,
|
||||
onClickItem = onClickItem,
|
||||
onLongClickItem = onLongClickItem,
|
||||
|
@ -94,25 +94,28 @@ fun AnimeSourcesScreen(
|
|||
|
||||
@Composable
|
||||
private fun AnimeSourceHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
language: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(language, context),
|
||||
modifier = modifier
|
||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
style = MaterialTheme.typography.header,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimeSourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: AnimeSource,
|
||||
onClickItem: (AnimeSource, Listing) -> Unit,
|
||||
onLongClickItem: (AnimeSource) -> Unit,
|
||||
onClickPin: (AnimeSource) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseAnimeSourceItem(
|
||||
modifier = modifier,
|
||||
|
@ -144,7 +147,13 @@ private fun AnimeSourcePinButton(
|
|||
onClick: () -> Unit,
|
||||
) {
|
||||
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
||||
val tint = if (isPinned) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onBackground.copy(alpha = SecondaryItemAlpha)
|
||||
val tint = if (isPinned) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onBackground.copy(
|
||||
alpha = SecondaryItemAlpha,
|
||||
)
|
||||
}
|
||||
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
||||
IconButton(onClick = onClick) {
|
||||
Icon(
|
||||
|
@ -192,7 +201,7 @@ fun AnimeSourceOptionsDialog(
|
|||
)
|
||||
}
|
||||
|
||||
sealed class AnimeSourceUiModel {
|
||||
data class Item(val source: AnimeSource) : AnimeSourceUiModel()
|
||||
data class Header(val language: String) : AnimeSourceUiModel()
|
||||
sealed interface AnimeSourceUiModel {
|
||||
data class Item(val source: AnimeSource) : AnimeSourceUiModel
|
||||
data class Header(val language: String) : AnimeSourceUiModel
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
|
@ -24,6 +25,7 @@ import eu.kanade.presentation.components.AppBar
|
|||
import eu.kanade.presentation.util.formattedMessage
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
@ -61,12 +63,12 @@ fun BrowseAnimeSourceContent(
|
|||
if (animeList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = getErrorMessage(errorState),
|
||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
||||
actionLabel = context.getString(R.string.action_retry),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||
SnackbarResult.ActionPerformed -> animeList.refresh()
|
||||
SnackbarResult.ActionPerformed -> animeList.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,15 +78,15 @@ fun BrowseAnimeSourceContent(
|
|||
modifier = Modifier.padding(contentPadding),
|
||||
message = getErrorMessage(errorState),
|
||||
actions = if (source is LocalAnimeSource) {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.local_source_help_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onLocalAnimeSourceHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
|
@ -97,7 +99,7 @@ fun BrowseAnimeSourceContent(
|
|||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.label_help,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
|
@ -145,7 +147,7 @@ fun BrowseAnimeSourceContent(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MissingSourceScreen(
|
||||
internal fun MissingSourceScreen(
|
||||
source: StubAnimeSource,
|
||||
navigateUp: () -> Unit,
|
||||
) {
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
package eu.kanade.presentation.browse.anime
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchToolbar
|
||||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchCardRow
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.GlobalAnimeSearchState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun GlobalAnimeSearchScreen(
|
||||
state: GlobalAnimeSearchState,
|
||||
state: AnimeSearchScreenModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
|
@ -36,19 +32,23 @@ fun GlobalAnimeSearchScreen(
|
|||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
GlobalSearchToolbar(
|
||||
GlobalAnimeSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
total = state.total,
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
GlobalAnimeSearchContent(
|
||||
items = state.items,
|
||||
GlobalSearchContent(
|
||||
items = state.filteredItems,
|
||||
contentPadding = paddingValues,
|
||||
getAnime = getAnime,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -59,13 +59,14 @@ fun GlobalAnimeSearchScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun GlobalAnimeSearchContent(
|
||||
internal fun GlobalSearchContent(
|
||||
items: Map<AnimeCatalogueSource, AnimeSearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onLongClickItem: (Anime) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
|
@ -73,7 +74,8 @@ private fun GlobalAnimeSearchContent(
|
|||
items.forEach { (source, result) ->
|
||||
item(key = source.id) {
|
||||
GlobalSearchResultItem(
|
||||
title = source.name,
|
||||
title = fromSourceId
|
||||
?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
) {
|
||||
|
@ -82,18 +84,6 @@ private fun GlobalAnimeSearchContent(
|
|||
GlobalSearchLoadingResultItem()
|
||||
}
|
||||
is AnimeSearchItemResult.Success -> {
|
||||
if (result.isEmpty) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results_found),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
)
|
||||
return@GlobalSearchResultItem
|
||||
}
|
||||
|
||||
GlobalAnimeSearchCardRow(
|
||||
titles = result.result,
|
||||
getAnime = getAnime,
|
||||
|
|
|
@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
|
|||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.entries.anime.components.BaseAnimeListItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.anime.MigrateAnimeState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.anime.MigrateAnimeScreenModel
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
@ -18,7 +18,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
fun MigrateAnimeScreen(
|
||||
navigateUp: () -> Unit,
|
||||
title: String?,
|
||||
state: MigrateAnimeState,
|
||||
state: MigrateAnimeScreenModel.State,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onClickCover: (Anime) -> Unit,
|
||||
) {
|
||||
|
@ -51,7 +51,7 @@ fun MigrateAnimeScreen(
|
|||
@Composable
|
||||
private fun MigrateAnimeContent(
|
||||
contentPadding: PaddingValues,
|
||||
state: MigrateAnimeState,
|
||||
state: MigrateAnimeScreenModel.State,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onClickCover: (Anime) -> Unit,
|
||||
) {
|
||||
|
@ -70,10 +70,10 @@ private fun MigrateAnimeContent(
|
|||
|
||||
@Composable
|
||||
private fun MigrateAnimeItem(
|
||||
modifier: Modifier = Modifier,
|
||||
anime: Anime,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onClickCover: (Anime) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseAnimeListItem(
|
||||
modifier = modifier,
|
||||
|
|
|
@ -1,49 +1,48 @@
|
|||
package eu.kanade.presentation.browse.anime
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import eu.kanade.presentation.browse.GlobalSearchEmptyResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchToolbar
|
||||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchCardRow
|
||||
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.search.MigrateAnimeSearchState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
|
||||
@Composable
|
||||
fun MigrateAnimeSearchScreen(
|
||||
state: AnimeSearchScreenModel.State,
|
||||
fromSourceId: Long?,
|
||||
navigateUp: () -> Unit,
|
||||
state: MigrateAnimeSearchState,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onLongClickItem: (Anime) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
GlobalSearchToolbar(
|
||||
GlobalAnimeSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
total = state.total,
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
MigrateAnimeSearchContent(
|
||||
sourceId = state.anime?.source ?: -1,
|
||||
items = state.items,
|
||||
GlobalSearchContent(
|
||||
fromSourceId = fromSourceId,
|
||||
items = state.filteredItems,
|
||||
contentPadding = paddingValues,
|
||||
getAnime = getAnime,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -52,50 +51,3 @@ fun MigrateAnimeSearchScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MigrateAnimeSearchContent(
|
||||
sourceId: Long,
|
||||
items: Map<AnimeCatalogueSource, AnimeSearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getAnime: @Composable (Anime) -> State<Anime>,
|
||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||
onClickItem: (Anime) -> Unit,
|
||||
onLongClickItem: (Anime) -> Unit,
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items.forEach { (source, result) ->
|
||||
item(key = source.id) {
|
||||
GlobalSearchResultItem(
|
||||
title = if (source.id == sourceId) "▶ ${source.name}" else source.name,
|
||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
) {
|
||||
when (result) {
|
||||
AnimeSearchItemResult.Loading -> {
|
||||
GlobalSearchLoadingResultItem()
|
||||
}
|
||||
is AnimeSearchItemResult.Success -> {
|
||||
if (result.isEmpty) {
|
||||
GlobalSearchEmptyResultItem()
|
||||
return@GlobalSearchResultItem
|
||||
}
|
||||
|
||||
GlobalAnimeSearchCardRow(
|
||||
titles = result.result,
|
||||
getAnime = getAnime,
|
||||
onClick = onClickItem,
|
||||
onLongClick = onLongClickItem,
|
||||
)
|
||||
}
|
||||
is AnimeSearchItemResult.Error -> {
|
||||
GlobalSearchErrorResultItem(message = result.throwable.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ import eu.kanade.domain.source.service.SetMigrateSorting
|
|||
import eu.kanade.presentation.browse.anime.components.AnimeSourceIcon
|
||||
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.sources.MigrateAnimeSourceState
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.migration.sources.MigrateAnimeSourceScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.presentation.core.components.Badge
|
||||
|
@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||
|
||||
@Composable
|
||||
fun MigrateAnimeSourceScreen(
|
||||
state: MigrateAnimeSourceState,
|
||||
state: MigrateAnimeSourceScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onClickItem: (AnimeSource) -> Unit,
|
||||
onToggleSortingDirection: () -> Unit,
|
||||
|
@ -51,7 +51,7 @@ fun MigrateAnimeSourceScreen(
|
|||
) {
|
||||
val context = LocalContext.current
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> EmptyScreen(
|
||||
textResource = R.string.information_empty_library,
|
||||
modifier = Modifier.padding(contentPadding),
|
||||
|
@ -102,14 +102,26 @@ private fun MigrateAnimeSourceList(
|
|||
|
||||
IconButton(onClick = onToggleSortingMode) {
|
||||
when (sortingMode) {
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||
Icons.Outlined.SortByAlpha,
|
||||
contentDescription = stringResource(R.string.action_sort_alpha),
|
||||
)
|
||||
SetMigrateSorting.Mode.TOTAL -> Icon(
|
||||
Icons.Outlined.Numbers,
|
||||
contentDescription = stringResource(R.string.action_sort_count),
|
||||
)
|
||||
}
|
||||
}
|
||||
IconButton(onClick = onToggleSortingDirection) {
|
||||
when (sortingDirection) {
|
||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
||||
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||
Icons.Outlined.ArrowUpward,
|
||||
contentDescription = stringResource(R.string.action_asc),
|
||||
)
|
||||
SetMigrateSorting.Direction.DESCENDING -> Icon(
|
||||
Icons.Outlined.ArrowDownward,
|
||||
contentDescription = stringResource(R.string.action_desc),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +132,7 @@ private fun MigrateAnimeSourceList(
|
|||
key = { (source, _) -> "migrate-${source.id}" },
|
||||
) { (source, count) ->
|
||||
MigrateAnimeSourceItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
source = source,
|
||||
count = count,
|
||||
onClickItem = { onClickItem(source) },
|
||||
|
@ -132,11 +144,11 @@ private fun MigrateAnimeSourceList(
|
|||
|
||||
@Composable
|
||||
private fun MigrateAnimeSourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: AnimeSource,
|
||||
count: Long,
|
||||
onClickItem: () -> Unit,
|
||||
onLongClickItem: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseAnimeSourceItem(
|
||||
modifier = modifier,
|
||||
|
|
|
@ -17,8 +17,8 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||
|
||||
@Composable
|
||||
fun BaseAnimeSourceItem(
|
||||
modifier: Modifier = Modifier,
|
||||
source: AnimeSource,
|
||||
modifier: Modifier = Modifier,
|
||||
showLanguageInContent: Boolean = true,
|
||||
onClickItem: () -> Unit = {},
|
||||
onLongClickItem: () -> Unit = {},
|
||||
|
@ -26,7 +26,9 @@ fun BaseAnimeSourceItem(
|
|||
action: @Composable RowScope.(AnimeSource) -> Unit = {},
|
||||
content: @Composable RowScope.(AnimeSource, String?) -> Unit = defaultContent,
|
||||
) {
|
||||
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf { showLanguageInContent }
|
||||
val sourceLangString = LocaleHelper.getSourceDisplayName(source.lang, LocalContext.current).takeIf {
|
||||
showLanguageInContent
|
||||
}
|
||||
BaseBrowseItem(
|
||||
modifier = modifier,
|
||||
onClickItem = onClickItem,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package eu.kanade.presentation.browse.anime.components
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.DisplayMetrics
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
@ -31,6 +30,7 @@ import eu.kanade.domain.source.anime.model.icon
|
|||
import eu.kanade.presentation.util.rememberResourceBitmapPainter
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||
|
@ -127,7 +127,10 @@ private fun AnimeExtension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT
|
|||
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||
withIOContext {
|
||||
value = try {
|
||||
val appInfo = context.packageManager.getApplicationInfo(pkgName, PackageManager.GET_META_DATA)
|
||||
val appInfo = AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(
|
||||
context,
|
||||
pkgName,
|
||||
)!!.applicationInfo
|
||||
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||
Result.Success(
|
||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||
|
@ -142,7 +145,7 @@ private fun AnimeExtension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT
|
|||
}
|
||||
|
||||
sealed class Result<out T> {
|
||||
object Loading : Result<Nothing>()
|
||||
object Error : Result<Nothing>()
|
||||
data object Loading : Result<Nothing>()
|
||||
data object Error : Result<Nothing>()
|
||||
data class Success<out T>(val value: T) : Result<T>()
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ fun BrowseAnimeSourceComfortableGrid(
|
|||
}
|
||||
}
|
||||
|
||||
items(animeList.itemCount) { index ->
|
||||
items(count = animeList.itemCount) { index ->
|
||||
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||
BrowseAnimeSourceComfortableGridItem(
|
||||
anime = anime,
|
||||
|
|
|
@ -40,7 +40,7 @@ fun BrowseAnimeSourceCompactGrid(
|
|||
}
|
||||
}
|
||||
|
||||
items(animeList.itemCount) { index ->
|
||||
items(count = animeList.itemCount) { index ->
|
||||
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||
BrowseAnimeSourceCompactGridItem(
|
||||
anime = anime,
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
package eu.kanade.presentation.browse.anime.components
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.items
|
||||
import eu.kanade.presentation.browse.InLibraryBadge
|
||||
import eu.kanade.presentation.browse.manga.components.BrowseSourceLoadingItem
|
||||
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
||||
|
@ -15,7 +15,6 @@ import eu.kanade.presentation.library.EntryListItem
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
import tachiyomi.presentation.core.util.plus
|
||||
|
||||
@Composable
|
||||
|
@ -34,9 +33,8 @@ fun BrowseAnimeSourceList(
|
|||
}
|
||||
}
|
||||
|
||||
items(animeList) { animeflow ->
|
||||
animeflow ?: return@items
|
||||
val anime by animeflow.collectAsState()
|
||||
items(count = animeList.itemCount) { index ->
|
||||
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||
BrowseAnimeSourceListItem(
|
||||
anime = anime,
|
||||
onClick = { onAnimeClick(anime) },
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.presentation.browse.anime.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||
import androidx.compose.material.icons.filled.ViewList
|
||||
import androidx.compose.material.icons.filled.ViewModule
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -20,6 +21,7 @@ import eu.kanade.presentation.components.SearchToolbar
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||
|
||||
|
@ -53,29 +55,45 @@ fun BrowseAnimeSourceToolbar(
|
|||
onClickCloseSearch = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = listOfNotNull(
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_display_mode),
|
||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
||||
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||
Icons.AutoMirrored.Filled.ViewList
|
||||
} else {
|
||||
Icons.Filled.ViewModule
|
||||
},
|
||||
onClick = { selectingDisplayMode = true },
|
||||
),
|
||||
)
|
||||
if (isLocalSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.label_help),
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_open_in_web_view),
|
||||
onClick = onWebViewClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
if (isConfigurableSource) {
|
||||
add(
|
||||
AppBar.OverflowAction(
|
||||
title = stringResource(R.string.action_settings),
|
||||
onClick = onSettingsClick,
|
||||
).takeIf { isConfigurableSource },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
|
||||
DropdownMenu(
|
||||
expanded = selectingDisplayMode,
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
package eu.kanade.presentation.browse.anime.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.getValue
|
||||
import eu.kanade.presentation.browse.GlobalSearchCard
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import eu.kanade.presentation.browse.InLibraryBadge
|
||||
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
||||
import eu.kanade.presentation.library.EntryComfortableGridItem
|
||||
import eu.kanade.tachiyomi.R
|
||||
import tachiyomi.domain.entries.anime.model.Anime
|
||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||
import tachiyomi.domain.entries.anime.model.asAnimeCover
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
|
@ -20,13 +31,18 @@ fun GlobalAnimeSearchCardRow(
|
|||
onClick: (Anime) -> Unit,
|
||||
onLongClick: (Anime) -> Unit,
|
||||
) {
|
||||
if (titles.isEmpty()) {
|
||||
EmptyResultItem()
|
||||
return
|
||||
}
|
||||
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
||||
) {
|
||||
items(titles) {
|
||||
val title by getAnime(it)
|
||||
GlobalSearchCard(
|
||||
AnimeItem(
|
||||
title = title.title,
|
||||
cover = title.asAnimeCover(),
|
||||
isFavorite = title.favorite,
|
||||
|
@ -36,3 +52,38 @@ fun GlobalAnimeSearchCardRow(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimeItem(
|
||||
title: String,
|
||||
cover: AnimeCover,
|
||||
isFavorite: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
) {
|
||||
Box(modifier = Modifier.width(96.dp)) {
|
||||
EntryComfortableGridItem(
|
||||
title = title,
|
||||
titleMaxLines = 3,
|
||||
coverData = cover,
|
||||
coverBadgeStart = {
|
||||
InLibraryBadge(enabled = isFavorite)
|
||||
},
|
||||
coverAlpha = if (isFavorite) CommonEntryItemDefaults.BrowseFavoriteCoverAlpha else 1f,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyResultItem() {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results_found),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package eu.kanade.presentation.browse.anime.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.DoneAll
|
||||
import androidx.compose.material.icons.outlined.FilterList
|
||||
import androidx.compose.material.icons.outlined.PushPin
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.FilterChipDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.components.SearchToolbar
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSourceFilter
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun GlobalAnimeSearchToolbar(
|
||||
searchQuery: String?,
|
||||
progress: Int,
|
||||
total: Int,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
sourceFilter: AnimeSourceFilter,
|
||||
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||
onlyShowHasResults: Boolean,
|
||||
onToggleResults: () -> Unit,
|
||||
scrollBehavior: TopAppBarScrollBehavior,
|
||||
) {
|
||||
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
|
||||
Box {
|
||||
SearchToolbar(
|
||||
searchQuery = searchQuery,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
onClickCloseSearch = navigateUp,
|
||||
navigateUp = navigateUp,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
if (progress in 1..<total) {
|
||||
LinearProgressIndicator(
|
||||
progress = { progress / total.toFloat() },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.padding(horizontal = MaterialTheme.padding.small),
|
||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small),
|
||||
) {
|
||||
// TODO: make this UX better; it only applies when triggering a new search
|
||||
FilterChip(
|
||||
selected = sourceFilter == AnimeSourceFilter.PinnedOnly,
|
||||
onClick = { onChangeSearchFilter(AnimeSourceFilter.PinnedOnly) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.PushPin,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.pinned_sources))
|
||||
},
|
||||
)
|
||||
FilterChip(
|
||||
selected = sourceFilter == AnimeSourceFilter.All,
|
||||
onClick = { onChangeSearchFilter(AnimeSourceFilter.All) },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.DoneAll,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.all))
|
||||
},
|
||||
)
|
||||
|
||||
VerticalDivider()
|
||||
|
||||
FilterChip(
|
||||
selected = onlyShowHasResults,
|
||||
onClick = { onToggleResults() },
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FilterList,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(FilterChipDefaults.IconSize),
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(text = stringResource(id = R.string.has_results))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Public
|
||||
import androidx.compose.material.icons.outlined.Refresh
|
||||
|
@ -24,6 +25,7 @@ import eu.kanade.presentation.components.AppBar
|
|||
import eu.kanade.presentation.util.formattedMessage
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.source.MangaSource
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||
|
@ -61,12 +63,12 @@ fun BrowseSourceContent(
|
|||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||
val result = snackbarHostState.showSnackbar(
|
||||
message = getErrorMessage(errorState),
|
||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
||||
actionLabel = context.getString(R.string.action_retry),
|
||||
duration = SnackbarDuration.Indefinite,
|
||||
)
|
||||
when (result) {
|
||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||
SnackbarResult.ActionPerformed -> mangaList.refresh()
|
||||
SnackbarResult.ActionPerformed -> mangaList.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -76,15 +78,15 @@ fun BrowseSourceContent(
|
|||
modifier = Modifier.padding(contentPadding),
|
||||
message = getErrorMessage(errorState),
|
||||
actions = if (source is LocalMangaSource) {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.local_source_help_guide,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onLocalSourceHelpClick,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
persistentListOf(
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.action_retry,
|
||||
icon = Icons.Outlined.Refresh,
|
||||
|
@ -97,7 +99,7 @@ fun BrowseSourceContent(
|
|||
),
|
||||
EmptyScreenAction(
|
||||
stringResId = R.string.label_help,
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onHelpClick,
|
||||
),
|
||||
)
|
||||
|
@ -145,7 +147,7 @@ fun BrowseSourceContent(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun MissingSourceScreen(
|
||||
internal fun MissingSourceScreen(
|
||||
source: StubMangaSource,
|
||||
navigateUp: () -> Unit,
|
||||
) {
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
package eu.kanade.presentation.browse.manga
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.browse.GlobalSearchToolbar
|
||||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchCardRow
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.GlobalMangaSearchState
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.globalsearch.MangaSourceFilter
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.entries.manga.model.Manga
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
|
||||
@Composable
|
||||
fun GlobalMangaSearchScreen(
|
||||
state: GlobalMangaSearchState,
|
||||
state: MangaSearchScreenModel.State,
|
||||
navigateUp: () -> Unit,
|
||||
onChangeSearchQuery: (String?) -> Unit,
|
||||
onSearch: (String) -> Unit,
|
||||
onChangeSearchFilter: (MangaSourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
|
@ -36,19 +32,23 @@ fun GlobalMangaSearchScreen(
|
|||
) {
|
||||
Scaffold(
|
||||
topBar = { scrollBehavior ->
|
||||
GlobalSearchToolbar(
|
||||
GlobalMangaSearchToolbar(
|
||||
searchQuery = state.searchQuery,
|
||||
progress = state.progress,
|
||||
total = state.total,
|
||||
navigateUp = navigateUp,
|
||||
onChangeSearchQuery = onChangeSearchQuery,
|
||||
onSearch = onSearch,
|
||||
sourceFilter = state.sourceFilter,
|
||||
onChangeSearchFilter = onChangeSearchFilter,
|
||||
onlyShowHasResults = state.onlyShowHasResults,
|
||||
onToggleResults = onToggleResults,
|
||||
scrollBehavior = scrollBehavior,
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
GlobalSearchContent(
|
||||
items = state.items,
|
||||
items = state.filteredItems,
|
||||
contentPadding = paddingValues,
|
||||
getManga = getManga,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -59,13 +59,14 @@ fun GlobalMangaSearchScreen(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun GlobalSearchContent(
|
||||
internal fun GlobalSearchContent(
|
||||
items: Map<CatalogueSource, MangaSearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
) {
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
|
@ -73,7 +74,8 @@ private fun GlobalSearchContent(
|
|||
items.forEach { (source, result) ->
|
||||
item(key = source.id) {
|
||||
GlobalSearchResultItem(
|
||||
title = source.name,
|
||||
title = fromSourceId
|
||||
?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||
onClick = { onClickSource(source) },
|
||||
) {
|
||||
|
@ -82,18 +84,6 @@ private fun GlobalSearchContent(
|
|||
GlobalSearchLoadingResultItem()
|
||||
}
|
||||
is MangaSearchItemResult.Success -> {
|
||||
if (result.isEmpty) {
|
||||
Text(
|
||||
text = stringResource(R.string.no_results_found),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
horizontal = MaterialTheme.padding.medium,
|
||||
vertical = MaterialTheme.padding.small,
|
||||
),
|
||||
)
|
||||
return@GlobalSearchResultItem
|
||||
}
|
||||
|
||||
GlobalMangaSearchCardRow(
|
||||
titles = result.result,
|
||||
getManga = getManga,
|
||||
|
|
|
@ -10,19 +10,19 @@ import androidx.compose.foundation.layout.Arrangement
|
|||
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.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.History
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -30,6 +30,7 @@ import androidx.compose.material3.OutlinedButton
|
|||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.VerticalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -53,11 +54,10 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.details.MangaExtensionDetailsState
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.details.MangaExtensionDetailsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.DIVIDER_ALPHA
|
||||
import tachiyomi.presentation.core.components.material.Divider
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.components.material.padding
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
@ -65,7 +65,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
@Composable
|
||||
fun ExtensionDetailsScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: MangaExtensionDetailsState,
|
||||
state: MangaExtensionDetailsScreenModel.State,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickWhatsNew: () -> Unit,
|
||||
onClickReadme: () -> Unit,
|
||||
|
@ -82,7 +82,8 @@ fun ExtensionDetailsScreen(
|
|||
navigateUp = navigateUp,
|
||||
actions = {
|
||||
AppBarActions(
|
||||
actions = buildList {
|
||||
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||
.apply {
|
||||
if (state.extension?.isUnofficial == false) {
|
||||
add(
|
||||
AppBar.Action(
|
||||
|
@ -94,7 +95,7 @@ fun ExtensionDetailsScreen(
|
|||
add(
|
||||
AppBar.Action(
|
||||
title = stringResource(R.string.action_faq_and_guides),
|
||||
icon = Icons.Outlined.HelpOutline,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = onClickReadme,
|
||||
),
|
||||
)
|
||||
|
@ -115,7 +116,8 @@ fun ExtensionDetailsScreen(
|
|||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
|
@ -176,7 +178,8 @@ private fun ExtensionDetails(
|
|||
data = Uri.fromParts("package", extension.pkgName, null)
|
||||
context.startActivity(this)
|
||||
}
|
||||
},
|
||||
Unit
|
||||
}.takeIf { extension.isShared },
|
||||
onClickAgeRating = {
|
||||
showNsfwWarning = true
|
||||
},
|
||||
|
@ -188,7 +191,7 @@ private fun ExtensionDetails(
|
|||
key = { it.source.id },
|
||||
) { source ->
|
||||
SourceSwitchPreference(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
source = source,
|
||||
onClickSourcePreferences = onClickSourcePreferences,
|
||||
onClickSource = onClickSource,
|
||||
|
@ -209,7 +212,7 @@ private fun DetailsHeader(
|
|||
extension: MangaExtension,
|
||||
onClickAgeRating: () -> Unit,
|
||||
onClickUninstall: () -> Unit,
|
||||
onClickAppInfo: () -> Unit,
|
||||
onClickAppInfo: (() -> Unit)?,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
@ -293,6 +296,7 @@ private fun DetailsHeader(
|
|||
top = MaterialTheme.padding.small,
|
||||
bottom = MaterialTheme.padding.medium,
|
||||
),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
OutlinedButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
|
@ -301,8 +305,7 @@ private fun DetailsHeader(
|
|||
Text(stringResource(R.string.ext_uninstall))
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(16.dp))
|
||||
|
||||
if (onClickAppInfo != null) {
|
||||
Button(
|
||||
modifier = Modifier.weight(1f),
|
||||
onClick = onClickAppInfo,
|
||||
|
@ -313,17 +316,18 @@ private fun DetailsHeader(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InfoText(
|
||||
modifier: Modifier,
|
||||
primaryText: String,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
secondaryText: String,
|
||||
modifier: Modifier = Modifier,
|
||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||
onClick: (() -> Unit)? = null,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
@ -356,20 +360,17 @@ private fun InfoText(
|
|||
|
||||
@Composable
|
||||
private fun InfoDivider() {
|
||||
Divider(
|
||||
modifier = Modifier
|
||||
.height(20.dp)
|
||||
.width(1.dp),
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
||||
VerticalDivider(
|
||||
modifier = Modifier.height(20.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SourceSwitchPreference(
|
||||
modifier: Modifier = Modifier,
|
||||
source: MangaExtensionSourceItem,
|
||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||
onClickSource: (sourceId: Long) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
|
@ -415,7 +416,7 @@ fun NsfwWarningDialog(
|
|||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onClickConfirm) {
|
||||
Text(text = stringResource(android.R.string.ok))
|
||||
Text(text = stringResource(R.string.action_ok))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onClickConfirm,
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.manga
|
|||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -12,7 +13,6 @@ import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
|||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionFilterState
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.Scaffold
|
||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||
|
||||
|
@ -53,12 +53,12 @@ private fun ExtensionFilterContent(
|
|||
onClickLang: (String) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
FastScrollLazyColumn(
|
||||
LazyColumn(
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(state.languages) { language ->
|
||||
SwitchPreferenceWidget(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||
checked = language in state.enabledLanguages,
|
||||
onCheckedChanged = { onClickLang(language) },
|
||||
|
|
|
@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
|
|||
import eu.kanade.tachiyomi.extension.InstallStep
|
||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionUiModel
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionsState
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionsScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||
|
@ -57,7 +57,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
|||
|
||||
@Composable
|
||||
fun MangaExtensionScreen(
|
||||
state: MangaExtensionsState,
|
||||
state: MangaExtensionsScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
searchQuery: String?,
|
||||
onLongClickItem: (MangaExtension) -> Unit,
|
||||
|
@ -73,10 +73,10 @@ fun MangaExtensionScreen(
|
|||
PullRefresh(
|
||||
refreshing = state.isRefreshing,
|
||||
onRefresh = onRefresh,
|
||||
enabled = !state.isLoading,
|
||||
enabled = { !state.isLoading },
|
||||
) {
|
||||
when {
|
||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
||||
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||
state.isEmpty -> {
|
||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||
R.string.no_results_found
|
||||
|
@ -108,7 +108,7 @@ fun MangaExtensionScreen(
|
|||
|
||||
@Composable
|
||||
private fun ExtensionContent(
|
||||
state: MangaExtensionsState,
|
||||
state: MangaExtensionsScreenModel.State,
|
||||
contentPadding: PaddingValues,
|
||||
onLongClickItem: (MangaExtension) -> Unit,
|
||||
onClickItemCancel: (MangaExtension) -> Unit,
|
||||
|
@ -148,14 +148,14 @@ private fun ExtensionContent(
|
|||
}
|
||||
ExtensionHeader(
|
||||
textRes = header.textRes,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
action = action,
|
||||
)
|
||||
}
|
||||
is MangaExtensionUiModel.Header.Text -> {
|
||||
ExtensionHeader(
|
||||
text = header.text,
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ private fun ExtensionContent(
|
|||
key = { "extension-${it.hashCode()}" },
|
||||
) { item ->
|
||||
ExtensionItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
item = item,
|
||||
onClickItem = {
|
||||
when (it) {
|
||||
|
@ -217,12 +217,12 @@ private fun ExtensionContent(
|
|||
|
||||
@Composable
|
||||
private fun ExtensionItem(
|
||||
modifier: Modifier = Modifier,
|
||||
item: MangaExtensionUiModel.Item,
|
||||
onClickItem: (MangaExtension) -> Unit,
|
||||
onLongClickItem: (MangaExtension) -> Unit,
|
||||
onClickItemCancel: (MangaExtension) -> Unit,
|
||||
onClickItemAction: (MangaExtension) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (extension, installStep) = item
|
||||
BaseBrowseItem(
|
||||
|
@ -247,7 +247,10 @@ private fun ExtensionItem(
|
|||
)
|
||||
}
|
||||
|
||||
val padding by animateDpAsState(targetValue = if (idle) 0.dp else 8.dp)
|
||||
val padding by animateDpAsState(
|
||||
targetValue = if (idle) 0.dp else 8.dp,
|
||||
label = "iconPadding",
|
||||
)
|
||||
MangaExtensionIcon(
|
||||
extension = extension,
|
||||
modifier = Modifier
|
||||
|
@ -296,7 +299,10 @@ private fun ExtensionItemContent(
|
|||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||
if (extension is MangaExtension.Installed && extension.lang.isNotEmpty()) {
|
||||
Text(
|
||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
||||
text = LocaleHelper.getSourceDisplayName(
|
||||
extension.lang,
|
||||
LocalContext.current,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import eu.kanade.presentation.browse.manga.components.BaseMangaSourceItem
|
|||
import eu.kanade.presentation.components.AppBar
|
||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.MangaSourcesFilterState
|
||||
import eu.kanade.tachiyomi.ui.browse.manga.source.MangaSourcesFilterScreenModel
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import tachiyomi.domain.source.manga.model.Source
|
||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||
|
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
|||
@Composable
|
||||
fun MangaSourcesFilterScreen(
|
||||
navigateUp: () -> Unit,
|
||||
state: MangaSourcesFilterState.Success,
|
||||
state: MangaSourcesFilterScreenModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
) {
|
||||
|
@ -54,7 +54,7 @@ fun MangaSourcesFilterScreen(
|
|||
@Composable
|
||||
private fun SourcesFilterContent(
|
||||
contentPadding: PaddingValues,
|
||||
state: MangaSourcesFilterState.Success,
|
||||
state: MangaSourcesFilterScreenModel.State.Success,
|
||||
onClickLanguage: (String) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
) {
|
||||
|
@ -64,24 +64,24 @@ private fun SourcesFilterContent(
|
|||
state.items.forEach { (language, sources) ->
|
||||
val enabled = language in state.enabledLanguages
|
||||
item(
|
||||
key = language.hashCode(),
|
||||
key = language,
|
||||
contentType = "source-filter-header",
|
||||
) {
|
||||
SourcesFilterHeader(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
language = language,
|
||||
enabled = enabled,
|
||||
onClickItem = onClickLanguage,
|
||||
)
|
||||
}
|
||||
if (!enabled) return@forEach
|
||||
if (enabled) {
|
||||
items(
|
||||
items = sources,
|
||||
key = { "source-filter-${it.key()}" },
|
||||
contentType = { "source-filter-item" },
|
||||
) { source ->
|
||||
SourcesFilterItem(
|
||||
modifier = Modifier.animateItemPlacement(),
|
||||
|
||||
source = source,
|
||||
enabled = "${source.id}" !in state.disabledSources,
|
||||
onClickItem = onClickSource,
|
||||
|
@ -89,14 +89,16 @@ private fun SourcesFilterContent(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SourcesFilterHeader(
|
||||
modifier: Modifier,
|
||||
language: String,
|
||||
enabled: Boolean,
|
||||
onClickItem: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
|
||||
) {
|
||||
SwitchPreferenceWidget(
|
||||
modifier = modifier,
|
||||
|
@ -108,10 +110,10 @@ private fun SourcesFilterHeader(
|
|||
|
||||
@Composable
|
||||
private fun SourcesFilterItem(
|
||||
modifier: Modifier,
|
||||
source: Source,
|
||||
enabled: Boolean,
|
||||
onClickItem: (Source) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BaseMangaSourceItem(
|
||||
modifier = modifier,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue