mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-23 21:27:40 +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}]
|
[*.{kt,kts}]
|
||||||
indent_size=4
|
max_line_length = 120
|
||||||
insert_final_newline=true
|
indent_size = 4
|
||||||
ij_kotlin_allow_trailing_comma=true
|
insert_final_newline = true
|
||||||
ij_kotlin_allow_trailing_comma_on_call_site=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 = 2147483647
|
||||||
ij_kotlin_name_count_to_use_star_import_for_members = 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:
|
- I have updated:
|
||||||
- To the latest version of the app (stable is v0.12.3.10)
|
- To the latest version of the app (stable is v0.12.3.10)
|
||||||
- All extensions
|
- 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
|
- 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 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
|
- 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
|
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
|
about: Issues and requests for extensions and sources should be opened in the aniyomi-extensions repository instead
|
||||||
- name: 📦 Aniyomi extensions
|
- name: 📦 Aniyomi extensions
|
||||||
url: https://aniyomi.org/extensions
|
url: https://aniyomi.org/extensions/
|
||||||
about: Anime extensions and sources
|
about: Anime extensions and sources
|
||||||
- name: 🧑💻 Aniyomi help discord
|
- name: 🧑💻 Aniyomi help discord
|
||||||
url: https://discord.gg/F32UjdJZrR
|
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
|
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).
|
- 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
|
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
|
required: true
|
||||||
- label: I have updated the app to version **[0.12.3.10](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
|
- label: I have updated the app to version **[0.12.3.10](https://github.com/aniyomiorg/aniyomi/releases/latest)**.
|
||||||
required: true
|
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:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
@ -37,4 +37,4 @@ jobs:
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
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:
|
steps:
|
||||||
- name: Clone repo
|
- name: Clone repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Validate Gradle Wrapper
|
- name: Validate Gradle Wrapper
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
||||||
- name: Build app and run unit tests
|
- name: Build app and run unit tests
|
||||||
uses: gradle/gradle-command-action@v2
|
uses: gradle/gradle-command-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: lintKotlin assembleStandardRelease testStandardReleaseUnitTest
|
arguments: ktlintCheck assembleStandardRelease testReleaseUnitTest
|
||||||
|
|
||||||
# Sign APK and create release for tags
|
# Sign APK and create release for tags
|
||||||
|
|
||||||
|
@ -120,3 +120,14 @@ jobs:
|
||||||
prerelease: false
|
prerelease: false
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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",
|
"type": "body",
|
||||||
"regex": ".*\\* (Aniyomi version|Android version|Device): \\?.*",
|
"regex": ".*\\* (Aniyomi version|Android version|Device): \\?.*",
|
||||||
"message": "Requested information in the template was not filled out."
|
"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
|
auto-close-ignore-label: do-not-autoclose
|
||||||
|
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
|
@ -12,7 +12,7 @@ jobs:
|
||||||
lock:
|
lock:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: dessant/lock-threads@v4
|
- uses: dessant/lock-threads@v5
|
||||||
with:
|
with:
|
||||||
github-token: ${{ github.token }}
|
github-token: ${{ github.token }}
|
||||||
issue-inactive-days: '2'
|
issue-inactive-days: '2'
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,7 +3,8 @@
|
||||||
/acra.properties
|
/acra.properties
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/*
|
||||||
|
!.idea/icon.png
|
||||||
*iml
|
*iml
|
||||||
*.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)
|
- [Android Studio](https://developer.android.com/studio)
|
||||||
- Emulator or phone with developer options enabled to test changes.
|
- Emulator or phone with developer options enabled to test changes.
|
||||||
|
|
||||||
|
## Linting
|
||||||
|
|
||||||
|
To auto-fix some linting errors, run the `ktlintFormat` Gradle task.
|
||||||
|
|
||||||
## Getting help
|
## Getting help
|
||||||
|
|
||||||
- Join [the Discord server](https://discord.gg/F32UjdJZrR) for online help and to ask questions while developing.
|
- Join [the Discord server](https://discord.gg/F32UjdJZrR) for online help and to ask questions while developing.
|
||||||
|
|
||||||
# Translations
|
# 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
|
# Forks
|
||||||
|
|
|
@ -31,7 +31,7 @@ Please make sure to read the full guidelines. Your issue may be closed without w
|
||||||
|
|
||||||
<details><summary>Issues</summary>
|
<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)
|
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>
|
</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)
|
* Include version (More → About → Version)
|
||||||
* If not latest, try updating, it may have already been solved
|
* 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 steps to reproduce (if not obvious from description)
|
||||||
* Include screenshot (if needed)
|
* Include screenshot (if needed)
|
||||||
* If it could be device-dependent, try reproducing on another device (if possible)
|
* If it could be device-dependent, try reproducing on another device (if possible)
|
||||||
|
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
|
@ -1,4 +1,3 @@
|
||||||
/build
|
/build
|
||||||
*iml
|
*iml
|
||||||
*.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.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import org.jmailen.gradle.kotlinter.tasks.LintTask
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.util.Properties
|
import java.util.Properties
|
||||||
|
|
||||||
|
@ -21,8 +20,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "xyz.jmir.tachiyomi.mi"
|
applicationId = "xyz.jmir.tachiyomi.mi"
|
||||||
|
|
||||||
versionCode = 106
|
versionCode = 110
|
||||||
versionName = "0.14.6"
|
versionName = "0.14.7"
|
||||||
|
|
||||||
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
buildConfigField("String", "COMMIT_COUNT", "\"${getCommitCount()}\"")
|
||||||
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
buildConfigField("String", "COMMIT_SHA", "\"${getGitSha()}\"")
|
||||||
|
@ -73,11 +72,11 @@ android {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
buildConfigField("boolean", "PREVIEW", "true")
|
buildConfigField("boolean", "PREVIEW", "true")
|
||||||
|
|
||||||
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
|
matchingFallbacks.add("release")
|
||||||
val debugType = getByName("debug")
|
val debugType = getByName("debug")
|
||||||
signingConfig = debugType.signingConfig
|
|
||||||
versionNameSuffix = debugType.versionNameSuffix
|
versionNameSuffix = debugType.versionNameSuffix
|
||||||
applicationIdSuffix = debugType.applicationIdSuffix
|
applicationIdSuffix = debugType.applicationIdSuffix
|
||||||
matchingFallbacks.add("release")
|
|
||||||
}
|
}
|
||||||
create("benchmark") {
|
create("benchmark") {
|
||||||
initWith(getByName("release"))
|
initWith(getByName("release"))
|
||||||
|
@ -85,6 +84,7 @@ android {
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
matchingFallbacks.add("release")
|
matchingFallbacks.add("release")
|
||||||
isDebuggable = false
|
isDebuggable = false
|
||||||
|
isProfileable = true
|
||||||
versionNameSuffix = "-benchmark"
|
versionNameSuffix = "-benchmark"
|
||||||
applicationIdSuffix = ".benchmark"
|
applicationIdSuffix = ".benchmark"
|
||||||
}
|
}
|
||||||
|
@ -110,15 +110,17 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
packaging {
|
packaging {
|
||||||
resources.excludes.addAll(listOf(
|
resources.excludes.addAll(
|
||||||
"META-INF/DEPENDENCIES",
|
listOf(
|
||||||
"LICENSE.txt",
|
"META-INF/DEPENDENCIES",
|
||||||
"META-INF/LICENSE",
|
"LICENSE.txt",
|
||||||
"META-INF/LICENSE.txt",
|
"META-INF/LICENSE",
|
||||||
"META-INF/README.md",
|
"META-INF/LICENSE.txt",
|
||||||
"META-INF/NOTICE",
|
"META-INF/README.md",
|
||||||
"META-INF/*.kotlin_module",
|
"META-INF/NOTICE",
|
||||||
))
|
"META-INF/*.kotlin_module",
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
dependenciesInfo {
|
dependenciesInfo {
|
||||||
|
@ -165,12 +167,13 @@ dependencies {
|
||||||
implementation(compose.material.icons)
|
implementation(compose.material.icons)
|
||||||
implementation(compose.animation)
|
implementation(compose.animation)
|
||||||
implementation(compose.animation.graphics)
|
implementation(compose.animation.graphics)
|
||||||
implementation(compose.ui.tooling)
|
debugImplementation(compose.ui.tooling)
|
||||||
|
implementation(compose.ui.tooling.preview)
|
||||||
implementation(compose.ui.util)
|
implementation(compose.ui.util)
|
||||||
implementation(compose.accompanist.webview)
|
implementation(compose.accompanist.webview)
|
||||||
implementation(compose.accompanist.permissions)
|
implementation(compose.accompanist.permissions)
|
||||||
implementation(compose.accompanist.themeadapter)
|
|
||||||
implementation(compose.accompanist.systemuicontroller)
|
implementation(compose.accompanist.systemuicontroller)
|
||||||
|
lintChecks(compose.lintchecks)
|
||||||
|
|
||||||
implementation(androidx.paging.runtime)
|
implementation(androidx.paging.runtime)
|
||||||
implementation(androidx.paging.compose)
|
implementation(androidx.paging.compose)
|
||||||
|
@ -178,6 +181,7 @@ dependencies {
|
||||||
implementation(libs.bundles.sqlite)
|
implementation(libs.bundles.sqlite)
|
||||||
|
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
implementation(kotlinx.immutables)
|
||||||
|
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
implementation(kotlinx.bundles.coroutines)
|
implementation(kotlinx.bundles.coroutines)
|
||||||
|
@ -187,7 +191,6 @@ dependencies {
|
||||||
implementation(androidx.appcompat)
|
implementation(androidx.appcompat)
|
||||||
implementation(androidx.biometricktx)
|
implementation(androidx.biometricktx)
|
||||||
implementation(androidx.constraintlayout)
|
implementation(androidx.constraintlayout)
|
||||||
implementation(androidx.coordinatorlayout)
|
|
||||||
implementation(androidx.corektx)
|
implementation(androidx.corektx)
|
||||||
implementation(androidx.splashscreen)
|
implementation(androidx.splashscreen)
|
||||||
implementation(androidx.recyclerview)
|
implementation(androidx.recyclerview)
|
||||||
|
@ -198,10 +201,10 @@ dependencies {
|
||||||
implementation(androidx.bundles.lifecycle)
|
implementation(androidx.bundles.lifecycle)
|
||||||
|
|
||||||
// Job scheduling
|
// Job scheduling
|
||||||
implementation(androidx.bundles.workmanager)
|
implementation(androidx.workmanager)
|
||||||
|
|
||||||
// RxJava
|
// RxJava
|
||||||
implementation(libs.bundles.reactivex)
|
implementation(libs.rxjava)
|
||||||
implementation(libs.flowreactivenetwork)
|
implementation(libs.flowreactivenetwork)
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
|
@ -227,6 +230,7 @@ dependencies {
|
||||||
implementation(libs.injekt.core)
|
implementation(libs.injekt.core)
|
||||||
|
|
||||||
// Image loading
|
// Image loading
|
||||||
|
implementation(platform(libs.coil.bom))
|
||||||
implementation(libs.bundles.coil)
|
implementation(libs.bundles.coil)
|
||||||
implementation(libs.subsamplingscaleimageview) {
|
implementation(libs.subsamplingscaleimageview) {
|
||||||
exclude(module = "image-decoder")
|
exclude(module = "image-decoder")
|
||||||
|
@ -236,7 +240,6 @@ dependencies {
|
||||||
// UI libraries
|
// UI libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.flexible.adapter.core)
|
implementation(libs.flexible.adapter.core)
|
||||||
implementation(libs.flexible.adapter.ui)
|
|
||||||
implementation(libs.photoview)
|
implementation(libs.photoview)
|
||||||
implementation(libs.directionalviewpager) {
|
implementation(libs.directionalviewpager) {
|
||||||
exclude(group = "androidx.viewpager", module = "viewpager")
|
exclude(group = "androidx.viewpager", module = "viewpager")
|
||||||
|
@ -245,9 +248,8 @@ dependencies {
|
||||||
implementation(libs.bundles.richtext)
|
implementation(libs.bundles.richtext)
|
||||||
implementation(libs.aboutLibraries.compose)
|
implementation(libs.aboutLibraries.compose)
|
||||||
implementation(libs.bundles.voyager)
|
implementation(libs.bundles.voyager)
|
||||||
implementation(libs.compose.cascade)
|
|
||||||
implementation(libs.compose.materialmotion)
|
implementation(libs.compose.materialmotion)
|
||||||
implementation(libs.compose.simpleicons)
|
implementation(libs.swipe)
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation(libs.logcat)
|
implementation(libs.logcat)
|
||||||
|
@ -281,7 +283,9 @@ androidComponents {
|
||||||
beforeVariants { variantBuilder ->
|
beforeVariants { variantBuilder ->
|
||||||
// Disables standardBenchmark
|
// Disables standardBenchmark
|
||||||
if (variantBuilder.buildType == "benchmark") {
|
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")) {
|
onVariants(selector().withFlavor("default" to "standard")) {
|
||||||
|
@ -292,16 +296,10 @@ androidComponents {
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
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)
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-Xcontext-receivers",
|
"-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.foundation.layout.ExperimentalLayoutApi",
|
||||||
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
"-opt-in=androidx.compose.material.ExperimentalMaterialApi",
|
||||||
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
|
||||||
|
@ -310,6 +308,8 @@ tasks {
|
||||||
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
|
||||||
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
|
||||||
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
|
||||||
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||||
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
"-opt-in=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
|
@ -320,12 +320,12 @@ tasks {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
"-P",
|
"-P",
|
||||||
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
|
||||||
project.buildDir.absolutePath + "/compose_metrics"
|
project.layout.buildDirectory.dir("compose_metrics").get().asFile.absolutePath,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
-keepclassmembers class * implements android.os.Parcelable {
|
-keepclassmembers class * implements android.os.Parcelable {
|
||||||
public static final ** CREATOR;
|
public static final ** CREATOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
-keep class androidx.annotation.Keep
|
-keep class androidx.annotation.Keep
|
||||||
|
|
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
|
@ -11,10 +11,11 @@
|
||||||
-keep,allowoptimization class kotlin.** { public protected *; }
|
-keep,allowoptimization class kotlin.** { public protected *; }
|
||||||
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
-keep,allowoptimization class kotlinx.coroutines.** { public protected *; }
|
||||||
-keep,allowoptimization class kotlinx.serialization.** { 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 okhttp3.** { public protected *; }
|
||||||
-keep,allowoptimization class okio.** { public protected *; }
|
-keep,allowoptimization class okio.** { public protected *; }
|
||||||
-keep,allowoptimization class rx.** { public protected *; }
|
|
||||||
-keep,allowoptimization class org.jsoup.** { 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 app.cash.quickjs.** { public protected *; }
|
||||||
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
-keep,allowoptimization class uy.kohesive.injekt.** { public protected *; }
|
||||||
-keep,allowoptimization class is.xyz.mpv.** { public protected *; }
|
-keep,allowoptimization class is.xyz.mpv.** { public protected *; }
|
||||||
|
@ -74,4 +75,4 @@
|
||||||
##---------------End: proguard configuration for kotlinx.serialization ----------
|
##---------------End: proguard configuration for kotlinx.serialization ----------
|
||||||
|
|
||||||
# XmlUtil
|
# XmlUtil
|
||||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
|
@ -1,5 +1,4 @@
|
||||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<shortcut
|
<shortcut
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:icon="@drawable/sc_collections_bookmark_48dp"
|
android:icon="@drawable/sc_collections_bookmark_48dp"
|
||||||
|
|
|
@ -29,21 +29,17 @@
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:localeConfig="@xml/locales_config"
|
android:localeConfig="@xml/locales_config"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:theme="@style/Theme.Tachiyomi"
|
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config">
|
android:theme="@style/Theme.Tachiyomi">
|
||||||
|
|
||||||
<!-- enable profiling by macrobenchmark -->
|
|
||||||
<profileable
|
|
||||||
android:shell="true"
|
|
||||||
tools:targetApi="q" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.MainActivity"
|
android:name=".ui.main.MainActivity"
|
||||||
|
@ -67,10 +63,10 @@
|
||||||
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.DeepLinkAnimeActivity"
|
android:name=".ui.deeplink.anime.DeepLinkAnimeActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:label="@string/action_global_anime_search"
|
android:label="@string/action_search"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
@ -94,10 +90,10 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.main.DeepLinkMangaActivity"
|
android:name=".ui.deeplink.manga.DeepLinkMangaActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@android:style/Theme.NoDisplay"
|
android:theme="@android:style/Theme.NoDisplay"
|
||||||
android:label="@string/action_global_manga_search"
|
android:label="@string/action_search"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEARCH" />
|
<action android:name="android.intent.action.SEARCH" />
|
||||||
|
@ -172,8 +168,8 @@
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.setting.track.AnilistLoginActivity"
|
android:name=".ui.setting.track.TrackLoginActivity"
|
||||||
android:label="Anilist"
|
android:label="@string/track_activity_name"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
@ -181,69 +177,21 @@
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:host="anilist-auth"/>
|
||||||
android:host="anilist-auth"
|
<data android:host="bangumi-auth"/>
|
||||||
android:scheme="tachiyomi" />
|
<data android:host="myanimelist-auth"/>
|
||||||
|
<data android:host="shikimori-auth"/>
|
||||||
|
|
||||||
|
<data android:scheme="tachiyomi"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:name=".ui.setting.track.MyAnimeListLoginActivity"
|
|
||||||
android:label="MyAnimeList"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
<data
|
<data android:host="simkl-auth"/>
|
||||||
android:host="myanimelist-auth"
|
<data android:scheme="aniyomi"/>
|
||||||
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" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
@ -251,34 +199,6 @@
|
||||||
android:name=".data.notification.NotificationReceiver"
|
android:name=".data.notification.NotificationReceiver"
|
||||||
android:exported="false" />
|
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
|
<service
|
||||||
android:name=".data.download.manga.MangaDownloadService"
|
android:name=".data.download.manga.MangaDownloadService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -288,13 +208,11 @@
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".data.updater.AppUpdateService"
|
android:name=".extension.manga.util.MangaExtensionInstallService"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<service android:name=".extension.manga.util.MangaExtensionInstallService"
|
<service
|
||||||
android:exported="false" />
|
android:name=".extension.anime.util.AnimeExtensionInstallService"
|
||||||
|
|
||||||
<service android:name=".extension.anime.util.AnimeExtensionInstallService"
|
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
<service
|
<service
|
||||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
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 eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import uy.kohesive.injekt.injectLazy
|
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 {
|
suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response {
|
||||||
val imageUrl = page.imageUrl ?: return getImage(page)
|
val imageUrl = page.imageUrl ?: return getImage(page)
|
||||||
page.imageUrl = dataSaver.compress(imageUrl)
|
page.imageUrl = dataSaver.compress(imageUrl)
|
||||||
|
@ -74,7 +64,13 @@ private class BandwidthHeroDataSaver(preferences: SourcePreferences) : DataSaver
|
||||||
override fun compress(imageUrl: String): String {
|
override fun compress(imageUrl: String): String {
|
||||||
return if (dataSavedServer.isNotBlank() && !imageUrl.contains(dataSavedServer)) {
|
return if (dataSavedServer.isNotBlank() && !imageUrl.contains(dataSavedServer)) {
|
||||||
when {
|
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)
|
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||||
else -> getUrl(imageUrl)
|
else -> getUrl(imageUrl)
|
||||||
}
|
}
|
||||||
|
@ -100,7 +96,13 @@ private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver {
|
||||||
|
|
||||||
override fun compress(imageUrl: String): String {
|
override fun compress(imageUrl: String): String {
|
||||||
return when {
|
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)
|
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||||
else -> getUrl(imageUrl)
|
else -> getUrl(imageUrl)
|
||||||
}
|
}
|
||||||
|
@ -108,7 +110,11 @@ private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver {
|
||||||
|
|
||||||
private fun getUrl(imageUrl: String): String {
|
private fun getUrl(imageUrl: String): String {
|
||||||
// Network Request sent to wsrv
|
// 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) {
|
if (!format) {
|
||||||
// Preserve output image extension for animated images(.webp and .gif)
|
// Preserve output image extension for animated images(.webp and .gif)
|
||||||
"&q=$quality&n=-1"
|
"&q=$quality&n=-1"
|
||||||
|
@ -140,7 +146,13 @@ private class ReSmushItDataSaver(preferences: SourcePreferences) : DataSaver {
|
||||||
|
|
||||||
override fun compress(imageUrl: String): String {
|
override fun compress(imageUrl: String): String {
|
||||||
return when {
|
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)
|
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
|
||||||
else -> getUrl(imageUrl)
|
else -> getUrl(imageUrl)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class PreferenceMutableState<T>(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun component2(): (T) -> Unit {
|
override fun component2(): (T) -> Unit {
|
||||||
return { preference.set(it) }
|
return preference::set
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
import androidx.compose.ui.util.fastForEach
|
import androidx.compose.ui.util.fastForEach
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
|
@ -20,15 +19,6 @@ fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
return newList
|
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) {
|
fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
||||||
if (shouldAdd) {
|
if (shouldAdd) {
|
||||||
add(value)
|
add(value)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package eu.kanade.domain
|
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.download.manga.interactor.DeleteChapterDownload
|
||||||
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
import eu.kanade.domain.entries.anime.interactor.SetAnimeViewerFlags
|
||||||
import eu.kanade.domain.entries.anime.interactor.UpdateAnime
|
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.SetMangaViewerFlags
|
||||||
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
import eu.kanade.domain.entries.manga.interactor.UpdateManga
|
||||||
import eu.kanade.domain.extension.anime.interactor.GetAnimeExtensionLanguages
|
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.GetExtensionSources
|
||||||
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages
|
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionLanguages
|
||||||
import eu.kanade.domain.extension.manga.interactor.GetMangaExtensionsByType
|
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.SetReadStatus
|
||||||
import eu.kanade.domain.items.chapter.interactor.SyncChaptersWithSource
|
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.SetSeenStatus
|
||||||
import eu.kanade.domain.items.episode.interactor.SyncEpisodesWithSource
|
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.GetAnimeSourcesWithFavoriteCount
|
||||||
import eu.kanade.domain.source.anime.interactor.GetEnabledAnimeSources
|
import eu.kanade.domain.source.anime.interactor.GetEnabledAnimeSources
|
||||||
import eu.kanade.domain.source.anime.interactor.GetLanguagesWithAnimeSources
|
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.manga.interactor.ToggleMangaSourcePin
|
||||||
import eu.kanade.domain.source.service.SetMigrateSorting
|
import eu.kanade.domain.source.service.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.service.ToggleLanguage
|
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.anime.AnimeCategoryRepositoryImpl
|
||||||
import tachiyomi.data.category.manga.MangaCategoryRepositoryImpl
|
import tachiyomi.data.category.manga.MangaCategoryRepositoryImpl
|
||||||
import tachiyomi.data.entries.anime.AnimeRepositoryImpl
|
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.history.manga.MangaHistoryRepositoryImpl
|
||||||
import tachiyomi.data.items.chapter.ChapterRepositoryImpl
|
import tachiyomi.data.items.chapter.ChapterRepositoryImpl
|
||||||
import tachiyomi.data.items.episode.EpisodeRepositoryImpl
|
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.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.MangaSourceRepositoryImpl
|
||||||
|
import tachiyomi.data.source.manga.MangaStubSourceRepositoryImpl
|
||||||
import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl
|
import tachiyomi.data.track.anime.AnimeTrackRepositoryImpl
|
||||||
import tachiyomi.data.track.manga.MangaTrackRepositoryImpl
|
import tachiyomi.data.track.manga.MangaTrackRepositoryImpl
|
||||||
import tachiyomi.data.updates.anime.AnimeUpdatesRepositoryImpl
|
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.ReorderAnimeCategory
|
||||||
import tachiyomi.domain.category.anime.interactor.ResetAnimeCategoryFlags
|
import tachiyomi.domain.category.anime.interactor.ResetAnimeCategoryFlags
|
||||||
import tachiyomi.domain.category.anime.interactor.SetAnimeCategories
|
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.SetSortModeForAnimeCategory
|
||||||
import tachiyomi.domain.category.anime.interactor.UpdateAnimeCategory
|
import tachiyomi.domain.category.anime.interactor.UpdateAnimeCategory
|
||||||
import tachiyomi.domain.category.anime.repository.AnimeCategoryRepository
|
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.RenameMangaCategory
|
||||||
import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory
|
import tachiyomi.domain.category.manga.interactor.ReorderMangaCategory
|
||||||
import tachiyomi.domain.category.manga.interactor.ResetMangaCategoryFlags
|
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.SetMangaCategories
|
||||||
|
import tachiyomi.domain.category.manga.interactor.SetMangaDisplayMode
|
||||||
import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory
|
import tachiyomi.domain.category.manga.interactor.SetSortModeForMangaCategory
|
||||||
import tachiyomi.domain.category.manga.interactor.UpdateMangaCategory
|
import tachiyomi.domain.category.manga.interactor.UpdateMangaCategory
|
||||||
import tachiyomi.domain.category.manga.repository.MangaCategoryRepository
|
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.GetAnime
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites
|
import tachiyomi.domain.entries.anime.interactor.GetAnimeFavorites
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
|
import tachiyomi.domain.entries.anime.interactor.GetAnimeWithEpisodes
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
|
import tachiyomi.domain.entries.anime.interactor.GetDuplicateLibraryAnime
|
||||||
import tachiyomi.domain.entries.anime.interactor.GetLibraryAnime
|
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.NetworkToLocalAnime
|
||||||
import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags
|
import tachiyomi.domain.entries.anime.interactor.ResetAnimeViewerFlags
|
||||||
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
|
import tachiyomi.domain.entries.anime.interactor.SetAnimeEpisodeFlags
|
||||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
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.GetDuplicateLibraryManga
|
||||||
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
import tachiyomi.domain.entries.manga.interactor.GetLibraryManga
|
||||||
import tachiyomi.domain.entries.manga.interactor.GetManga
|
import tachiyomi.domain.entries.manga.interactor.GetManga
|
||||||
import tachiyomi.domain.entries.manga.interactor.GetMangaFavorites
|
import tachiyomi.domain.entries.manga.interactor.GetMangaFavorites
|
||||||
import tachiyomi.domain.entries.manga.interactor.GetMangaWithChapters
|
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.NetworkToLocalManga
|
||||||
import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags
|
import tachiyomi.domain.entries.manga.interactor.ResetMangaViewerFlags
|
||||||
import tachiyomi.domain.entries.manga.interactor.SetMangaChapterFlags
|
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.interactor.UpsertMangaHistory
|
||||||
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
import tachiyomi.domain.history.manga.repository.MangaHistoryRepository
|
||||||
import tachiyomi.domain.items.chapter.interactor.GetChapter
|
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.SetMangaDefaultChapterFlags
|
||||||
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
import tachiyomi.domain.items.chapter.interactor.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
import tachiyomi.domain.items.chapter.repository.ChapterRepository
|
||||||
import tachiyomi.domain.items.episode.interactor.GetEpisode
|
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.SetAnimeDefaultEpisodeFlags
|
||||||
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
import tachiyomi.domain.items.episode.interactor.ShouldUpdateDbEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
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.GetAnimeSourcesWithNonLibraryAnime
|
||||||
import tachiyomi.domain.source.anime.interactor.GetRemoteAnime
|
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.AnimeSourceRepository
|
||||||
|
import tachiyomi.domain.source.anime.repository.AnimeStubSourceRepository
|
||||||
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
import tachiyomi.domain.source.manga.interactor.GetMangaSourcesWithNonLibraryManga
|
||||||
import tachiyomi.domain.source.manga.interactor.GetRemoteManga
|
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.MangaSourceRepository
|
||||||
|
import tachiyomi.domain.source.manga.repository.MangaStubSourceRepository
|
||||||
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
|
import tachiyomi.domain.track.anime.interactor.DeleteAnimeTrack
|
||||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||||
import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime
|
import tachiyomi.domain.track.anime.interactor.GetTracksPerAnime
|
||||||
|
@ -148,7 +166,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetAnimeCategories(get()) }
|
addFactory { GetAnimeCategories(get()) }
|
||||||
addFactory { GetVisibleAnimeCategories(get()) }
|
addFactory { GetVisibleAnimeCategories(get()) }
|
||||||
addFactory { ResetAnimeCategoryFlags(get(), get()) }
|
addFactory { ResetAnimeCategoryFlags(get(), get()) }
|
||||||
addFactory { SetDisplayModeForAnimeCategory(get(), get()) }
|
addFactory { SetAnimeDisplayMode(get()) }
|
||||||
addFactory { SetSortModeForAnimeCategory(get(), get()) }
|
addFactory { SetSortModeForAnimeCategory(get(), get()) }
|
||||||
addFactory { CreateAnimeCategoryWithName(get(), get()) }
|
addFactory { CreateAnimeCategoryWithName(get(), get()) }
|
||||||
addFactory { RenameAnimeCategory(get()) }
|
addFactory { RenameAnimeCategory(get()) }
|
||||||
|
@ -161,7 +179,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetMangaCategories(get()) }
|
addFactory { GetMangaCategories(get()) }
|
||||||
addFactory { GetVisibleMangaCategories(get()) }
|
addFactory { GetVisibleMangaCategories(get()) }
|
||||||
addFactory { ResetMangaCategoryFlags(get(), get()) }
|
addFactory { ResetMangaCategoryFlags(get(), get()) }
|
||||||
addFactory { SetDisplayModeForMangaCategory(get(), get()) }
|
addFactory { SetMangaDisplayMode(get()) }
|
||||||
addFactory { SetSortModeForMangaCategory(get(), get()) }
|
addFactory { SetSortModeForMangaCategory(get(), get()) }
|
||||||
addFactory { CreateMangaCategoryWithName(get(), get()) }
|
addFactory { CreateMangaCategoryWithName(get(), get()) }
|
||||||
addFactory { RenameMangaCategory(get()) }
|
addFactory { RenameMangaCategory(get()) }
|
||||||
|
@ -175,14 +193,16 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetAnimeFavorites(get()) }
|
addFactory { GetAnimeFavorites(get()) }
|
||||||
addFactory { GetLibraryAnime(get()) }
|
addFactory { GetLibraryAnime(get()) }
|
||||||
addFactory { GetAnimeWithEpisodes(get(), get()) }
|
addFactory { GetAnimeWithEpisodes(get(), get()) }
|
||||||
|
addFactory { GetAnimeByUrlAndSourceId(get()) }
|
||||||
addFactory { GetAnime(get()) }
|
addFactory { GetAnime(get()) }
|
||||||
addFactory { GetNextEpisodes(get(), get(), get()) }
|
addFactory { GetNextEpisodes(get(), get(), get()) }
|
||||||
addFactory { ResetAnimeViewerFlags(get()) }
|
addFactory { ResetAnimeViewerFlags(get()) }
|
||||||
addFactory { SetAnimeEpisodeFlags(get()) }
|
addFactory { SetAnimeEpisodeFlags(get()) }
|
||||||
|
addFactory { AnimeFetchInterval(get()) }
|
||||||
addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) }
|
addFactory { SetAnimeDefaultEpisodeFlags(get(), get(), get()) }
|
||||||
addFactory { SetAnimeViewerFlags(get()) }
|
addFactory { SetAnimeViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalAnime(get()) }
|
addFactory { NetworkToLocalAnime(get()) }
|
||||||
addFactory { UpdateAnime(get()) }
|
addFactory { UpdateAnime(get(), get()) }
|
||||||
addFactory { SetAnimeCategories(get()) }
|
addFactory { SetAnimeCategories(get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
|
||||||
|
@ -190,10 +210,12 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetMangaFavorites(get()) }
|
addFactory { GetMangaFavorites(get()) }
|
||||||
addFactory { GetLibraryManga(get()) }
|
addFactory { GetLibraryManga(get()) }
|
||||||
addFactory { GetMangaWithChapters(get(), get()) }
|
addFactory { GetMangaWithChapters(get(), get()) }
|
||||||
|
addFactory { GetMangaByUrlAndSourceId(get()) }
|
||||||
addFactory { GetManga(get()) }
|
addFactory { GetManga(get()) }
|
||||||
addFactory { GetNextChapters(get(), get(), get()) }
|
addFactory { GetNextChapters(get(), get(), get()) }
|
||||||
addFactory { ResetMangaViewerFlags(get()) }
|
addFactory { ResetMangaViewerFlags(get()) }
|
||||||
addFactory { SetMangaChapterFlags(get()) }
|
addFactory { SetMangaChapterFlags(get()) }
|
||||||
|
addFactory { MangaFetchInterval(get()) }
|
||||||
addFactory {
|
addFactory {
|
||||||
SetMangaDefaultChapterFlags(
|
SetMangaDefaultChapterFlags(
|
||||||
get(),
|
get(),
|
||||||
|
@ -203,45 +225,59 @@ class DomainModule : InjektModule {
|
||||||
}
|
}
|
||||||
addFactory { SetMangaViewerFlags(get()) }
|
addFactory { SetMangaViewerFlags(get()) }
|
||||||
addFactory { NetworkToLocalManga(get()) }
|
addFactory { NetworkToLocalManga(get()) }
|
||||||
addFactory { UpdateManga(get()) }
|
addFactory { UpdateManga(get(), get()) }
|
||||||
addFactory { SetMangaCategories(get()) }
|
addFactory { SetMangaCategories(get()) }
|
||||||
|
addFactory { GetExcludedScanlators(get()) }
|
||||||
|
addFactory { SetExcludedScanlators(get()) }
|
||||||
|
|
||||||
|
addSingletonFactory<ReleaseService> { ReleaseServiceImpl(get(), get()) }
|
||||||
|
addFactory { GetApplicationRelease(get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeTrackRepository> { AnimeTrackRepositoryImpl(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 { DeleteAnimeTrack(get()) }
|
||||||
addFactory { GetTracksPerAnime(get()) }
|
addFactory { GetTracksPerAnime(get()) }
|
||||||
addFactory { GetAnimeTracks(get()) }
|
addFactory { GetAnimeTracks(get()) }
|
||||||
addFactory { InsertAnimeTrack(get()) }
|
addFactory { InsertAnimeTrack(get()) }
|
||||||
|
addFactory { SyncEpisodeProgressWithTrack(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaTrackRepository> { MangaTrackRepositoryImpl(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 { DeleteMangaTrack(get()) }
|
||||||
addFactory { GetTracksPerManga(get()) }
|
addFactory { GetTracksPerManga(get()) }
|
||||||
addFactory { GetMangaTracks(get()) }
|
addFactory { GetMangaTracks(get()) }
|
||||||
addFactory { InsertMangaTrack(get()) }
|
addFactory { InsertMangaTrack(get()) }
|
||||||
|
addFactory { SyncChapterProgressWithTrack(get(), get(), get()) }
|
||||||
|
|
||||||
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
addSingletonFactory<EpisodeRepository> { EpisodeRepositoryImpl(get()) }
|
||||||
addFactory { GetEpisode(get()) }
|
addFactory { GetEpisode(get()) }
|
||||||
addFactory { GetEpisodeByAnimeId(get()) }
|
addFactory { GetEpisodesByAnimeId(get()) }
|
||||||
|
addFactory { GetEpisodeByUrlAndAnimeId(get()) }
|
||||||
addFactory { UpdateEpisode(get()) }
|
addFactory { UpdateEpisode(get()) }
|
||||||
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
addFactory { SetSeenStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbEpisode() }
|
addFactory { ShouldUpdateDbEpisode() }
|
||||||
addFactory { SyncEpisodesWithSource(get(), get(), get(), get()) }
|
addFactory { SyncEpisodesWithSource(get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { SyncEpisodesWithTrackServiceTwoWay(get(), get()) }
|
|
||||||
|
|
||||||
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
|
||||||
addFactory { GetChapter(get()) }
|
addFactory { GetChapter(get()) }
|
||||||
addFactory { GetChapterByMangaId(get()) }
|
addFactory { GetChaptersByMangaId(get()) }
|
||||||
|
addFactory { GetChapterByUrlAndMangaId(get()) }
|
||||||
addFactory { UpdateChapter(get()) }
|
addFactory { UpdateChapter(get()) }
|
||||||
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
addFactory { SetReadStatus(get(), get(), get(), get()) }
|
||||||
addFactory { ShouldUpdateDbChapter() }
|
addFactory { ShouldUpdateDbChapter() }
|
||||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get()) }
|
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
|
addFactory { GetAvailableScanlators(get()) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeHistoryRepository> { AnimeHistoryRepositoryImpl(get()) }
|
addSingletonFactory<AnimeHistoryRepository> { AnimeHistoryRepositoryImpl(get()) }
|
||||||
addFactory { GetAnimeHistory(get()) }
|
addFactory { GetAnimeHistory(get()) }
|
||||||
addFactory { UpsertAnimeHistory(get()) }
|
addFactory { UpsertAnimeHistory(get()) }
|
||||||
addFactory { RemoveAnimeHistory(get()) }
|
addFactory { RemoveAnimeHistory(get()) }
|
||||||
|
|
||||||
addFactory { DeleteAnimeDownload(get(), get()) }
|
addFactory { DeleteEpisodeDownload(get(), get()) }
|
||||||
|
|
||||||
addFactory { GetAnimeExtensionsByType(get(), get()) }
|
addFactory { GetAnimeExtensionsByType(get(), get()) }
|
||||||
addFactory { GetAnimeExtensionSources(get()) }
|
addFactory { GetAnimeExtensionSources(get()) }
|
||||||
|
@ -266,7 +302,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { GetMangaUpdates(get()) }
|
addFactory { GetMangaUpdates(get()) }
|
||||||
|
|
||||||
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<AnimeSourceRepository> { AnimeSourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<AnimeSourceDataRepository> { AnimeSourceDataRepositoryImpl(get()) }
|
addSingletonFactory<AnimeStubSourceRepository> { AnimeStubSourceRepositoryImpl(get()) }
|
||||||
addFactory { GetEnabledAnimeSources(get(), get()) }
|
addFactory { GetEnabledAnimeSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
addFactory { GetLanguagesWithAnimeSources(get(), get()) }
|
||||||
addFactory { GetRemoteAnime(get()) }
|
addFactory { GetRemoteAnime(get()) }
|
||||||
|
@ -276,7 +312,7 @@ class DomainModule : InjektModule {
|
||||||
addFactory { ToggleAnimeSourcePin(get()) }
|
addFactory { ToggleAnimeSourcePin(get()) }
|
||||||
|
|
||||||
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
addSingletonFactory<MangaSourceRepository> { MangaSourceRepositoryImpl(get(), get()) }
|
||||||
addSingletonFactory<MangaSourceDataRepository> { MangaSourceDataRepositoryImpl(get()) }
|
addSingletonFactory<MangaStubSourceRepository> { MangaStubSourceRepositoryImpl(get()) }
|
||||||
addFactory { GetEnabledMangaSources(get(), get()) }
|
addFactory { GetEnabledMangaSources(get(), get()) }
|
||||||
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
addFactory { GetLanguagesWithMangaSources(get(), get()) }
|
||||||
addFactory { GetRemoteManga(get()) }
|
addFactory { GetRemoteManga(get()) }
|
||||||
|
|
|
@ -3,9 +3,11 @@ package eu.kanade.domain.base
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.annotation.StringRes
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
class BasePreferences(
|
class BasePreferences(
|
||||||
|
@ -13,21 +15,28 @@ class BasePreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
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(Preference.appStateKey("incognito_mode"), false)
|
||||||
|
|
||||||
fun incognitoMode() = preferenceStore.getBoolean("incognito_mode", false)
|
|
||||||
|
|
||||||
fun extensionInstaller() = ExtensionInstallerPreference(context, preferenceStore)
|
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),
|
LEGACY(R.string.ext_installer_legacy),
|
||||||
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
PACKAGEINSTALLER(R.string.ext_installer_packageinstaller),
|
||||||
SHIZUKU(R.string.ext_installer_shizuku),
|
SHIZUKU(R.string.ext_installer_shizuku),
|
||||||
|
PRIVATE(R.string.ext_installer_private),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ExtensionInstallerPreference(
|
||||||
|
|
||||||
override fun key() = "extension_installer"
|
override fun key() = "extension_installer"
|
||||||
|
|
||||||
val entries get() = ExtensionInstaller.values().run {
|
val entries get() = ExtensionInstaller.entries.run {
|
||||||
if (context.hasMiuiPackageInstaller) {
|
if (context.hasMiuiPackageInstaller) {
|
||||||
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
filter { it != ExtensionInstaller.PACKAGEINSTALLER }
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
import tachiyomi.domain.source.anime.service.AnimeSourceManager
|
||||||
|
|
||||||
class DeleteAnimeDownload(
|
class DeleteEpisodeDownload(
|
||||||
private val sourceManager: AnimeSourceManager,
|
private val sourceManager: AnimeSourceManager,
|
||||||
private val downloadManager: AnimeDownloadManager,
|
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.domain.entries.anime.model.hasCustomCover
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
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.Anime
|
||||||
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
import tachiyomi.domain.entries.anime.model.AnimeUpdate
|
||||||
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
import tachiyomi.domain.entries.anime.repository.AnimeRepository
|
||||||
import tachiyomi.source.local.entries.anime.isLocal
|
import tachiyomi.source.local.entries.anime.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class UpdateAnime(
|
class UpdateAnime(
|
||||||
private val animeRepository: AnimeRepository,
|
private val animeRepository: AnimeRepository,
|
||||||
|
private val animeFetchInterval: AnimeFetchInterval,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(animeUpdate: AnimeUpdate): Boolean {
|
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 {
|
suspend fun awaitUpdateLastUpdate(animeId: Long): Boolean {
|
||||||
return animeRepository.updateAnime(AnimeUpdate(id = animeId, lastUpdate = Date().time))
|
return animeRepository.updateAnime(AnimeUpdate(id = animeId, lastUpdate = Date().time))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
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 {
|
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.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||||
import eu.kanade.tachiyomi.data.cache.AnimeCoverCache
|
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 tachiyomi.domain.entries.anime.model.Anime
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
val Anime.downloadedFilter: TriStateFilter
|
val Anime.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||||
return when (downloadedFilterRaw) {
|
return when (downloadedFilterRaw) {
|
||||||
Anime.EPISODE_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
Anime.EPISODE_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
Anime.EPISODE_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
else -> TriStateFilter.DISABLED
|
else -> TriState.DISABLED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun Anime.episodesFiltered(): Boolean {
|
fun Anime.episodesFiltered(): Boolean {
|
||||||
return unseenFilter != TriStateFilter.DISABLED ||
|
return unseenFilter != TriState.DISABLED ||
|
||||||
downloadedFilter != TriStateFilter.DISABLED ||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
bookmarkedFilter != TriStateFilter.DISABLED
|
bookmarkedFilter != TriState.DISABLED
|
||||||
}
|
}
|
||||||
fun Anime.forceDownloaded(): Boolean {
|
fun Anime.forceDownloaded(): Boolean {
|
||||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
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
|
package eu.kanade.domain.entries.manga.interactor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
||||||
|
|
||||||
|
@ -9,22 +9,22 @@ class SetMangaViewerFlags(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
|
suspend fun awaitSetReadingMode(id: Long, flag: Long) {
|
||||||
val manga = mangaRepository.getMangaById(id)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.updateManga(
|
mangaRepository.updateManga(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
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)
|
val manga = mangaRepository.getMangaById(id)
|
||||||
mangaRepository.updateManga(
|
mangaRepository.updateManga(
|
||||||
MangaUpdate(
|
MangaUpdate(
|
||||||
id = id,
|
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.domain.entries.manga.model.hasCustomCover
|
||||||
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
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.Manga
|
||||||
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
import tachiyomi.domain.entries.manga.model.MangaUpdate
|
||||||
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
import tachiyomi.domain.entries.manga.repository.MangaRepository
|
||||||
import tachiyomi.source.local.entries.manga.isLocal
|
import tachiyomi.source.local.entries.manga.isLocal
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class UpdateManga(
|
class UpdateManga(
|
||||||
private val mangaRepository: MangaRepository,
|
private val mangaRepository: MangaRepository,
|
||||||
|
private val mangaFetchInterval: MangaFetchInterval,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(mangaUpdate: MangaUpdate): Boolean {
|
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 {
|
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
|
||||||
return mangaRepository.updateManga(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
return mangaRepository.updateManga(MangaUpdate(id = mangaId, lastUpdate = Date().time))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun awaitUpdateCoverLastModified(mangaId: Long): Boolean {
|
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 {
|
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.domain.base.BasePreferences
|
||||||
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
import eu.kanade.tachiyomi.data.cache.MangaCoverCache
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
import tachiyomi.core.metadata.comicinfo.ComicInfo
|
||||||
import tachiyomi.core.metadata.comicinfo.ComicInfoPublishingStatus
|
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.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
// TODO: move these into the domain model
|
// TODO: move these into the domain model
|
||||||
val Manga.readingModeType: Long
|
val Manga.readingMode: Long
|
||||||
get() = viewerFlags and ReadingModeType.MASK.toLong()
|
get() = viewerFlags and ReadingMode.MASK.toLong()
|
||||||
|
|
||||||
val Manga.orientationType: Long
|
val Manga.readerOrientation: Long
|
||||||
get() = viewerFlags and OrientationType.MASK.toLong()
|
get() = viewerFlags and ReaderOrientation.MASK.toLong()
|
||||||
|
|
||||||
val Manga.downloadedFilter: TriStateFilter
|
val Manga.downloadedFilter: TriState
|
||||||
get() {
|
get() {
|
||||||
if (forceDownloaded()) return TriStateFilter.ENABLED_IS
|
if (forceDownloaded()) return TriState.ENABLED_IS
|
||||||
return when (downloadedFilterRaw) {
|
return when (downloadedFilterRaw) {
|
||||||
Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateFilter.ENABLED_IS
|
Manga.CHAPTER_SHOW_DOWNLOADED -> TriState.ENABLED_IS
|
||||||
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateFilter.ENABLED_NOT
|
Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriState.ENABLED_NOT
|
||||||
else -> TriStateFilter.DISABLED
|
else -> TriState.DISABLED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun Manga.chaptersFiltered(): Boolean {
|
fun Manga.chaptersFiltered(): Boolean {
|
||||||
return unreadFilter != TriStateFilter.DISABLED ||
|
return unreadFilter != TriState.DISABLED ||
|
||||||
downloadedFilter != TriStateFilter.DISABLED ||
|
downloadedFilter != TriState.DISABLED ||
|
||||||
bookmarkedFilter != TriStateFilter.DISABLED
|
bookmarkedFilter != TriState.DISABLED
|
||||||
}
|
}
|
||||||
fun Manga.forceDownloaded(): Boolean {
|
fun Manga.forceDownloaded(): Boolean {
|
||||||
return favorite && Injekt.get<BasePreferences>().downloadedOnly().get()
|
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.
|
* 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),
|
title = ComicInfo.Title(chapter.name),
|
||||||
series = ComicInfo.Series(manga.title),
|
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),
|
web = ComicInfo.Web(chapterUrl),
|
||||||
summary = manga.description?.let { ComicInfo.Summary(it) },
|
summary = manga.description?.let { ComicInfo.Summary(it) },
|
||||||
writer = manga.author?.let { ComicInfo.Writer(it) },
|
writer = manga.author?.let { ComicInfo.Writer(it) },
|
||||||
|
@ -107,6 +114,7 @@ fun getComicInfo(manga: Manga, chapter: Chapter, chapterUrl: String) = ComicInfo
|
||||||
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
publishingStatus = ComicInfo.PublishingStatusTachiyomi(
|
||||||
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
ComicInfoPublishingStatus.toComicInfoValue(manga.status),
|
||||||
),
|
),
|
||||||
|
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||||
inker = null,
|
inker = null,
|
||||||
colorist = null,
|
colorist = null,
|
||||||
letterer = 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) =
|
suspend fun await(manga: Manga, read: Boolean) =
|
||||||
await(manga.id, read)
|
await(manga.id, read)
|
||||||
|
|
||||||
sealed class Result {
|
sealed interface Result {
|
||||||
object Success : Result()
|
data object Success : Result
|
||||||
object NoChapters : Result()
|
data object NoChapters : Result
|
||||||
data class InternalError(val error: Throwable) : Result()
|
data class InternalError(val error: Throwable) : Result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.kanade.domain.items.chapter.interactor
|
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.interactor.UpdateManga
|
||||||
import eu.kanade.domain.entries.manga.model.toSManga
|
import eu.kanade.domain.entries.manga.model.toSManga
|
||||||
import eu.kanade.domain.items.chapter.model.copyFromSChapter
|
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 eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import tachiyomi.data.items.chapter.ChapterSanitizer
|
import tachiyomi.data.items.chapter.ChapterSanitizer
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
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.ShouldUpdateDbChapter
|
||||||
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
import tachiyomi.domain.items.chapter.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
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.repository.ChapterRepository
|
||||||
import tachiyomi.domain.items.chapter.service.ChapterRecognition
|
import tachiyomi.domain.items.chapter.service.ChapterRecognition
|
||||||
import tachiyomi.source.local.entries.manga.isLocal
|
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.lang.Long.max
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
class SyncChaptersWithSource(
|
class SyncChaptersWithSource(
|
||||||
private val downloadManager: MangaDownloadManager = Injekt.get(),
|
private val downloadManager: MangaDownloadManager,
|
||||||
private val downloadProvider: MangaDownloadProvider = Injekt.get(),
|
private val downloadProvider: MangaDownloadProvider,
|
||||||
private val chapterRepository: ChapterRepository = Injekt.get(),
|
private val chapterRepository: ChapterRepository,
|
||||||
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
|
private val shouldUpdateDbChapter: ShouldUpdateDbChapter,
|
||||||
private val updateManga: UpdateManga = Injekt.get(),
|
private val updateManga: UpdateManga,
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter,
|
||||||
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
|
private val getExcludedScanlators: GetExcludedScanlators,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,11 +49,15 @@ class SyncChaptersWithSource(
|
||||||
rawSourceChapters: List<SChapter>,
|
rawSourceChapters: List<SChapter>,
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
source: MangaSource,
|
source: MangaSource,
|
||||||
|
manualFetch: Boolean = false,
|
||||||
|
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||||
): List<Chapter> {
|
): List<Chapter> {
|
||||||
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
if (rawSourceChapters.isEmpty() && !source.isLocal()) {
|
||||||
throw NoChaptersException()
|
throw NoChaptersException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
|
||||||
val sourceChapters = rawSourceChapters
|
val sourceChapters = rawSourceChapters
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.mapIndexed { i, sChapter ->
|
.mapIndexed { i, sChapter ->
|
||||||
|
@ -63,7 +68,7 @@ class SyncChaptersWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters from db.
|
// Chapters from db.
|
||||||
val dbChapters = getChapterByMangaId.await(manga.id)
|
val dbChapters = getChaptersByMangaId.await(manga.id)
|
||||||
|
|
||||||
// Chapters from the source not in db.
|
// Chapters from the source not in db.
|
||||||
val toAdd = mutableListOf<Chapter>()
|
val toAdd = mutableListOf<Chapter>()
|
||||||
|
@ -96,7 +101,11 @@ class SyncChaptersWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recognize chapter number for the chapter.
|
// 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)
|
chapter = chapter.copy(chapterNumber = chapterNumber)
|
||||||
|
|
||||||
val dbChapter = dbChapters.find { it.url == chapter.url }
|
val dbChapter = dbChapters.find { it.url == chapter.url }
|
||||||
|
@ -112,8 +121,16 @@ class SyncChaptersWithSource(
|
||||||
toAdd.add(toAddChapter)
|
toAdd.add(toAddChapter)
|
||||||
} else {
|
} else {
|
||||||
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
if (shouldUpdateDbChapter.await(dbChapter, chapter)) {
|
||||||
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(dbChapter, chapter) &&
|
val shouldRenameChapter = downloadProvider.isChapterDirNameChanged(
|
||||||
downloadManager.isChapterDownloaded(dbChapter.name, dbChapter.scanlator, manga.title, manga.source)
|
dbChapter,
|
||||||
|
chapter,
|
||||||
|
) &&
|
||||||
|
downloadManager.isChapterDownloaded(
|
||||||
|
dbChapter.name,
|
||||||
|
dbChapter.scanlator,
|
||||||
|
manga.title,
|
||||||
|
manga.source,
|
||||||
|
)
|
||||||
|
|
||||||
if (shouldRenameChapter) {
|
if (shouldRenameChapter) {
|
||||||
downloadManager.renameChapter(source, manga, dbChapter, chapter)
|
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.
|
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||||
|
if (manualFetch || manga.fetchInterval == 0 || manga.nextUpdate < fetchWindow.first) {
|
||||||
|
updateManga.awaitUpdateFetchInterval(
|
||||||
|
manga,
|
||||||
|
now,
|
||||||
|
fetchWindow,
|
||||||
|
)
|
||||||
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Chapter>()
|
val reAdded = mutableListOf<Chapter>()
|
||||||
|
|
||||||
val deletedChapterNumbers = TreeSet<Float>()
|
val deletedChapterNumbers = TreeSet<Double>()
|
||||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
val deletedReadChapterNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
val deletedBookmarkedChapterNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
toDelete.forEach { chapter ->
|
toDelete.forEach { chapter ->
|
||||||
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
if (chapter.read) deletedReadChapterNumbers.add(chapter.chapterNumber)
|
||||||
|
@ -188,6 +212,7 @@ class SyncChaptersWithSource(
|
||||||
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
val chapterUpdates = toChange.map { it.toChapterUpdate() }
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
}
|
}
|
||||||
|
updateManga.awaitUpdateFetchInterval(manga, now, fetchWindow)
|
||||||
|
|
||||||
// Set this manga as updated since chapters were changed
|
// Set this manga as updated since chapters were changed
|
||||||
// Note that last_update actually represents last time the chapter list changed at all
|
// 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()
|
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
|
package eu.kanade.domain.items.chapter.model
|
||||||
|
|
||||||
import data.Chapters
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl
|
import eu.kanade.tachiyomi.data.database.models.manga.ChapterImpl
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
import tachiyomi.domain.items.chapter.model.Chapter
|
||||||
|
@ -12,7 +11,7 @@ fun Chapter.toSChapter(): SChapter {
|
||||||
it.url = url
|
it.url = url
|
||||||
it.name = name
|
it.name = name
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.scanlator = scanlator
|
it.scanlator = scanlator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,18 +21,8 @@ fun Chapter.copyFromSChapter(sChapter: SChapter): Chapter {
|
||||||
name = sChapter.name,
|
name = sChapter.name,
|
||||||
url = sChapter.url,
|
url = sChapter.url,
|
||||||
dateUpload = sChapter.date_upload,
|
dateUpload = sChapter.date_upload,
|
||||||
chapterNumber = sChapter.chapter_number,
|
chapterNumber = sChapter.chapter_number.toDouble(),
|
||||||
scanlator = sChapter.scanlator?.ifBlank { null },
|
scanlator = sChapter.scanlator?.ifBlank { null }?.trim(),
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +37,6 @@ fun Chapter.toDbChapter(): DbChapter = ChapterImpl().also {
|
||||||
it.last_page_read = lastPageRead.toInt()
|
it.last_page_read = lastPageRead.toInt()
|
||||||
it.date_fetch = dateFetch
|
it.date_fetch = dateFetch
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.chapter_number = chapterNumber
|
it.chapter_number = chapterNumber.toFloat()
|
||||||
it.source_order = sourceOrder.toInt()
|
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.domain.entries.manga.model.downloadedFilter
|
||||||
import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager
|
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.applyFilter
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
import tachiyomi.domain.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
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(bookmarkedFilter) { chapter.bookmark } }
|
||||||
.filter { chapter ->
|
.filter { chapter ->
|
||||||
applyFilter(downloadedFilter) {
|
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
|
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.
|
* Applies the view filters to the list of chapters obtained from the database.
|
||||||
* @return an observable of the list of chapters filtered and sorted.
|
* @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 isLocalManga = manga.isLocal()
|
||||||
val unreadFilter = manga.unreadFilter
|
val unreadFilter = manga.unreadFilter
|
||||||
val downloadedFilter = manga.downloadedFilter
|
val downloadedFilter = manga.downloadedFilter
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.kanade.domain.items.episode.interactor
|
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 logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withNonCancellableContext
|
import tachiyomi.core.util.lang.withNonCancellableContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
@ -13,7 +13,7 @@ import tachiyomi.domain.items.episode.repository.EpisodeRepository
|
||||||
|
|
||||||
class SetSeenStatus(
|
class SetSeenStatus(
|
||||||
private val downloadPreferences: DownloadPreferences,
|
private val downloadPreferences: DownloadPreferences,
|
||||||
private val deleteDownload: DeleteAnimeDownload,
|
private val deleteDownload: DeleteEpisodeDownload,
|
||||||
private val animeRepository: AnimeRepository,
|
private val animeRepository: AnimeRepository,
|
||||||
private val episodeRepository: EpisodeRepository,
|
private val episodeRepository: EpisodeRepository,
|
||||||
) {
|
) {
|
||||||
|
@ -72,9 +72,9 @@ class SetSeenStatus(
|
||||||
suspend fun await(anime: Anime, seen: Boolean) =
|
suspend fun await(anime: Anime, seen: Boolean) =
|
||||||
await(anime.id, seen)
|
await(anime.id, seen)
|
||||||
|
|
||||||
sealed class Result {
|
sealed interface Result {
|
||||||
object Success : Result()
|
data object Success : Result
|
||||||
object NoEpisodes : Result()
|
data object NoEpisodes : Result
|
||||||
data class InternalError(val error: Throwable) : 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 eu.kanade.tachiyomi.data.download.anime.AnimeDownloadProvider
|
||||||
import tachiyomi.data.items.episode.EpisodeSanitizer
|
import tachiyomi.data.items.episode.EpisodeSanitizer
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
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.ShouldUpdateDbEpisode
|
||||||
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
import tachiyomi.domain.items.episode.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
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.repository.EpisodeRepository
|
||||||
import tachiyomi.domain.items.episode.service.EpisodeRecognition
|
import tachiyomi.domain.items.episode.service.EpisodeRecognition
|
||||||
import tachiyomi.source.local.entries.anime.isLocal
|
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.lang.Long.max
|
||||||
|
import java.time.ZonedDateTime
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
class SyncEpisodesWithSource(
|
class SyncEpisodesWithSource(
|
||||||
private val downloadManager: AnimeDownloadManager = Injekt.get(),
|
private val downloadManager: AnimeDownloadManager,
|
||||||
private val downloadProvider: AnimeDownloadProvider = Injekt.get(),
|
private val downloadProvider: AnimeDownloadProvider,
|
||||||
private val episodeRepository: EpisodeRepository = Injekt.get(),
|
private val episodeRepository: EpisodeRepository,
|
||||||
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode = Injekt.get(),
|
private val shouldUpdateDbEpisode: ShouldUpdateDbEpisode,
|
||||||
private val updateAnime: UpdateAnime = Injekt.get(),
|
private val updateAnime: UpdateAnime,
|
||||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
private val updateEpisode: UpdateEpisode,
|
||||||
private val getEpisodeByAnimeId: GetEpisodeByAnimeId = Injekt.get(),
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,11 +47,15 @@ class SyncEpisodesWithSource(
|
||||||
rawSourceEpisodes: List<SEpisode>,
|
rawSourceEpisodes: List<SEpisode>,
|
||||||
anime: Anime,
|
anime: Anime,
|
||||||
source: AnimeSource,
|
source: AnimeSource,
|
||||||
|
manualFetch: Boolean = false,
|
||||||
|
fetchWindow: Pair<Long, Long> = Pair(0, 0),
|
||||||
): List<Episode> {
|
): List<Episode> {
|
||||||
if (rawSourceEpisodes.isEmpty() && !source.isLocal()) {
|
if (rawSourceEpisodes.isEmpty() && !source.isLocal()) {
|
||||||
throw NoEpisodesException()
|
throw NoEpisodesException()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val now = ZonedDateTime.now()
|
||||||
|
|
||||||
val sourceEpisodes = rawSourceEpisodes
|
val sourceEpisodes = rawSourceEpisodes
|
||||||
.distinctBy { it.url }
|
.distinctBy { it.url }
|
||||||
.mapIndexed { i, sEpisode ->
|
.mapIndexed { i, sEpisode ->
|
||||||
|
@ -63,7 +66,7 @@ class SyncEpisodesWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Episodes from db.
|
// Episodes from db.
|
||||||
val dbEpisodes = getEpisodeByAnimeId.await(anime.id)
|
val dbEpisodes = getEpisodesByAnimeId.await(anime.id)
|
||||||
|
|
||||||
// Episodes from the source not in db.
|
// Episodes from the source not in db.
|
||||||
val toAdd = mutableListOf<Episode>()
|
val toAdd = mutableListOf<Episode>()
|
||||||
|
@ -96,7 +99,11 @@ class SyncEpisodesWithSource(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recognize episode number for the episode.
|
// 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)
|
episode = episode.copy(episodeNumber = episodeNumber)
|
||||||
|
|
||||||
val dbEpisode = dbEpisodes.find { it.url == episode.url }
|
val dbEpisode = dbEpisodes.find { it.url == episode.url }
|
||||||
|
@ -112,8 +119,16 @@ class SyncEpisodesWithSource(
|
||||||
toAdd.add(toAddEpisode)
|
toAdd.add(toAddEpisode)
|
||||||
} else {
|
} else {
|
||||||
if (shouldUpdateDbEpisode.await(dbEpisode, episode)) {
|
if (shouldUpdateDbEpisode.await(dbEpisode, episode)) {
|
||||||
val shouldRenameEpisode = downloadProvider.isEpisodeDirNameChanged(dbEpisode, episode) &&
|
val shouldRenameEpisode = downloadProvider.isEpisodeDirNameChanged(
|
||||||
downloadManager.isEpisodeDownloaded(dbEpisode.name, dbEpisode.scanlator, anime.title, anime.source)
|
dbEpisode,
|
||||||
|
episode,
|
||||||
|
) &&
|
||||||
|
downloadManager.isEpisodeDownloaded(
|
||||||
|
dbEpisode.name,
|
||||||
|
dbEpisode.scanlator,
|
||||||
|
anime.title,
|
||||||
|
anime.source,
|
||||||
|
)
|
||||||
|
|
||||||
if (shouldRenameEpisode) {
|
if (shouldRenameEpisode) {
|
||||||
downloadManager.renameEpisode(source, anime, dbEpisode, episode)
|
downloadManager.renameEpisode(source, anime, dbEpisode, episode)
|
||||||
|
@ -125,7 +140,9 @@ class SyncEpisodesWithSource(
|
||||||
sourceOrder = episode.sourceOrder,
|
sourceOrder = episode.sourceOrder,
|
||||||
)
|
)
|
||||||
if (episode.dateUpload != 0L) {
|
if (episode.dateUpload != 0L) {
|
||||||
toChangeEpisode = toChangeEpisode.copy(dateUpload = sourceEpisode.dateUpload)
|
toChangeEpisode = toChangeEpisode.copy(
|
||||||
|
dateUpload = sourceEpisode.dateUpload,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
toChange.add(toChangeEpisode)
|
toChange.add(toChangeEpisode)
|
||||||
}
|
}
|
||||||
|
@ -134,14 +151,21 @@ class SyncEpisodesWithSource(
|
||||||
|
|
||||||
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
// Return if there's nothing to add, delete or change, avoiding unnecessary db transactions.
|
||||||
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
if (toAdd.isEmpty() && toDelete.isEmpty() && toChange.isEmpty()) {
|
||||||
|
if (manualFetch || anime.fetchInterval == 0 || anime.nextUpdate < fetchWindow.first) {
|
||||||
|
updateAnime.awaitUpdateFetchInterval(
|
||||||
|
anime,
|
||||||
|
now,
|
||||||
|
fetchWindow,
|
||||||
|
)
|
||||||
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val reAdded = mutableListOf<Episode>()
|
val reAdded = mutableListOf<Episode>()
|
||||||
|
|
||||||
val deletedEpisodeNumbers = TreeSet<Float>()
|
val deletedEpisodeNumbers = TreeSet<Double>()
|
||||||
val deletedSeenEpisodeNumbers = TreeSet<Float>()
|
val deletedSeenEpisodeNumbers = TreeSet<Double>()
|
||||||
val deletedBookmarkedEpisodeNumbers = TreeSet<Float>()
|
val deletedBookmarkedEpisodeNumbers = TreeSet<Double>()
|
||||||
|
|
||||||
toDelete.forEach { episode ->
|
toDelete.forEach { episode ->
|
||||||
if (episode.seen) deletedSeenEpisodeNumbers.add(episode.episodeNumber)
|
if (episode.seen) deletedSeenEpisodeNumbers.add(episode.episodeNumber)
|
||||||
|
@ -188,6 +212,7 @@ class SyncEpisodesWithSource(
|
||||||
val episodeUpdates = toChange.map { it.toEpisodeUpdate() }
|
val episodeUpdates = toChange.map { it.toEpisodeUpdate() }
|
||||||
updateEpisode.awaitAll(episodeUpdates)
|
updateEpisode.awaitAll(episodeUpdates)
|
||||||
}
|
}
|
||||||
|
updateAnime.awaitUpdateFetchInterval(anime, now, fetchWindow)
|
||||||
|
|
||||||
// Set this anime as updated since episodes were changed
|
// Set this anime as updated since episodes were changed
|
||||||
// Note that last_update actually represents last time the episode list changed at all
|
// 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
|
package eu.kanade.domain.items.episode.model
|
||||||
|
|
||||||
import dataanime.Episodes
|
|
||||||
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
import eu.kanade.tachiyomi.animesource.model.SEpisode
|
||||||
import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl
|
import eu.kanade.tachiyomi.data.database.models.anime.EpisodeImpl
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
import tachiyomi.domain.items.episode.model.Episode
|
||||||
|
@ -12,7 +11,7 @@ fun Episode.toSEpisode(): SEpisode {
|
||||||
it.url = url
|
it.url = url
|
||||||
it.name = name
|
it.name = name
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.episode_number = episodeNumber
|
it.episode_number = episodeNumber.toFloat()
|
||||||
it.scanlator = scanlator
|
it.scanlator = scanlator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,21 +21,11 @@ fun Episode.copyFromSEpisode(sEpisode: SEpisode): Episode {
|
||||||
name = sEpisode.name,
|
name = sEpisode.name,
|
||||||
url = sEpisode.url,
|
url = sEpisode.url,
|
||||||
dateUpload = sEpisode.date_upload,
|
dateUpload = sEpisode.date_upload,
|
||||||
episodeNumber = sEpisode.episode_number,
|
episodeNumber = sEpisode.episode_number.toDouble(),
|
||||||
scanlator = sEpisode.scanlator?.ifBlank { null },
|
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 {
|
fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
||||||
it.id = id
|
it.id = id
|
||||||
it.anime_id = animeId
|
it.anime_id = animeId
|
||||||
|
@ -49,6 +38,6 @@ fun Episode.toDbEpisode(): DbEpisode = EpisodeImpl().also {
|
||||||
it.total_seconds = totalSeconds
|
it.total_seconds = totalSeconds
|
||||||
it.date_fetch = dateFetch
|
it.date_fetch = dateFetch
|
||||||
it.date_upload = dateUpload
|
it.date_upload = dateUpload
|
||||||
it.episode_number = episodeNumber
|
it.episode_number = episodeNumber.toFloat()
|
||||||
it.source_order = sourceOrder.toInt()
|
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.domain.entries.anime.model.downloadedFilter
|
||||||
import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager
|
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.anime.model.Anime
|
||||||
import tachiyomi.domain.entries.applyFilter
|
import tachiyomi.domain.entries.applyFilter
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
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(bookmarkedFilter) { episode.bookmark } }
|
||||||
.filter { episode ->
|
.filter { episode ->
|
||||||
applyFilter(downloadedFilter) {
|
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
|
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.
|
* Applies the view filters to the list of episodes obtained from the database.
|
||||||
* @return an observable of the list of episodes filtered and sorted.
|
* @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 isLocalAnime = anime.isLocal()
|
||||||
val unseenFilter = anime.unseenFilter
|
val unseenFilter = anime.unseenFilter
|
||||||
val downloadedFilter = anime.downloadedFilter
|
val downloadedFilter = anime.downloadedFilter
|
||||||
|
|
|
@ -4,12 +4,11 @@ import eu.kanade.domain.source.service.SetMigrateSorting
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
import eu.kanade.domain.source.service.SourcePreferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.core.util.lang.compareToWithCollator
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||||
import java.text.Collator
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class GetAnimeSourcesWithFavoriteCount(
|
class GetAnimeSourcesWithFavoriteCount(
|
||||||
private val repository: AnimeSourceRepository,
|
private val repository: AnimeSourceRepository,
|
||||||
|
@ -32,17 +31,13 @@ class GetAnimeSourcesWithFavoriteCount(
|
||||||
direction: SetMigrateSorting.Direction,
|
direction: SetMigrateSorting.Direction,
|
||||||
sorting: SetMigrateSorting.Mode,
|
sorting: SetMigrateSorting.Mode,
|
||||||
): java.util.Comparator<Pair<AnimeSource, Long>> {
|
): 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 ->
|
val sortFn: (Pair<AnimeSource, Long>, Pair<AnimeSource, Long>) -> Int = { a, b ->
|
||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
b.first.isStub && a.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 -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
|
|
@ -6,13 +6,14 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
import tachiyomi.domain.source.anime.repository.AnimeSourceRepository
|
||||||
|
import java.util.SortedMap
|
||||||
|
|
||||||
class GetLanguagesWithAnimeSources(
|
class GetLanguagesWithAnimeSources(
|
||||||
private val repository: AnimeSourceRepository,
|
private val repository: AnimeSourceRepository,
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribe(): Flow<Map<String, List<AnimeSource>>> {
|
fun subscribe(): Flow<SortedMap<String, List<AnimeSource>>> {
|
||||||
return combine(
|
return combine(
|
||||||
preferences.enabledLanguages().changes(),
|
preferences.enabledLanguages().changes(),
|
||||||
preferences.disabledAnimeSources().changes(),
|
preferences.disabledAnimeSources().changes(),
|
||||||
|
@ -23,7 +24,8 @@ class GetLanguagesWithAnimeSources(
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
)
|
)
|
||||||
|
|
||||||
sortedSources.groupBy { it.lang }
|
sortedSources
|
||||||
|
.groupBy { it.lang }
|
||||||
.toSortedMap(
|
.toSortedMap(
|
||||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,7 +21,13 @@ class ToggleAnimeSource(
|
||||||
fun await(sourceIds: List<Long>, enable: Boolean) {
|
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||||
val transformedSourceIds = sourceIds.map { it.toString() }
|
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||||
preferences.disabledAnimeSources().getAndSet { disabled ->
|
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 kotlinx.coroutines.flow.combine
|
||||||
import tachiyomi.domain.source.manga.model.Source
|
import tachiyomi.domain.source.manga.model.Source
|
||||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||||
|
import java.util.SortedMap
|
||||||
|
|
||||||
class GetLanguagesWithMangaSources(
|
class GetLanguagesWithMangaSources(
|
||||||
private val repository: MangaSourceRepository,
|
private val repository: MangaSourceRepository,
|
||||||
private val preferences: SourcePreferences,
|
private val preferences: SourcePreferences,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun subscribe(): Flow<Map<String, List<Source>>> {
|
fun subscribe(): Flow<SortedMap<String, List<Source>>> {
|
||||||
return combine(
|
return combine(
|
||||||
preferences.enabledLanguages().changes(),
|
preferences.enabledLanguages().changes(),
|
||||||
preferences.disabledMangaSources().changes(),
|
preferences.disabledMangaSources().changes(),
|
||||||
|
@ -23,7 +24,8 @@ class GetLanguagesWithMangaSources(
|
||||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name },
|
||||||
)
|
)
|
||||||
|
|
||||||
sortedSources.groupBy { it.lang }
|
sortedSources
|
||||||
|
.groupBy { it.lang }
|
||||||
.toSortedMap(
|
.toSortedMap(
|
||||||
compareBy<String> { it !in enabledLanguage }.then(LocaleHelper.comparator),
|
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 eu.kanade.domain.source.service.SourcePreferences
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import tachiyomi.core.util.lang.compareToWithCollator
|
||||||
import tachiyomi.domain.source.manga.model.Source
|
import tachiyomi.domain.source.manga.model.Source
|
||||||
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
import tachiyomi.domain.source.manga.repository.MangaSourceRepository
|
||||||
import tachiyomi.source.local.entries.manga.LocalMangaSource
|
import tachiyomi.source.local.entries.manga.LocalMangaSource
|
||||||
import java.text.Collator
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class GetMangaSourcesWithFavoriteCount(
|
class GetMangaSourcesWithFavoriteCount(
|
||||||
private val repository: MangaSourceRepository,
|
private val repository: MangaSourceRepository,
|
||||||
|
@ -32,17 +31,13 @@ class GetMangaSourcesWithFavoriteCount(
|
||||||
direction: SetMigrateSorting.Direction,
|
direction: SetMigrateSorting.Direction,
|
||||||
sorting: SetMigrateSorting.Mode,
|
sorting: SetMigrateSorting.Mode,
|
||||||
): java.util.Comparator<Pair<Source, Long>> {
|
): 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 ->
|
val sortFn: (Pair<Source, Long>, Pair<Source, Long>) -> Int = { a, b ->
|
||||||
when (sorting) {
|
when (sorting) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
SetMigrateSorting.Mode.ALPHABETICAL -> {
|
||||||
when {
|
when {
|
||||||
a.first.isStub && b.first.isStub.not() -> -1
|
a.first.isStub && b.first.isStub.not() -> -1
|
||||||
b.first.isStub && a.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 -> {
|
SetMigrateSorting.Mode.TOTAL -> {
|
||||||
|
|
|
@ -21,7 +21,13 @@ class ToggleMangaSource(
|
||||||
fun await(sourceIds: List<Long>, enable: Boolean) {
|
fun await(sourceIds: List<Long>, enable: Boolean) {
|
||||||
val transformedSourceIds = sourceIds.map { it.toString() }
|
val transformedSourceIds = sourceIds.map { it.toString() }
|
||||||
preferences.disabledMangaSources().getAndSet { disabled ->
|
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
|
package eu.kanade.domain.source.service
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.preference.getEnum
|
import tachiyomi.core.preference.getEnum
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
@ -11,17 +12,31 @@ class SourcePreferences(
|
||||||
|
|
||||||
// Common options
|
// 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 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
|
// Mixture Sources
|
||||||
|
|
||||||
|
@ -31,18 +46,27 @@ class SourcePreferences(
|
||||||
fun pinnedAnimeSources() = preferenceStore.getStringSet("pinned_anime_catalogues", emptySet())
|
fun pinnedAnimeSources() = preferenceStore.getStringSet("pinned_anime_catalogues", emptySet())
|
||||||
fun pinnedMangaSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
fun pinnedMangaSources() = preferenceStore.getStringSet("pinned_catalogues", emptySet())
|
||||||
|
|
||||||
fun lastUsedAnimeSource() = preferenceStore.getLong("last_anime_catalogue_source", -1)
|
fun lastUsedAnimeSource() = preferenceStore.getLong(
|
||||||
fun lastUsedMangaSource() = preferenceStore.getLong("last_catalogue_source", -1)
|
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 animeExtensionUpdatesCount() = preferenceStore.getInt("animeext_updates_count", 0)
|
||||||
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
fun mangaExtensionUpdatesCount() = preferenceStore.getInt("ext_updates_count", 0)
|
||||||
|
|
||||||
fun searchPinnedAnimeSourcesOnly() = preferenceStore.getBoolean("search_pinned_anime_sources_only", false)
|
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean(
|
||||||
fun searchPinnedMangaSourcesOnly() = preferenceStore.getBoolean("search_pinned_sources_only", false)
|
"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",
|
||||||
fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
|
|
||||||
|
@ -62,7 +86,10 @@ class SourcePreferences(
|
||||||
|
|
||||||
fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80)
|
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", "")
|
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.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 logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
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.interactor.UpdateEpisode
|
||||||
import tachiyomi.domain.items.episode.model.Episode
|
|
||||||
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
import tachiyomi.domain.items.episode.model.toEpisodeUpdate
|
||||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
||||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
import tachiyomi.domain.track.anime.model.AnimeTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class SyncEpisodesWithTrackServiceTwoWay(
|
class SyncEpisodeProgressWithTrack(
|
||||||
private val updateEpisode: UpdateEpisode = Injekt.get(),
|
private val updateEpisode: UpdateEpisode,
|
||||||
private val insertTrack: InsertAnimeTrack = Injekt.get(),
|
private val insertTrack: InsertAnimeTrack,
|
||||||
|
private val getEpisodesByAnimeId: GetEpisodesByAnimeId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
episodes: List<Episode>,
|
animeId: Long,
|
||||||
remoteTrack: AnimeTrack,
|
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
|
val episodeUpdates = sortedEpisodes
|
||||||
.filter { episode -> episode.episodeNumber <= remoteTrack.lastEpisodeSeen && !episode.seen }
|
.filter { episode -> episode.episodeNumber <= remoteTrack.lastEpisodeSeen && !episode.seen }
|
||||||
.map { it.copy(seen = true).toEpisodeUpdate() }
|
.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.id = id
|
||||||
it.anime_id = animeId
|
it.anime_id = animeId
|
||||||
it.media_id = remoteId
|
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.last_episode_seen = lastEpisodeSeen.toFloat()
|
||||||
it.total_episodes = totalEpisodes.toInt()
|
it.total_episodes = totalEpisodes.toInt()
|
||||||
it.status = status.toInt()
|
it.status = status.toInt()
|
||||||
it.score = score
|
it.score = score.toFloat()
|
||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_watching_date = startDate
|
it.started_watching_date = startDate
|
||||||
it.finished_watching_date = finishDate
|
it.finished_watching_date = finishDate
|
||||||
|
@ -40,7 +42,7 @@ fun DbAnimeTrack.toDomainTrack(idRequired: Boolean = true): AnimeTrack? {
|
||||||
lastEpisodeSeen = last_episode_seen.toDouble(),
|
lastEpisodeSeen = last_episode_seen.toDouble(),
|
||||||
totalEpisodes = total_episodes.toLong(),
|
totalEpisodes = total_episodes.toLong(),
|
||||||
status = status.toLong(),
|
status = status.toLong(),
|
||||||
score = score,
|
score = score.toDouble(),
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_watching_date,
|
startDate = started_watching_date,
|
||||||
finishDate = finished_watching_date,
|
finishDate = finished_watching_date,
|
||||||
|
|
|
@ -8,30 +8,32 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkerParameters
|
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.domain.track.anime.store.DelayedAnimeTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
import tachiyomi.domain.track.anime.interactor.GetAnimeTracks
|
||||||
import tachiyomi.domain.track.anime.interactor.InsertAnimeTrack
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.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) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val getTracks = Injekt.get<GetAnimeTracks>()
|
if (runAttemptCount > 3) {
|
||||||
val insertTrack = Injekt.get<InsertAnimeTrack>()
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
val getTracks = Injekt.get<GetAnimeTracks>()
|
||||||
|
val trackEpisode = Injekt.get<TrackEpisode>()
|
||||||
|
|
||||||
val trackManager = Injekt.get<TrackManager>()
|
|
||||||
val delayedTrackingStore = Injekt.get<DelayedAnimeTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedAnimeTrackingStore>()
|
||||||
|
|
||||||
val results = withIOContext {
|
withIOContext {
|
||||||
delayedTrackingStore.getAnimeItems()
|
delayedTrackingStore.getAnimeItems()
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
val track = getTracks.awaitOne(it.trackId)
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
|
@ -40,24 +42,16 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
||||||
}
|
}
|
||||||
track?.copy(lastEpisodeSeen = it.lastEpisodeSeen.toDouble())
|
track?.copy(lastEpisodeSeen = it.lastEpisodeSeen.toDouble())
|
||||||
}
|
}
|
||||||
.mapNotNull { animeTrack ->
|
.forEach { animeTrack ->
|
||||||
try {
|
logcat(LogPriority.DEBUG) {
|
||||||
val service = trackManager.getService(animeTrack.syncId)
|
"Updating delayed track item: ${animeTrack.animeId}" +
|
||||||
if (service != null && service.isLogged) {
|
", last chapter read: ${animeTrack.lastEpisodeSeen}"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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 {
|
companion object {
|
||||||
|
@ -70,7 +64,7 @@ class DelayedAnimeTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
||||||
|
|
||||||
val request = OneTimeWorkRequestBuilder<DelayedAnimeTrackingUpdateJob>()
|
val request = OneTimeWorkRequestBuilder<DelayedAnimeTrackingUpdateJob>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.anime.model.AnimeTrack
|
|
||||||
|
|
||||||
class DelayedAnimeTrackingStore(context: Context) {
|
class DelayedAnimeTrackingStore(context: Context) {
|
||||||
|
|
||||||
|
@ -13,13 +12,12 @@ class DelayedAnimeTrackingStore(context: Context) {
|
||||||
*/
|
*/
|
||||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun addAnimeItem(track: AnimeTrack) {
|
fun addAnime(trackId: Long, lastEpisodeSeen: Double) {
|
||||||
val trackId = track.id.toString()
|
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||||
val lastEpisodeSeen = preferences.getFloat(trackId, 0f)
|
if (lastEpisodeSeen > previousLastChapterRead) {
|
||||||
if (track.lastEpisodeSeen > lastEpisodeSeen) {
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last episode seen: $lastEpisodeSeen" }
|
||||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last episode seen: ${track.lastEpisodeSeen}" }
|
|
||||||
preferences.edit {
|
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.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 logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
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.interactor.UpdateChapter
|
||||||
import tachiyomi.domain.items.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
import tachiyomi.domain.items.chapter.model.toChapterUpdate
|
||||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
||||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
import tachiyomi.domain.track.manga.model.MangaTrack
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class SyncChaptersWithTrackServiceTwoWay(
|
class SyncChapterProgressWithTrack(
|
||||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
private val updateChapter: UpdateChapter,
|
||||||
private val insertTrack: InsertMangaTrack = Injekt.get(),
|
private val insertTrack: InsertMangaTrack,
|
||||||
|
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun await(
|
suspend fun await(
|
||||||
chapters: List<Chapter>,
|
mangaId: Long,
|
||||||
remoteTrack: MangaTrack,
|
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
|
val chapterUpdates = sortedChapters
|
||||||
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
|
||||||
.map { it.copy(read = true).toChapterUpdate() }
|
.map { it.copy(read = true).toChapterUpdate() }
|
||||||
|
@ -32,7 +39,7 @@ class SyncChaptersWithTrackServiceTwoWay(
|
||||||
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
val updatedTrack = remoteTrack.copy(lastChapterRead = localLastRead.toDouble())
|
||||||
|
|
||||||
try {
|
try {
|
||||||
service.update(updatedTrack.toDbTrack())
|
tracker.update(updatedTrack.toDbTrack())
|
||||||
updateChapter.awaitAll(chapterUpdates)
|
updateChapter.awaitAll(chapterUpdates)
|
||||||
insertTrack.await(updatedTrack)
|
insertTrack.await(updatedTrack)
|
||||||
} catch (e: Throwable) {
|
} 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.id = id
|
||||||
it.manga_id = mangaId
|
it.manga_id = mangaId
|
||||||
it.media_id = remoteId
|
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.last_chapter_read = lastChapterRead.toFloat()
|
||||||
it.total_chapters = totalChapters.toInt()
|
it.total_chapters = totalChapters.toInt()
|
||||||
it.status = status.toInt()
|
it.status = status.toInt()
|
||||||
it.score = score
|
it.score = score.toFloat()
|
||||||
it.tracking_url = remoteUrl
|
it.tracking_url = remoteUrl
|
||||||
it.started_reading_date = startDate
|
it.started_reading_date = startDate
|
||||||
it.finished_reading_date = finishDate
|
it.finished_reading_date = finishDate
|
||||||
|
@ -40,7 +42,7 @@ fun DbMangaTrack.toDomainTrack(idRequired: Boolean = true): MangaTrack? {
|
||||||
lastChapterRead = last_chapter_read.toDouble(),
|
lastChapterRead = last_chapter_read.toDouble(),
|
||||||
totalChapters = total_chapters.toLong(),
|
totalChapters = total_chapters.toLong(),
|
||||||
status = status.toLong(),
|
status = status.toLong(),
|
||||||
score = score,
|
score = score.toDouble(),
|
||||||
remoteUrl = tracking_url,
|
remoteUrl = tracking_url,
|
||||||
startDate = started_reading_date,
|
startDate = started_reading_date,
|
||||||
finishDate = finished_reading_date,
|
finishDate = finished_reading_date,
|
||||||
|
|
|
@ -8,30 +8,32 @@ import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.WorkerParameters
|
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.domain.track.manga.store.DelayedMangaTrackingStore
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.workManager
|
import eu.kanade.tachiyomi.util.system.workManager
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
import tachiyomi.domain.track.manga.interactor.GetMangaTracks
|
||||||
import tachiyomi.domain.track.manga.interactor.InsertMangaTrack
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.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) {
|
CoroutineWorker(context, workerParams) {
|
||||||
|
|
||||||
override suspend fun doWork(): Result {
|
override suspend fun doWork(): Result {
|
||||||
val getTracks = Injekt.get<GetMangaTracks>()
|
if (runAttemptCount > 3) {
|
||||||
val insertTrack = Injekt.get<InsertMangaTrack>()
|
return Result.failure()
|
||||||
|
}
|
||||||
|
|
||||||
|
val getTracks = Injekt.get<GetMangaTracks>()
|
||||||
|
val trackChapter = Injekt.get<TrackChapter>()
|
||||||
|
|
||||||
val trackManager = Injekt.get<TrackManager>()
|
|
||||||
val delayedTrackingStore = Injekt.get<DelayedMangaTrackingStore>()
|
val delayedTrackingStore = Injekt.get<DelayedMangaTrackingStore>()
|
||||||
|
|
||||||
val results = withIOContext {
|
withIOContext {
|
||||||
delayedTrackingStore.getMangaItems()
|
delayedTrackingStore.getMangaItems()
|
||||||
.mapNotNull {
|
.mapNotNull {
|
||||||
val track = getTracks.awaitOne(it.trackId)
|
val track = getTracks.awaitOne(it.trackId)
|
||||||
|
@ -40,24 +42,15 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
||||||
}
|
}
|
||||||
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
track?.copy(lastChapterRead = it.lastChapterRead.toDouble())
|
||||||
}
|
}
|
||||||
.mapNotNull { track ->
|
.forEach { track ->
|
||||||
try {
|
logcat(LogPriority.DEBUG) {
|
||||||
val service = trackManager.getService(track.syncId)
|
"Updating delayed track item: ${track.mangaId}, last chapter read: ${track.lastChapterRead}"
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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 {
|
companion object {
|
||||||
|
@ -70,7 +63,7 @@ class DelayedMangaTrackingUpdateJob(context: Context, workerParams: WorkerParame
|
||||||
|
|
||||||
val request = OneTimeWorkRequestBuilder<DelayedMangaTrackingUpdateJob>()
|
val request = OneTimeWorkRequestBuilder<DelayedMangaTrackingUpdateJob>()
|
||||||
.setConstraints(constraints)
|
.setConstraints(constraints)
|
||||||
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 20, TimeUnit.SECONDS)
|
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 5.minutes.toJavaDuration())
|
||||||
.addTag(TAG)
|
.addTag(TAG)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
import tachiyomi.domain.track.manga.model.MangaTrack
|
|
||||||
|
|
||||||
class DelayedMangaTrackingStore(context: Context) {
|
class DelayedMangaTrackingStore(context: Context) {
|
||||||
|
|
||||||
|
@ -13,13 +12,12 @@ class DelayedMangaTrackingStore(context: Context) {
|
||||||
*/
|
*/
|
||||||
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
private val preferences = context.getSharedPreferences("tracking_queue", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
fun addMangaItem(track: MangaTrack) {
|
fun addManga(trackId: Long, lastChapterRead: Double) {
|
||||||
val trackId = track.id.toString()
|
val previousLastChapterRead = preferences.getFloat(trackId.toString(), 0f)
|
||||||
val lastChapterRead = preferences.getFloat(trackId, 0f)
|
if (lastChapterRead > previousLastChapterRead) {
|
||||||
if (track.lastChapterRead > lastChapterRead) {
|
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: $lastChapterRead" }
|
||||||
logcat(LogPriority.DEBUG) { "Queuing track item: $trackId, last chapter read: ${track.lastChapterRead}" }
|
|
||||||
preferences.edit {
|
preferences.edit {
|
||||||
putFloat(trackId, track.lastChapterRead.toFloat())
|
putFloat(trackId.toString(), lastChapterRead.toFloat())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.kanade.domain.track.service
|
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 eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
|
|
||||||
|
@ -8,16 +8,16 @@ class TrackPreferences(
|
||||||
private val preferenceStore: PreferenceStore,
|
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)
|
trackUsername(sync).set(username)
|
||||||
trackPassword(sync).set(password)
|
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)
|
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 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 {
|
companion object {
|
||||||
fun trackUsername(syncId: Long) = "pref_mangasync_username_$syncId"
|
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 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", "")
|
fun dateFormat() = preferenceStore.getString("app_date_format", "")
|
||||||
|
|
||||||
|
|
|
@ -5,21 +5,21 @@ import eu.kanade.tachiyomi.R
|
||||||
enum class AppTheme(val titleResId: Int?) {
|
enum class AppTheme(val titleResId: Int?) {
|
||||||
DEFAULT(R.string.label_default),
|
DEFAULT(R.string.label_default),
|
||||||
MONET(R.string.theme_monet),
|
MONET(R.string.theme_monet),
|
||||||
|
CLOUDFLARE(R.string.theme_cloudflare),
|
||||||
COTTONCANDY(R.string.theme_cottoncandy),
|
COTTONCANDY(R.string.theme_cottoncandy),
|
||||||
|
DOOM(R.string.theme_doom),
|
||||||
GREEN_APPLE(R.string.theme_greenapple),
|
GREEN_APPLE(R.string.theme_greenapple),
|
||||||
LAVENDER(R.string.theme_lavender),
|
LAVENDER(R.string.theme_lavender),
|
||||||
|
MATRIX(R.string.theme_matrix),
|
||||||
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
MIDNIGHT_DUSK(R.string.theme_midnightdusk),
|
||||||
MOCHA(R.string.theme_mocha),
|
MOCHA(R.string.theme_mocha),
|
||||||
|
SAPPHIRE(R.string.theme_sapphire),
|
||||||
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
STRAWBERRY_DAIQUIRI(R.string.theme_strawberrydaiquiri),
|
||||||
TAKO(R.string.theme_tako),
|
TAKO(R.string.theme_tako),
|
||||||
TEALTURQUOISE(R.string.theme_tealturquoise),
|
TEALTURQUOISE(R.string.theme_tealturquoise),
|
||||||
TIDAL_WAVE(R.string.theme_tidalwave),
|
TIDAL_WAVE(R.string.theme_tidalwave),
|
||||||
YINYANG(R.string.theme_yinyang),
|
YINYANG(R.string.theme_yinyang),
|
||||||
YOTSUBA(R.string.theme_yotsuba),
|
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
|
// Deprecated
|
||||||
DARK_BLUE(null),
|
DARK_BLUE(null),
|
||||||
|
|
|
@ -25,7 +25,10 @@ fun BaseBrowseItem(
|
||||||
onClick = onClickItem,
|
onClick = onClickItem,
|
||||||
onLongClick = onLongClickItem,
|
onLongClick = onLongClickItem,
|
||||||
)
|
)
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
icon()
|
icon()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
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.ArrowForward
|
||||||
import androidx.compose.material.icons.outlined.Error
|
import androidx.compose.material.icons.outlined.Error
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
@ -54,25 +55,13 @@ fun GlobalSearchResultItem(
|
||||||
Text(text = subtitle)
|
Text(text = subtitle)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(imageVector = Icons.Outlined.ArrowForward, contentDescription = null)
|
Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content()
|
content()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GlobalSearchEmptyResultItem() {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.no_results_found),
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
horizontal = MaterialTheme.padding.medium,
|
|
||||||
vertical = MaterialTheme.padding.small,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalSearchLoadingResultItem() {
|
fun GlobalSearchLoadingResultItem() {
|
||||||
Box(
|
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.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
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.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -52,11 +53,10 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
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.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
@ -64,7 +64,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimeExtensionDetailsScreen(
|
fun AnimeExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: AnimeExtensionDetailsState,
|
state: AnimeExtensionDetailsScreenModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickWhatsNew: () -> Unit,
|
onClickWhatsNew: () -> Unit,
|
||||||
onClickReadme: () -> Unit,
|
onClickReadme: () -> Unit,
|
||||||
|
@ -81,40 +81,42 @@ fun AnimeExtensionDetailsScreen(
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
if (state.extension?.isUnofficial == false) {
|
.apply {
|
||||||
add(
|
if (state.extension?.isUnofficial == false) {
|
||||||
AppBar.Action(
|
add(
|
||||||
title = stringResource(R.string.whats_new),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.History,
|
title = stringResource(R.string.whats_new),
|
||||||
onClick = onClickWhatsNew,
|
icon = Icons.Outlined.History,
|
||||||
),
|
onClick = onClickWhatsNew,
|
||||||
)
|
),
|
||||||
add(
|
)
|
||||||
AppBar.Action(
|
add(
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.HelpOutline,
|
title = stringResource(R.string.action_faq_and_guides),
|
||||||
onClick = onClickReadme,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
|
onClick = onClickReadme,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addAll(
|
||||||
|
listOf(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_enable_all),
|
||||||
|
onClick = onClickEnableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_disable_all),
|
||||||
|
onClick = onClickDisableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.pref_clear_cookies),
|
||||||
|
onClick = onClickClearCookies,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addAll(
|
.build(),
|
||||||
listOf(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_enable_all),
|
|
||||||
onClick = onClickEnableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_disable_all),
|
|
||||||
onClick = onClickDisableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
|
||||||
onClick = onClickClearCookies,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
@ -175,7 +177,8 @@ private fun AnimeExtensionDetails(
|
||||||
data = Uri.fromParts("package", extension.pkgName, null)
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
context.startActivity(this)
|
context.startActivity(this)
|
||||||
}
|
}
|
||||||
},
|
Unit
|
||||||
|
}.takeIf { extension.isShared },
|
||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
|
@ -187,7 +190,6 @@ private fun AnimeExtensionDetails(
|
||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -208,7 +210,7 @@ private fun DetailsHeader(
|
||||||
extension: AnimeExtension,
|
extension: AnimeExtension,
|
||||||
onClickAgeRating: () -> Unit,
|
onClickAgeRating: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: () -> Unit,
|
onClickAppInfo: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
@ -237,7 +239,9 @@ private fun DetailsHeader(
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
|
|
||||||
val strippedPkgName = extension.pkgName.substringAfter("eu.kanade.tachiyomi.animeextension.")
|
val strippedPkgName = extension.pkgName.substringAfter(
|
||||||
|
"eu.kanade.tachiyomi.animeextension.",
|
||||||
|
)
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = strippedPkgName,
|
text = strippedPkgName,
|
||||||
|
@ -292,6 +296,7 @@ private fun DetailsHeader(
|
||||||
top = MaterialTheme.padding.small,
|
top = MaterialTheme.padding.small,
|
||||||
bottom = MaterialTheme.padding.medium,
|
bottom = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
@ -300,29 +305,29 @@ private fun DetailsHeader(
|
||||||
Text(stringResource(R.string.ext_uninstall))
|
Text(stringResource(R.string.ext_uninstall))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.width(16.dp))
|
if (onClickAppInfo != null) {
|
||||||
|
Button(
|
||||||
Button(
|
modifier = Modifier.weight(1f),
|
||||||
modifier = Modifier.weight(1f),
|
onClick = onClickAppInfo,
|
||||||
onClick = onClickAppInfo,
|
) {
|
||||||
) {
|
Text(
|
||||||
Text(
|
text = stringResource(R.string.ext_app_info),
|
||||||
text = stringResource(R.string.ext_app_info),
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoText(
|
private fun InfoText(
|
||||||
modifier: Modifier,
|
|
||||||
primaryText: String,
|
primaryText: String,
|
||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
|
||||||
secondaryText: String,
|
secondaryText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
@ -355,20 +360,17 @@ private fun InfoText(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoDivider() {
|
private fun InfoDivider() {
|
||||||
Divider(
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier.height(20.dp),
|
||||||
.height(20.dp)
|
|
||||||
.width(1.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceSwitchPreference(
|
private fun SourceSwitchPreference(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: AnimeExtensionSourceItem,
|
source: AnimeExtensionSourceItem,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
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.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionFilterState
|
import eu.kanade.tachiyomi.ui.browse.anime.extension.AnimeExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
|
@ -53,12 +53,11 @@ private fun AnimeExtensionFilterContent(
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
FastScrollLazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
|
|
@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
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.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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
|
@ -56,7 +56,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimeExtensionScreen(
|
fun AnimeExtensionScreen(
|
||||||
state: AnimeExtensionsState,
|
state: AnimeExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (AnimeExtension) -> Unit,
|
onLongClickItem: (AnimeExtension) -> Unit,
|
||||||
|
@ -72,10 +72,10 @@ fun AnimeExtensionScreen(
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !state.isLoading,
|
enabled = { !state.isLoading },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> {
|
state.isEmpty -> {
|
||||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
R.string.no_results_found
|
||||||
|
@ -107,7 +107,7 @@ fun AnimeExtensionScreen(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeExtensionContent(
|
private fun AnimeExtensionContent(
|
||||||
state: AnimeExtensionsState,
|
state: AnimeExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (AnimeExtension) -> Unit,
|
onLongClickItem: (AnimeExtension) -> Unit,
|
||||||
onClickItemCancel: (AnimeExtension) -> Unit,
|
onClickItemCancel: (AnimeExtension) -> Unit,
|
||||||
|
@ -147,14 +147,13 @@ private fun AnimeExtensionContent(
|
||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AnimeExtensionUiModel.Header.Text -> {
|
is AnimeExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,7 +165,7 @@ private fun AnimeExtensionContent(
|
||||||
key = { "extension-${it.hashCode()}" },
|
key = { "extension-${it.hashCode()}" },
|
||||||
) { item ->
|
) { item ->
|
||||||
AnimeExtensionItem(
|
AnimeExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -216,12 +215,12 @@ private fun AnimeExtensionContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeExtensionItem(
|
private fun AnimeExtensionItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: AnimeExtensionUiModel.Item,
|
item: AnimeExtensionUiModel.Item,
|
||||||
onClickItem: (AnimeExtension) -> Unit,
|
onClickItem: (AnimeExtension) -> Unit,
|
||||||
onLongClickItem: (AnimeExtension) -> Unit,
|
onLongClickItem: (AnimeExtension) -> Unit,
|
||||||
onClickItemCancel: (AnimeExtension) -> Unit,
|
onClickItemCancel: (AnimeExtension) -> Unit,
|
||||||
onClickItemAction: (AnimeExtension) -> Unit,
|
onClickItemAction: (AnimeExtension) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val (extension, installStep) = item
|
val (extension, installStep) = item
|
||||||
BaseBrowseItem(
|
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(
|
AnimeExtensionIcon(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -295,7 +297,10 @@ private fun AnimeExtensionItemContent(
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
if (extension is AnimeExtension.Installed && extension.lang.isNotEmpty()) {
|
if (extension is AnimeExtension.Installed && extension.lang.isNotEmpty()) {
|
||||||
Text(
|
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.components.AppBar
|
||||||
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
import eu.kanade.presentation.more.settings.widget.SwitchPreferenceWidget
|
||||||
import eu.kanade.tachiyomi.R
|
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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
|
@ -22,7 +22,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimeSourcesFilterScreen(
|
fun AnimeSourcesFilterScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: AnimeSourcesFilterState.Success,
|
state: AnimeSourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (AnimeSource) -> Unit,
|
onClickSource: (AnimeSource) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -54,7 +54,7 @@ fun AnimeSourcesFilterScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeSourcesFilterContent(
|
private fun AnimeSourcesFilterContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: AnimeSourcesFilterState.Success,
|
state: AnimeSourcesFilterScreenModel.State.Success,
|
||||||
onClickLanguage: (String) -> Unit,
|
onClickLanguage: (String) -> Unit,
|
||||||
onClickSource: (AnimeSource) -> Unit,
|
onClickSource: (AnimeSource) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -64,28 +64,29 @@ private fun AnimeSourcesFilterContent(
|
||||||
state.items.forEach { (language, sources) ->
|
state.items.forEach { (language, sources) ->
|
||||||
val enabled = language in state.enabledLanguages
|
val enabled = language in state.enabledLanguages
|
||||||
item(
|
item(
|
||||||
key = language.hashCode(),
|
key = language,
|
||||||
contentType = "source-filter-header",
|
contentType = "source-filter-header",
|
||||||
) {
|
) {
|
||||||
AnimeSourcesFilterHeader(
|
AnimeSourcesFilterHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
language = language,
|
language = language,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClickItem = onClickLanguage,
|
onClickItem = onClickLanguage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!enabled) return@forEach
|
if (enabled) {
|
||||||
items(
|
items(
|
||||||
items = sources,
|
items = sources,
|
||||||
key = { "source-filter-${it.key()}" },
|
key = { "source-filter-${it.key()}" },
|
||||||
contentType = { "source-filter-item" },
|
contentType = { "source-filter-item" },
|
||||||
) { source ->
|
) { source ->
|
||||||
AnimeSourcesFilterItem(
|
AnimeSourcesFilterItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
source = source,
|
source = source,
|
||||||
isEnabled = "${source.id}" !in state.disabledSources,
|
isEnabled = "${source.id}" !in state.disabledSources,
|
||||||
onClickItem = onClickSource,
|
onClickItem = onClickSource,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,10 +94,10 @@ private fun AnimeSourcesFilterContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimeSourcesFilterHeader(
|
fun AnimeSourcesFilterHeader(
|
||||||
modifier: Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
onClickItem: (String) -> Unit,
|
onClickItem: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -108,10 +109,10 @@ fun AnimeSourcesFilterHeader(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeSourcesFilterItem(
|
private fun AnimeSourcesFilterItem(
|
||||||
modifier: Modifier,
|
|
||||||
source: AnimeSource,
|
source: AnimeSource,
|
||||||
isEnabled: Boolean,
|
isEnabled: Boolean,
|
||||||
onClickItem: (AnimeSource) -> Unit,
|
onClickItem: (AnimeSource) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseAnimeSourceItem(
|
BaseAnimeSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
||||||
import eu.kanade.tachiyomi.R
|
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.ui.browse.anime.source.browse.BrowseAnimeSourceScreenModel.Listing
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
|
@ -40,14 +40,14 @@ import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimeSourcesScreen(
|
fun AnimeSourcesScreen(
|
||||||
state: AnimeSourcesState,
|
state: AnimeSourcesScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (AnimeSource, Listing) -> Unit,
|
onClickItem: (AnimeSource, Listing) -> Unit,
|
||||||
onClickPin: (AnimeSource) -> Unit,
|
onClickPin: (AnimeSource) -> Unit,
|
||||||
onLongClickItem: (AnimeSource) -> Unit,
|
onLongClickItem: (AnimeSource) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.source_empty_screen,
|
textResource = R.string.source_empty_screen,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
@ -74,12 +74,12 @@ fun AnimeSourcesScreen(
|
||||||
when (model) {
|
when (model) {
|
||||||
is AnimeSourceUiModel.Header -> {
|
is AnimeSourceUiModel.Header -> {
|
||||||
AnimeSourceHeader(
|
AnimeSourceHeader(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
language = model.language,
|
language = model.language,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is AnimeSourceUiModel.Item -> AnimeSourceItem(
|
is AnimeSourceUiModel.Item -> AnimeSourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
source = model.source,
|
source = model.source,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
onLongClickItem = onLongClickItem,
|
onLongClickItem = onLongClickItem,
|
||||||
|
@ -94,25 +94,28 @@ fun AnimeSourcesScreen(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeSourceHeader(
|
private fun AnimeSourceHeader(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
language: String,
|
language: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
Text(
|
Text(
|
||||||
text = LocaleHelper.getSourceDisplayName(language, context),
|
text = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
|
.padding(
|
||||||
|
horizontal = MaterialTheme.padding.medium,
|
||||||
|
vertical = MaterialTheme.padding.small,
|
||||||
|
),
|
||||||
style = MaterialTheme.typography.header,
|
style = MaterialTheme.typography.header,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun AnimeSourceItem(
|
private fun AnimeSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: AnimeSource,
|
source: AnimeSource,
|
||||||
onClickItem: (AnimeSource, Listing) -> Unit,
|
onClickItem: (AnimeSource, Listing) -> Unit,
|
||||||
onLongClickItem: (AnimeSource) -> Unit,
|
onLongClickItem: (AnimeSource) -> Unit,
|
||||||
onClickPin: (AnimeSource) -> Unit,
|
onClickPin: (AnimeSource) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseAnimeSourceItem(
|
BaseAnimeSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
@ -144,7 +147,13 @@ private fun AnimeSourcePinButton(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val icon = if (isPinned) Icons.Filled.PushPin else Icons.Outlined.PushPin
|
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
|
val description = if (isPinned) R.string.action_unpin else R.string.action_pin
|
||||||
IconButton(onClick = onClick) {
|
IconButton(onClick = onClick) {
|
||||||
Icon(
|
Icon(
|
||||||
|
@ -192,7 +201,7 @@ fun AnimeSourceOptionsDialog(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class AnimeSourceUiModel {
|
sealed interface AnimeSourceUiModel {
|
||||||
data class Item(val source: AnimeSource) : AnimeSourceUiModel()
|
data class Item(val source: AnimeSource) : AnimeSourceUiModel
|
||||||
data class Header(val language: String) : 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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
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.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
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.presentation.util.formattedMessage
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
@ -61,12 +63,12 @@ fun BrowseAnimeSourceContent(
|
||||||
if (animeList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
if (animeList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
actionLabel = context.getString(R.string.action_retry),
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
when (result) {
|
when (result) {
|
||||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
SnackbarResult.ActionPerformed -> animeList.refresh()
|
SnackbarResult.ActionPerformed -> animeList.retry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,15 +78,15 @@ fun BrowseAnimeSourceContent(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (source is LocalAnimeSource) {
|
actions = if (source is LocalAnimeSource) {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onLocalAnimeSourceHelpClick,
|
onClick = onLocalAnimeSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringResId = R.string.action_retry,
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
|
@ -97,7 +99,7 @@ fun BrowseAnimeSourceContent(
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringResId = R.string.label_help,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -145,7 +147,7 @@ fun BrowseAnimeSourceContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MissingSourceScreen(
|
internal fun MissingSourceScreen(
|
||||||
source: StubAnimeSource,
|
source: StubAnimeSource,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,34 +1,30 @@
|
||||||
package eu.kanade.presentation.browse.anime
|
package eu.kanade.presentation.browse.anime
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
||||||
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
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.GlobalAnimeSearchCardRow
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeCatalogueSource
|
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.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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
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.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalAnimeSearchScreen(
|
fun GlobalAnimeSearchScreen(
|
||||||
state: GlobalAnimeSearchState,
|
state: AnimeSearchScreenModel.State,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
getAnime: @Composable (Anime) -> State<Anime>,
|
getAnime: @Composable (Anime) -> State<Anime>,
|
||||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
|
@ -36,19 +32,23 @@ fun GlobalAnimeSearchScreen(
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
GlobalSearchToolbar(
|
GlobalAnimeSearchToolbar(
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
progress = state.progress,
|
progress = state.progress,
|
||||||
total = state.total,
|
total = state.total,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalAnimeSearchContent(
|
GlobalSearchContent(
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getAnime = getAnime,
|
getAnime = getAnime,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -59,13 +59,14 @@ fun GlobalAnimeSearchScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GlobalAnimeSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
items: Map<AnimeCatalogueSource, AnimeSearchItemResult>,
|
items: Map<AnimeCatalogueSource, AnimeSearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getAnime: @Composable (Anime) -> State<Anime>,
|
getAnime: @Composable (Anime) -> State<Anime>,
|
||||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
onLongClickItem: (Anime) -> Unit,
|
onLongClickItem: (Anime) -> Unit,
|
||||||
|
fromSourceId: Long? = null,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
|
@ -73,7 +74,8 @@ private fun GlobalAnimeSearchContent(
|
||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = source.name,
|
title = fromSourceId
|
||||||
|
?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
|
@ -82,18 +84,6 @@ private fun GlobalAnimeSearchContent(
|
||||||
GlobalSearchLoadingResultItem()
|
GlobalSearchLoadingResultItem()
|
||||||
}
|
}
|
||||||
is AnimeSearchItemResult.Success -> {
|
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(
|
GlobalAnimeSearchCardRow(
|
||||||
titles = result.result,
|
titles = result.result,
|
||||||
getAnime = getAnime,
|
getAnime = getAnime,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier
|
||||||
import eu.kanade.presentation.components.AppBar
|
import eu.kanade.presentation.components.AppBar
|
||||||
import eu.kanade.presentation.entries.anime.components.BaseAnimeListItem
|
import eu.kanade.presentation.entries.anime.components.BaseAnimeListItem
|
||||||
import eu.kanade.tachiyomi.R
|
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.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
@ -18,7 +18,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
fun MigrateAnimeScreen(
|
fun MigrateAnimeScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
title: String?,
|
title: String?,
|
||||||
state: MigrateAnimeState,
|
state: MigrateAnimeScreenModel.State,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
onClickCover: (Anime) -> Unit,
|
onClickCover: (Anime) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -51,7 +51,7 @@ fun MigrateAnimeScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateAnimeContent(
|
private fun MigrateAnimeContent(
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
state: MigrateAnimeState,
|
state: MigrateAnimeScreenModel.State,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
onClickCover: (Anime) -> Unit,
|
onClickCover: (Anime) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -70,10 +70,10 @@ private fun MigrateAnimeContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateAnimeItem(
|
private fun MigrateAnimeItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
anime: Anime,
|
anime: Anime,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
onClickCover: (Anime) -> Unit,
|
onClickCover: (Anime) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseAnimeListItem(
|
BaseAnimeListItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
@ -1,49 +1,48 @@
|
||||||
package eu.kanade.presentation.browse.anime
|
package eu.kanade.presentation.browse.anime
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import eu.kanade.presentation.browse.GlobalSearchEmptyResultItem
|
import eu.kanade.presentation.browse.anime.components.GlobalAnimeSearchToolbar
|
||||||
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.animesource.AnimeCatalogueSource
|
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.AnimeSearchScreenModel
|
||||||
import eu.kanade.tachiyomi.ui.browse.anime.source.globalsearch.AnimeSearchItemResult
|
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.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.presentation.core.components.LazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateAnimeSearchScreen(
|
fun MigrateAnimeSearchScreen(
|
||||||
|
state: AnimeSearchScreenModel.State,
|
||||||
|
fromSourceId: Long?,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: MigrateAnimeSearchState,
|
|
||||||
getAnime: @Composable (Anime) -> State<Anime>,
|
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (AnimeSourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
|
getAnime: @Composable (Anime) -> State<Anime>,
|
||||||
onClickSource: (AnimeCatalogueSource) -> Unit,
|
onClickSource: (AnimeCatalogueSource) -> Unit,
|
||||||
onClickItem: (Anime) -> Unit,
|
onClickItem: (Anime) -> Unit,
|
||||||
onLongClickItem: (Anime) -> Unit,
|
onLongClickItem: (Anime) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
GlobalSearchToolbar(
|
GlobalAnimeSearchToolbar(
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
progress = state.progress,
|
progress = state.progress,
|
||||||
total = state.total,
|
total = state.total,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
MigrateAnimeSearchContent(
|
GlobalSearchContent(
|
||||||
sourceId = state.anime?.source ?: -1,
|
fromSourceId = fromSourceId,
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getAnime = getAnime,
|
getAnime = getAnime,
|
||||||
onClickSource = onClickSource,
|
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.AnimeSourceIcon
|
||||||
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
import eu.kanade.presentation.browse.anime.components.BaseAnimeSourceItem
|
||||||
import eu.kanade.tachiyomi.R
|
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 eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
import tachiyomi.presentation.core.components.Badge
|
import tachiyomi.presentation.core.components.Badge
|
||||||
|
@ -43,7 +43,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MigrateAnimeSourceScreen(
|
fun MigrateAnimeSourceScreen(
|
||||||
state: MigrateAnimeSourceState,
|
state: MigrateAnimeSourceScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onClickItem: (AnimeSource) -> Unit,
|
onClickItem: (AnimeSource) -> Unit,
|
||||||
onToggleSortingDirection: () -> Unit,
|
onToggleSortingDirection: () -> Unit,
|
||||||
|
@ -51,7 +51,7 @@ fun MigrateAnimeSourceScreen(
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> EmptyScreen(
|
state.isEmpty -> EmptyScreen(
|
||||||
textResource = R.string.information_empty_library,
|
textResource = R.string.information_empty_library,
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
|
@ -102,14 +102,26 @@ private fun MigrateAnimeSourceList(
|
||||||
|
|
||||||
IconButton(onClick = onToggleSortingMode) {
|
IconButton(onClick = onToggleSortingMode) {
|
||||||
when (sortingMode) {
|
when (sortingMode) {
|
||||||
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(Icons.Outlined.SortByAlpha, contentDescription = stringResource(R.string.action_sort_alpha))
|
SetMigrateSorting.Mode.ALPHABETICAL -> Icon(
|
||||||
SetMigrateSorting.Mode.TOTAL -> Icon(Icons.Outlined.Numbers, contentDescription = stringResource(R.string.action_sort_count))
|
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) {
|
IconButton(onClick = onToggleSortingDirection) {
|
||||||
when (sortingDirection) {
|
when (sortingDirection) {
|
||||||
SetMigrateSorting.Direction.ASCENDING -> Icon(Icons.Outlined.ArrowUpward, contentDescription = stringResource(R.string.action_asc))
|
SetMigrateSorting.Direction.ASCENDING -> Icon(
|
||||||
SetMigrateSorting.Direction.DESCENDING -> Icon(Icons.Outlined.ArrowDownward, contentDescription = stringResource(R.string.action_desc))
|
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}" },
|
key = { (source, _) -> "migrate-${source.id}" },
|
||||||
) { (source, count) ->
|
) { (source, count) ->
|
||||||
MigrateAnimeSourceItem(
|
MigrateAnimeSourceItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
source = source,
|
source = source,
|
||||||
count = count,
|
count = count,
|
||||||
onClickItem = { onClickItem(source) },
|
onClickItem = { onClickItem(source) },
|
||||||
|
@ -132,11 +144,11 @@ private fun MigrateAnimeSourceList(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MigrateAnimeSourceItem(
|
private fun MigrateAnimeSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: AnimeSource,
|
source: AnimeSource,
|
||||||
count: Long,
|
count: Long,
|
||||||
onClickItem: () -> Unit,
|
onClickItem: () -> Unit,
|
||||||
onLongClickItem: () -> Unit,
|
onLongClickItem: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BaseAnimeSourceItem(
|
BaseAnimeSourceItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
@ -17,8 +17,8 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BaseAnimeSourceItem(
|
fun BaseAnimeSourceItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: AnimeSource,
|
source: AnimeSource,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
showLanguageInContent: Boolean = true,
|
showLanguageInContent: Boolean = true,
|
||||||
onClickItem: () -> Unit = {},
|
onClickItem: () -> Unit = {},
|
||||||
onLongClickItem: () -> Unit = {},
|
onLongClickItem: () -> Unit = {},
|
||||||
|
@ -26,7 +26,9 @@ fun BaseAnimeSourceItem(
|
||||||
action: @Composable RowScope.(AnimeSource) -> Unit = {},
|
action: @Composable RowScope.(AnimeSource) -> Unit = {},
|
||||||
content: @Composable RowScope.(AnimeSource, String?) -> Unit = defaultContent,
|
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(
|
BaseBrowseItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onClickItem = onClickItem,
|
onClickItem = onClickItem,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.presentation.browse.anime.components
|
package eu.kanade.presentation.browse.anime.components
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.layout.Box
|
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.presentation.util.rememberResourceBitmapPainter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
|
||||||
|
import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.domain.source.anime.model.AnimeSource
|
import tachiyomi.domain.source.anime.model.AnimeSource
|
||||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
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) {
|
return produceState<Result<ImageBitmap>>(initialValue = Result.Loading, this) {
|
||||||
withIOContext {
|
withIOContext {
|
||||||
value = try {
|
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)
|
val appResources = context.packageManager.getResourcesForApplication(appInfo)
|
||||||
Result.Success(
|
Result.Success(
|
||||||
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
appResources.getDrawableForDensity(appInfo.icon, density, null)!!
|
||||||
|
@ -142,7 +145,7 @@ private fun AnimeExtension.getIcon(density: Int = DisplayMetrics.DENSITY_DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Result<out T> {
|
sealed class Result<out T> {
|
||||||
object Loading : Result<Nothing>()
|
data object Loading : Result<Nothing>()
|
||||||
object Error : Result<Nothing>()
|
data object Error : Result<Nothing>()
|
||||||
data class Success<out T>(val value: T) : Result<T>()
|
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
|
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||||
BrowseAnimeSourceComfortableGridItem(
|
BrowseAnimeSourceComfortableGridItem(
|
||||||
anime = anime,
|
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
|
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||||
BrowseAnimeSourceCompactGridItem(
|
BrowseAnimeSourceCompactGridItem(
|
||||||
anime = anime,
|
anime = anime,
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
package eu.kanade.presentation.browse.anime.components
|
package eu.kanade.presentation.browse.anime.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.paging.LoadState
|
import androidx.paging.LoadState
|
||||||
import androidx.paging.compose.LazyPagingItems
|
import androidx.paging.compose.LazyPagingItems
|
||||||
import androidx.paging.compose.items
|
|
||||||
import eu.kanade.presentation.browse.InLibraryBadge
|
import eu.kanade.presentation.browse.InLibraryBadge
|
||||||
import eu.kanade.presentation.browse.manga.components.BrowseSourceLoadingItem
|
import eu.kanade.presentation.browse.manga.components.BrowseSourceLoadingItem
|
||||||
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
import eu.kanade.presentation.library.CommonEntryItemDefaults
|
||||||
|
@ -15,7 +15,6 @@ import eu.kanade.presentation.library.EntryListItem
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.entries.anime.model.Anime
|
import tachiyomi.domain.entries.anime.model.Anime
|
||||||
import tachiyomi.domain.entries.anime.model.AnimeCover
|
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||||
import tachiyomi.presentation.core.components.LazyColumn
|
|
||||||
import tachiyomi.presentation.core.util.plus
|
import tachiyomi.presentation.core.util.plus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -34,9 +33,8 @@ fun BrowseAnimeSourceList(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items(animeList) { animeflow ->
|
items(count = animeList.itemCount) { index ->
|
||||||
animeflow ?: return@items
|
val anime by animeList[index]?.collectAsState() ?: return@items
|
||||||
val anime by animeflow.collectAsState()
|
|
||||||
BrowseAnimeSourceListItem(
|
BrowseAnimeSourceListItem(
|
||||||
anime = anime,
|
anime = anime,
|
||||||
onClick = { onAnimeClick(anime) },
|
onClick = { onAnimeClick(anime) },
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.presentation.browse.anime.components
|
package eu.kanade.presentation.browse.anime.components
|
||||||
|
|
||||||
import androidx.compose.material.icons.Icons
|
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.ViewList
|
||||||
import androidx.compose.material.icons.filled.ViewModule
|
import androidx.compose.material.icons.filled.ViewModule
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
@ -20,6 +21,7 @@ import eu.kanade.presentation.components.SearchToolbar
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.animesource.AnimeSource
|
import eu.kanade.tachiyomi.animesource.AnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
import tachiyomi.source.local.entries.anime.LocalAnimeSource
|
||||||
|
|
||||||
|
@ -53,28 +55,44 @@ fun BrowseAnimeSourceToolbar(
|
||||||
onClickCloseSearch = navigateUp,
|
onClickCloseSearch = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = listOfNotNull(
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
AppBar.Action(
|
.apply {
|
||||||
title = stringResource(R.string.action_display_mode),
|
add(
|
||||||
icon = if (displayMode == LibraryDisplayMode.List) Icons.Filled.ViewList else Icons.Filled.ViewModule,
|
AppBar.Action(
|
||||||
onClick = { selectingDisplayMode = true },
|
title = stringResource(R.string.action_display_mode),
|
||||||
),
|
icon = if (displayMode == LibraryDisplayMode.List) {
|
||||||
if (isLocalSource) {
|
Icons.AutoMirrored.Filled.ViewList
|
||||||
AppBar.OverflowAction(
|
} else {
|
||||||
title = stringResource(R.string.label_help),
|
Icons.Filled.ViewModule
|
||||||
onClick = onHelpClick,
|
},
|
||||||
|
onClick = { selectingDisplayMode = true },
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
if (isLocalSource) {
|
||||||
AppBar.OverflowAction(
|
add(
|
||||||
title = stringResource(R.string.action_open_in_web_view),
|
AppBar.OverflowAction(
|
||||||
onClick = onWebViewClick,
|
title = stringResource(R.string.label_help),
|
||||||
)
|
onClick = onHelpClick,
|
||||||
},
|
),
|
||||||
AppBar.OverflowAction(
|
)
|
||||||
title = stringResource(R.string.action_settings),
|
} else {
|
||||||
onClick = onSettingsClick,
|
add(
|
||||||
).takeIf { isConfigurableSource },
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
|
|
|
@ -1,15 +1,26 @@
|
||||||
package eu.kanade.presentation.browse.anime.components
|
package eu.kanade.presentation.browse.anime.components
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
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.LazyRow
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
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.Anime
|
||||||
|
import tachiyomi.domain.entries.anime.model.AnimeCover
|
||||||
import tachiyomi.domain.entries.anime.model.asAnimeCover
|
import tachiyomi.domain.entries.anime.model.asAnimeCover
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
|
|
||||||
|
@ -20,13 +31,18 @@ fun GlobalAnimeSearchCardRow(
|
||||||
onClick: (Anime) -> Unit,
|
onClick: (Anime) -> Unit,
|
||||||
onLongClick: (Anime) -> Unit,
|
onLongClick: (Anime) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
if (titles.isEmpty()) {
|
||||||
|
EmptyResultItem()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
contentPadding = PaddingValues(MaterialTheme.padding.small),
|
||||||
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
|
||||||
) {
|
) {
|
||||||
items(titles) {
|
items(titles) {
|
||||||
val title by getAnime(it)
|
val title by getAnime(it)
|
||||||
GlobalSearchCard(
|
AnimeItem(
|
||||||
title = title.title,
|
title = title.title,
|
||||||
cover = title.asAnimeCover(),
|
cover = title.asAnimeCover(),
|
||||||
isFavorite = title.favorite,
|
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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material.icons.Icons
|
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.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.Public
|
import androidx.compose.material.icons.outlined.Public
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
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.presentation.util.formattedMessage
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.source.MangaSource
|
import eu.kanade.tachiyomi.source.MangaSource
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
import tachiyomi.domain.entries.manga.model.Manga
|
||||||
import tachiyomi.domain.library.model.LibraryDisplayMode
|
import tachiyomi.domain.library.model.LibraryDisplayMode
|
||||||
|
@ -61,12 +63,12 @@ fun BrowseSourceContent(
|
||||||
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
if (mangaList.itemCount > 0 && errorState != null && errorState is LoadState.Error) {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actionLabel = context.getString(R.string.action_webview_refresh),
|
actionLabel = context.getString(R.string.action_retry),
|
||||||
duration = SnackbarDuration.Indefinite,
|
duration = SnackbarDuration.Indefinite,
|
||||||
)
|
)
|
||||||
when (result) {
|
when (result) {
|
||||||
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
SnackbarResult.Dismissed -> snackbarHostState.currentSnackbarData?.dismiss()
|
||||||
SnackbarResult.ActionPerformed -> mangaList.refresh()
|
SnackbarResult.ActionPerformed -> mangaList.retry()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,15 +78,15 @@ fun BrowseSourceContent(
|
||||||
modifier = Modifier.padding(contentPadding),
|
modifier = Modifier.padding(contentPadding),
|
||||||
message = getErrorMessage(errorState),
|
message = getErrorMessage(errorState),
|
||||||
actions = if (source is LocalMangaSource) {
|
actions = if (source is LocalMangaSource) {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.local_source_help_guide,
|
stringResId = R.string.local_source_help_guide,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onLocalSourceHelpClick,
|
onClick = onLocalSourceHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(
|
persistentListOf(
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.action_retry,
|
stringResId = R.string.action_retry,
|
||||||
icon = Icons.Outlined.Refresh,
|
icon = Icons.Outlined.Refresh,
|
||||||
|
@ -97,7 +99,7 @@ fun BrowseSourceContent(
|
||||||
),
|
),
|
||||||
EmptyScreenAction(
|
EmptyScreenAction(
|
||||||
stringResId = R.string.label_help,
|
stringResId = R.string.label_help,
|
||||||
icon = Icons.Outlined.HelpOutline,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
onClick = onHelpClick,
|
onClick = onHelpClick,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -145,7 +147,7 @@ fun BrowseSourceContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MissingSourceScreen(
|
internal fun MissingSourceScreen(
|
||||||
source: StubMangaSource,
|
source: StubMangaSource,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,34 +1,30 @@
|
||||||
package eu.kanade.presentation.browse.manga
|
package eu.kanade.presentation.browse.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
import eu.kanade.presentation.browse.GlobalSearchErrorResultItem
|
||||||
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem
|
||||||
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
import eu.kanade.presentation.browse.GlobalSearchResultItem
|
||||||
import eu.kanade.presentation.browse.GlobalSearchToolbar
|
|
||||||
import eu.kanade.presentation.browse.manga.components.GlobalMangaSearchCardRow
|
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.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.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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.domain.entries.manga.model.Manga
|
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.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobalMangaSearchScreen(
|
fun GlobalMangaSearchScreen(
|
||||||
state: GlobalMangaSearchState,
|
state: MangaSearchScreenModel.State,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
onChangeSearchQuery: (String?) -> Unit,
|
onChangeSearchQuery: (String?) -> Unit,
|
||||||
onSearch: (String) -> Unit,
|
onSearch: (String) -> Unit,
|
||||||
|
onChangeSearchFilter: (MangaSourceFilter) -> Unit,
|
||||||
|
onToggleResults: () -> Unit,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
|
@ -36,19 +32,23 @@ fun GlobalMangaSearchScreen(
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { scrollBehavior ->
|
topBar = { scrollBehavior ->
|
||||||
GlobalSearchToolbar(
|
GlobalMangaSearchToolbar(
|
||||||
searchQuery = state.searchQuery,
|
searchQuery = state.searchQuery,
|
||||||
progress = state.progress,
|
progress = state.progress,
|
||||||
total = state.total,
|
total = state.total,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
onChangeSearchQuery = onChangeSearchQuery,
|
onChangeSearchQuery = onChangeSearchQuery,
|
||||||
onSearch = onSearch,
|
onSearch = onSearch,
|
||||||
|
sourceFilter = state.sourceFilter,
|
||||||
|
onChangeSearchFilter = onChangeSearchFilter,
|
||||||
|
onlyShowHasResults = state.onlyShowHasResults,
|
||||||
|
onToggleResults = onToggleResults,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
GlobalSearchContent(
|
GlobalSearchContent(
|
||||||
items = state.items,
|
items = state.filteredItems,
|
||||||
contentPadding = paddingValues,
|
contentPadding = paddingValues,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -59,13 +59,14 @@ fun GlobalMangaSearchScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun GlobalSearchContent(
|
internal fun GlobalSearchContent(
|
||||||
items: Map<CatalogueSource, MangaSearchItemResult>,
|
items: Map<CatalogueSource, MangaSearchItemResult>,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
getManga: @Composable (Manga) -> State<Manga>,
|
getManga: @Composable (Manga) -> State<Manga>,
|
||||||
onClickSource: (CatalogueSource) -> Unit,
|
onClickSource: (CatalogueSource) -> Unit,
|
||||||
onClickItem: (Manga) -> Unit,
|
onClickItem: (Manga) -> Unit,
|
||||||
onLongClickItem: (Manga) -> Unit,
|
onLongClickItem: (Manga) -> Unit,
|
||||||
|
fromSourceId: Long? = null,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
|
@ -73,7 +74,8 @@ private fun GlobalSearchContent(
|
||||||
items.forEach { (source, result) ->
|
items.forEach { (source, result) ->
|
||||||
item(key = source.id) {
|
item(key = source.id) {
|
||||||
GlobalSearchResultItem(
|
GlobalSearchResultItem(
|
||||||
title = source.name,
|
title = fromSourceId
|
||||||
|
?.let { "▶ ${source.name}".takeIf { source.id == fromSourceId } } ?: source.name,
|
||||||
subtitle = LocaleHelper.getDisplayName(source.lang),
|
subtitle = LocaleHelper.getDisplayName(source.lang),
|
||||||
onClick = { onClickSource(source) },
|
onClick = { onClickSource(source) },
|
||||||
) {
|
) {
|
||||||
|
@ -82,18 +84,6 @@ private fun GlobalSearchContent(
|
||||||
GlobalSearchLoadingResultItem()
|
GlobalSearchLoadingResultItem()
|
||||||
}
|
}
|
||||||
is MangaSearchItemResult.Success -> {
|
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(
|
GlobalMangaSearchCardRow(
|
||||||
titles = result.result,
|
titles = result.result,
|
||||||
getManga = getManga,
|
getManga = getManga,
|
||||||
|
|
|
@ -10,19 +10,19 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
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.HelpOutline
|
||||||
import androidx.compose.material.icons.outlined.History
|
import androidx.compose.material.icons.outlined.History
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
@ -30,6 +30,7 @@ import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.VerticalDivider
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -53,11 +54,10 @@ import eu.kanade.presentation.more.settings.widget.TrailingWidgetBuffer
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import tachiyomi.presentation.core.components.ScrollbarLazyColumn
|
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.Scaffold
|
||||||
import tachiyomi.presentation.core.components.material.padding
|
import tachiyomi.presentation.core.components.material.padding
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
@ -65,7 +65,7 @@ import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
@Composable
|
@Composable
|
||||||
fun ExtensionDetailsScreen(
|
fun ExtensionDetailsScreen(
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
state: MangaExtensionDetailsState,
|
state: MangaExtensionDetailsScreenModel.State,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickWhatsNew: () -> Unit,
|
onClickWhatsNew: () -> Unit,
|
||||||
onClickReadme: () -> Unit,
|
onClickReadme: () -> Unit,
|
||||||
|
@ -82,40 +82,42 @@ fun ExtensionDetailsScreen(
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
actions = {
|
actions = {
|
||||||
AppBarActions(
|
AppBarActions(
|
||||||
actions = buildList {
|
actions = persistentListOf<AppBar.AppBarAction>().builder()
|
||||||
if (state.extension?.isUnofficial == false) {
|
.apply {
|
||||||
add(
|
if (state.extension?.isUnofficial == false) {
|
||||||
AppBar.Action(
|
add(
|
||||||
title = stringResource(R.string.whats_new),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.History,
|
title = stringResource(R.string.whats_new),
|
||||||
onClick = onClickWhatsNew,
|
icon = Icons.Outlined.History,
|
||||||
),
|
onClick = onClickWhatsNew,
|
||||||
)
|
),
|
||||||
add(
|
)
|
||||||
AppBar.Action(
|
add(
|
||||||
title = stringResource(R.string.action_faq_and_guides),
|
AppBar.Action(
|
||||||
icon = Icons.Outlined.HelpOutline,
|
title = stringResource(R.string.action_faq_and_guides),
|
||||||
onClick = onClickReadme,
|
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||||
|
onClick = onClickReadme,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addAll(
|
||||||
|
listOf(
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_enable_all),
|
||||||
|
onClick = onClickEnableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.action_disable_all),
|
||||||
|
onClick = onClickDisableAll,
|
||||||
|
),
|
||||||
|
AppBar.OverflowAction(
|
||||||
|
title = stringResource(R.string.pref_clear_cookies),
|
||||||
|
onClick = onClickClearCookies,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
addAll(
|
.build(),
|
||||||
listOf(
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_enable_all),
|
|
||||||
onClick = onClickEnableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.action_disable_all),
|
|
||||||
onClick = onClickDisableAll,
|
|
||||||
),
|
|
||||||
AppBar.OverflowAction(
|
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
|
||||||
onClick = onClickClearCookies,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
@ -176,7 +178,8 @@ private fun ExtensionDetails(
|
||||||
data = Uri.fromParts("package", extension.pkgName, null)
|
data = Uri.fromParts("package", extension.pkgName, null)
|
||||||
context.startActivity(this)
|
context.startActivity(this)
|
||||||
}
|
}
|
||||||
},
|
Unit
|
||||||
|
}.takeIf { extension.isShared },
|
||||||
onClickAgeRating = {
|
onClickAgeRating = {
|
||||||
showNsfwWarning = true
|
showNsfwWarning = true
|
||||||
},
|
},
|
||||||
|
@ -188,7 +191,7 @@ private fun ExtensionDetails(
|
||||||
key = { it.source.id },
|
key = { it.source.id },
|
||||||
) { source ->
|
) { source ->
|
||||||
SourceSwitchPreference(
|
SourceSwitchPreference(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
source = source,
|
source = source,
|
||||||
onClickSourcePreferences = onClickSourcePreferences,
|
onClickSourcePreferences = onClickSourcePreferences,
|
||||||
onClickSource = onClickSource,
|
onClickSource = onClickSource,
|
||||||
|
@ -209,7 +212,7 @@ private fun DetailsHeader(
|
||||||
extension: MangaExtension,
|
extension: MangaExtension,
|
||||||
onClickAgeRating: () -> Unit,
|
onClickAgeRating: () -> Unit,
|
||||||
onClickUninstall: () -> Unit,
|
onClickUninstall: () -> Unit,
|
||||||
onClickAppInfo: () -> Unit,
|
onClickAppInfo: (() -> Unit)?,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
@ -293,6 +296,7 @@ private fun DetailsHeader(
|
||||||
top = MaterialTheme.padding.small,
|
top = MaterialTheme.padding.small,
|
||||||
bottom = MaterialTheme.padding.medium,
|
bottom = MaterialTheme.padding.medium,
|
||||||
),
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
) {
|
) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
@ -301,29 +305,29 @@ private fun DetailsHeader(
|
||||||
Text(stringResource(R.string.ext_uninstall))
|
Text(stringResource(R.string.ext_uninstall))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.width(16.dp))
|
if (onClickAppInfo != null) {
|
||||||
|
Button(
|
||||||
Button(
|
modifier = Modifier.weight(1f),
|
||||||
modifier = Modifier.weight(1f),
|
onClick = onClickAppInfo,
|
||||||
onClick = onClickAppInfo,
|
) {
|
||||||
) {
|
Text(
|
||||||
Text(
|
text = stringResource(R.string.ext_app_info),
|
||||||
text = stringResource(R.string.ext_app_info),
|
color = MaterialTheme.colorScheme.onPrimary,
|
||||||
color = MaterialTheme.colorScheme.onPrimary,
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
HorizontalDivider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoText(
|
private fun InfoText(
|
||||||
modifier: Modifier,
|
|
||||||
primaryText: String,
|
primaryText: String,
|
||||||
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
|
||||||
secondaryText: String,
|
secondaryText: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge,
|
||||||
onClick: (() -> Unit)? = null,
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
@ -356,20 +360,17 @@ private fun InfoText(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun InfoDivider() {
|
private fun InfoDivider() {
|
||||||
Divider(
|
VerticalDivider(
|
||||||
modifier = Modifier
|
modifier = Modifier.height(20.dp),
|
||||||
.height(20.dp)
|
|
||||||
.width(1.dp),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = DIVIDER_ALPHA),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SourceSwitchPreference(
|
private fun SourceSwitchPreference(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
source: MangaExtensionSourceItem,
|
source: MangaExtensionSourceItem,
|
||||||
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
onClickSourcePreferences: (sourceId: Long) -> Unit,
|
||||||
onClickSource: (sourceId: Long) -> Unit,
|
onClickSource: (sourceId: Long) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
@ -415,7 +416,7 @@ fun NsfwWarningDialog(
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onClickConfirm) {
|
TextButton(onClick = onClickConfirm) {
|
||||||
Text(text = stringResource(android.R.string.ok))
|
Text(text = stringResource(R.string.action_ok))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDismissRequest = onClickConfirm,
|
onDismissRequest = onClickConfirm,
|
||||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.presentation.browse.manga
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.R
|
||||||
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionFilterState
|
import eu.kanade.tachiyomi.ui.browse.manga.extension.MangaExtensionFilterState
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
|
||||||
import tachiyomi.presentation.core.components.material.Scaffold
|
import tachiyomi.presentation.core.components.material.Scaffold
|
||||||
import tachiyomi.presentation.core.screens.EmptyScreen
|
import tachiyomi.presentation.core.screens.EmptyScreen
|
||||||
|
|
||||||
|
@ -53,12 +53,12 @@ private fun ExtensionFilterContent(
|
||||||
onClickLang: (String) -> Unit,
|
onClickLang: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
FastScrollLazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
) {
|
) {
|
||||||
items(state.languages) { language ->
|
items(state.languages) { language ->
|
||||||
SwitchPreferenceWidget(
|
SwitchPreferenceWidget(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
title = LocaleHelper.getSourceDisplayName(language, context),
|
title = LocaleHelper.getSourceDisplayName(language, context),
|
||||||
checked = language in state.enabledLanguages,
|
checked = language in state.enabledLanguages,
|
||||||
onCheckedChanged = { onClickLang(language) },
|
onCheckedChanged = { onClickLang(language) },
|
||||||
|
|
|
@ -43,7 +43,7 @@ import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.extension.InstallStep
|
import eu.kanade.tachiyomi.extension.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
|
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.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 eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
import tachiyomi.presentation.core.components.FastScrollLazyColumn
|
||||||
import tachiyomi.presentation.core.components.material.PullRefresh
|
import tachiyomi.presentation.core.components.material.PullRefresh
|
||||||
|
@ -57,7 +57,7 @@ import tachiyomi.presentation.core.util.secondaryItemAlpha
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MangaExtensionScreen(
|
fun MangaExtensionScreen(
|
||||||
state: MangaExtensionsState,
|
state: MangaExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
searchQuery: String?,
|
searchQuery: String?,
|
||||||
onLongClickItem: (MangaExtension) -> Unit,
|
onLongClickItem: (MangaExtension) -> Unit,
|
||||||
|
@ -73,10 +73,10 @@ fun MangaExtensionScreen(
|
||||||
PullRefresh(
|
PullRefresh(
|
||||||
refreshing = state.isRefreshing,
|
refreshing = state.isRefreshing,
|
||||||
onRefresh = onRefresh,
|
onRefresh = onRefresh,
|
||||||
enabled = !state.isLoading,
|
enabled = { !state.isLoading },
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
state.isLoading -> LoadingScreen(modifier = Modifier.padding(contentPadding))
|
state.isLoading -> LoadingScreen(Modifier.padding(contentPadding))
|
||||||
state.isEmpty -> {
|
state.isEmpty -> {
|
||||||
val msg = if (!searchQuery.isNullOrEmpty()) {
|
val msg = if (!searchQuery.isNullOrEmpty()) {
|
||||||
R.string.no_results_found
|
R.string.no_results_found
|
||||||
|
@ -108,7 +108,7 @@ fun MangaExtensionScreen(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionContent(
|
private fun ExtensionContent(
|
||||||
state: MangaExtensionsState,
|
state: MangaExtensionsScreenModel.State,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
onLongClickItem: (MangaExtension) -> Unit,
|
onLongClickItem: (MangaExtension) -> Unit,
|
||||||
onClickItemCancel: (MangaExtension) -> Unit,
|
onClickItemCancel: (MangaExtension) -> Unit,
|
||||||
|
@ -148,14 +148,14 @@ private fun ExtensionContent(
|
||||||
}
|
}
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
textRes = header.textRes,
|
textRes = header.textRes,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
action = action,
|
action = action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is MangaExtensionUiModel.Header.Text -> {
|
is MangaExtensionUiModel.Header.Text -> {
|
||||||
ExtensionHeader(
|
ExtensionHeader(
|
||||||
text = header.text,
|
text = header.text,
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ private fun ExtensionContent(
|
||||||
key = { "extension-${it.hashCode()}" },
|
key = { "extension-${it.hashCode()}" },
|
||||||
) { item ->
|
) { item ->
|
||||||
ExtensionItem(
|
ExtensionItem(
|
||||||
modifier = Modifier.animateItemPlacement(),
|
|
||||||
item = item,
|
item = item,
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
when (it) {
|
when (it) {
|
||||||
|
@ -217,12 +217,12 @@ private fun ExtensionContent(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ExtensionItem(
|
private fun ExtensionItem(
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: MangaExtensionUiModel.Item,
|
item: MangaExtensionUiModel.Item,
|
||||||
onClickItem: (MangaExtension) -> Unit,
|
onClickItem: (MangaExtension) -> Unit,
|
||||||
onLongClickItem: (MangaExtension) -> Unit,
|
onLongClickItem: (MangaExtension) -> Unit,
|
||||||
onClickItemCancel: (MangaExtension) -> Unit,
|
onClickItemCancel: (MangaExtension) -> Unit,
|
||||||
onClickItemAction: (MangaExtension) -> Unit,
|
onClickItemAction: (MangaExtension) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val (extension, installStep) = item
|
val (extension, installStep) = item
|
||||||
BaseBrowseItem(
|
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(
|
MangaExtensionIcon(
|
||||||
extension = extension,
|
extension = extension,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -296,7 +299,10 @@ private fun ExtensionItemContent(
|
||||||
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
ProvideTextStyle(value = MaterialTheme.typography.bodySmall) {
|
||||||
if (extension is MangaExtension.Installed && extension.lang.isNotEmpty()) {
|
if (extension is MangaExtension.Installed && extension.lang.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = LocaleHelper.getSourceDisplayName(extension.lang, LocalContext.current),
|
text = LocaleHelper.getSourceDisplayName(
|
||||||
|
extension.lang,
|
||||||
|
LocalContext.current,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue