Compare commits

..

567 Commits

Author SHA1 Message Date
tom5079
a4e8f20b26 Dependency update, added LocalActivity provider 2022-11-08 14:21:37 -08:00
tom5079
4531a6b05f Merge pull request #133 from tom5079/Pupil-129
Pupil-129 [Source] Implement in-app update
2022-05-06 12:49:00 +09:00
tom5079
2ef70d0da0 Update Jetpack Compose version to 1.2.0-alpha07 2022-05-06 12:48:19 +09:00
tom5079
4fe769cbbf Pupil-129 [Source] Implement in-app update 2022-05-02 21:29:58 +09:00
tom5079
de068a760e Changed PupilHttpClient.downloadApk to receive url instead of RemoteSourceInfo 2022-05-02 16:48:57 +09:00
tom5079
a183ff803d Delete SourceLoaderInstrumentedTest 2022-05-02 16:48:10 +09:00
tom5079
e84c381423 Added latestRelease to PupilHttpClient 2022-05-02 16:47:36 +09:00
tom5079
85a7eeeeea Remove unnecessary annotations 2022-05-01 15:43:00 +09:00
tom5079
0d9fb97bbb Show update button in Explore menu when installed version is not the latest 2022-05-01 15:42:03 +09:00
tom5079
9ed3631c30 Merge pull request #132 from tom5079/Pupil-130
Pupil-130 [Source] Show progress indicator right away when update button is clicked
2022-05-01 15:36:54 +09:00
tom5079
0a68df6492 Pupil-130 [Source] Show progress indicator right away when update button is clicked 2022-05-01 15:36:12 +09:00
tom5079
f3f47d9407 Implement source update
Refactor codes for testability
2022-04-30 22:48:29 +09:00
tom5079
b6ff956637 Migrate to ktor 2.0 2022-04-24 21:06:29 +09:00
tom5079
aa99c18a1b updated .gitignore 2022-04-16 06:28:54 +09:00
tom5079
1be5ecac6b updated .gitignore 2022-04-16 06:27:01 +09:00
tom5079
c8eff885b5 updated .gitignore 2022-04-16 06:26:48 +09:00
tom5079
e0eb187442 updated .gitignore 2022-04-16 06:25:36 +09:00
tom5079
0fbce15e41 AGP update 2022-04-13 08:44:22 +09:00
tom5079
f11dce968e Cache loadSource 2022-04-04 23:46:32 +09:00
tom5079
df341c777e clean up dependencies 2022-04-04 11:42:21 +09:00
tom5079
bed6d7d6ab Removed build files from repository 2022-04-03 23:25:35 +09:00
tom5079
97eb85e97c windowSoftInputMode="adjustResize" for Insets 2022-04-02 11:30:54 +09:00
tom5079
4448f61430 Sign release build 2022-04-01 18:52:04 +09:00
tom5079
9beb4ded2e Added Crossfade when entering source 2022-04-01 16:27:42 +09:00
tom5079
fc3a0fa178 Fixed app crashing when using rememberInstance() to retrieve Application
Applied systemBarsPadding() to SourceSelector
Dependency Update
2022-04-01 16:20:28 +09:00
tom5079
03e71b3000 AGP update 2022-02-21 11:50:36 +09:00
tom5079
8503c64f04 Import optimizations 2022-01-22 20:13:28 +09:00
tom5079
0dd25faced Implemented source launching 2022-01-22 19:59:43 +09:00
tom5079
00cf429bd9 Dependency update 2022-01-02 16:00:00 +09:00
tom5079
628d42703f Dependency update 2022-01-01 16:30:29 +09:00
tom5079
f271e61ea2 External Sources 2021-12-31 14:03:52 +09:00
tom5079
2e11a4907a Prepare to export sources 2021-12-30 13:00:22 +09:00
tom5079
0e19d6c9b2 [Manatoki] Fixed crashing 2021-12-27 12:33:57 +09:00
tom5079
850ac3ea83 Decentralize database 2021-12-27 11:57:26 +09:00
tom5079
c3c5761ffa [Manatoki] Implemented next episode button in reader
[Hitomi] Adjusted Search result padding
2021-12-26 21:19:51 +09:00
tom5079
0ff91d76b1 [Manatoki] Fixed app crash when clicking list in reader 2021-12-26 15:32:05 +09:00
tom5079
cd4be5898b Beta bad 2021-12-26 15:10:02 +09:00
tom5079
d80de6fde7 Dependency Update 2021-12-26 13:10:00 +09:00
tom5079
193db578f0 Fixed layout
[Hitomi] Fixed image not loading
2021-12-26 12:28:52 +09:00
tom5079
3abd015505 Dependency upgrade
[Manatoki] Implemented Artist suggestions
2021-12-26 11:46:51 +09:00
tom5079
84c536a597 [Manatoki] Implemented Recent page 2021-12-26 10:09:41 +09:00
tom5079
480bbd3628 [ReaderBase] Fixed No Padding + Horizontal layout not working 2021-12-26 09:23:14 +09:00
tom5079
b708437a16 Fixed a few things...
Can't really write them all down :v
2021-12-25 23:33:13 +09:00
tom5079
fcbe107fe7 [Reader] Enable Gesture, Adjusted BrokenImage tint
[NetworkCache] Don't download existing file again
2021-12-25 14:19:28 +09:00
tom5079
bf3e7d7117 [Reader] Improved layout config 2021-12-25 10:56:56 +09:00
tom5079
f78c66a9f4 [Reader] WIP 2021-12-24 17:10:22 +09:00
tom5079
7e52a2e296 [Reader] ReaderOptions TopSheet 2021-12-23 22:37:38 +09:00
tom5079
4625bb5806 [Manatoki] MangaListingBottomSheet show current item indicator 2021-12-22 21:33:55 +09:00
tom5079
d626cc09d5 [Manatoki] cache all requests 2021-12-22 21:23:10 +09:00
tom5079
57c4e249cf [Manatoki] changed mangaListing scroll behavior to instant 2021-12-22 18:43:10 +09:00
tom5079
016ce3ff42 Renamed SearchOptionDrawer to ModalTopSheetLayout and promoted to shared composables 2021-12-22 18:32:48 +09:00
tom5079
383baa900c [Manatoki] Fixed tag not visible on MangaListingBottomSheet 2021-12-22 18:25:33 +09:00
tom5079
551b4cae80 [Manatoki] Search text fillMaxWidth 2021-12-21 19:58:20 +09:00
tom5079
0c13ad6869 Fixed unable to download files without extension 2021-12-21 19:12:37 +09:00
tom5079
8b2e388a81 [Manatoki] Bug fix 2021-12-21 19:05:15 +09:00
tom5079
c34b0f6f0f [Manatoki] Drawer highlight 2021-12-21 18:04:54 +09:00
tom5079
f6f0ed40c1 Reader bug fix 2021-12-20 18:04:29 +09:00
tom5079
b82ef8695c [Manatoki] Main/Reader OK 2021-12-20 11:44:13 +09:00
tom5079
0f4e1a8e0d Readerbase actions 2021-12-19 12:42:19 +09:00
tom5079
20ddf04614 Insets
Nested navigation
Hitomi main actions
2021-12-19 12:33:47 +09:00
tom5079
7befa24aff Pageturn 2021-12-19 00:07:38 +09:00
tom5079
93d68d3867 Fix navigation bug 2021-12-18 22:07:09 +09:00
tom5079
9037b41b49 WIP 2021-12-18 20:19:06 +09:00
tom5079
02751233f8 Manatoki 2021-12-18 14:57:25 +09:00
tom5079
a57b1d5614 [WIP] Pageturn 2021-12-18 13:32:07 +09:00
tom5079
adf18341d0 Changed app name to Pupil-Beta 2021-12-18 09:36:46 +09:00
tom5079
bdd2bc8645 Share image with long click 2021-12-18 00:48:49 +09:00
tom5079
338b789e62 Protobuf 2021-12-18 00:23:27 +09:00
tom5079
98fda1a53f Bookmark 2021-12-17 22:34:46 +09:00
tom5079
e7debfec46 Gestures
OpenWithIDDialog
2021-12-17 20:39:30 +09:00
tom5079
62d0de3ef6 Changed Theme / Deprecated Preferences. Use DataStore instead. 2021-12-17 11:56:34 +09:00
tom5079
ef0f71310b Code Refactor / Dependency Update 2021-12-17 11:23:32 +09:00
tom5079
052990c4ef Added hiyobi.io 2021-12-16 22:51:17 +09:00
tom5079
077d9b976c SourceSelectDialog 2021-12-16 16:38:59 +09:00
tom5079
78ba11ca5f NetworkCache 2021-12-16 12:19:32 +09:00
tom5079
b690d01243 FloatingSearchBar 2021-12-15 16:30:05 +09:00
tom5079
458530e80c [WIP] 2021-12-14 22:19:15 +09:00
tom5079
ddbfd0a201 Copyright 2021-12-12 19:19:26 +09:00
tom5079
6c13a624a9 Shows Image 2021-12-01 17:18:19 +09:00
tom5079
70452ba7a6 Dependency update 2021-11-25 10:17:05 +09:00
tom5079
14c64299ec WIP 2021-09-18 14:48:55 +09:00
tom5079
c2626cdee4 [Reader] Implemented Fullscreen 2021-09-17 18:26:45 +09:00
tom5079
52a945d0d9 Floating Menu 2021-09-17 17:25:21 +09:00
tom5079
29aefa4197 Floating Menu 2021-09-17 17:22:41 +09:00
tom5079
cfe6a814d4 [FAB] WIP 2021-09-17 10:22:30 +09:00
tom5079
9ef7852bab WIP 2021-09-16 23:04:32 +09:00
tom5079
0a1e0a2dcf WIP 2021-09-15 11:15:09 +09:00
tom5079
5b9a83cbcc dependency update 2021-07-24 13:06:20 +09:00
tom5079
fc61522955 Migrate to Kotlin DSL 2021-07-24 12:41:26 +09:00
tom5079
35ee438376 Reimplemented sort
[WIP] ImHentai
2021-07-24 09:48:54 +09:00
tom5079
8cc89101e7 changed di() to closestDI() 2021-07-14 09:04:38 +09:00
tom5079
2150d086e0 Migrated to Ktor-client 2021-07-12 08:44:43 +09:00
tom5079
a9a07ddcfa Simplify Source definition 2021-07-03 23:22:41 +09:00
tom5079
32d49833d8 LF to CRLF 2021-07-03 09:01:56 +09:00
tom5079
2a92d287af Download tab 2021-06-25 14:45:10 +09:00
tom5079
975b98e4dc Show progress drawable when backup 2021-06-24 19:15:19 +09:00
tom5079
237d5accc5 WIP 2021-06-18 07:27:33 +09:00
tom5079
760194bde8 [WIP] Downloads 2021-06-08 18:24:52 +09:00
tom5079
ff0df0d9cc Fixed radio button acting up 2021-06-08 15:57:22 +09:00
tom5079
dd60a1fdfb Source settings 2021-05-18 10:39:35 +09:00
tom5079
5a19fb8336 show languages in local name 2021-05-15 19:27:07 +09:00
tom5079
51851addc1 Dependency update / Dropped features that supports <Android 21 / Dropped support for hiyobi.me 2021-05-15 06:43:14 +09:00
tom5079
00c8078642 fixed SavedCollections not inserting first item 2021-04-04 15:43:42 +09:00
tom5079
ca54fb6eb0 Fixed Proxy dialog 2021-04-04 08:38:50 +09:00
tom5079
1107cf1a9c Dependency update 2021-04-04 08:28:22 +09:00
tom5079
1dea88a135 Dependency update 2021-03-14 14:34:59 +09:00
tom5079
6fae9e9a30 Dependency update 2021-03-11 12:32:34 +09:00
tom5079
a1c6d87c54 Added History 2021-02-21 10:34:26 +09:00
tom5079
80b7293879 Patched memory leak 2021-02-17 20:20:32 +09:00
tom5079
2f57ee4c83 Handle backpress 2021-02-17 19:25:45 +09:00
tom5079
3f8aa744e7 Fixed app crashing on invoking MainViewModel.sourceName.value 2021-02-17 19:12:42 +09:00
tom5079
fb11149b78 Dependency update 2021-02-13 17:19:52 +09:00
tom5079
8b41c706b6 WIP 2021-02-11 19:27:02 +09:00
tom5079
5a61fcf6ee WIP 2021-02-11 19:24:40 +09:00
tom5079
c7b3ae7ed1 Gallery Dialog 2021-01-29 19:44:09 +09:00
tom5079
4aea7d08ce fixed cleanup() deleting .cache
fixed empty tag showing up
2021-01-18 22:17:48 +09:00
tom5079
2f16838e1e Cache 2021-01-18 21:50:21 +09:00
tom5079
619730e2ab WIP 2021-01-09 19:18:26 +09:00
tom5079
c8aa26e2d9 SAF 2020-12-27 19:36:23 +09:00
tom5079
8703fde9b1 WIP 2020-12-27 13:39:57 +09:00
tom5079
3051d800bd WIP 2020-12-23 17:09:48 +09:00
tom5079
521f3ad809 Images 2020-12-04 01:09:22 +09:00
tom5079
730a3baedc Suggestion 2020-12-02 09:57:16 +09:00
tom5079
26c5e07f04 Minor fix 2020-11-29 15:23:16 +09:00
tom5079
3feae80359 Implemented Source Selector 2020-11-29 14:09:34 +09:00
tom5079
24aedfc400 Minor fix 2020-11-29 14:09:34 +09:00
tom5079
aa6cc80172 Rebase source onto dev 2020-11-29 14:02:49 +09:00
tom5079
74ed9e9e42 Fixed show extra tags button not showing up & version up 2020-11-29 14:02:47 +09:00
tom5079
2b7b86da96 fixed gallery import 2020-11-29 14:02:42 +09:00
tom5079
9f9a4c81b3 migrated to ViewBinding 2020-11-29 14:01:56 +09:00
tom5079
d567b30f4b Fixed typo & Built apk 2020-11-29 14:01:56 +09:00
tom5079
6d7c4ce0ab Fixed show extra tags button not showing up & version up 2020-11-29 14:01:51 +09:00
tom5079
e062b8f9e9 Implemented proper Page Turn without relying on RecyclerView 2020-11-29 14:01:46 +09:00
tom5079
08403b7a4e fixed gallery import 2020-11-29 14:01:41 +09:00
tom5079
c6ed5d35e7 ProgressCard 2020-11-29 14:01:25 +09:00
tom5079
dba3460b60 Fixes unable to recursively delete when the download folder is not not SAF based 2020-11-29 14:01:09 +09:00
tom5079
f07f624fcf search bug fix 2020-10-25 00:15:21 +09:00
tom5079
48ff2f328f search bug fix 2020-10-24 23:55:50 +09:00
tom5079
9ae2423a40 search bug fix 2020-10-24 23:05:11 +09:00
tom5079
2bc3c78c75 search bug fix 2020-10-24 23:04:49 +09:00
tom5079
18e9fe75fb hiyobi.me fix 2020-10-24 11:48:14 +09:00
tom5079
880a741a44 hiyobi.me fix 2020-10-24 11:25:16 +09:00
tom5079
2c6ddcc64b hitomi.la image not loading fix 2020-10-21 14:42:07 +09:00
tom5079
8f2e757b77 Update README.md 2020-10-15 15:53:30 +09:00
tom5079
ff177955b3 Update README.md 2020-10-15 15:49:07 +09:00
tom5079
8bb8066a98 Apk built 2020-10-15 14:37:13 +09:00
tom5079
2747ddbf65 Adjust gallery_id margin 2020-10-15 14:32:36 +09:00
tom5079
b939e9424d Translate tag by default 2020-10-15 14:29:22 +09:00
tom5079
fb9dea5d1e Copy galleryID by clicking galleryblock_id 2020-10-15 12:52:38 +09:00
tom5079
da4d5d711b Prefetch
Resolves #109
2020-10-15 10:20:36 +09:00
tom5079
331cbec5f1 Bug fix 2020-10-14 18:36:36 +09:00
tom5079
7f02284285 Update README.md 2020-10-14 00:26:06 +09:00
tom5079
ac2c3a6d97 Merge remote-tracking branch 'origin/master' into master 2020-10-14 00:25:44 +09:00
tom5079
c3bc80fec6 Bug fix 2020-10-14 00:24:38 +09:00
tom5079
09779a0710 Update README.md 2020-10-13 23:47:56 +09:00
tom5079
e82c6ef866 App built
Possible build time optimization
2020-10-13 23:40:53 +09:00
tom5079
861ae9be64 Merge remote-tracking branch 'origin/dev' into dev 2020-10-13 23:34:28 +09:00
tom5079
96108bc1ec Improves Scroll Jitter 2020-10-13 23:34:16 +09:00
tom5079
016f217db0 Merge pull request #108 from klx7007/patch-1
Fix FloatingSearchView imeOptions to only affect keyboard visibility
2020-10-13 23:05:34 +09:00
tom5079
0688294f18 Dependency update
Support non external storage document Uris

Support non external storage document Uris
2020-10-13 22:59:29 +09:00
klx7007
9ad008255d FloatingSearchView imeOptions 수정
imeOption을 덮어씌워서 search할때 키보드만 숨겨짐
2020-10-13 22:58:13 +09:00
tom5079
4c5a862dd6 Update README.md 2020-10-13 17:57:19 +09:00
tom5079
b165a2308f Merge remote-tracking branch 'origin/master' into master 2020-10-13 17:13:10 +09:00
tom5079
8757b08cd2 Fixed pagecount not showing up 2020-10-13 17:12:53 +09:00
tom5079
3800543fba Update README.md 2020-10-13 13:59:02 +09:00
tom5079
02ef60c818 Update README.md 2020-10-13 13:57:23 +09:00
tom5079
88f3b30266 Merge branch 'dev' into master 2020-10-13 13:52:34 +09:00
tom5079
9203dc0112 Tag translation 2020-10-13 13:51:53 +09:00
tom5079
4c683bec68 Dependency update
Fixes concurrentmodificationexception
2020-10-13 08:34:04 +09:00
tom5079
0cfd1eb453 Update README.md 2020-10-04 23:05:30 +09:00
tom5079
19744dab37 Merge remote-tracking branch 'origin/master' into master 2020-10-04 23:04:30 +09:00
tom5079
12d58e5aa7 Don't cancel download onPause 2020-10-04 23:04:12 +09:00
tom5079
e46dd37a26 Update README.md 2020-10-04 22:47:45 +09:00
tom5079
49c3ebc36b Concurrency issue fixed
Don't cancel download onPause
Limit folder length to 127 characters
2020-10-04 22:44:31 +09:00
tom5079
11e9bc2235 Added five entries per page option 2020-10-04 20:39:33 +09:00
tom5079
3029b3bf0e Update README.md 2020-10-03 20:20:41 +09:00
tom5079
9a6c6f67ce Merge branch 'dev' into master 2020-10-03 20:18:53 +09:00
tom5079
a6ed0baef2 Fix auto cache cleanup 2020-10-03 20:18:20 +09:00
tom5079
d3b43d80da Update README.md 2020-10-03 10:06:17 +09:00
tom5079
46d4316d49 Merge remote-tracking branch 'origin/master' into master 2020-10-03 10:05:30 +09:00
tom5079
ade2864351 Fix auto cache cleanup 2020-10-03 10:03:57 +09:00
tom5079
365fc56e9d Update README.md 2020-10-02 13:09:46 +09:00
tom5079
54a5cd21ea Merge branch 'dev' into master 2020-10-02 13:00:02 +09:00
tom5079
38c0399b09 App built 2020-10-02 12:59:32 +09:00
tom5079
2b67858453 Auto cache clean 2020-10-02 12:51:59 +09:00
tom5079
87fdbdbb6e Open GalleryDialog first instead of opening Reader directly 2020-10-02 00:19:56 +09:00
tom5079
bab77a4116 (KO) Added support link 2020-10-02 00:13:34 +09:00
tom5079
d20756ab96 Reset security mode 2020-10-01 22:51:52 +09:00
tom5079
dc75a753c3 Minimum thumbnail height 2020-10-01 22:46:03 +09:00
tom5079
4712d47903 Show 10 tags maximum 2020-10-01 22:20:49 +09:00
tom5079
c5561801e1 Add group name to GalleryBlock 2020-10-01 21:32:42 +09:00
tom5079
5c259fa07a Dependency update
Fixed duplicated download file
Better download progress update handling

TODO: Add group name to GalleryBlock
2020-10-01 21:24:32 +09:00
tom5079
60e8b18702 Update README.md 2020-09-29 16:53:15 +09:00
tom5079
a8317824a9 Merge remote-tracking branch 'origin/master' into master 2020-09-27 21:40:32 +09:00
tom5079
0c3c78cc72 Fixed app crashing when loading thumbnail 2020-09-27 21:40:22 +09:00
tom5079
cfd4a8faac Update README.md 2020-09-27 21:39:05 +09:00
tom5079
7f3fb0db0d Update README.md 2020-09-27 20:30:47 +09:00
tom5079
385d3f0d1b Update README.md 2020-09-27 20:29:50 +09:00
tom5079
8fa6bd12a2 Update README.md 2020-09-27 20:27:29 +09:00
tom5079
57c2004e46 Update README.md 2020-09-27 20:21:45 +09:00
tom5079
c6b069bbfb Update README.md 2020-09-27 20:19:53 +09:00
tom5079
c18bffd08f Fixed app crashing when thumbnail is null 2020-09-27 20:18:04 +09:00
tom5079
47ec181439 Fixed app crashing when thumbnail is null 2020-09-27 20:15:43 +09:00
tom5079
90ad40b1b7 Update README.md 2020-09-27 19:32:18 +09:00
tom5079
4d3f20cf98 Update README.md 2020-09-27 19:31:30 +09:00
tom5079
86df9d52bc Update README.md 2020-09-27 19:15:46 +09:00
tom5079
1bd025e070 Fixed ProxyDialog not showing up 2020-09-27 15:09:19 +09:00
tom5079
86ee239c71 App built 2020-09-27 14:39:47 +09:00
tom5079
27d0c01e1f Don't refresh onResume 2020-09-27 14:37:16 +09:00
tom5079
7a9507be01 Somewhat working 2020-09-27 14:29:02 +09:00
tom5079
1490035893 Does not work 2020-09-27 10:04:26 +09:00
tom5079
a6afcb0ed0 Consistent usage of quotation marks 2020-09-26 22:41:51 +09:00
tom5079
ea7e8584cb Consistent usage of quotation marks 2020-09-26 22:36:48 +09:00
tom5079
608c6e6a1d App built 2020-09-26 21:01:36 +09:00
tom5079
bb2c91145f Dependency update 2020-09-26 20:58:46 +09:00
tom5079
db074df0f7 Fixed Download Concurrency issue
Fixed image not showing up after reader is paused and resumed
2020-09-26 11:07:35 +09:00
tom5079
f7c45df9a6 Tag favorite bug fix 2020-09-26 09:36:20 +09:00
tom5079
44e3d16cd6 Merge branch 'dev' into master 2020-09-26 09:08:29 +09:00
tom5079
a973cdfe0b Download Bug fix
Added favorite to TagChip
Improved eyeblink recognition
2020-09-26 09:07:52 +09:00
tom5079
fca42c79a8 Updated startActivityForResult to launchers 2020-09-25 15:39:07 +09:00
tom5079
f236775599 Bug fix
Remember thin mode preference
TagChip favorites
2020-09-25 15:17:05 +09:00
tom5079
360decd37c FloatingSearchView migration 2020-09-16 14:31:45 +09:00
tom5079
998433479b Merge branch 'dev' into master 2020-09-15 23:20:01 +09:00
tom5079
c7e75aacf0 Layout fix
History fix
2020-09-15 23:19:26 +09:00
tom5079
690338273a Merge branch 'dev' into master 2020-09-15 02:42:33 +09:00
tom5079
4207ea494d Bug fix 2020-09-15 02:42:18 +09:00
tom5079
265473a15a Merge branch 'dev' into master
# Conflicts:
#	app/release/app-release.apk
#	app/release/output-metadata.json
2020-09-15 02:13:53 +09:00
tom5079
b907d36770 Bug fix 2020-09-15 02:13:25 +09:00
tom5079
fee280341a Blink Recognition 2020-09-15 01:12:29 +09:00
tom5079
0f1ef70752 Bug fix 2020-09-14 22:34:51 +09:00
tom5079
0f8c68b22e Fixed to work on old Androids 2020-09-13 21:42:02 +09:00
tom5079
701017d2ca Merge branch 'face-recog' into dev 2020-09-13 21:10:29 +09:00
tom5079
be6903ca12 App built 2020-09-13 16:24:23 +09:00
tom5079
1521bc1223 Downloader Bug fix
UI Optimized
Scroller autohide, track disable
2020-09-13 16:19:32 +09:00
tom5079
7ed66b827f Implemented eye recognition
TODO: Move pages according to eye blinking
2020-09-12 20:25:55 +09:00
tom5079
df3a478ef3 Bug fix 2020-09-12 20:22:34 +09:00
tom5079
974ddf69d5 Bug fix 2020-09-12 19:33:13 +09:00
tom5079
56a91268de Fixed fastscroll 2020-09-12 19:10:15 +09:00
tom5079
3dda2f9a1c Dependency update 2020-09-12 12:35:10 +09:00
tom5079
ed20456f9f VD Optimization 2020-09-12 12:17:02 +09:00
tom5079
281d4a0023 Adjusted Fastscroll UI
VD Optimized
2020-09-12 11:23:38 +09:00
tom5079
2170403662 Fixed Image UI 2020-09-12 10:32:23 +09:00
tom5079
b1c1e96135 Smol fix 2020-09-11 20:45:31 +09:00
tom5079
a8de1429c1 Bug fix 2020-09-11 19:53:49 +09:00
tom5079
3ba6cb81ae Bug fix 2020-09-11 19:40:56 +09:00
tom5079
acc85da80f Bug fix 2020-09-10 22:47:57 +09:00
tom5079
b53de8624d Bug fix 2020-09-10 22:45:27 +09:00
tom5079
6e2eeb29cc Bug fix 2020-09-10 21:41:57 +09:00
tom5079
62eb28ac01 Bug fix 2020-09-10 19:44:08 +09:00
tom5079
fd298529bf Memory usage optimization 2020-09-10 19:16:42 +09:00
tom5079
297ce506b1 Bug fix 2020-09-10 17:03:17 +09:00
tom5079
18c6954be3 Improved Suggestions
resolves #100
2020-09-10 16:50:26 +09:00
tom5079
cea3fb1e65 Bug fix 2020-09-09 16:58:01 +09:00
tom5079
7f274fd238 Added OSS Notice 2020-09-09 13:06:32 +09:00
tom5079
439a8e93ec App built 2020-09-09 11:25:53 +09:00
tom5079
83801feee9 Bug fix 2020-09-09 11:23:22 +09:00
tom5079
8a6860c96e Import cleanup 2020-09-09 09:29:33 +09:00
tom5079
5c959f2987 Bug fix 2020-09-09 09:29:33 +09:00
tom5079
4e4397287a Implemented fast scroll 2020-09-09 09:22:17 +09:00
tom5079
fe02abc9e8 Bug fix 2020-09-08 20:04:12 +09:00
tom5079
59347ab317 Bug fix 2020-09-08 19:16:15 +09:00
tom5079
f408a91176 Bug fix 2020-09-07 10:00:10 +09:00
tom5079
6f6956ce27 Fixed DownloadLocationDialogFragment keep showing up when any button is clicked 2020-09-06 17:19:18 +09:00
tom5079
4ecad8eccc Fixed migration 2020-09-05 18:31:53 +09:00
tom5079
486fbe46a0 Fixed migration 2020-09-05 18:11:20 +09:00
tom5079
1ddb636dd0 Fixed migration 2020-09-05 18:00:15 +09:00
tom5079
081c890b4e Bug fix 2020-09-05 12:51:41 +09:00
tom5079
86d528ba13 v5.0-beta1 2020-09-05 09:13:03 +09:00
tom5079
6bda3cb75a Performance improvement 2020-09-05 08:57:10 +09:00
tom5079
12d8949c9e Fixed context not attached error 2020-09-05 08:31:30 +09:00
tom5079
ffc7c2aa67 Bug fix 2020-09-04 11:05:29 +09:00
tom5079
5ec67488eb Bug fix 2020-09-04 07:51:55 +09:00
tom5079
be64703d3c Minor bug fix 2020-09-03 18:28:40 +09:00
tom5079
705925a050 Migration bug fix 2020-09-03 15:49:04 +09:00
tom5079
29665be34d Added Migration 2020-09-03 10:54:07 +09:00
tom5079
1edf986acf Added Download Folder Dialog 2020-09-02 21:59:17 +09:00
tom5079
37be8ccf7f Bug fix 2020-09-02 20:15:26 +09:00
tom5079
ead68b5201 Moved storage managing settings to another fragment 2020-09-02 13:28:05 +09:00
tom5079
4409664698 nomedia 2020-09-02 13:05:36 +09:00
tom5079
fc6bc7965c Added download folder name change 2020-09-02 12:58:31 +09:00
tom5079
f70eccb1da Created TagChip.kt 2020-09-02 12:29:34 +09:00
tom5079
861994e804 Fixed crash when download folder is changed 2020-09-02 12:15:37 +09:00
tom5079
2b8facfb97 End service when completed
Auto redownload
2020-09-02 11:03:46 +09:00
tom5079
9583897ada working 2020-09-02 00:13:25 +09:00
tom5079
7704c96955 what i got so far 2020-09-01 18:07:16 +09:00
tom5079
c96d609803 Created new cache/downloader 2020-08-31 11:41:12 +09:00
tom5079
aa0e5000ab Deleted redundant code 2020-08-30 20:12:06 +09:00
tom5079
7ca4418a50 Added global preferences object 2020-08-30 19:52:51 +09:00
tom5079
fdd9b02388 (Almost) Full OkHTTP implementation 2020-08-28 22:43:47 +09:00
tom5079
ece127e982 Fixed downloader not fully cancelled after DownloadWorker.cancel() is called 2020-08-28 21:51:44 +09:00
tom5079
5488e14f32 Moved history adding code to ReaderActivity.kt 2020-08-26 23:47:07 +09:00
tom5079
3558d826fb Implemented new favorite backup/restore feature 2020-08-26 23:40:36 +09:00
tom5079
68c94d1d8b Removed old features 2020-08-26 20:00:30 +09:00
tom5079
1a4ae5dfc6 Dependency update 2020-08-26 19:19:26 +09:00
tom5079
1a95afe266 Implemented Right-to-Left pageturn 2020-08-26 18:47:27 +09:00
tom5079
6579db3cc8 Added Search all galleries feature 2020-08-26 18:36:59 +09:00
tom5079
ceac01533a Updated History.kt to use kotlin's delegate feature 2020-08-26 18:01:12 +09:00
tom5079
216914882c Separated libpupil to standalone repository
Migrated to Kotlin 1.4
2020-08-23 20:26:23 +09:00
tom5079
735dbab695 Build optimization 2020-08-10 01:53:11 +09:00
tom5079
dbaab152ef Build optimization 2020-08-10 01:32:56 +09:00
tom5079
9da1b30984 i'm dumb 2020-08-08 16:29:04 +09:00
tom5079
9415ab4ef9 Fixed some images crashing
Added auto pageturn timer
2020-08-08 15:55:17 +09:00
tom5079
647294daf2 what were i thinking?! 2020-08-04 17:50:07 +09:00
tom5079
6ebc386474 Fixed app crashing when deleting cache/download 2020-08-04 12:14:14 +09:00
tom5079
3e657bdc09 Merge branch 'dev' 2020-08-03 21:54:28 +09:00
tom5079
0b0adb76a1 Merge branch 'master' into dev
# Conflicts:
#	app/build.gradle
#	app/release/app-release.apk
#	app/release/output-metadata.json
2020-08-03 21:54:14 +09:00
tom5079
17b3e010aa fuck git 2020-08-03 21:49:04 +09:00
tom5079
20003acd73 App built
resolves #98
2020-08-03 21:34:24 +09:00
tom5079
2ab7672092 Search Backtracking Added 2020-08-03 21:31:39 +09:00
tom5079
c317abe64b Open Gallery Info dialog instead of opening up the gallery when opening a random gallery 2020-08-03 21:19:08 +09:00
tom5079
bc33ce1ebc Fix Settings opening up too late if the download folder is too big 2020-08-03 21:14:47 +09:00
Pupil
684c5cf38b Merge pull request #105 from tom5079/dev
4.19-hotfix2
2020-08-03 02:10:17 +09:00
tom5079
c34e15f0a1 Fixed image corruption when not using cache 2020-08-03 02:06:37 +09:00
Pupil
bad004f892 Merge pull request #104 from tom5079/dev
Version 4.19-hotfix1
2020-08-02 17:48:32 +09:00
tom5079
828d3de020 Fixed app crashing between android version 5.0 and 5.1 2020-08-02 17:45:24 +09:00
tom5079
132b3b9be1 Unable to fetch thumbnails fixed 2020-07-29 10:53:50 +09:00
Pupil
388bc6fda5 Merge pull request #103 from tom5079/Pupil-99
resolves #99
2020-07-29 10:28:01 +09:00
tom5079
a93edc184d resolves #99 2020-07-23 22:42:38 +09:00
tom5079
08672d10ac oops 2020-07-23 20:48:24 +09:00
tom5079
b563dae3a8 App rebuilt 2020-07-23 20:37:38 +09:00
tom5079
917f9672dd bug fix - unable to run on older devices
Version 4.18.4
2020-07-23 20:32:45 +09:00
Pupil
9ddb19530b Merge pull request #101 from tom5079/dev
Version 4.18.3
2020-07-12 20:09:12 +09:00
tom5079
431e56a9f1 Version 4.18.3 2020-07-12 20:07:59 +09:00
Pupil
71093aac4c Update README.md 2020-07-12 19:53:28 +09:00
tom5079
47c9e8127e Remove logic for resetting low quality setting 2020-07-07 10:21:30 +09:00
tom5079
24b801b346 resolves #52 2020-07-05 19:45:55 +09:00
tom5079
70608c3ed9 resolves #52 2020-07-05 19:40:14 +09:00
tom5079
f185196e85 Version Update 2020-07-05 19:33:56 +09:00
tom5079
a8766a8bbe Drawer logo fixed 2020-06-23 20:37:20 +09:00
Pupil
27a8c93cfe Merge pull request #97 from tom5079/Pupil-95
Pupil 95
2020-06-23 18:49:57 +09:00
tom5079
a3cd29fda9 Merge remote-tracking branch 'origin/Pupil-95' into Pupil-95 2020-06-23 18:48:24 +09:00
tom5079
adda8ab640 Pupil-95 Search tag icon not visible in light theme 2020-06-23 18:48:13 +09:00
tom5079
1538ea5fc8 Merge remote-tracking branch 'origin/dev' into dev 2020-06-23 18:47:58 +09:00
tom5079
2367a97a54 App built 2020-06-23 18:47:48 +09:00
tom5079
090ec0e4af Auto-Retry
Bug fix
2020-06-23 18:47:48 +09:00
tom5079
de7f552e5c Pupil-95 Search tag icon not visible in light theme 2020-06-23 18:40:43 +09:00
tom5079
d763f5dca0 App built 2020-06-22 09:52:25 +09:00
tom5079
9f41116241 Auto-Retry
Bug fix
2020-06-22 09:48:04 +09:00
Pupil
57faada201 Merge pull request #93 from tom5079/dev
Version 4.18.1
2020-06-21 23:03:59 +09:00
tom5079
1edb95f0c5 Prevent OOM when cache is disabled 2020-06-21 23:03:10 +09:00
tom5079
9f363d8900 idk wtf i'm doin' 2020-06-21 22:43:47 +09:00
Pupil
0bf2f1b6e1 Merge pull request #92 from tom5079/dev
Version 4.18
2020-06-21 18:19:40 +09:00
tom5079
68c7a38390 Several fixes 2020-06-21 18:19:13 +09:00
tom5079
841c8a7a15 Download Retry Button Added 2020-06-21 18:12:46 +09:00
Pupil
6c9688183b Merge pull request #91 from tom5079/Pupil-90
Pupil 90
2020-06-21 17:40:54 +09:00
tom5079
ccd84c91f6 Retry 5 times when failed 2020-06-21 17:40:03 +09:00
tom5079
318d6f9b52 User ID added for crash analysis 2020-06-21 17:31:46 +09:00
tom5079
8f5d612ee0 Migrate to vector image 2020-06-21 16:55:56 +09:00
tom5079
56b2a05596 SearchView Dark Theme 2020-06-21 16:30:10 +09:00
tom5079
4db0022d6a Fixed nomedia creation menu 2020-06-21 15:32:55 +09:00
Pupil
67f37d3188 Merge pull request #87 from tom5079/Pupil-77
Pupil 77
2020-06-21 15:00:39 +09:00
tom5079
ed81cc7207 APCJSA enabling dialog added 2020-06-21 15:00:05 +09:00
tom5079
065845f1be Cache disable setting added 2020-06-21 14:45:57 +09:00
tom5079
902f705e89 Added Filter 2020-06-21 11:55:34 +09:00
tom5079
ec2e0ef773 SuppressLint 2020-06-21 11:42:50 +09:00
tom5079
d28c5741d0 SuppressLint 2020-06-21 11:42:38 +09:00
Pupil
e6e3f9e8f8 Merge pull request #86 from tom5079/Pupil-59
Pupil 59
2020-06-21 11:36:13 +09:00
tom5079
90e1dc59bd Disable fingerprint when all the locks are disabled 2020-06-21 11:31:37 +09:00
tom5079
0b1c9b097c Disable fingerprint when all the locks are disabled 2020-06-21 11:24:30 +09:00
tom5079
2b553d1116 Changed Title/Subtitle 2020-06-20 23:32:38 +09:00
tom5079
567eec8bc5 Added Fingerprint Lock 2020-06-20 23:23:07 +09:00
tom5079
293ca5b31d Added PIN Lock 2020-06-20 22:48:47 +09:00
Pupil
0d0f2bd827 Merge pull request #85 from tom5079/Pupil-84
Pupil-84 Title doesn't show up when using hiyobi
2020-06-20 15:28:37 +09:00
tom5079
5bc4610061 Pupil-84 Title doesn't show up when using hiyobi 2020-06-20 15:28:01 +09:00
Pupil
e6b7c107f2 Merge pull request #83 from tom5079/Pupil-76
Pupil-76
2020-06-20 14:32:26 +09:00
tom5079
51a9bf2570 Pupil-76 Add Page count 2020-06-20 14:30:20 +09:00
Pupil
8385f6f390 Merge pull request #82 from tom5079/Pupil-79
Pupil-79
2020-06-20 14:05:36 +09:00
tom5079
772e9daf57 App Link Updated 2020-06-20 14:04:46 +09:00
Pupil
8adc4405c5 Merge pull request #81 from tom5079/Pupil-78
Pupil-78 Shorten the timout when hiyobi is down
2020-06-20 13:16:57 +09:00
tom5079
349da7aa81 Pupil-78 Shorten the timout when hiyobi is down 2020-06-20 13:14:09 +09:00
tom5079
01a01d481d Version up 2020-06-20 13:13:57 +09:00
tom5079
2f8445fb83 Dependency upgrade 2020-06-19 16:34:05 +09:00
tom5079
b04a5fc150 Dependency upgrade 2020-06-19 15:18:16 +09:00
Pupil
bbe29941df Merge pull request #75 from tom5079/dev
Version 4.17
2020-06-15 08:20:29 +09:00
tom5079
2720e445ea Changed low quality settting to true by default 2020-06-15 08:19:16 +09:00
tom5079
49ba579a59 Random Gallery Added
Changed tag search behavior
Loading time improved for hitomi.la
Bug fixed
2020-06-14 16:53:30 +09:00
Pupil
3198c6cbfd Merge pull request #74 from tom5079/dev
Version 4.15
2020-03-25 19:02:50 -07:00
pupil
b3feee6d9d Fast scroll Added
Not able to download after finished downloading to cache Fixed
Trying to move files from different threads causing exceptions Fixed
Import old galleries Added
2020-03-25 10:29:05 -07:00
Pupil
f0f53e6bce Fixed - Jump to page in Reader not working 2020-03-02 20:30:26 +09:00
Pupil
24486d13f2 Bug fix
Memory usage optimization
2020-02-29 13:23:37 +09:00
Pupil
20bc9461de Merge branch 'dev_rescued' 2020-02-29 09:43:36 +09:00
Pupil
c8e94cc295 Build tool update 2020-02-29 09:09:51 +09:00
Pupil
b2bfb0c237 Bug fix
Update downloader changed to DownloadManager
Fixed wierd download path
Fixed Crash on MainActivity
Fixed Crash when non-integer is inputted as Gallery ID

Version 4.13
2020-02-27 19:16:38 +09:00
Pupil
0a003da724 Bug fix 2020-02-26 09:51:16 +09:00
Pupil
b4f2a33016 Merge pull request #72 from tom5079/dev
Modified proguard rules to fix error occurs on Android 4
2020-02-25 22:02:48 +09:00
Pupil
ee7ede2885 Modified proguard rules to fix error occurs on Android 4 2020-02-25 21:50:36 +09:00
Pupil
6abc404eb7 Merge pull request #71 from tom5079/dev
Version 4.11
2020-02-25 20:35:41 +09:00
Pupil
61afe01e36 Fixed image loading from hiyobi.me 2020-02-25 20:34:31 +09:00
Pupil
c3e60f9988 Typo fixed 2020-02-25 19:19:27 +09:00
Pupil
593197cd7e Bug fix
Thin mode added
Cancel all downloads added
2020-02-25 19:17:23 +09:00
Pupil
ee1592b478 Bug fix 2020-02-25 10:38:11 +09:00
Pupil
dfe435c4f3 Merge pull request #70 from tom5079/dev
Version 4.9
2020-02-24 21:11:03 +09:00
Pupil
69e85f8b90 Bug fix 2020-02-24 21:10:10 +09:00
Pupil
c9bde3c487 Merge pull request #69 from tom5079/dev
Version 4.8
2020-02-24 20:48:55 +09:00
Pupil
65e9557d9f Bug fix 2020-02-24 20:02:44 +09:00
Pupil
4f249c07e7 Merge pull request #68 from tom5079/dev
Version 4.7
2020-02-24 12:49:56 +09:00
Pupil
5fd35b492c Bug fix 2020-02-24 12:49:19 +09:00
Pupil
9bddf95013 Image loading fixed 2020-02-23 21:18:19 +09:00
Pupil
03444f070f App built 2020-02-23 10:40:09 +09:00
Pupil
2f1a63eb64 Confilict resolved 2020-02-23 10:32:10 +09:00
Pupil
9d0898b26c Fixed image loading bug 2020-02-23 10:30:57 +09:00
Pupil
994aa99797 Fixed image loading bug 2020-02-23 10:28:29 +09:00
Pupil
8204a15276 Proxy applied to thumbnails 2020-02-22 20:30:42 +09:00
Pupil
4a8bff0b98 Merge pull request #67 from tom5079/dev
Version 4.6
2020-02-22 11:09:19 +09:00
Pupil
a4336cd954 Version 4.6 2020-02-22 11:08:30 +09:00
Pupil
4f0dbead79 Hiyobi file structure changed 2020-02-22 11:02:58 +09:00
Pupil
c0e7c87ca4 Fixed image loading error 2020-02-22 09:30:24 +09:00
Pupil
b967bf9a26 Merge branch 'issue-65' into dev 2020-02-21 20:44:03 +09:00
Pupil
764a265053 Image loading optimization 2020-02-21 20:11:43 +09:00
Pupil
68c2b2dbfa Update README.md
Added discord banner
2020-02-21 20:11:27 +09:00
Pupil
061f1263f4 App naming changed from beta to alpha 2020-02-17 20:33:12 +09:00
Pupil
2a27355479 App built 2020-02-17 20:31:36 +09:00
Pupil
ae2a8e8ada Fixed low quality settings not affected 2020-02-17 19:56:57 +09:00
Pupil
68dcc2333b App built 2020-02-17 19:09:45 +09:00
Pupil
66fb2e9a62 Fixed ArrayIndexOutOFBoundsException 2020-02-17 18:50:58 +09:00
Pupil
1dbfc64f37 Fixed not able to load from hiyobi 2020-02-17 16:46:51 +09:00
Pupil
98d1f88579 Fixed infinite loading when there's no result 2020-02-16 22:18:31 +09:00
Pupil
bb6fadc182 Fixed unending loading screen 2020-02-16 20:11:20 +09:00
Pupil
ac1ca71299 Proxy implemented 2020-02-16 19:59:51 +09:00
Pupil
0d93785581 Fixed proxy not applied 2020-02-16 18:23:50 +09:00
Pupil
69a9d63e1d Proxy added 2020-02-15 12:40:10 +09:00
Pupil
5dea35343b Fixed preference bug
Version fix
2020-02-15 01:59:42 +09:00
Pupil
5c768d2121 Firebase enabled 2020-02-15 00:25:59 +09:00
Pupil
4d5834821a Fixed wrong radio button selected when download folder is not selected 2020-02-14 20:48:33 +09:00
Pupil
ca077c4fee Apk built 2020-02-14 20:37:48 +09:00
Pupil
85d01f60f1 Changed galleryblock retrieve url 2020-02-14 20:31:10 +09:00
Pupil
066d73b217 Generated APK 2020-02-14 20:13:26 +09:00
Pupil
ba069d8f8e Image loading optimization 2020-02-14 20:10:04 +09:00
Pupil
275684c9ce now able to install Debug and release builds in one device
Fixed shrink serialization error
2020-02-14 17:02:53 +09:00
Pupil
49d87a08d2 Set download notifications non-dismissable 2020-02-13 20:29:45 +09:00
Pupil
04c500f3d8 Improved galleryBlock loading logic 2020-02-13 20:15:17 +09:00
Pupil
d05c1e4d08 Improved galleryBlock loading logic 2020-02-13 20:14:26 +09:00
Pupil
bb63959678 Allow download multiple galleries concurrently 2020-02-13 20:07:16 +09:00
Pupil
842148647f Changed to log fetchGallery exceptions 2020-02-13 19:42:25 +09:00
Pupil
19308d840a Merge branch 'master' into hotfix 2020-02-12 18:51:17 +09:00
Pupil
46bd1318cd Merge branch 'master' into old 2020-02-12 18:49:35 +09:00
Pupil
9d1998fe52 deleted deleteRecursively 2020-02-12 09:03:47 +09:00
Pupil
a714a8230b ugh 2020-02-11 21:23:34 +09:00
Pupil
b5432cd0b4 ugh 2020-02-11 09:29:58 +09:00
Pupil
5634e94f3e DocumentFileX 2020-02-10 16:28:13 +09:00
Pupil
c1a71b0db3 hotfix 2020-02-10 12:17:05 +09:00
Pupil
d93e7f8834 Fixed renamed file 2020-02-09 18:26:52 +09:00
Pupil
3175b2c45c Fixed renamed file 2020-02-09 18:17:21 +09:00
Pupil
547b6e8e3b Fixed renamed file 2020-02-09 18:16:45 +09:00
Pupil
d88ac27e72 Fixed renamed file 2020-02-09 18:15:46 +09:00
Pupil
e551a40d08 Fixed renamed file 2020-02-09 18:13:16 +09:00
Pupil
e810abe33a Bug fix 2020-02-09 17:57:18 +09:00
Pupil
6172a73719 Bug fix 2020-02-09 17:36:28 +09:00
Pupil
7455e68a45 Bug fix 2020-02-09 17:35:34 +09:00
Pupil
748495ca64 Downloader thread number to 4 2020-02-09 17:25:55 +09:00
Pupil
f6d9c7f550 Bug fix
Networking optimized
2020-02-09 17:11:35 +09:00
Pupil
384e6c61b0 Fixed 'Can't locate argument-less serializer for class xyz.quaver.pupil.d.e. For generic classes, such as lists, please provide serializer explicitly.' 2020-02-08 22:16:26 +09:00
Pupil
d49c9cec20 Merge pull request #64 from tom5079/development
Version 5.0
2020-02-08 20:38:51 +09:00
Pupil
4b27f1aba1 Added support for gallery unregistered in hitomi 2020-02-08 20:37:59 +09:00
Pupil
a0a989c785 Added support for gallery unregistered in hitomi 2020-02-08 20:36:08 +09:00
Pupil
ecaecc1b91 Added Custom download folder 2020-02-08 19:01:45 +09:00
Pupil
938156aa71 Merge pull request #58 from tom5079/issue-35
Might be a fix for #35
2020-02-03 12:01:06 +09:00
Pupil
d30c51bb3a Finished integrating new downloader 2020-02-03 11:54:29 +09:00
Pupil
874606bff9 Added Download feature 2020-02-01 12:50:26 +09:00
Pupil
07643e4b4c fixed conflict 2020-01-31 11:11:34 +09:00
Pupil
20bc5423cf Merge pull request #63 from tom5079/development
Version 4.3-hotfix1
2020-01-31 10:39:06 +09:00
Pupil
b84cddffdc Version up & dependency update 2020-01-31 10:32:21 +09:00
Pupil
e46d1123df resolves #62 2020-01-31 10:24:19 +09:00
Pupil
48f90faf4e Applying changed Download routines 2020-01-31 10:12:44 +09:00
Pupil
615b52c4fa Merge branch 'development' into issue-35 2020-01-30 11:58:24 +09:00
Pupil
2c9c8e223c Fixes bug when trying to open hiyobi-only galleries 2020-01-30 00:01:53 +09:00
Pupil
01a653835e Deprecate GalleryDownloader 2020-01-29 20:58:20 +09:00
Pupil
9d80857a38 Rebuilding Downloader 2020-01-29 15:46:23 +09:00
Pupil
8a9ab6b36c Rebuilding Downloader 2020-01-28 12:56:32 +09:00
Pupil
4edc87c197 Rebuilding Downloader 2020-01-28 12:54:34 +09:00
Pupil
10712e6e62 Rebuilding Downloader 2020-01-28 10:48:31 +09:00
Pupil
d73dc19d3d Fixed enlarged chip spacing 2020-01-24 17:02:53 +09:00
Pupil
c204353220 Added mirror selector
Developing new Downloader under xyz.quaver.pupil.util.download
2020-01-24 15:11:35 +09:00
Pupil
37123a2cd5 Merge remote-tracking branch 'origin/issue-35' into issue-35
# Conflicts:
#	app/src/main/res/layout/item_reader.xml
2020-01-22 23:22:44 +09:00
Pupil
a39484b6ea Might be a fix for #35
but it's quite dirty :v
2020-01-22 23:17:28 +09:00
Pupil
e81b5a4e3a Added pinch-zoom 2020-01-22 23:14:57 +09:00
Pupil
0b87c57fbf Dependency update 2020-01-22 23:11:54 +09:00
Pupil
5fd985ba39 Merge pull request #61 from tom5079/Pupil-57
Pupil-57 about the horizontal and search
2020-01-22 11:45:14 +09:00
Pupil
8c64548513 Pupil-57 about the horizontal and search 2020-01-22 11:42:27 +09:00
Pupil
a6de64ceb9 Some code cleanups :P #37 2020-01-22 11:21:18 +09:00
Pupil
16ebb437a3 Merge pull request #60 from tom5079/Pupil-25
Pupil-25 Add option to download jpg instead of webp files
2020-01-22 11:08:49 +09:00
Pupil
683118a3f4 Fixed old android not supporting ContentProvider 2020-01-22 11:07:55 +09:00
Pupil
08e38ed45c Pupil-25 Add option to download jpg instead of webp files 2020-01-22 10:50:55 +09:00
Pupil
7abf08f1fb Some code cleanups :P #37 2020-01-20 18:59:18 +09:00
Pupil
f3019e9b84 Some code cleanups :P #37 2020-01-20 18:58:19 +09:00
Pupil
9ea55664b6 Might be a fix for #35
but it's quite dirty :v
2020-01-17 10:30:39 +09:00
Pupil
c468764234 Fixed enlarged chip spacing 2020-01-16 20:37:43 +09:00
Pupil
31c3178430 Merge pull request #56 from tom5079/Pupil-54
Fixed thumbnail on gallery info
2020-01-15 11:29:58 +09:00
Pupil
e81c189afc Fixed thumbnail on gallery info 2020-01-15 11:28:38 +09:00
Pupil
e0ccac13c1 Forgot to handle error :P 2020-01-15 10:58:53 +09:00
Pupil
93228459d7 Fixed language selection based on locale 2020-01-13 20:53:27 +09:00
Pupil
63e07f56e0 Merge pull request #51 from tom5079/development
Version 4.3
2020-01-13 20:35:43 +09:00
Pupil
ee87122bb2 Fixed Checking permission despite requiring no permission 2020-01-13 20:32:50 +09:00
tom5079
290dda9018 Added log to indicate firebase status 2020-01-13 15:25:44 +09:00
Pupil
1d3d78b936 Merge pull request #50 from tom5079/development
Version 4.2
2020-01-13 15:07:00 +09:00
Pupil
a947bc6415 Merge pull request #49 from tom5079/Pupil-28
Created beta channel update feature
2020-01-13 15:05:58 +09:00
tom5079
9ca891b2f5 Changed SwitchPreference to SwitchPreferenceCompat for better integration 2020-01-13 15:02:00 +09:00
tom5079
48e0ebc8ae Pupil-28 Add option to select update channels 2020-01-13 14:43:55 +09:00
tom5079
b323353006 Merge remote-tracking branch 'origin/development' into development 2020-01-13 14:11:07 +09:00
Pupil
c85d3ebe81 Merge pull request #48 from tom5079/issue-39
Added Changing Download directory feature
2020-01-13 14:09:41 +09:00
tom5079
ce843abec8 Changed logic to update app from utilizing DownloadManager to manual download 2020-01-13 14:08:31 +09:00
tom5079
6b43faa70e Fixed crash when built without google-services.json 2020-01-13 14:08:31 +09:00
tom5079
2d0c997b2e Updated build.gradle 2020-01-13 14:08:31 +09:00
tom5079
1db5118377 Updated .gitignore 2020-01-13 14:08:31 +09:00
tom5079
26b53ed7ac Fixed crash when built without google-services.json 2020-01-12 19:12:47 +09:00
tom5079
2c85ea6443 Removes Permission check for downloading updates
TODO: write logic for downloading update file instead of using DownloadManager(Permission problem)
2020-01-11 18:51:15 +09:00
tom5079
cbc2b30f47 resolves #39 2020-01-11 06:51:51 +09:00
tom5079
0b58deb92c Updated build.gradle 2020-01-04 14:01:54 +09:00
tom5079
ed1cf23c91 Updated .gitignore 2020-01-04 14:00:31 +09:00
tom5079
6fbb644e4b Added download directory entry on preferences
Changed download folder
2020-01-04 13:16:39 +09:00
Pupil
774867502d Merge pull request #47 from tom5079/issue-42
Fixed #42
2020-01-02 10:31:39 +09:00
tom5079
c8b1439aeb Fixed #42 2020-01-02 10:30:55 +09:00
tom5079
38c16adffe Fixed to be able to build without google-services.json 2020-01-02 10:18:39 +09:00
Pupil
18aede2701 Merge pull request #45 from tom5079/issue-44
issue-44
2019-12-29 14:46:13 +09:00
tom5079
c59d08a0a1 Fixes #44 2019-12-29 14:42:28 +09:00
tom5079
66ae29eb5b Fixes #44 2019-12-29 14:24:20 +09:00
tom5079
7d9cb3e150 Dependency update 2019-12-29 13:34:45 +09:00
Pupil
9922a9f82a Merge pull request #41 from tom5079/hotfix-40
Fixes #40
2019-12-19 09:37:23 +09:00
tom5079
445b9b4673 Fixes #40 2019-12-19 09:36:51 +09:00
tom5079
0ef7b358e0 Fixes #40 2019-12-19 09:33:10 +09:00
tom5079
2d3fb75576 Fixes wierd crash 2019-12-14 17:04:43 +09:00
tom5079
d55ff6d68e Fixes wierd crash 2019-12-14 17:04:04 +09:00
Pupil
079654a9c7 Merge pull request #36 from tom5079/Pupil-35
fixes #35
2019-12-14 16:56:58 +09:00
tom5079
30263c6260 fixes #35
warning: this can cause OOM
2019-12-14 16:54:59 +09:00
Pupil
3159c343c1 Merge pull request #34 from tom5079/development
Version 4.2-beta1
2019-12-13 20:10:55 +09:00
tom5079
ceaa930623 bug fix 2019-12-13 20:03:11 +09:00
tom5079
6a8539106b bug fix 2019-12-13 20:01:45 +09:00
tom5079
7a24c3c08e bug fix 2019-12-13 19:50:14 +09:00
tom5079
251abeb090 Merge remote-tracking branch 'origin/development' into development 2019-12-13 19:43:42 +09:00
Pupil
a61fe9f98c Merge pull request #33 from tom5079/Pupil-29
Pupil-29
2019-12-13 19:42:44 +09:00
tom5079
d29c7bf91a Apply update on startup 2019-12-13 19:30:19 +09:00
tom5079
ed4911c441 Updated serialization library 2019-12-13 18:39:12 +09:00
tom5079
d40b4f3748 Added update logic for outdated readers 2019-12-12 20:14:55 +09:00
tom5079
f3c4fe1914 Merge remote-tracking branch 'origin/development' into development 2019-12-11 20:23:45 +09:00
tom5079
55ee841bd0 resolves #31 2019-12-11 20:23:18 +09:00
tom5079
657fb488ee fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 20:23:18 +09:00
tom5079
4eef0b93fb bug fix 2019-12-11 20:23:17 +09:00
Pupil
f2be56435c Merge pull request #32 from tom5079/Pupil-31 2019-12-11 20:08:45 +09:00
tom5079
fa6b3ad7ba resolves #31 2019-12-11 20:03:55 +09:00
tom5079
52c05e6888 fixed #31 on libpupil
#TODO: fix app side to completely resolve the issue
2019-12-11 19:52:25 +09:00
tom5079
865bf0ba83 Added code for differentiating readers 2019-12-09 10:33:26 +09:00
tom5079
3f827d1bad bug fix 2019-12-09 10:15:53 +09:00
tom5079
0561d5f55c Added code for saved reader 2019-12-09 09:36:36 +09:00
Pupil
1bf2e1dacc Merge pull request #30 from tom5079/Pupil-24
fixed #24
2019-12-08 18:45:21 +09:00
tom5079
db5a221b56 kotlin plugin update 2019-12-08 18:10:35 +09:00
tom5079
295285f132 Turned off development only option 2019-12-02 19:04:09 +09:00
tom5079
5052b6c074 Removed folder opening feature due to its unstability 2019-12-02 18:55:38 +09:00
tom5079
f98f45dc54 Pupil-24 Absence of backing up favorites feature 2019-12-01 16:58:29 +09:00
tom5079
8d16950f46 Issue #27 fix 2019-11-30 16:10:47 +09:00
tom5079
74033b9f4a Issue #27 fix 2019-11-30 16:10:09 +09:00
tom5079
e497d47374 Fix for bug caused by changed hiyobi domain 2019-11-30 15:10:25 +09:00
tom5079
a97af59260 Potential fix for memory issues 2019-11-30 15:09:53 +09:00
tom5079
2197de98ea Potential fix for too large bitmap crash 2019-11-25 19:39:17 +09:00
tom5079
c004c7f71a Fixed bug fetching old galleries from hiyobi 2019-11-15 19:47:09 +09:00
tom5079
69fc3ad4e8 typo 2019-11-02 21:50:03 +09:00
tom5079
678a8f0914 Merge pull request #21 from tom5079/development
Fixed bug with missing hash
2019-11-02 21:46:11 +09:00
tom5079
08c4c0bf1f Fixed bug with missing hash
Version 4.1
2019-11-02 21:45:27 +09:00
tom5079
f2a2656837 Merge pull request #20 from tom5079/development
Development
2019-11-02 20:30:04 +09:00
tom5079
2011572270 Merge remote-tracking branch 'origin/development' into development 2019-11-02 20:29:24 +09:00
tom5079
3b682667e1 Fixed bug caused by updated hitomi server structure
Version 4.0
2019-11-02 20:25:03 +09:00
tom5079
6da8de6463 Merge pull request #19 from tom5079/master
merge readme
2019-11-02 20:08:28 +09:00
tom5079
039d415871 Update README.md
r/engrish
2019-08-31 23:59:41 +09:00
tom5079
776f53bde0 Update README.md 2019-08-30 22:28:07 +09:00
tom5079
58e535595e Update README.md 2019-08-30 22:27:32 +09:00
tom5079
96ad5f6a6c Update README.md 2019-08-30 22:27:08 +09:00
tom5079
043f7bedd8 Added quick download/delete 2019-08-30 15:24:51 +09:00
tom5079
8a58564812 Version 3.2 2019-08-29 12:10:51 +09:00
220 changed files with 4229 additions and 7458 deletions

46
.gitignore vendored
View File

@@ -1,19 +1,33 @@
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
#Github pages
/gh-pages
# Keystore files
*.jks
*.keystore
#Private files
/app/google-services.json
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof

View File

@@ -1,113 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View File

@@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value=" Copyright &amp;#36;today.year tom5079&#10;&#10; Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10; you may not use this file except in compliance with the License.&#10; You may obtain a copy of the License at&#10;&#10; http://www.apache.org/licenses/LICENSE-2.0&#10;&#10; Unless required by applicable law or agreed to in writing, software&#10; distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10; See the License for the specific language governing permissions and&#10; limitations under the License." />
<option name="myName" value="Apache" />
</copyright>
</component>

View File

@@ -1,6 +0,0 @@
<component name="CopyrightManager">
<copyright>
<option name="notice" value=" Pupil, Hitomi.la viewer for Android&#10; Copyright (C) &amp;#36;today.year tom5079&#10;&#10; This program is free software: you can redistribute it and/or modify&#10; it under the terms of the GNU General Public License as published by&#10; the Free Software Foundation, either version 3 of the License, or&#10; (at your option) any later version.&#10;&#10; This program is distributed in the hope that it will be useful,&#10; but WITHOUT ANY WARRANTY; without even the implied warranty of&#10; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the&#10; GNU General Public License for more details.&#10;&#10; You should have received a copy of the GNU General Public License&#10; along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;." />
<option name="myName" value="GPL" />
</copyright>
</component>

View File

@@ -1,8 +0,0 @@
<component name="CopyrightManager">
<settings>
<module2copyright>
<element module="Pupil" copyright="GPL" />
<element module="libpupil" copyright="Apache" />
</module2copyright>
</settings>
</component>

4
.idea/encodings.xml generated
View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

19
.idea/gradle.xml generated
View File

@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/libpupil" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

9
.idea/misc.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="Pupil" pattern="file[app]:*/" />
</component>

View File

@@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="libpupil" pattern="file[libpupil]:*/" />
</component>

6
.idea/vcs.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,2 +1,26 @@
# Pupil
Hitomi.la viewer for Android
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.1.6-hotfix7/Pupil-v5.1.6-hotfix7.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.1.6-hotfix7/Pupil-v5.1.6-hotfix7.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Features
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.jpg?raw=true)
# Installation
Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or
Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean)
or Build app yourself
# Manual
[Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean. Consider using translator.
# Contribution
Any kind of contribution is appriciated. Feel free to leave PR!
## Tag Translation
Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags)

View File

@@ -1,83 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlinx-serialization'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric'
apply plugin: 'com.google.firebase.firebase-perf'
android {
compileSdkVersion 29
defaultConfig {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 29
versionCode 27
versionName "3.2-beta2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true
vectorDrawables.useSupportLibrary = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'true')
it.buildConfigField('boolean', 'CENSOR', 'false')
}
}
kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
def markwonVersion = "3.0.1"
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.11.0"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-rc01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.google.android.material:material:1.1.0-alpha09'
implementation 'com.google.firebase:firebase-core:17.1.0'
implementation 'com.google.firebase:firebase-perf:19.0.0'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.clans:fab:1.6.4'
implementation 'com.github.bumptech.glide:glide:4.9.0'
implementation ("com.github.bumptech.glide:recyclerview-integration:4.9.0") {
transitive = false
}
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
implementation 'com.jsibbold:zoomage:1.3.0'
implementation "ru.noties.markwon:core:${markwonVersion}"
kapt 'com.github.bumptech.glide:compiler:4.9.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation project(path: ':libpupil')
}
androidExtensions {
experimental = true
}

183
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,183 @@
import com.google.protobuf.gradle.*
plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("kotlin-parcelize")
id("kotlinx-serialization")
id("com.google.android.gms.oss-licenses-plugin")
id("com.google.gms.google-services")
id("com.google.firebase.crashlytics")
id("com.google.firebase.firebase-perf")
id("com.google.protobuf")
}
android {
compileSdk = 33
signingConfigs {
create("release") {
storeFile = File(System.getenv("SIGNING_STORE_FILE"))
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
}
}
defaultConfig {
applicationId = "xyz.quaver.pupil"
minSdk = 21
targetSdk = 33
versionCode = 600
versionName = VERSION
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("debug") {
isDebuggable = true
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
extra.set("enableCrashlytics", false)
extra.set("alwaysUpdateBuildId", false)
}
getByName("release") {
isMinifyEnabled = false
applicationIdSuffix = ".beta"
isCrunchPngs = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
signingConfig = signingConfigs.getByName("release")
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.JETPACK_COMPOSE
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
}
packagingOptions {
resources.excludes.addAll(
listOf(
"META-INF/AL2.0",
"META-INF/LGPL2.1"
)
)
}
namespace = "xyz.quaver.pupil"
}
dependencies {
implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation(Kotlin.SERIALIZATION)
implementation(Kotlin.COROUTINE)
implementation("androidx.activity:activity-compose:1.6.1")
implementation("androidx.navigation:navigation-compose:2.5.3")
implementation(JetpackCompose.FOUNDATION)
implementation(JetpackCompose.UI)
implementation(JetpackCompose.UI_UTIL)
implementation(JetpackCompose.UI_TOOLING)
implementation(JetpackCompose.ANIMATION)
implementation(JetpackCompose.MATERIAL)
implementation(JetpackCompose.MATERIAL_ICONS)
implementation(JetpackCompose.RUNTIME_LIVEDATA)
// implementation(JetpackCompose.MARKDOWN)
implementation(Accompanist.INSETS)
implementation(Accompanist.INSETS_UI)
implementation(Accompanist.FLOW_LAYOUT)
implementation(Accompanist.SYSTEM_UI_CONTROLLER)
implementation(Accompanist.DRAWABLE_PAINTER)
implementation(Accompanist.APPCOMPAT_THEME)
implementation("io.coil-kt:coil-compose:2.0.0-rc03")
implementation(KtorClient.CORE)
implementation(KtorClient.OKHTTP)
implementation(KtorClient.CONTENT_NEGOTIATION)
implementation(KtorClient.SERIALIZATION)
implementation("androidx.room:room-runtime:2.4.3")
annotationProcessor("androidx.room:room-compiler:2.4.3")
kapt("androidx.room:room-compiler:2.4.3")
implementation("androidx.room:room-ktx:2.4.3")
implementation("androidx.datastore:datastore:1.0.0")
implementation("androidx.datastore:datastore-preferences:1.0.0")
implementation("org.kodein.di:kodein-di-framework-compose:7.11.0")
implementation(platform("com.google.firebase:firebase-bom:29.0.3"))
implementation("com.google.firebase:firebase-analytics-ktx")
implementation("com.google.firebase:firebase-crashlytics-ktx")
implementation("com.google.firebase:firebase-perf-ktx")
implementation("com.google.protobuf:protobuf-javalite:3.19.1")
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
implementation("org.jsoup:jsoup:1.14.3")
implementation("xyz.quaver.pupil.sources:core:0.0.1-alpha01-DEV29")
implementation("xyz.quaver:documentfilex:0.7.2")
implementation("xyz.quaver:subsampledimage:0.0.1-alpha22-SNAPSHOT")
implementation("org.kodein.log:kodein-log:0.12.0")
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.8.1")
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-inline:4.4.0")
testImplementation(KtorClient.TEST)
testImplementation(Kotlin.COROUTINE_TEST)
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test:rules:1.4.0")
androidTestImplementation("androidx.test:runner:1.4.0")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation(KtorClient.TEST)
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.1.1")
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.19.1"
}
generateProtoTasks {
all().forEach { task ->
task.builtins {
id("java") {
option("lite")
}
}
}
}
}
task<Exec>("clearAppCache") {
commandLine("adb", "shell", "pm", "clear", "xyz.quaver.pupil.debug")
}

1
app/credentials.json Normal file
View File

@@ -0,0 +1 @@
{"installed":{"client_id":"644157827114-rnbcmlqiaqgg295o45kavchnvi3dedbo.apps.googleusercontent.com","project_id":"pupil-1598439316578","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}}

View File

@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
@@ -18,4 +18,28 @@
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
#-renamesourcefileattribute SourceFile
-dontobfuscate
-keepattributes *Annotation*, InnerClasses
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
-keepclasseswithmembers class kotlinx.serialization.json.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; }
-keepclassmembers class xyz.quaver.** {
*** Companion;
}
-keepclasseswithmembers class xyz.quaver.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite {
<fields>;
}

View File

@@ -0,0 +1,20 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "xyz.quaver.pupil.beta",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 600,
"versionName": "6.0.0-alpha02",
"outputFile": "app-release.apk"
}
],
"elementType": "File"
}

View File

@@ -1 +0,0 @@
[{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":25,"versionName":"3.1","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}]

View File

@@ -20,22 +20,20 @@
package xyz.quaver.pupil
import android.content.Intent
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import org.junit.Assert.assertEquals
import org.junit.Rule
import com.google.api.Http
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.Test
import org.junit.runner.RunWith
import xyz.quaver.hitomi.fetchNozomi
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.getReader
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.ui.LockActivity
import java.net.URL
import javax.net.ssl.HttpsURLConnection
/**
* Instrumented test, which will execute on an Android device.
@@ -49,34 +47,5 @@ class ExampleInstrumentedTest {
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("xyz.quaver.pupil", appContext.packageName)
Log.d("Pupil", fetchNozomi().first.size.toString())
}
@Test
fun checkCacheDir() {
val activityTestRule = ActivityTestRule(LockActivity::class.java)
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
activityTestRule.launchActivity(Intent())
while(true);
}
@Test
fun test_doSearch() {
val reader = getReader(1426382)
val data: ByteArray
with(URL(reader.readerItems[0].url).openConnection() as HttpsURLConnection) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
data = inputStream.readBytes()
}
Log.d("Pupil", data.size.toString())
}
}
}

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~ Copyright (C) 2020 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -16,12 +17,6 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" translatable="false" tools:override="true">Pupil-Debug</string>
</resources>

View File

@@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.quaver.pupil">
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="21"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="21"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<application
android:name=".Pupil"
@@ -15,115 +19,38 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
android:windowSoftInputMode="adjustResize"
tools:replace="android:theme"
tools:ignore="UnusedAttribute">
<activity android:name=".ui.LockActivity"/>
<activity
android:name=".ui.ReaderActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:parentActivityName=".ui.MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<provider
android:authorities="${applicationId}.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true">
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<data
android:host="hitomi.la"
android:pathPrefix="/galleries"
android:scheme="https" />
</intent-filter>
<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="히요비.asia"
android:pathPrefix="/reader"
android:scheme="https" />
</intent-filter>
<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="xn--9w3b15m8vo.asia"
android:pathPrefix="/reader"
android:scheme="https" />
</intent-filter>
<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="e-hentai.org"
android:pathPrefix="/g"
android:scheme="https" />
</intent-filter>
<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="hitomi.la"
android:pathPrefix="/galleries"
android:scheme="http" />
</intent-filter>
<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="히요비.asia"
android:pathPrefix="/reader"
android:scheme="http" />
</intent-filter>
<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="xn--9w3b15m8vo.asia"
android:pathPrefix="/reader"
android:scheme="http" />
</intent-filter>
<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="e-hentai.org"
android:pathPrefix="/g"
android:scheme="http" />
</intent-filter>
</activity>
<activity
android:name=".ui.SettingsActivity"
android:label="@string/settings_title" />
<activity
android:name=".ui.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/NoActionBarAppTheme">
android:theme="@style/NoActionBarAppTheme"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
</application>

View File

@@ -18,37 +18,39 @@
package xyz.quaver.pupil
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat
import androidx.multidex.MultiDexApplication
import androidx.preference.PreferenceManager
import com.google.android.gms.common.GooglePlayServicesNotAvailableException
import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller
import xyz.quaver.pupil.util.Histories
import java.io.File
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.serialization.kotlinx.json.*
import org.kodein.di.*
import org.kodein.di.android.x.androidXModule
import xyz.quaver.pupil.sources.core.NetworkCache
import xyz.quaver.pupil.sources.core.settingsDataStore
import xyz.quaver.pupil.util.PupilHttpClient
class Pupil : MultiDexApplication() {
class Pupil : Application(), DIAware {
lateinit var histories: Histories
lateinit var downloads: Histories
lateinit var favorites: Histories
override val di: DI by DI.lazy {
import(androidXModule(this@Pupil))
init {
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
bind { singleton { NetworkCache(this@Pupil) } }
bindSingleton { settingsDataStore }
bind { singleton { PupilHttpClient(OkHttp.create()) } }
}
override fun onCreate() {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json"))
favorites = Histories(File(ContextCompat.getDataDir(this), "favorites.json"))
super.onCreate()
try {
ProviderInstaller.installIfNeeded(this)
@@ -58,27 +60,29 @@ class Pupil : MultiDexApplication() {
e.printStackTrace()
}
if (!preference.getBoolean("channel_created", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
description = getString(R.string.channel_download_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
}
manager.createNotificationChannel(channel)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
preference.edit().putBoolean("channel_created", true).apply()
manager.createNotificationChannel(NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
description = getString(R.string.channel_download_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
manager.createNotificationChannel(NotificationChannel("downloader", getString(R.string.channel_downloader), NotificationManager.IMPORTANCE_LOW).apply {
description = getString(R.string.channel_downloader_description)
enableLights(false)
enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
description = getString(R.string.channel_update_description)
enableLights(true)
enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET
})
}
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
super.onCreate()
}
}

View File

@@ -1,331 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.adapters
import android.graphics.drawable.Drawable
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.item_galleryblock.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.Reader
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.wordCapitalize
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val glide: RequestManager, private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
enum class ViewType {
NEXT,
GALLERY,
PREV
}
private lateinit var favorites: Histories
inner class GalleryViewHolder(val view: CardView) : RecyclerView.ViewHolder(view) {
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
with(view) {
val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
val (galleryBlock: GalleryBlock, thumbnail: Deferred<String>) = item
val artists = galleryBlock.artists
val series = galleryBlock.series
CoroutineScope(Dispatchers.Main).launch {
val cache = thumbnail.await()
glide
.load(cache)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.into(galleryblock_thumbnail)
}
//Check cache
val readerCache = { File(getCachedGallery(context, galleryBlock.id), "reader.json") }
val imageCache = { File(getCachedGallery(context, galleryBlock.id), "images") }
try {
Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
} catch(e: Exception) {
readerCache.invoke().delete()
}
if (readerCache.invoke().exists()) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
with(galleryblock_progressbar) {
max = reader.readerItems.size
progress = imageCache.invoke().list()?.size ?: 0
visibility = View.VISIBLE
}
} else {
galleryblock_progressbar.visibility = View.GONE
}
if (refreshTasks[this@GalleryViewHolder] == null) {
val refresh = Timer(false).schedule(0, 1000) {
post {
with(view.galleryblock_progressbar) {
progress = imageCache.invoke().list()?.size ?: 0
if (!readerCache.invoke().exists()) {
visibility = View.GONE
max = 0
progress = 0
view.galleryblock_progress_complete.visibility = View.INVISIBLE
} else {
if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable)
.parse(Reader.serializer(), readerCache.invoke().readText())
max = reader.readerItems.size
visibility = View.VISIBLE
}
if (progress == max) {
if (completeFlag.get(galleryBlock.id, false)) {
with(view.galleryblock_progress_complete) {
setImageResource(R.drawable.ic_progressbar)
visibility = View.VISIBLE
}
} else {
with(view.galleryblock_progress_complete) {
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete).apply {
this?.start()
})
visibility = View.VISIBLE
}
completeFlag.put(galleryBlock.id, true)
}
} else
view.galleryblock_progress_complete.visibility = View.INVISIBLE
}
}
}
}
refreshTasks[this@GalleryViewHolder] = refresh
}
galleryblock_title.text = galleryBlock.title
with(galleryblock_artist) {
text = artists.joinToString(", ") { it.wordCapitalize() }
visibility = when {
artists.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
}
with(galleryblock_series) {
text =
resources.getString(
R.string.galleryblock_series,
series.joinToString(", ") { it.wordCapitalize() })
visibility = when {
series.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
}
galleryblock_type.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
with(galleryblock_language) {
text =
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
visibility = when {
galleryBlock.language.isNotEmpty() -> View.VISIBLE
else -> View.GONE
}
}
galleryblock_tag_group.removeAllViews()
galleryBlock.relatedTags.forEach {
galleryblock_tag_group.addView(Chip(context).apply {
val tag = Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
}
}
chipIcon = when(tag.area) {
"male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
}
"female" -> {
setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
}
text = tag.tag.wordCapitalize()
setOnClickListener {
for (callback in onChipClickedHandler)
callback.invoke(tag)
}
})
}
galleryblock_id.text = galleryBlock.id.toString()
if (!::favorites.isInitialized)
favorites = (context.applicationContext as Pupil).favorites
with(galleryblock_favorite) {
setImageResource(if (favorites.contains(galleryBlock.id)) R.drawable.ic_star_filled else R.drawable.ic_star_empty)
setOnClickListener {
when {
favorites.contains(galleryBlock.id) -> {
favorites.remove(galleryBlock.id)
setImageResource(R.drawable.ic_star_empty)
}
else -> {
favorites.add(galleryBlock.id)
setImageDrawable(AnimatedVectorDrawableCompat.create(context, R.drawable.avd_star).apply {
this ?: return@apply
registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
setImageResource(R.drawable.ic_star_filled)
}
})
start()
})
}
}
}
}
}
}
}
class NextViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
class PrevViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
class ViewHolderFactory {
companion object {
fun getLayoutID(type: Int): Int {
return when(ViewType.values()[type]) {
ViewType.NEXT -> R.layout.item_next
ViewType.PREV -> R.layout.item_prev
ViewType.GALLERY -> R.layout.item_galleryblock
}
}
}
}
private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
var showNext = false
var showPrev = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
fun getViewHolder(type: Int, view: View): RecyclerView.ViewHolder {
return when(ViewType.values()[type]) {
ViewType.NEXT -> NextViewHolder(view as LinearLayout)
ViewType.PREV -> PrevViewHolder(view as LinearLayout)
ViewType.GALLERY -> GalleryViewHolder(view as CardView)
}
}
return getViewHolder(
viewType,
LayoutInflater.from(parent.context).inflate(
ViewHolderFactory.getLayoutID(viewType),
parent,
false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GalleryViewHolder)
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GalleryViewHolder) {
val task = refreshTasks[holder] ?: return
task.cancel()
refreshTasks.remove(holder)
}
}
override fun getItemCount() =
(if (galleries.isEmpty()) 0 else galleries.size)+
(if (showNext) 1 else 0)+
(if (showPrev) 1 else 0)
override fun getItemViewType(position: Int): Int {
return when {
showPrev && position == 0 -> ViewType.PREV
showNext && position == galleries.size+(if (showPrev) 1 else 0) -> ViewType.NEXT
else -> ViewType.GALLERY
}.ordinal
}
}

View File

@@ -1,95 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.adapters
import android.graphics.drawable.Drawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import xyz.quaver.pupil.BuildConfig
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.getCachedGallery
import java.io.File
class ReaderAdapter(private val glide: RequestManager,
private val galleryID: Int,
private val images: List<String>) : RecyclerView.Adapter<ReaderAdapter.ViewHolder>() {
var isFullScreen = false
private var prev : Drawable? = null
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return LayoutInflater.from(parent.context).inflate(
R.layout.item_reader, parent, false
).let {
ViewHolder(it)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.view as ImageView
glide
.load(File(getCachedGallery(holder.view.context, galleryID), images[position]))
.dontTransform()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true)
.error(R.drawable.image_broken_variant)
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
if (isFullScreen)
placeholder(prev)
}
.listener(object: RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
) = false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
prev = resource?.constantState?.newDrawable()?.mutate()
return false
}
})
.into(holder.view)
}
override fun getItemCount() = images.size
}

View File

@@ -1,47 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.adapters
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
import xyz.quaver.pupil.BuildConfig
class ThumbnailAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ImageView(parent.context))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
glide
.load(thumbnails[position])
.apply {
if (BuildConfig.CENSOR)
override(5, 8)
}
.into(holder.view)
}
override fun getItemCount() = thumbnails.size
}

View File

@@ -0,0 +1,108 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2020 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.sources
import android.app.Application
import android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalContext
import dalvik.system.PathClassLoader
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import xyz.quaver.pupil.sources.core.Source
@Composable
fun rememberLocalSourceList(context: Context = LocalContext.current): State<List<SourceEntry>> = produceState(emptyList()) {
while (true) {
value = loadSourceList(context)
delay(1000)
}
}
suspend fun loadSource(context: Context, sourceEntry: SourceEntry): Source = coroutineScope {
sourceCacheMutex.withLock {
sourceCache[sourceEntry.packageName] ?: run {
val classLoader = PathClassLoader(sourceEntry.sourceDir, null, context.classLoader)
Class.forName("${sourceEntry.packagePath}${sourceEntry.sourcePath}", false, classLoader)
.getConstructor(Application::class.java)
.newInstance(context.applicationContext) as Source
}.also { sourceCache[sourceEntry.packageName] = it }
}
}
private const val SOURCES_FEATURE = "pupil.sources"
private const val SOURCES_PACKAGE_PREFIX = "xyz.quaver.pupil.sources"
private const val SOURCES_PATH = "pupil.sources.path"
private val PackageInfo.isSourceFeatureEnabled
get() = this.reqFeatures.orEmpty().any { it.name == SOURCES_FEATURE }
private fun loadSource(context: Context, packageInfo: PackageInfo): List<SourceEntry> {
val packageManager = context.packageManager
val applicationInfo = packageInfo.applicationInfo
val packageName = packageManager.getApplicationLabel(applicationInfo).toString().substringAfter("[Pupil] ")
val packagePath = packageInfo.packageName
val icon = packageManager.getApplicationIcon(applicationInfo)
val version = packageInfo.versionName
return packageInfo
.applicationInfo
.metaData
?.getString(SOURCES_PATH)
?.split(';')
?.map { source ->
val (sourceName, sourcePath) = source.split(':', limit = 2)
SourceEntry(
packageName,
packagePath,
sourceName,
sourcePath,
applicationInfo.sourceDir,
icon,
version
)
}.orEmpty()
}
private val sourceCacheMutex = Mutex()
private val sourceCache = mutableMapOf<String, Source>()
private fun loadSourceList(context: Context): List<SourceEntry> {
val packageManager = context.packageManager
val packages = packageManager.getInstalledPackages(
PackageManager.GET_CONFIGURATIONS or PackageManager.GET_META_DATA
)
return packages.flatMap { packageInfo ->
if (packageInfo.isSourceFeatureEnabled)
loadSource(context, packageInfo)
else
emptyList()
}
}

View File

@@ -0,0 +1,36 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.sources
import androidx.compose.runtime.*
import kotlinx.coroutines.delay
import org.kodein.di.compose.localDI
import org.kodein.di.compose.rememberInstance
import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.pupil.util.PupilHttpClient
import xyz.quaver.pupil.util.RemoteSourceInfo
@Composable
fun rememberRemoteSourceList(client: PupilHttpClient = localDI().direct.instance()) = produceState<Map<String, RemoteSourceInfo>?>(null) {
while (true) {
value = client.getRemoteSourceList()
delay(1000)
}
}

View File

@@ -1,6 +1,6 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,26 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@file:Suppress("UNUSED_VARIABLE")
package xyz.quaver.pupil.sources
package xyz.quaver.pupil
import android.graphics.drawable.Drawable
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun test() {
val current = "0.1"
val latest = "0.2"
print(current < latest)
}
}
data class SourceEntry(
val packageName: String,
val packagePath: String,
val sourceName: String,
val sourcePath: String,
val sourceDir: String,
val icon: Drawable,
val version: String
)

View File

@@ -1,121 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.types
import kotlinx.serialization.Serializable
@Serializable
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
companion object {
fun parse(tag: String) : Tag {
if (tag.first() == '-') {
tag.substring(1).split(Regex(":"), 2).let {
return when(it.size) {
2 -> Tag(it[0], it[1], true)
else -> Tag(null, tag, true)
}
}
}
tag.split(Regex(":"), 2).let {
return when(it.size) {
2 -> Tag(it[0], it[1])
else -> Tag(null, tag)
}
}
}
}
override fun toString(): String {
return (if (isNegative) "-" else "") + when(area) {
null -> tag
else -> "$area:$tag"
}
}
fun toQuery(): String {
return toString().replace(' ', '_')
}
override fun equals(other: Any?): Boolean {
if (other !is Tag)
return false
if (other.area == area && other.tag == tag)
return true
return false
}
override fun hashCode(): Int {
return super.hashCode()
}
}
class Tags(tag: List<Tag?>?) : ArrayList<Tag>() {
companion object {
fun parse(tags: String) : Tags {
return Tags(
tags.split(' ').map {
if (it.isNotEmpty())
Tag.parse(it)
else
null
}
)
}
}
init {
tag?.forEach {
if (it != null)
add(it)
}
}
fun contains(element: String): Boolean {
forEach {
if (it.toString() == element)
return true
}
return false
}
fun add(element: String): Boolean {
return super.add(Tag.parse(element))
}
fun remove(element: String) {
filter { it.toString() == element }.forEach {
remove(it)
}
}
fun removeByArea(area: String, isNegative: Boolean? = null) {
filter { it.area == area && (if(isNegative == null) true else (it.isNegative == isNegative)) }.forEach {
remove(it)
}
}
override fun toString(): String {
return joinToString(" ") { it.toString() }
}
}

View File

@@ -1,276 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import androidx.core.content.ContextCompat
import androidx.gridlayout.widget.GridLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestManager
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_galleryblock.*
import kotlinx.android.synthetic.main.gallery_details.view.*
import kotlinx.android.synthetic.main.item_gallery_details.view.*
import kotlinx.coroutines.*
import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.adapters.ThumbnailAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.wordCapitalize
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
private val glide = Glide.with(context)
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_galleryblock)
window?.attributes.apply {
this ?: return@apply
width = LayoutParams.MATCH_PARENT
height = LayoutParams.MATCH_PARENT
}
with(gallery_fab) {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
setOnClickListener {
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
})
(context.applicationContext as Pupil).histories.add(galleryID)
}
}
CoroutineScope(Dispatchers.IO).launch {
try {
val gallery = getGallery(galleryID)
launch(Dispatchers.Main) {
gallery_progressbar.visibility = View.GONE
gallery_title.text = gallery.title
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
with(gallery_type) {
text = gallery.type.wordCapitalize()
setOnClickListener {
gallery.type.let {
when (it) {
"artist CG" -> "artistcg"
"game CG" -> "gamecg"
else -> it
}
}.let {
onChipClickedHandler.forEach { handler ->
handler.invoke(Tag("type", it))
}
}
}
}
Glide.with(context)
.load(gallery.thumbnails.firstOrNull())
.into(gallery_thumbnail)
addDetails(gallery)
addThumbnails(gallery)
addRelated(gallery)
}
} catch (e: Exception) {
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
}
}
}
private fun addDetails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_details)
listOf(
R.string.gallery_artists,
R.string.gallery_groups,
R.string.gallery_language,
R.string.gallery_series,
R.string.gallery_characters,
R.string.gallery_tags
).zip(
listOf(
gallery.artists.map { Tag("artist", it) },
gallery.groups.map { Tag("group", it) },
listOf(gallery.language).map { Tag("language", it) },
gallery.series.map { Tag("series", it) },
gallery.characters.map { Tag("character", it) },
gallery.tags.map {
Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
}
}
}
)
).filter {
(_, content) -> content.isNotEmpty()
}.forEach { (title, content) ->
inflater.inflate(R.layout.item_gallery_details, gallery_details_contents, false).apply {
gallery_details_type.setText(title)
content.forEach { tag ->
gallery_details_tags.addView(
Chip(context).apply {
chipIcon = when(tag.area) {
"male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
}
"female" -> {
setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
}
text = when (tag.area) {
"language" -> languages[tag.tag]
else -> tag.tag.wordCapitalize()
}
setOnClickListener {
onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
)
}
}.let {
gallery_details_contents.addView(it)
}
}
}.let {
gallery_contents.addView(it)
}
}
private fun addThumbnails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_thumbnails)
RecyclerView(context).apply {
layoutManager = GridLayoutManager(context, 3)
adapter = ThumbnailAdapter(glide, gallery.thumbnails)
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
private fun addRelated(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
val adapter = GalleryBlockAdapter(glide, galleries).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
CoroutineScope(Dispatchers.Main).launch {
gallery.related.forEachIndexed { i, galleryID ->
async(Dispatchers.IO) {
getGalleryBlock(galleryID)
}.let {
val galleryBlock = it.await() ?: return@let
galleries.add(Pair(galleryBlock, GlobalScope.async { galleryBlock.thumbnails.first() }))
adapter.notifyItemInserted(i)
}
}
}
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_related)
RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ ->
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleries[position].first.id)
})
(context.applicationContext as Pupil).histories.add(galleries[position].first.id)
}
.setOnItemLongClickListener { _, position, _ ->
GalleryDialog(context, galleries[position].first.id).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
}
}.show()
true
}
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
}

View File

@@ -1,114 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.app.Activity
import android.app.AlertDialog
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.andrognito.patternlockview.PatternLockView
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_lock.*
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager
class LockActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_lock)
val lockManager = try {
LockManager(this)
} catch (e: Exception) {
AlertDialog.Builder(this).apply {
setTitle(R.string.warning)
setMessage(R.string.lock_corrupted)
setPositiveButton(android.R.string.ok) { _, _ ->
finish()
}
}.show()
return
}
val mode = intent.getStringExtra("mode")
lock_pattern.isEnabled = false
lock_pin.isEnabled = false
lock_fingerprint.isEnabled = false
lock_password.isEnabled = false
when(mode) {
null -> {
if (lockManager.isEmpty()) {
setResult(RESULT_OK)
finish()
return
}
}
"add_lock" -> {
when(intent.getStringExtra("type")!!) {
"pattern" -> {
}
}
}
}
supportFragmentManager.beginTransaction().add(
R.id.lock_content,
PatternLockFragment().apply {
var lastPass = ""
onPatternDrawn = {
when(mode) {
null -> {
val result = lockManager.check(it)
if (result == true) {
setResult(Activity.RESULT_OK)
finish()
} else
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
}
"add_lock" -> {
if (lastPass.isEmpty()) {
lastPass = it
Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show()
} else {
if (lastPass == it) {
LockManager(context!!).add(Lock.generate(Lock.Type.PATTERN, it))
finish()
} else {
lock_pattern_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
lastPass = ""
Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show()
}
}
}
}
}
}
).commit()
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.andrognito.patternlockview.PatternLockView
import com.andrognito.patternlockview.listener.PatternLockViewListener
import com.andrognito.patternlockview.utils.PatternLockUtils
import kotlinx.android.synthetic.main.fragment_pattern_lock.*
import kotlinx.android.synthetic.main.fragment_pattern_lock.view.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.hash
import xyz.quaver.pupil.util.hashWithSalt
class PatternLockFragment : Fragment(), PatternLockViewListener {
var onPatternDrawn: ((String) -> Unit)? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_pattern_lock, container, false).apply {
lock_pattern_view.addPatternLockListener(this@PatternLockFragment)
}
}
override fun onCleared() {
}
override fun onComplete(pattern: MutableList<PatternLockView.Dot>?) {
val password = PatternLockUtils.patternToMD5(lock_pattern_view, pattern)
onPatternDrawn?.invoke(password)
}
override fun onProgress(progressPattern: MutableList<PatternLockView.Dot>?) {
}
override fun onStarted() {
}
}

View File

@@ -1,430 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.Manifest
import android.content.Intent
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Bundle
import android.view.*
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.bumptech.glide.Glide
import com.crashlytics.android.Crashlytics
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.ImplicitReflectionSerializer
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.ReaderAdapter
import xyz.quaver.pupil.util.GalleryDownloader
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.hasPermission
class ReaderActivity : AppCompatActivity() {
private var galleryID = 0
private val images = ArrayList<String>()
private var gallerySize = 0
private var currentPage = 0
private var isScroll = true
private var isFullscreen = false
set(value) {
field = value
(reader_recyclerview.adapter as ReaderAdapter).isFullScreen = value
reader_progressbar.visibility = when {
value -> View.VISIBLE
else -> View.GONE
}
}
private lateinit var downloader: GalleryDownloader
private val snapHelper = PagerSnapHelper()
private var menu: Menu? = null
private lateinit var favorites: Histories
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = getString(R.string.reader_loading)
supportActionBar?.setDisplayHomeAsUpEnabled(false)
favorites = (application as Pupil).favorites
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
setContentView(R.layout.activity_reader)
handleIntent(intent)
Crashlytics.setInt("GalleryID", galleryID)
if (galleryID == 0) {
onBackPressed()
return
}
initDownloader()
initView()
if (!downloader.download)
downloader.start()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
if (intent.action == Intent.ACTION_VIEW) {
val uri = intent.data
val lastPathSegment = uri?.lastPathSegment
if (uri != null && lastPathSegment != null) {
val nonNumber = Regex("[^-?0-9]+")
galleryID = when (uri.host) {
"hitomi.la" -> lastPathSegment.replace(nonNumber, "").toInt()
"히요비.asia" -> lastPathSegment.toInt()
"xn--9w3b15m8vo.asia" -> lastPathSegment.toInt()
"e-hentai.org" -> uri.pathSegments[1].toInt()
else -> return
}
}
} else {
galleryID = intent.getIntExtra("galleryID", 0)
}
}
override fun onResume() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
if (preferences.getBoolean("security_mode", false))
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume()
}
@UseExperimental(ImplicitReflectionSerializer::class)
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.reader, menu)
with(menu?.findItem(R.id.reader_menu_favorite)) {
this ?: return@with
if (favorites.contains(galleryID))
(icon as Animatable).start()
}
this.menu = menu
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when(item?.itemId) {
R.id.reader_menu_page_indicator -> {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
with(view.dialog_number_picker) {
minValue=1
maxValue=gallerySize
value=currentPage
}
val dialog = AlertDialog.Builder(this).apply {
setView(view)
}.create()
view.dialog_ok.setOnClickListener {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.dialog_number_picker.value-1, 0)
dialog.dismiss()
}
dialog.show()
}
R.id.reader_menu_favorite -> {
val id = galleryID
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
if (favorites.contains(id)) {
favorites.remove(id)
favorite.icon = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_star)
} else {
favorites.add(id)
(favorite.icon as Animatable).start()
}
}
}
return true
}
override fun onDestroy() {
super.onDestroy()
if (::downloader.isInitialized && !downloader.download)
downloader.cancel()
}
override fun onBackPressed() {
if (isScroll and !isFullscreen)
super.onBackPressed()
if (isFullscreen) {
isFullscreen = false
fullscreen(false)
}
if (!isScroll) {
isScroll = true
scrollMode(true)
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
//currentPage is 1-based
return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage-2, 0)
true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(currentPage, 0)
true
}
else -> super.onKeyDown(keyCode, event)
}
}
private fun initDownloader() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryID)
if (d == null)
d = GalleryDownloader(this, galleryID)
downloader = d.apply {
onReaderLoadedHandler = {
CoroutineScope(Dispatchers.Main).launch {
title = it.title
with(reader_download_progressbar) {
max = it.readerItems.size
progress = 0
}
with(reader_progressbar) {
max = it.readerItems.size
progress = 0
}
gallerySize = it.readerItems.size
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/${it.readerItems.size}"
}
}
onProgressHandler = {
CoroutineScope(Dispatchers.Main).launch {
reader_download_progressbar.progress = it
menu?.findItem(R.id.reader_menu_use_hiyobi)?.isVisible = downloader.useHiyobi
}
}
onDownloadedHandler = {
val item = it.toList()
CoroutineScope(Dispatchers.Main).launch {
if (images.isEmpty()) {
images.addAll(item)
reader_recyclerview.adapter?.notifyDataSetChanged()
} else {
images.add(item.last())
reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
}
}
}
onErrorHandler = {
Snackbar
.make(reader_layout, it.message ?: it.javaClass.name, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.reader_help) { _ ->
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.error_help))))
}
.show()
downloader.download = false
}
onCompleteHandler = {
CoroutineScope(Dispatchers.Main).launch {
reader_download_progressbar.visibility = View.GONE
}
}
onNotifyChangedHandler = { notify ->
val fab = reader_fab_download
runOnUiThread {
if (notify) {
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
if (downloader.download)
fab.post {
icon.start()
fab.labelText = getString(R.string.reader_fab_download_cancel)
}
else
fab.post {
fab.setImageResource(R.drawable.ic_download)
fab.labelText = getString(R.string.reader_fab_download)
}
}
})
fab.setImageDrawable(icon)
icon?.start()
} else {
runOnUiThread {
fab.setImageResource(R.drawable.ic_download)
}
}
}
}
}
if (downloader.download) {
downloader.invokeOnReaderLoaded()
downloader.invokeOnNotifyChanged()
}
}
private fun initView() {
with(reader_recyclerview) {
adapter = ReaderAdapter(Glide.with(this@ReaderActivity), galleryID, images)
addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy < 0)
this@ReaderActivity.reader_fab.showMenuButton(true)
else if (dy > 0)
this@ReaderActivity.reader_fab.hideMenuButton(true)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (layoutManager.findFirstVisibleItemPosition() == -1)
return
currentPage = layoutManager.findFirstVisibleItemPosition()+1
menu?.findItem(R.id.reader_menu_page_indicator)?.title = "$currentPage/$gallerySize"
this@ReaderActivity.reader_progressbar.progress = currentPage
}
})
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, _, _ ->
if (isScroll) {
isScroll = false
isFullscreen = true
scrollMode(false)
fullscreen(true)
} else {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPosition(currentPage) //Moves to next page because currentPage is 1-based indexing
}
}
}
with(reader_fab_download) {
setImageResource(R.drawable.ic_download)
setOnClickListener {
if (!this@ReaderActivity.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
AlertDialog.Builder(this@ReaderActivity).apply {
setTitle(R.string.warning)
setMessage(R.string.update_no_permission)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
return@setOnClickListener
}
downloader.download = !downloader.download
if (!downloader.download)
downloader.clearNotification()
}
}
with(reader_fab_fullscreen) {
setImageResource(R.drawable.ic_fullscreen)
setOnClickListener {
isFullscreen = true
fullscreen(isFullscreen)
this@ReaderActivity.reader_fab.close(true)
}
}
}
private fun fullscreen(isFullscreen: Boolean) {
with(window.attributes) {
if (isFullscreen) {
flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
supportActionBar?.hide()
this@ReaderActivity.reader_fab.visibility = View.INVISIBLE
} else {
flags = flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
supportActionBar?.show()
this@ReaderActivity.reader_fab.visibility = View.VISIBLE
}
window.attributes = this
}
}
private fun scrollMode(isScroll: Boolean) {
if (isScroll) {
snapHelper.attachToRecyclerView(null)
reader_recyclerview.layoutManager = LinearLayoutManager(this)
} else {
snapHelper.attachToRecyclerView(reader_recyclerview)
reader_recyclerview.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
}
(reader_recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(currentPage-1, 0)
}
}

View File

@@ -1,432 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.WindowManager
import android.widget.ArrayAdapter
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.dialog_default_query.view.*
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Lock
import xyz.quaver.pupil.util.LockManager
import xyz.quaver.pupil.util.getDownloadDirectory
import java.io.File
class SettingsActivity : AppCompatActivity() {
val REQUEST_LOCK = 38238
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onResume() {
val preferences = PreferenceManager.getDefaultSharedPreferences(this)
if (preferences.getBoolean("security_mode", false))
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
super.onResume()
}
class SettingsFragment : PreferenceFragmentCompat() {
private val suffix = listOf(
"B",
"kB",
"MB",
"GB",
"TB" //really?
)
override fun onResume() {
super.onResume()
val lockManager = LockManager(context!!)
findPreference<Preference>("app_lock")?.summary = if (lockManager.locks.isNullOrEmpty()) {
getString(R.string.settings_lock_none)
} else {
lockManager.locks?.joinToString(", ") {
when(it.type) {
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
}
}
}
}
private fun getDirSize(dir: File) : String {
var size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0
while (size >= 1024) {
size /= 1024
suffixIndex++
}
return getString(R.string.settings_clear_summary, size, suffix[suffixIndex])
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
with(findPreference<Preference>("app_version")) {
this!!
val manager = context.packageManager
val info = manager.getPackageInfo(context.packageName, 0)
summary = info.versionName
}
with(findPreference<Preference>("delete_cache")) {
this!!
val dir = File(context.cacheDir, "imageCache")
summary = getDirSize(dir)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
if (dir.exists())
dir.deleteRecursively()
summary = getDirSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("delete_downloads")) {
this!!
val dir = getDownloadDirectory(context)!!
summary = getDirSize(dir)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_downloads_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
if (dir.exists())
dir.deleteRecursively()
val downloads = (activity!!.application as Pupil).downloads
downloads.clear()
summary = getDirSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("clear_history")) {
this!!
val histories = (activity!!.application as Pupil).histories
summary = getString(R.string.settings_clear_history_summary, histories.size)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
histories.clear()
summary = getString(R.string.settings_clear_history_summary, histories.size)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("default_query")) {
this!!
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
summary = preferences.getString("default_query", "") ?: ""
val languages = resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
val excludeBL = "-male:yaoi"
val excludeGuro = listOf("-female:guro", "-male:guro")
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val dialogView = LayoutInflater.from(context).inflate(
R.layout.dialog_default_query,
LinearLayout(context),
false
)
val tags = Tags.parse(
preferences.getString("default_query", "") ?: ""
)
summary = tags.toString()
with(dialogView.default_query_dialog_language_selector) {
adapter =
ArrayAdapter(
context,
android.R.layout.simple_spinner_dropdown_item,
arrayListOf(
getString(R.string.default_query_dialog_language_selector_none)
).apply {
addAll(languages.values)
}
)
if (tags.any { it.area == "language" && !it.isNegative }) {
val tag = languages[tags.first { it.area == "language" }.tag]
if (tag != null) {
setSelection(
@Suppress("UNCHECKED_CAST")
(adapter as ArrayAdapter<String>).getPosition(tag)
)
tags.removeByArea("language", false)
}
}
}
with(dialogView.default_query_dialog_BL_checkbox) {
isChecked = tags.contains(excludeBL)
if (tags.contains(excludeBL))
tags.remove(excludeBL)
}
with(dialogView.default_query_dialog_guro_checkbox) {
isChecked = excludeGuro.all { tags.contains(it) }
if (excludeGuro.all { tags.contains(it) })
excludeGuro.forEach {
tags.remove(it)
}
}
with(dialogView.default_query_dialog_edittext) {
setText(tags.toString(), TextView.BufferType.EDITABLE)
addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
s ?: return
if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase())
}
})
}
val dialog = AlertDialog.Builder(context!!).apply {
setView(dialogView)
}.create()
dialogView.default_query_dialog_ok.setOnClickListener {
val newTags = Tags.parse(dialogView.default_query_dialog_edittext.text.toString())
with(dialogView.default_query_dialog_language_selector) {
if (selectedItemPosition != 0)
newTags.add("language:${reverseLanguages[selectedItem]}")
}
if (dialogView.default_query_dialog_BL_checkbox.isChecked)
newTags.add(excludeBL)
if (dialogView.default_query_dialog_guro_checkbox.isChecked)
excludeGuro.forEach { tag ->
newTags.add(tag)
}
preferenceManager.sharedPreferences.edit().putString("default_query", newTags.toString()).apply()
summary = preferences.getString("default_query", "") ?: ""
tags.clear()
tags.addAll(newTags)
dialog.dismiss()
}
dialog.show()
true
}
}
with(findPreference<Preference>("app_lock")) {
this!!
val lockManager = LockManager(context)
summary = if (lockManager.locks.isNullOrEmpty()) {
getString(R.string.settings_lock_none)
} else {
lockManager.locks?.joinToString(", ") {
when(it.type) {
Lock.Type.PATTERN -> getString(R.string.settings_lock_pattern)
Lock.Type.PIN -> getString(R.string.settings_lock_pin)
Lock.Type.PASSWORD -> getString(R.string.settings_lock_password)
}
}
}
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(context, LockActivity::class.java)
activity?.startActivityForResult(intent, (activity as SettingsActivity).REQUEST_LOCK)
true
}
}
with(findPreference<Preference>("dark_mode")) {
this!!
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
true
}
}
}
}
class LockFragment : PreferenceFragmentCompat() {
override fun onResume() {
super.onResume()
val lockManager = LockManager(context!!)
findPreference<Preference>("lock_pattern")?.summary =
if (lockManager.contains(Lock.Type.PATTERN))
getString(R.string.settings_lock_enabled)
else
""
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.lock_preferences, rootKey)
with(findPreference<Preference>("lock_pattern")) {
this!!
if (LockManager(context!!).contains(Lock.Type.PATTERN))
summary = getString(R.string.settings_lock_enabled)
onPreferenceClickListener = Preference.OnPreferenceClickListener {
val lockManager = LockManager(context!!)
if (lockManager.contains(Lock.Type.PATTERN)) {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_lock_remove_message)
setPositiveButton(android.R.string.yes) { _, _ ->
lockManager.remove(Lock.Type.PATTERN)
onResume()
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
} else {
val intent = Intent(context, LockActivity::class.java).apply {
putExtra("mode", "add_lock")
putExtra("type", "pattern")
}
startActivity(intent)
}
true
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> onBackPressed()
}
return true
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) {
REQUEST_LOCK -> {
if (resultCode == Activity.RESULT_OK) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, LockFragment())
.addToBackStack("Lock")
.commitAllowingStateLoss()
}
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
}

View File

@@ -0,0 +1,352 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import android.app.Application
import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.DownloadDone
import androidx.compose.material.icons.filled.Explore
import androidx.compose.material.icons.outlined.Info
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import com.google.accompanist.drawablepainter.rememberDrawablePainter
import com.google.accompanist.insets.LocalWindowInsets
import com.google.accompanist.insets.rememberInsetsPaddingValues
import com.google.accompanist.insets.systemBarsPadding
import com.google.accompanist.insets.ui.BottomNavigation
import com.google.accompanist.insets.ui.Scaffold
import com.google.accompanist.insets.ui.TopAppBar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.kodein.di.DI
import org.kodein.di.DIAware
import org.kodein.di.compose.localDI
import org.kodein.di.instance
import xyz.quaver.pupil.sources.SourceEntry
import xyz.quaver.pupil.sources.rememberLocalSourceList
import xyz.quaver.pupil.sources.rememberRemoteSourceList
import xyz.quaver.pupil.util.PupilHttpClient
import xyz.quaver.pupil.util.RemoteSourceInfo
import xyz.quaver.pupil.util.launchApkInstaller
import java.io.File
private sealed class SourceSelectorScreen(val route: String, val icon: ImageVector) {
object Local: SourceSelectorScreen("local", Icons.Default.DownloadDone)
object Explore: SourceSelectorScreen("explore", Icons.Default.Explore)
}
private val sourceSelectorScreens = listOf(
SourceSelectorScreen.Local,
SourceSelectorScreen.Explore
)
private val RemoteSourceInfo.apkUrl: String
get() = "https://github.com/tom5079/PupilSources/releases/download/$name-$version/$projectName-release.apk"
class DownloadApkActionState(override val di: DI) : DIAware {
private val app: Application by instance()
private val client: PupilHttpClient by instance()
var progress by mutableStateOf<Float?>(null)
private set
suspend fun download(url: String): File? = withContext(Dispatchers.IO) {
progress = 0f
val file = File.createTempFile("pupil", ".apk", File(app.cacheDir, "apks").also {
it.mkdirs()
})
client.downloadFile(url, file).collect { progress = it }
if (progress == Float.POSITIVE_INFINITY) file else null
}
fun reset() {
progress = null
}
}
@Composable
fun rememberDownloadApkActionState(di: DI = localDI()) = remember { DownloadApkActionState(di) }
@Composable
fun DownloadApkAction(
state: DownloadApkActionState,
content: @Composable () -> Unit
) {
state.progress?.let { progress ->
Box(
Modifier.padding(12.dp, 0.dp)
) {
when {
progress.isFinite() && progress > 0f ->
CircularProgressIndicator(progress, modifier = Modifier.size(24.dp))
else ->
CircularProgressIndicator(modifier = Modifier.size(24.dp))
}
}
true
} ?: content()
}
@Composable
fun SourceListItem(icon: @Composable (Modifier) -> Unit = { }, name: String, version: String, actions: @Composable () -> Unit = { }) {
Card(
modifier = Modifier.padding(8.dp),
elevation = 4.dp
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
icon(Modifier.size(48.dp))
Column(
Modifier.weight(1f)
) {
Text(name.capitalize(Locale.current))
CompositionLocalProvider(LocalContentAlpha provides 0.5f) {
Text(
"v$version",
style = MaterialTheme.typography.caption
)
}
}
actions()
}
}
}
@Composable
fun Local(onSource: (SourceEntry) -> Unit) {
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val localSourceList by rememberLocalSourceList()
val remoteSourceList by rememberRemoteSourceList()
if (localSourceList.isEmpty()) {
Box(Modifier.fillMaxSize()) {
Column(
Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
CompositionLocalProvider(LocalContentAlpha provides 0.5f) {
Text("(´∇`)", style = MaterialTheme.typography.h2)
}
Text("No sources found!\nLet's go download one.", textAlign = TextAlign.Center)
}
}
} else {
LazyColumn {
items(localSourceList) { source ->
val actionState = rememberDownloadApkActionState()
SourceListItem(
icon = { modifier ->
Image(
rememberDrawablePainter(source.icon),
contentDescription = "source icon",
modifier = modifier
)
},
source.sourceName,
source.version
) {
DownloadApkAction(actionState) {
val remoteSource = remoteSourceList?.get(source.packageName)
if (remoteSource != null && remoteSource.version != source.version) {
TextButton(onClick = {
coroutineScope.launch {
val file = actionState.download(remoteSource.apkUrl)!! // TODO("Handle error")
context.launchApkInstaller(file)
actionState.reset()
}
}) {
Text("UPDATE")
}
} else {
TextButton(
onClick = { onSource(source) }
) {
Text("GO")
}
}
}
}
}
}
}
}
@Composable
fun Explore() {
val localSourceList by rememberLocalSourceList()
val localSources by derivedStateOf {
localSourceList.associateBy {
it.packageName
}
}
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val remoteSources by rememberRemoteSourceList()
Box(
Modifier.fillMaxSize()
) {
if (remoteSources == null)
CircularProgressIndicator(Modifier.align(Alignment.Center))
else
LazyColumn {
items(remoteSources?.values?.toList().orEmpty()) { sourceInfo ->
val actionState = rememberDownloadApkActionState()
SourceListItem(
icon = { modifier ->
AsyncImage(
"https://raw.githubusercontent.com/tom5079/PupilSources/master/${sourceInfo.projectName}/src/main/res/mipmap-xxxhdpi/ic_launcher.png",
contentDescription = "source icon",
modifier = modifier
)
},
sourceInfo.name,
sourceInfo.version
) {
DownloadApkAction(actionState) {
if (localSources[sourceInfo.name]?.version != sourceInfo.version) {
TextButton(onClick = {
coroutineScope.launch {
val file = actionState.download(sourceInfo.apkUrl)!! // TODO("Handle exception")
context.launchApkInstaller(file)
actionState.reset()
}
}) {
Text("UPDATE")
}
} else {
IconButton(onClick = {
if (sourceInfo.name in localSources) {
context.startActivity(
Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", localSources[sourceInfo.name]!!.packagePath, null)
)
)
} else coroutineScope.launch {
val file = actionState.download(sourceInfo.apkUrl)!! // TODO("Handle exception")
context.launchApkInstaller(file)
actionState.reset()
}
}) {
Icon(
if (sourceInfo.name !in localSources) Icons.Default.Download
else Icons.Outlined.Info,
contentDescription = "download"
)
}
}
}
}
}
}
}
}
@Composable
fun SourceSelector(onSource: (SourceEntry) -> Unit) {
val bottomNavController = rememberNavController()
Scaffold(
topBar = {
TopAppBar(
title = {
Text("Pupil")
},
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.statusBars)
)
},
bottomBar = {
BottomNavigation(
contentPadding = rememberInsetsPaddingValues(LocalWindowInsets.current.navigationBars)
) {
val navBackStackEntry by bottomNavController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
sourceSelectorScreens.forEach { screen ->
BottomNavigationItem(
icon = { Icon(screen.icon, contentDescription = screen.route) },
label = { Text(screen.route) },
selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
onClick = {
bottomNavController.navigate(screen.route) {
popUpTo(bottomNavController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
)
}
}
}
) { contentPadding ->
NavHost(bottomNavController, startDestination = "local", modifier = Modifier
.systemBarsPadding(top = false, bottom = false)
.padding(contentPadding)) {
composable(SourceSelectorScreen.Local.route) { Local(onSource) }
composable(SourceSelectorScreen.Explore.route) { Explore() }
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import kotlinx.coroutines.launch
import org.kodein.di.compose.onDIContext
import xyz.quaver.pupil.util.Release
import xyz.quaver.pupil.util.launchApkInstaller
import java.util.*
@Composable
fun UpdateAlertDialog(
show: Boolean,
release: Release,
onDismiss: () -> Unit
) {
val state = rememberDownloadApkActionState()
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
if (show) {
Dialog(onDismissRequest = { if (state.progress == null) onDismiss() }) {
Card {
val progress = state.progress
if (progress != null) {
if (progress.isFinite() && progress > 0)
LinearProgressIndicator(progress)
else
LinearProgressIndicator()
}
Column(
Modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp, bottom = 0.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
"Update Available",
style = MaterialTheme.typography.h6
)
Text(release.releaseNotes.getOrElse(Locale.getDefault()) { release.releaseNotes[Locale.ENGLISH]!! })
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismiss, enabled = progress == null) {
Text("DISMISS")
}
TextButton(
onClick = {
coroutineScope.launch {
val file = state.download(release.apkUrl)!! // TODO("Handle exception")
context.launchApkInstaller(file)
state.reset()
onDismiss()
}
},
enabled = progress == null
) {
Text("UPDATE")
}
}
}
}
}
}
}

View File

@@ -1,6 +1,6 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,20 +13,16 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.types
package xyz.quaver.pupil.ui.theme
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import kotlinx.android.parcel.Parcelize
import xyz.quaver.hitomi.Suggestion
import androidx.compose.ui.graphics.Color
@Parcelize
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
override fun getBody(): String {
return s
}
}
val LightBlue300 = Color(0xFF4FC3F7)
val LightBlue700 = Color(0xFF0288D1)
val Pink600 = Color(0xFFD81B60)
val Blue700 = Color(0xFF1976D2)
val GreenA700 = Color(0xFF00C853)
val Orange500 = Color(0xFFFF9800)

View File

@@ -0,0 +1,29 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.theme
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp),
large = RoundedCornerShape(0.dp)
)

View File

@@ -0,0 +1,57 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.contentColorFor
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorPalette = darkColors(
primary = LightBlue300,
primaryVariant = LightBlue700,
secondary = Pink600,
onPrimary = Color.White,
onSecondary = Color.White
)
private val LightColorPalette = lightColors(
primary = LightBlue300,
primaryVariant = LightBlue700,
secondary = Pink600,
onPrimary = Color.White,
onSecondary = Color.White
)
@Composable
fun PupilTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColorPalette else LightColorPalette
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}

View File

@@ -0,0 +1,33 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2021 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.ui.theme
import androidx.compose.material.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
val Typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
)
)

View File

@@ -1,325 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import android.app.PendingIntent
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.util.SparseArray
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.TaskStackBuilder
import androidx.preference.PreferenceManager
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.*
import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReader
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hiyobi.cookie
import xyz.quaver.hiyobi.user_agent
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R
import xyz.quaver.pupil.ui.ReaderActivity
import java.io.File
import java.io.FileOutputStream
import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.concurrent.schedule
class GalleryDownloader(
base: Context,
private val galleryID: Int,
_notify: Boolean = false
) : ContextWrapper(base) {
private val downloads = (applicationContext as Pupil).downloads
var useHiyobi = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("use_hiyobi", false)
var download: Boolean = false
set(value) {
if (value) {
field = true
notificationManager.notify(galleryID, notificationBuilder.build())
if (reader?.isActive == false && downloadJob?.isActive != true) {
val data = File(getDownloadDirectory(this), galleryID.toString())
val cache = File(cacheDir, "imageCache/$galleryID")
if (File(cache, "images").exists() && !data.exists()) {
cache.copyRecursively(data, true)
cache.deleteRecursively()
}
field = false
}
downloads.add(galleryID)
} else {
field = false
}
onNotifyChangedHandler?.invoke(value)
}
private val reader: Deferred<Reader>?
private var downloadJob: Job? = null
private lateinit var notificationBuilder: NotificationCompat.Builder
private lateinit var notificationManager: NotificationManagerCompat
var onReaderLoadedHandler: ((Reader) -> Unit)? = null
var onProgressHandler: ((Int) -> Unit)? = null
var onDownloadedHandler: ((List<String>) -> Unit)? = null
var onErrorHandler: ((Exception) -> Unit)? = null
var onCompleteHandler: (() -> Unit)? = null
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
companion object : SparseArray<GalleryDownloader>()
init {
put(galleryID, this)
initNotification()
reader = CoroutineScope(Dispatchers.IO).async {
try {
download = _notify
val json = Json(JsonConfiguration.Stable)
val serializer = Reader.serializer()
//Check cache
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "reader.json")
try {
json.parse(serializer, cache.readText())
} catch(e: Exception) {
cache.delete()
}
if (cache.exists()) {
val cached = json.parse(serializer, cache.readText())
if (cached.readerItems.isNotEmpty()) {
useHiyobi = when {
cached.readerItems[0].url.contains("hitomi.la") -> false
else -> true
}
onReaderLoadedHandler?.invoke(cached)
return@async cached
}
}
//Cache doesn't exist. Load from internet
val reader = when {
useHiyobi -> {
try {
xyz.quaver.hiyobi.getReader(galleryID)
} catch(e: Exception) {
useHiyobi = false
getReader(galleryID)
}
}
else -> {
getReader(galleryID)
}
}
if (reader.readerItems.isNotEmpty()) {
//Save cache
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
cache.writeText(json.stringify(serializer, reader))
}
reader
} catch (e: Exception) {
Crashlytics.logException(e)
Reader("", listOf())
}
}
}
private fun webpUrlFromUrl(url: String) = url.replace("/galleries/", "/webp/") + ".webp"
fun start() {
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader!!.await()
notificationBuilder.setContentTitle(reader.title)
if (reader.readerItems.isEmpty()) {
onErrorHandler?.invoke(IOException(getString(R.string.unable_to_connect)))
return@launch
}
val list = ArrayList<String>()
onReaderLoadedHandler?.invoke(reader)
notificationBuilder
.setProgress(reader.readerItems.size, 0, false)
.setContentText("0/${reader.readerItems.size}")
reader.readerItems.chunked(4).forEachIndexed { chunkIndex, chunked ->
chunked.mapIndexed { i, it ->
val index = chunkIndex*4+i
async(Dispatchers.IO) {
val url = if (it.galleryInfo?.haswebp == 1) webpUrlFromUrl(it.url) else it.url
val name = "$index".padStart(4, '0')
val ext = url.split('.').last()
val cache = File(getCachedGallery(this@GalleryDownloader, galleryID), "images/$name.$ext")
if (!cache.exists())
try {
with(URL(url).openConnection() as HttpsURLConnection) {
if (useHiyobi) {
setRequestProperty("User-Agent", user_agent)
setRequestProperty("Cookie", cookie)
} else
setRequestProperty("Referer", getReferer(galleryID))
if (cache.parentFile?.exists() == false)
cache.parentFile!!.mkdirs()
inputStream.copyTo(FileOutputStream(cache))
}
} catch (e: Exception) {
cache.delete()
onErrorHandler?.invoke(e)
notificationBuilder
.setContentTitle(reader.title)
.setContentText(getString(R.string.reader_notification_error))
.setProgress(0, 0, false)
notificationManager.notify(galleryID, notificationBuilder.build())
}
"images/$name.$ext"
}
}.forEach {
list.add(it.await())
val index = list.size
onProgressHandler?.invoke(index)
notificationBuilder
.setProgress(reader.readerItems.size, index, false)
.setContentText("$index/${reader.readerItems.size}")
if (download)
notificationManager.notify(galleryID, notificationBuilder.build())
onDownloadedHandler?.invoke(list)
}
}
Timer(false).schedule(1000) {
notificationBuilder
.setContentTitle(reader.title)
.setContentText(getString(R.string.reader_notification_complete))
.setProgress(0, 0, false)
if (download) {
File(cacheDir, "imageCache/${galleryID}").let {
if (it.exists()) {
val target = File(getDownloadDirectory(this@GalleryDownloader), galleryID.toString())
if (!target.exists())
target.mkdirs()
it.copyRecursively(target, true)
it.deleteRecursively()
}
}
notificationManager.notify(galleryID, notificationBuilder.build())
download = false
}
onCompleteHandler?.invoke()
}
remove(galleryID)
}
}
fun cancel() {
downloadJob?.cancel()
remove(galleryID)
}
suspend fun cancelAndJoin() {
downloadJob?.cancelAndJoin()
remove(galleryID)
}
fun invokeOnReaderLoaded() {
CoroutineScope(Dispatchers.Default).launch {
onReaderLoadedHandler?.invoke(reader?.await() ?: return@launch)
}
}
fun clearNotification() {
notificationManager.cancel(galleryID)
}
fun invokeOnNotifyChanged() {
onNotifyChangedHandler?.invoke(download)
}
private fun initNotification() {
val intent = Intent(this, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
}
val pendingIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(intent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
}
notificationManager = NotificationManagerCompat.from(this)
notificationBuilder = NotificationCompat.Builder(this, "download").apply {
setContentTitle(getString(R.string.reader_loading))
setContentText(getString(R.string.reader_notification_text))
setSmallIcon(android.R.drawable.stat_sys_download)
setContentIntent(pendingIntent)
setProgress(0, 0, true)
priority = NotificationCompat.PRIORITY_LOW
}
}
}

View File

@@ -1,107 +0,0 @@
package xyz.quaver.pupil.util;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import xyz.quaver.pupil.R;
/*
Source: http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
USAGE:
ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
*/
public class ItemClickSupport {
private final RecyclerView mRecyclerView;
private OnItemClickListener mOnItemClickListener;
private OnItemLongClickListener mOnItemLongClickListener;
private View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
}
};
private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if (mOnItemLongClickListener != null) {
RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
}
return false;
}
};
private RecyclerView.OnChildAttachStateChangeListener mAttachListener
= new RecyclerView.OnChildAttachStateChangeListener() {
@Override
public void onChildViewAttachedToWindow(@NonNull View view) {
if (mOnItemClickListener != null) {
view.setOnClickListener(mOnClickListener);
}
if (mOnItemLongClickListener != null) {
view.setOnLongClickListener(mOnLongClickListener);
}
}
@Override
public void onChildViewDetachedFromWindow(@NonNull View view) {
}
};
private ItemClickSupport(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
mRecyclerView.setTag(R.id.item_click_support, this);
mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
}
public static ItemClickSupport addTo(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support == null) {
support = new ItemClickSupport(view);
}
return support;
}
public static ItemClickSupport removeFrom(RecyclerView view) {
ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
if (support != null) {
support.detach(view);
}
return support;
}
public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
mOnItemLongClickListener = listener;
return this;
}
private void detach(RecyclerView view) {
view.removeOnChildAttachStateChangeListener(mAttachListener);
view.setTag(R.id.item_click_support, null);
}
public interface OnItemClickListener {
void onItemClicked(RecyclerView recyclerView, int position, View v);
}
public interface OnItemLongClickListener {
boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
}
}

View File

@@ -0,0 +1,156 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import androidx.compose.ui.res.stringArrayResource
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.utils.io.core.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.*
import java.io.File
import java.util.*
@Serializable
data class RemoteSourceInfo(
val projectName: String,
val name: String,
val version: String
)
class Release(
val version: String,
val apkUrl: String,
val releaseNotes: Map<Locale, String>
)
private val localeMap = mapOf(
"한국어" to Locale.KOREAN,
"日本語" to Locale.JAPANESE,
"English" to Locale.ENGLISH
)
class PupilHttpClient(engine: HttpClientEngine) {
private val httpClient = HttpClient(engine) {
install(ContentNegotiation) {
json()
}
}
/**
* Fetch a list of available sources from PupilSources repository.
* Returns empty map when exception occurs
*/
suspend fun getRemoteSourceList(): Map<String, RemoteSourceInfo> = withContext(Dispatchers.IO) {
runCatching {
httpClient.get("https://tom5079.github.io/PupilSources/versions.json").body<Map<String, RemoteSourceInfo>>()
}.getOrDefault(emptyMap())
}
/**
* Downloads specific file from :url to :dest.
* Returns flow that emits progress.
* when value emitted by flow {
* in 0f .. 1f -> downloading
* POSITIVE_INFINITY -> download finised
* NEGATIVE_INFINITY -> exception occured
* }
*/
fun downloadFile(url: String, dest: File) = flow {
runCatching {
httpClient.prepareGet(url).execute { response ->
val channel = response.bodyAsChannel()
val contentLength = response.contentLength() ?: -1
var readBytes = 0f
dest.outputStream().use { outputStream ->
while (!channel.isClosedForRead) {
val packet = channel.readRemaining(DEFAULT_BUFFER_SIZE.toLong())
while (!packet.isEmpty) {
val bytes = packet.readBytes()
outputStream.write(bytes)
readBytes += bytes.size
emit(readBytes / contentLength)
}
}
}
}
emit(Float.POSITIVE_INFINITY)
}.onFailure {
emit(Float.NEGATIVE_INFINITY)
}
}.flowOn(Dispatchers.IO)
/**
* Latest application release info from Github API.
* Returns null when exception occurs.
*/
suspend fun latestRelease(beta: Boolean = true): Release? = withContext(Dispatchers.IO) {
runCatching {
val releases = Json.parseToJsonElement(
httpClient.get("https://api.github.com/repos/tom5079/Pupil/releases").bodyAsText()
).jsonArray
val latestRelease = releases.first { release ->
beta || !release.jsonObject["prerelease"]!!.jsonPrimitive.boolean
}.jsonObject
val version = latestRelease["tag_name"]!!.jsonPrimitive.content
val apkUrl = latestRelease["assets"]!!.jsonArray.first { asset ->
val name = asset.jsonObject["name"]!!.jsonPrimitive.content
name.startsWith("Pupil-v") && name.endsWith(".apk")
}.jsonObject["browser_download_url"]!!.jsonPrimitive.content
val releaseNotes: Map<Locale, String> = buildMap {
val body = latestRelease["body"]!!.jsonPrimitive.content
var locale: Locale? = null
val stringBuilder = StringBuilder()
body.lineSequence().forEach { line ->
localeMap[line.drop(3)]?.let { newLocale ->
if (locale != null) {
put(locale!!, stringBuilder.deleteCharAt(stringBuilder.length-1).toString())
stringBuilder.clear()
}
locale = newLocale
return@forEach
}
if (locale != null) stringBuilder.appendLine(line)
}
put(locale!!, stringBuilder.deleteCharAt(stringBuilder.length-1).toString())
}
Release(version, apkUrl, releaseNotes)
}.getOrNull()
}
}

View File

@@ -1,37 +0,0 @@
package xyz.quaver.pupil.util
import android.graphics.Paint
import android.text.style.LineHeightSpan
class SetLineOverlap(private val overlap: Boolean) : LineHeightSpan {
companion object {
private var originalBottom = 15
private var originalDescent = 13
private var overlapSaved = false
}
override fun chooseHeight(
text: CharSequence?,
start: Int,
end: Int,
spanstartv: Int,
lineHeight: Int,
fm: Paint.FontMetricsInt?
) {
fm ?: return
if (overlap) {
if (overlapSaved) {
originalBottom = fm.bottom
originalDescent = fm.descent
overlapSaved = true
}
fm.bottom += fm.top
fm.descent += fm.top
} else {
fm.bottom = originalBottom
fm.descent = originalDescent
overlapSaved = false
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import android.content.Context
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.content.ContextCompat
import java.io.File
fun getCachedGallery(context: Context, galleryID: Int): File {
return File(getDownloadDirectory(context), galleryID.toString()).let {
when {
it.exists() -> it
else -> File(context.cacheDir, "imageCache/$galleryID")
}
}
}
@Suppress("DEPRECATION")
fun getDownloadDirectory(context: Context): File? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
context.getExternalFilesDir("Pupil")
else
File(Environment.getExternalStorageDirectory(), "Pupil")
}

View File

@@ -1,83 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.parseList
import kotlinx.serialization.stringify
import java.io.File
class Histories(private val file: File) : ArrayList<Int>() {
init {
if (!file.exists())
file.parentFile?.mkdirs()
try {
load()
} catch (e: Exception) {
save()
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun load() : Histories {
return apply {
super.clear()
addAll(
Json(JsonConfiguration.Stable).parseList(
file.bufferedReader().use { it.readText() }
)
)
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun save() {
file.writeText(Json(JsonConfiguration.Stable).stringify(this))
}
override fun add(element: Int): Boolean {
load()
if (contains(element))
super.remove(element)
super.add(0, element)
save()
return true
}
override fun remove(element: Int): Boolean {
load()
val retval = super.remove(element)
save()
return retval
}
override fun clear() {
super.clear()
save()
}
}

View File

@@ -1,125 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import android.content.Context
import android.content.ContextWrapper
import androidx.core.content.ContextCompat
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import java.io.File
import java.security.MessageDigest
fun hash(password: String): String {
val bytes = password.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
return md.digest(bytes).fold("") { str, it -> str + "%02x".format(it) }
}
// Ret1: SHA-256 Hash
// Ret2: Hash salt
fun hashWithSalt(password: String): Pair<String, String> {
val salt = (0 until 12).map { source.random() }.joinToString()
return Pair(hash(password+salt), salt)
}
val source = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
@Serializable
data class Lock(val type: Type, val hash: String, val salt: String) {
enum class Type {
PATTERN,
PIN,
PASSWORD
}
companion object {
fun generate(type: Type, password: String): Lock {
val (hash, salt) = hashWithSalt(password)
return Lock(type, hash, salt)
}
}
fun match(password: String): Boolean {
return hash(password+salt) == hash
}
}
class LockManager(base: Context): ContextWrapper(base) {
var locks: ArrayList<Lock>? = null
init {
load()
}
@UseExperimental(ImplicitReflectionSerializer::class)
private fun load() {
val lock = File(ContextCompat.getDataDir(this), "lock.json")
if (!lock.exists()) {
lock.createNewFile()
lock.writeText("[]")
}
locks = ArrayList(Json(JsonConfiguration.Stable).parseList(lock.readText()))
}
@UseExperimental(ImplicitReflectionSerializer::class)
private fun save() {
val lock = File(ContextCompat.getDataDir(this), "lock.json")
if (!lock.exists())
lock.createNewFile()
lock.writeText(Json(JsonConfiguration.Stable).stringify(locks?.toList() ?: listOf()))
}
fun add(lock: Lock) {
remove(lock.type)
locks?.add(lock)
save()
}
fun remove(type: Lock.Type) {
locks?.removeAll { it.type == type }
save()
}
fun check(password: String): Boolean? {
return locks?.any {
it.match(password)
}
}
fun isEmpty(): Boolean {
return locks.isNullOrEmpty()
}
fun isNotEmpty(): Boolean = !isEmpty()
fun contains(type: Lock.Type): Boolean {
return locks?.any { it.type == type } ?: false
}
}

View File

@@ -1,64 +0,0 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.util
import kotlinx.serialization.json.*
import xyz.quaver.pupil.BuildConfig
import java.net.URL
fun getReleases(url: String) : JsonArray {
return try {
URL(url).readText().let {
Json(JsonConfiguration.Stable).parse(JsonArray.serializer(), it)
}
} catch (e: Exception) {
JsonArray(emptyList())
}
}
fun checkUpdate(url: String) : JsonObject? {
val releases = getReleases(url)
if (releases.isEmpty())
return null
return releases.firstOrNull {
if (BuildConfig.PRERELEASE) {
true
} else {
it.jsonObject["prerelease"]?.boolean == false
}
}?.let {
if (it.jsonObject["tag_name"]?.content == BuildConfig.VERSION_NAME)
null
else
it.jsonObject
}
}
fun getApkUrl(releases: JsonObject) : Pair<String?, String?>? {
return releases["assets"]?.jsonArray?.firstOrNull {
Regex("Pupil-v(\\d+\\.)+\\d+\\.apk").matches(it.jsonObject["name"]?.content ?: "")
}.let {
if (it == null)
null
else
Pair(it.jsonObject["browser_download_url"]?.content, it.jsonObject["name"]?.content)
}
}

View File

@@ -1,6 +1,6 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
* Copyright (C) 2022 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -19,17 +19,20 @@
package xyz.quaver.pupil.util
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
import android.content.Intent
import android.webkit.MimeTypeMap
import androidx.core.content.FileProvider
import java.io.File
fun Context.hasPermission(permission: String) =
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
fun Context.launchApkInstaller(file: File) {
val uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", file)
fun String.wordCapitalize() : String {
val result = ArrayList<String>()
val intent = Intent(Intent.ACTION_VIEW).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_ACTIVITY_NEW_TASK
setDataAndType(uri, MimeTypeMap.getSingleton().getMimeTypeFromExtension("apk"))
}
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
startActivity(intent)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 793 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 802 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 817 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 979 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 636 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 947 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1001 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 892 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 B

Some files were not shown because too many files have changed in this diff Show More