Compare commits

...
Author SHA1 Message Date
tarsin ac20755c45 Bump Version
Releases / Build (push) Has been cancelled
2023-01-12 08:48:14 +08:00
renovate[bot] 610202bbbd Update dependency androidx.startup:startup-runtime to v1.2.0-alpha02 2023-01-12 07:23:13 +08:00
renovate[bot] 58ce7134e1 Update lifecycle_version to v2.6.0-alpha04 2023-01-12 07:22:59 +08:00
FooIbar 71f91cd79e Get updated positions in click listeners
Resolve #655
2023-01-12 07:22:32 +08:00
tarsin 8c821ecad3 Make EhEngine obtain its okhttpClient 2023-01-11 21:46:17 +08:00
FooIbar c704940462 Support drag reordering for downloads
Resolve #54
2023-01-11 21:31:18 +08:00
tarsin 6a7cd1b89a Add some comments and TODO 2023-01-11 14:35:43 +08:00
tarsin e29da0ba13 Cleanup res 2023-01-11 14:28:53 +08:00
tarsin 7791fa10f5 Getprofile in SelectSiteScreen 2023-01-11 14:11:36 +08:00
tarsin 48ed3c8f31 Avoid webview reentry 2023-01-11 14:07:44 +08:00
tarsin 7ad7a9b94e Add WebviewSignInScreen 2023-01-11 13:59:28 +08:00
tarsin 54b929ba24 Use LocalClipboardManager to better support multiplatform 2023-01-11 13:28:00 +08:00
FooIbar 7b655c6260 Show dialog before removing quick search 2023-01-11 13:24:33 +08:00
FooIbar 4e8d41b145 Show dialog before removing download label 2023-01-11 13:24:33 +08:00
tarsin f036774e07 Remove origin signin scene 2023-01-11 13:22:56 +08:00
tarsin 97d1bd7685 Start moving to new compose login activity 2023-01-11 13:12:16 +08:00
tarsin 8e39449856 Restore finish from SelectSiteScreen 2023-01-11 13:06:27 +08:00
tarsin 30f1a5a6eb Add SelectSiteScreen Impl 2023-01-11 13:01:46 +08:00
tarsin 627abb047c Compose dependency tweaks 2023-01-11 12:07:33 +08:00
tarsin 9284008afe Refine CookieSignInScreen 2023-01-11 11:44:13 +08:00
tarsin 9159f4616f Refine SignInScreen KeyBoard options 2023-01-11 10:41:42 +08:00
tarsin 73296b1430 Refine SignInScreen TextInput 2023-01-11 10:25:43 +08:00
tarsin c74e622d69 Add snackbar to CookieSignInScreen layout 2023-01-11 09:55:41 +08:00
tarsin f48eb55516 Basic CookieSignInScreen layout 2023-01-11 09:23:10 +08:00
tarsin b01f6ef8d3 Fixup! use local functions 2023-01-11 08:27:46 +08:00
tarsin 167185ef45 Basic signin refine 2023-01-11 07:58:40 +08:00
tarsin 777a8ffeaa Refine SignInScreen 2023-01-10 20:58:28 +08:00
FooIbar b2b3e6c485 Add tips for torrent download 2023-01-10 20:30:26 +08:00
tarsin 613d502fc1 Use Mdc3Theme 2023-01-10 20:26:39 +08:00
tarsin 79da457db4 Basic signin Compose layout 2023-01-10 20:21:35 +08:00
tarsin b1a431ed82 Use mca component to judge is dynamic color available 2023-01-10 18:17:10 +08:00
tarsin b0f2776a23 Cleanup compose theme and navigation skeleton 2023-01-10 18:10:24 +08:00
tarsin 900c346ee8 Fixup! Convert login to compose, step 1, skeleton 2023-01-10 17:57:04 +08:00
tarsin 7541a87b21 Convert login to compose, step 1, skeleton 2023-01-10 17:54:55 +08:00
tarsin 2547c67ebd Fixup migrate to androidx navigation 2023-01-09 19:05:37 +08:00
tarsin 5ae6a0b287 Fixup migrate to androidx navigation 2023-01-09 19:00:04 +08:00
tarsin f718fc0c89 Reduce one more layout depth 2023-01-09 18:07:05 +08:00
tarsin 57e69ba7be Layout tweaks 2023-01-09 17:47:44 +08:00
tarsin 6168b3e047 Fixup! Use viewLifecycleOwner 2023-01-09 14:17:49 +08:00
tarsin 3e500500e0 Rewrite SelectSiteScene 2023-01-09 13:41:45 +08:00
tarsin 42f06ff84a Rename .java to .kt 2023-01-09 13:41:45 +08:00
tarsin a7824fc473 Convert WebViewSignInScene to kotlin 2023-01-09 13:35:53 +08:00
tarsin 67e7fd6928 Rename .java to .kt 2023-01-09 13:35:53 +08:00
tarsin 450e8badba GetProfile when signin with cookies 2023-01-09 13:21:48 +08:00
tarsin 704c211e11 Rewrite CookieSignInScene with modern viewBinding 2023-01-09 13:14:30 +08:00
tarsin 0ad871cb5b Rename .java to .kt 2023-01-09 13:14:30 +08:00
tarsin 9920605570 Checkout xz to 5.4.0 2023-01-09 12:56:10 +08:00
tarsin b1e8678dbf Fixup! Rewrite SignInScene with modern coroutines 2023-01-08 22:30:35 +08:00
tarsin e2cd2ddc0d Rewrite SignInScene with modern coroutines 2023-01-08 22:22:34 +08:00
tarsin a3be0dfddf Rewrite SignInScene 2023-01-08 21:33:50 +08:00
tarsin 4b615ffc0b Rename .java to .kt 2023-01-08 21:33:50 +08:00
tarsin 64cbd189ad Convert to kotlin 2023-01-08 21:15:26 +08:00
tarsin 0fae5a128f Rename .java to .kt 2023-01-08 21:15:26 +08:00
tarsin 518b44216f Navchecked item now handled internally 2023-01-08 20:57:59 +08:00
tarsin 9ef4512ef9 Reformat code 2023-01-08 20:42:23 +08:00
tarsin 8bb5f1e955 Initial migrate to androidx navigation (#647)
* Neutralize scene firstly

* Remove StageLayout

* Add nav graph

* Rename .java to .kt

* Convert BaseScene to kotlin

* Add GalleryDetailScene to navgraph

No need to check topfragment is callback target fragment, since these callback must be cancelled in fragment's lifecycle when DestroyView

* Restore funcs

* Restore preview

* Revert "Convert BaseScene to kotlin"

Should not do it here
This reverts commit 5d2e3af6ad85fef7604d8df19ee58a957390cdb5.

* Revert "Rename .java to .kt"

This reverts commit 128c655bc98fc54528d58534651d61ff47b2284d.

* Restore drawer

* Remove all getScene usage

* Remove all getStageActivity usage

* Remove all getStageActivity usage

* setupWithNavController

* Refactor MainActivity with viewBinding

* Add navdrawer corresponding navdestination

* Migrate All normal fragment

* Add anims

* Fix statusbar flicker

* Remove SceneApplication

* Remove SceneFragment

* Remove StageActivity

* Drop scene totally

* Dynamically set navgraph

* Restore signin

* Restore login via cookies

* Restore login via webview

* Add some TODOs

* Fixup

* Restore onProvideAssistContent

* Restore checkClipboard

* Restore intent handle

* Fix handle intent

* Remove unused EhUrlOpener

* Let system handle that intent

* Restore handle download intent

* Restore AboveSnackerLayout

* Fix the last navigation
2023-01-08 20:37:11 +08:00
FooIbar 8f8932f617 Fix potential integer overflow 2023-01-08 18:06:49 +08:00
tarsin 23fd1d75c4 Fix supertype corresponding parameter warning 2023-01-08 09:15:22 +08:00
stevenyomi 593dcef550 Fix floating-point error in navigate pan (#8856) 2023-01-08 09:04:53 +08:00
o0kam1 d769fe9074 fix #642 2023-01-07 22:13:16 +08:00
tarsin 77a8fec790 Bump Version
Releases / Build (push) Has been cancelled
2023-01-07 09:10:50 +08:00
renovate[bot] 34440edfaa Update dependency com.google.android.material:material to v1.8.0-rc01 2023-01-06 21:54:07 +00:00
tarsin 3c3d8784ad Fixup cleanup Mainthread db query 2023-01-06 18:48:06 +08:00
tarsin 1591abc92c It's impossible no activity receive that Intent, remove workaround 2023-01-06 18:38:38 +08:00
tarsin 0ffa0c34f0 Remove some NotifyDataSetChanged usage 2023-01-06 18:27:54 +08:00
tarsin c7dc94b61b Singletonize GalleryListScene OnBackPressedCallback 2023-01-06 18:11:41 +08:00
tarsin a8b83871a4 Convert GalleryListScene drawer to ViewBinding 2023-01-06 18:07:57 +08:00
tarsin 6f547984ff Convert GalleryListScene to ViewBinding 2023-01-06 17:59:15 +08:00
tarsin 3979c11317 Refine GalleryListScene nullability 2023-01-06 17:36:55 +08:00
tarsin 2a1ec985f4 GalleryListScene urlBuilder cleanup 2023-01-06 17:27:30 +08:00
tarsin 2a1d49e945 Fix DownloadsScene delete label, #629 2023-01-06 13:02:12 +08:00
FooIbar 7b7e6140d2 Improve domain fronting description 2023-01-06 12:56:25 +08:00
tarsin 907cfd9694 Fix DownloadsScene add label, #629 2023-01-06 12:42:13 +08:00
tarsin b51b1d1ec6 Convert DownloadsScene to kotlin, #629 2023-01-06 12:37:24 +08:00
tarsin 6a026300e4 Rename .java to .kt 2023-01-06 12:37:24 +08:00
tarsin 8511907390 Fix HistoryScene history clearall, #629 2023-01-06 12:22:57 +08:00
tarsin 05a5be336e Fix HistoryScene history delete, #629 2023-01-06 12:21:18 +08:00
tarsin 5434b1ce19 Fix GalleryListScene quicksearch delete, #629 2023-01-06 12:19:20 +08:00
tarsin c2d4a4367c Fix GalleryListScene quicksearch insert, #629 2023-01-06 12:16:37 +08:00
tarsin de2f277513 Convert GalleryListScene to kotlin, #629 2023-01-06 12:11:20 +08:00
tarsin 283e522037 Rename .java to .kt 2023-01-06 12:11:20 +08:00
tarsin caebe4c23b Initialize database earlier, #629 2023-01-06 11:54:26 +08:00
tarsin b195277c9a Minor Refactor 2023-01-06 11:32:53 +08:00
tarsin 6b99cb6253 Make GalleryDetail well typed and implement Parcelable 2023-01-06 11:07:45 +08:00
txyyh a822bb670a Change bug-report issue template 2023-01-06 11:05:24 +08:00
tarsin af7cc11647 Extract properties to a standalone AbstractGalleryInfo 2023-01-06 11:04:18 +08:00
tarsin 0afe331d3d Rename GalleryInfoImpl to BaseGalleryInfo 2023-01-06 11:02:44 +08:00
tarsin 7af40f0cd7 Revert "Not good solution :("
This reverts commit 3283851dc4.
2023-01-06 11:00:28 +08:00
tarsin 52dc699ee2 Revert "Cleanup primary constructors"
This reverts commit ec28e21897.
2023-01-06 11:00:17 +08:00
tarsin ec28e21897 Cleanup primary constructors 2023-01-06 10:41:33 +08:00
tarsin 3283851dc4 Not good solution :( 2023-01-06 10:30:31 +08:00
tarsin ec5e0c7b25 Fuck you Jetbrains! 2023-01-06 09:55:09 +08:00
tarsin 13660cb70a Cleanup JvmFields in GalleryInfo 2023-01-06 09:33:24 +08:00
tarsin ce008f5576 Bump Version
Releases / Build (push) Has been cancelled
2023-01-06 08:49:47 +08:00
tarsin 25f7e8b35c Always show toast in BottomSheet mode, Resolve #633 2023-01-06 08:47:37 +08:00
tarsin 109398d955 Fix enter Reader from previews 2023-01-06 08:38:57 +08:00
tarsin 4a8551087b Fix crash when open history 2023-01-06 08:15:42 +08:00
tarsin 34febe3545 Fix crash when open download 2023-01-06 08:09:11 +08:00
93 changed files with 5364 additions and 8238 deletions

No files matched your search

+4 -11
View File
@@ -17,12 +17,14 @@ body:
attributes:
label: 复现步骤 / Step to reproduce
description: |
在此处写下复现的方式
Describe how to reproduce here
请从全新安装应用开始,在此处写下复现的方式。请尽量附带录屏
Describe how to reproduce here with fresh installation, with screen recording if possible.
placeholder: |
0. 重新安装应用 / Fresh install
1.
2.
3.
[录屏] / [Screen recording]
validations:
required: true
@@ -111,15 +113,6 @@ body:
validations:
required: true
- type: textarea
id: log
attributes:
label: 应用日志 / App logs
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
placeholder: 06-15 17:44:53.704 23382 23382 E ...
validations:
required: true
- type: checkboxes
id: check
attributes:
+12 -11
View File
@@ -33,7 +33,7 @@ android {
minSdkVersion 28
targetSdkVersion 33
versionCode 180030
versionName "1.8.5.3-alpha01"
versionName "1.8.5.3-alpha04"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -104,6 +104,7 @@ android {
dependencies {
def activity_version = "1.6.1"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.activity:activity-compose:$activity_version"
implementation "androidx.annotation:annotation:1.5.0"
implementation "androidx.annotation:annotation-experimental:1.3.0"
@@ -121,13 +122,9 @@ dependencies {
implementation "androidx.collection:collection-ktx:$collection_version"
implementation platform("androidx.compose:compose-bom:2022.12.00")
implementation "androidx.compose.animation:animation"
implementation "androidx.compose.foundation:foundation"
implementation "androidx.compose.foundation:foundation-layout"
implementation "androidx.compose.material:material-icons-extended"
implementation "androidx.compose.material3:material3"
implementation "androidx.compose.runtime:runtime"
implementation "androidx.compose.runtime:runtime-saveable"
implementation "androidx.compose.ui:ui"
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha05"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
@@ -141,7 +138,7 @@ dependencies {
def fragment_version = "1.6.0-alpha04"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha03"
def lifecycle_version = "2.6.0-alpha04"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
@@ -154,6 +151,7 @@ dependencies {
def nav_version = "2.5.3"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-compose:$nav_version"
implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03"
@@ -165,7 +163,7 @@ dependencies {
ksp "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-paging:$room_version"
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.startup:startup-runtime:1.2.0-alpha02'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation "androidx.transition:transition-ktx:1.4.1"
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
@@ -174,7 +172,11 @@ dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.8.0-beta01'
def accompanist_version = "0.28.0"
implementation "com.google.accompanist:accompanist-themeadapter-material3:$accompanist_version"
implementation 'com.google.android.material:material:1.8.0-rc01'
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11'
implementation 'com.squareup.okhttp3:okhttp'
@@ -197,7 +199,6 @@ dependencies {
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
exclude group: 'androidx.compose.material'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-android-extensions-runtime'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7'
exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8'
+21 -38
View File
@@ -1,19 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
@@ -28,13 +13,12 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
android:name="com.hippo.ehviewer.EhApplication"
android:name=".EhApplication"
android:allowBackup="true"
android:appCategory="image"
android:dataExtractionRules="@xml/data_extraction_rules"
@@ -42,29 +26,33 @@
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:label="@string/app_name"
android:localeConfig="@xml/locales_config"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="tiramisu">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:name=".ui.LoginActivity"
android:exported="false"
android:theme="@style/AppTheme"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
@@ -85,26 +73,26 @@
<data android:host="g.e-hentai.org" />
<data android:host="lofi.e-hentai.org" />
</intent-filter>
<intent-filter android:label="@string/keyword_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter android:label="@string/image_search">
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="eu.kanade.tachiyomi.ui.reader.ReaderActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -138,21 +126,18 @@
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
<activity
android:name="com.hippo.ehviewer.ui.SettingsActivity"
android:name=".ui.SettingsActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.ui.SecurityActivity"
android:name=".ui.SecurityActivity"
android:configChanges="screenSize"
android:label="@string/settings"
android:theme="@style/AppTheme" />
<activity
android:name="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
android:name=".shortcuts.ShortcutsActivity"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
@@ -166,9 +151,8 @@
android:name="autoStoreLocales"
android:value="true" />
</service>
<service
android:name="com.hippo.ehviewer.download.DownloadService"
android:name=".download.DownloadService"
android:foregroundServiceType="dataSync"
android:label="@string/download_service_label" />
@@ -177,11 +161,10 @@
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths" />
</provider>
</application>
</manifest>
</manifest>
@@ -352,21 +352,7 @@ public final class DiskLruCache implements Closeable {
i.remove();
}
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
}
/**
* Creates a new journal that omits redundant information. This replaces the
@@ -406,7 +392,21 @@ public final class DiskLruCache implements Closeable {
journalWriter = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII));
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
/**
* Return {@code true} if it contains the key
@@ -16,7 +16,6 @@
package com.hippo.ehviewer;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -25,7 +24,6 @@ import android.os.Debug;
import androidx.annotation.NonNull;
import com.hippo.scene.StageActivity;
import com.hippo.util.PackageUtils;
import com.hippo.util.ReadableTime;
import com.hippo.yorozuya.FileUtils;
@@ -78,27 +76,10 @@ public final class Crash {
// Runtime
String topActivityClazzName = "null";
String topSceneClazzName = "null";
try {
Activity topActivity = ((EhApplication) context.getApplicationContext()).getTopActivity();
if (null != topActivity) {
topActivityClazzName = topActivity.getClass().getName();
if (topActivity instanceof StageActivity) {
Class<?> clazz = ((StageActivity) topActivity).getTopSceneClass();
if (clazz != null) {
topSceneClazzName = clazz.getName();
}
}
}
} catch (Throwable e) {
// Ignore
}
fw.write("======== Runtime ========\r\n");
fw.write("TopActivity=");
fw.write(avoidNull(topActivityClazzName));
fw.write("\r\n");
fw.write("TopScene=");
fw.write(avoidNull(topSceneClazzName));
fw.write("\r\n");
fw.write("\r\n");
@@ -19,6 +19,7 @@ package com.hippo.ehviewer
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.content.ComponentCallbacks2
import android.text.Html
import android.text.method.LinkMovementMethod
@@ -44,11 +45,11 @@ import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhX509TrustManager
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.ehviewer.client.parser.EventPaneParser
import com.hippo.ehviewer.dao.buildMainDB
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderDen
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.scene.SceneApplication
import com.hippo.util.BitmapUtils
import com.hippo.util.ExceptionUtils
import com.hippo.util.ReadableTime
@@ -70,7 +71,7 @@ import java.util.Arrays
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager
class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderFactory {
class EhApplication : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
private val mIdGenerator = IntIdGenerator()
private val mGlobalStuffMap = HashMap<Int, Any>()
private val mActivityList = ArrayList<Activity>()
@@ -100,18 +101,20 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
}
handler?.uncaughtException(t, e)
}
super<SceneApplication>.onCreate()
super<Application>.onCreate()
Native.initialize()
GetText.initialize(this)
Settings.initialize()
ReadableTime.initialize(this)
AppConfig.initialize(this)
SpiderDen.initialize(this)
EhDB.initialize(this)
BitmapUtils.initialize(this)
EhTagDatabase.update()
AppCompatDelegate.setDefaultNightMode(Settings.getTheme())
launchIO {
launchIO {
ehDatabase
}
launchIO {
downloadManager
}
@@ -342,5 +345,8 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
@JvmStatic
val readerPreferences by lazy { ReaderPreferences(AndroidPreferenceStore(application)) }
@JvmStatic
val ehDatabase by lazy { buildMainDB(application) }
}
}
+29 -9
View File
@@ -58,7 +58,7 @@ import java.util.List;
public class EhDB {
private static final int CUR_DB_VER = 4;
private static EhDatabase db;
private static final EhDatabase db = EhApplication.getEhDatabase();
private static void upgradeDB(SQLiteDatabase db, int oldVersion) {
switch (oldVersion) {
@@ -99,10 +99,6 @@ public class EhDB {
}
}
public static void initialize(Context context) {
db = Room.databaseBuilder(context, EhDatabase.class, "eh.db").allowMainThreadQueries().build();
}
public static synchronized List<DownloadInfo> getAllDownloadInfo() {
DownloadsDao dao = db.downloadsDao();
List<DownloadInfo> list = dao.list();
@@ -115,10 +111,34 @@ public class EhDB {
return list;
}
public static synchronized void moveDownloadInfo(int fromPosition, int toPosition) {
if (fromPosition == toPosition) {
return;
}
boolean reverse = fromPosition > toPosition;
int offset = reverse ? toPosition : fromPosition;
int limit = reverse ? fromPosition - toPosition + 1 : toPosition - fromPosition + 1;
DownloadsDao dao = db.downloadsDao();
List<DownloadInfo> list = dao.list(offset, limit);
int step = reverse ? 1 : -1;
int start = reverse ? limit - 1 : 0;
int end = reverse ? 0 : limit - 1;
long toTime = list.get(end).time;
for (int i = end; reverse ? i < start : i > start; i += step) {
list.get(i).time = list.get(i + step).time;
}
list.get(start).time = toTime;
dao.update(list);
}
// Insert or update
public static synchronized void putDownloadInfo(DownloadInfo downloadInfo) {
DownloadsDao dao = db.downloadsDao();
if (null != dao.load(downloadInfo.gid)) {
if (null != dao.load(downloadInfo.getGid())) {
// Update
dao.update(downloadInfo);
} else {
@@ -256,7 +276,7 @@ public class EhDB {
public static synchronized void putLocalFavorites(GalleryInfo galleryInfo) {
LocalFavoritesDao dao = db.localFavoritesDao();
if (null == dao.load(galleryInfo.gid)) {
if (null == dao.load(galleryInfo.getGid())) {
LocalFavoriteInfo info;
if (galleryInfo instanceof LocalFavoriteInfo) {
info = (LocalFavoriteInfo) galleryInfo;
@@ -335,7 +355,7 @@ public class EhDB {
info = new HistoryInfo(galleryInfo);
}
info.time = System.currentTimeMillis();
if (null != dao.load(info.gid)) {
if (null != dao.load(info.getGid())) {
dao.update(info);
} else {
dao.insert(info);
@@ -345,7 +365,7 @@ public class EhDB {
public static synchronized void putHistoryInfo(List<HistoryInfo> historyInfoList) {
HistoryDao dao = db.historyDao();
for (HistoryInfo info : historyInfoList) {
if (null == dao.load(info.gid)) {
if (null == dao.load(info.getGid())) {
dao.insert(info);
}
}
@@ -51,7 +51,7 @@ public class FavouriteStatusRouter {
for (Map<Long, GalleryInfo> map : maps.values()) {
GalleryInfo info = map.get(gid);
if (info != null) {
info.favoriteSlot = slot;
info.setFavoriteSlot(slot);
}
}
@@ -26,12 +26,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
@@ -56,7 +52,7 @@ public final class UrlOpener {
if (galleryDetail != null) {
GalleryPageUrlParser.Result result = GalleryPageUrlParser.parse(url);
if (result != null) {
if (result.gid == galleryDetail.gid) {
if (result.gid == galleryDetail.getGid()) {
intent = new Intent(context, ReaderActivity.class);
intent.setAction(ReaderActivity.ACTION_EH);
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, galleryDetail);
@@ -77,16 +73,6 @@ public final class UrlOpener {
}
}
}
Announcer announcer = EhUrlOpener.parseUrl(url);
if (null != announcer) {
intent = new Intent(context, MainActivity.class);
intent.setAction(StageActivity.ACTION_START_SCENE);
intent.putExtra(StageActivity.KEY_SCENE_NAME, announcer.getClazz().getName());
intent.putExtra(StageActivity.KEY_SCENE_ARGS, announcer.getArgs());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
return;
}
}
CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder();
@@ -15,7 +15,6 @@
*/
package com.hippo.ehviewer.client
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.CancellationException
@@ -26,136 +25,13 @@ object EhClient {
internal fun enqueue(request: EhRequest, scope: CoroutineScope) {
check(!request.isActive) // Abort if attempt to execute an active request
request.job = scope.launchIO {
val method = request.method
val params = request.args
val callback: Callback<Any?>? = request.callback
try {
val result: Any? = when (method) {
METHOD_SIGN_IN -> EhEngine.signIn(
mOkHttpClient,
params!![0] as String,
params[1] as String
)
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
mOkHttpClient,
params!![0] as String
)
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
mOkHttpClient,
params!![0] as String?
)
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
mOkHttpClient,
params!![0] as String?
)
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Float
)
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
mOkHttpClient,
params!![0] as String?,
params[1] as String,
params[2] as String?
)
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Int
)
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
mOkHttpClient,
params!![0] as String
)
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Int,
params[3] as String?
)
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
mOkHttpClient,
params!![0] as LongArray,
params[1] as Array<String?>,
params[2] as Int
)
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
mOkHttpClient,
params!![0] as String,
params[1] as LongArray,
params[2] as Int
)
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
mOkHttpClient,
params!![0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_GET_PROFILE -> EhEngine.getProfile(mOkHttpClient)
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Long,
params[5] as Int
)
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
mOkHttpClient,
params!![0] as File,
params[1] as Boolean,
params[2] as Boolean,
params[3] as Boolean
)
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
mOkHttpClient,
params!![0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as String?,
params[3] as String?,
params[4] as Boolean
)
METHOD_VOTE_TAG -> EhEngine.voteTag(
mOkHttpClient,
params!![0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as String?,
params[5] as Int
)
METHOD_GET_UCONFIG -> EhEngine.getUConfig(mOkHttpClient)
else -> throw IllegalStateException("Can't detect method $method")
val result: Any? = request.run {
if (args == null)
execute(method)
else
execute(method, *args!!)
}
withUIContext { callback?.onSuccess(result) }
} catch (e: Exception) {
@@ -167,6 +43,119 @@ object EhClient {
}
}
suspend fun execute(method: Int, vararg params: Any?): Any? {
return when (method) {
METHOD_SIGN_IN -> EhEngine.signIn(
params[0] as String,
params[1] as String
)
METHOD_GET_GALLERY_LIST -> EhEngine.getGalleryList(
params[0] as String
)
METHOD_GET_GALLERY_DETAIL -> EhEngine.getGalleryDetail(
params[0] as String?
)
METHOD_GET_PREVIEW_SET -> EhEngine.getPreviewSet(
params[0] as String?
)
METHOD_GET_RATE_GALLERY -> EhEngine.rateGallery(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Float
)
METHOD_GET_COMMENT_GALLERY -> EhEngine.commentGallery(
params[0] as String?,
params[1] as String,
params[2] as String?
)
METHOD_GET_GALLERY_TOKEN -> EhEngine.getGalleryToken(
params[0] as Long,
params[1] as String?,
params[2] as Int
)
METHOD_GET_FAVORITES -> EhEngine.getFavorites(
params[0] as String
)
METHOD_ADD_FAVORITES -> EhEngine.addFavorites(
params[0] as Long,
params[1] as String?,
params[2] as Int,
params[3] as String?
)
METHOD_ADD_FAVORITES_RANGE -> @Suppress("UNCHECKED_CAST") EhEngine.addFavoritesRange(
params[0] as LongArray,
params[1] as Array<String?>,
params[2] as Int
)
METHOD_MODIFY_FAVORITES -> EhEngine.modifyFavorites(
params[0] as String,
params[1] as LongArray,
params[2] as Int
)
METHOD_GET_TORRENT_LIST -> EhEngine.getTorrentList(
params[0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_GET_PROFILE -> EhEngine.getProfile()
METHOD_VOTE_COMMENT -> EhEngine.voteComment(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as Long,
params[5] as Int
)
METHOD_IMAGE_SEARCH -> EhEngine.imageSearch(
params[0] as File,
params[1] as Boolean,
params[2] as Boolean,
params[3] as Boolean
)
METHOD_ARCHIVE_LIST -> EhEngine.getArchiveList(
params[0] as String?,
params[1] as Long,
params[2] as String?
)
METHOD_DOWNLOAD_ARCHIVE -> EhEngine.downloadArchive(
params[0] as Long,
params[1] as String?,
params[2] as String?,
params[3] as String?,
params[4] as Boolean
)
METHOD_VOTE_TAG -> EhEngine.voteTag(
params[0] as Long,
params[1] as String?,
params[2] as Long,
params[3] as String?,
params[4] as String?,
params[5] as Int
)
METHOD_GET_UCONFIG -> EhEngine.getUConfig()
else -> throw IllegalStateException("Can't detect method $method")
}
}
interface Callback<E> {
fun onSuccess(result: E)
fun onFailure(e: Exception)
@@ -192,5 +181,4 @@ object EhClient {
const val METHOD_DOWNLOAD_ARCHIVE = 18
const val METHOD_VOTE_TAG = 19
const val METHOD_GET_UCONFIG = 20
private val mOkHttpClient = okHttpClient
}
@@ -19,6 +19,7 @@ import android.text.TextUtils
import android.util.Log
import android.util.Pair
import com.hippo.ehviewer.AppConfig
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhApplication.Companion.application
import com.hippo.ehviewer.GetText
import com.hippo.ehviewer.R
@@ -55,7 +56,6 @@ import okhttp3.Headers
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
@@ -76,6 +76,7 @@ object EhEngine {
private const val KOKOMADE_URL = "https://exhentai.org/img/kokomade.jpg"
private val MEDIA_TYPE_JPEG: MediaType = "image/jpeg".toMediaType()
private var sEhFilter = EhFilter.getInstance()
private val okHttpClient = EhApplication.okHttpClient
private fun transformException(code: Int, headers: Headers?, body: String?, e: Throwable?) {
// Check sad panda
@@ -116,7 +117,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun signIn(okHttpClient: OkHttpClient, username: String, password: String): String {
suspend fun signIn(username: String, password: String): String {
val referer = "https://forums.e-hentai.org/index.php?act=Login&CODE=00"
val builder = FormBody.Builder()
.add("referer", referer)
@@ -151,7 +152,6 @@ object EhEngine {
@Throws(Throwable::class)
private suspend fun fillGalleryList(
okHttpClient: OkHttpClient,
list: MutableList<GalleryInfo>,
url: String,
filter: Boolean
@@ -188,7 +188,7 @@ object EhEngine {
filter && sEhFilter.needTags() && !hasTags || Settings.getShowGalleryPages() && !hasPages ||
hasRated
if (needApi) {
fillGalleryListByApi(okHttpClient, list, url)
fillGalleryListByApi(list, url)
}
// Filter tag
@@ -212,7 +212,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun getGalleryList(okHttpClient: OkHttpClient, url: String): GalleryListParser.Result {
suspend fun getGalleryList(url: String): GalleryListParser.Result {
val referer = EhUrl.getReferer()
Log.d(TAG, url)
val request = EhRequestBuilder(url, referer).build()
@@ -234,7 +234,7 @@ object EhEngine {
transformException(code, headers, body, e)
throw e
}
fillGalleryList(okHttpClient, result.galleryInfoList, url, true)
fillGalleryList(result.galleryInfoList, url, true)
return result
}
@@ -242,7 +242,6 @@ object EhEngine {
@JvmStatic
@Throws(Throwable::class)
suspend fun fillGalleryListByApi(
okHttpClient: OkHttpClient,
galleryInfoList: List<GalleryInfo>,
referer: String
): List<GalleryInfo> {
@@ -252,7 +251,7 @@ object EhEngine {
while (i < size) {
requestItems.add(galleryInfoList[i])
if (requestItems.size == MAX_REQUEST_SIZE || i == size - 1) {
doFillGalleryListByApi(okHttpClient, requestItems, referer)
doFillGalleryListByApi(requestItems, referer)
requestItems.clear()
}
i++
@@ -262,7 +261,6 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun doFillGalleryListByApi(
okHttpClient: OkHttpClient,
galleryInfoList: List<GalleryInfo>,
referer: String
) {
@@ -307,7 +305,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun getGalleryDetail(okHttpClient: OkHttpClient, url: String?): GalleryDetail {
suspend fun getGalleryDetail(url: String?): GalleryDetail {
val referer = EhUrl.getReferer()
Log.d(TAG, url!!)
val request = EhRequestBuilder(url, referer).build()
@@ -335,7 +333,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun getPreviewSet(okHttpClient: OkHttpClient, url: String?): Pair<PreviewSet, Int> {
suspend fun getPreviewSet(url: String?): Pair<PreviewSet, Int> {
val referer = EhUrl.getReferer()
Log.d(TAG, url!!)
val request = EhRequestBuilder(url, referer).build()
@@ -363,7 +361,6 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun rateGallery(
okHttpClient: OkHttpClient,
apiUid: Long, apiKey: String?, gid: Long, token: String?,
rating: Float
): RateGalleryParser.Result {
@@ -403,7 +400,6 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun commentGallery(
okHttpClient: OkHttpClient,
url: String?, comment: String, id: String?
): GalleryCommentList {
val builder = FormBody.Builder()
@@ -444,7 +440,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun getGalleryToken(
okHttpClient: OkHttpClient, gid: Long,
gid: Long,
gtoken: String?, page: Int
): String {
val json = JSONObject()
@@ -482,7 +478,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun getFavorites(
okHttpClient: OkHttpClient, url: String
url: String
): FavoritesParser.Result {
val referer = EhUrl.getReferer()
Log.d(TAG, url)
@@ -505,7 +501,7 @@ object EhEngine {
transformException(code, headers, body, e)
throw e
}
fillGalleryList(okHttpClient, result.galleryInfoList, url, false)
fillGalleryList(result.galleryInfoList, url, false)
return result
}
@@ -515,7 +511,7 @@ object EhEngine {
*/
@Throws(Throwable::class)
suspend fun addFavorites(
okHttpClient: OkHttpClient, gid: Long,
gid: Long,
token: String?, dstCat: Int, note: String?
): Void? {
val catStr: String = when (dstCat) {
@@ -564,14 +560,14 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun addFavoritesRange(
okHttpClient: OkHttpClient, gidArray: LongArray,
gidArray: LongArray,
tokenArray: Array<String?>, dstCat: Int
): Void? {
AssertUtils.assertEquals(gidArray.size, tokenArray.size)
var i = 0
val n = gidArray.size
while (i < n) {
addFavorites(okHttpClient, gidArray[i], tokenArray[i], dstCat, null)
addFavorites(gidArray[i], tokenArray[i], dstCat, null)
i++
}
return null
@@ -579,7 +575,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun modifyFavorites(
okHttpClient: OkHttpClient, url: String,
url: String,
gidArray: LongArray, dstCat: Int
): FavoritesParser.Result {
val catStr: String = when (dstCat) {
@@ -623,13 +619,13 @@ object EhEngine {
transformException(code, headers, body, e)
throw e
}
fillGalleryList(okHttpClient, result.galleryInfoList, url, false)
fillGalleryList(result.galleryInfoList, url, false)
return result
}
@Throws(Throwable::class)
suspend fun getTorrentList(
okHttpClient: OkHttpClient, url: String?,
url: String?,
gid: Long, token: String?
): List<TorrentParser.Result> {
val referer = EhUrl.getGalleryDetailUrl(gid, token)
@@ -657,7 +653,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun getArchiveList(
okHttpClient: OkHttpClient, url: String?,
url: String?,
gid: Long, token: String?
): ArchiveParser.Result {
val referer = EhUrl.getGalleryDetailUrl(gid, token)
@@ -685,7 +681,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun downloadArchive(
okHttpClient: OkHttpClient, gid: Long,
gid: Long,
token: String?, or: String?, res: String?, isHAtH: Boolean
): String? {
if (or.isNullOrEmpty()) {
@@ -756,7 +752,6 @@ object EhEngine {
@Throws(Throwable::class)
private suspend fun getProfileInternal(
okHttpClient: OkHttpClient,
url: String, referer: String
): ProfileParser.Result {
Log.d(TAG, url)
@@ -780,9 +775,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun getProfile(
okHttpClient: OkHttpClient
): ProfileParser.Result {
suspend fun getProfile(): ProfileParser.Result {
val url = EhUrl.URL_FORUMS
Log.d(TAG, url)
val request = EhRequestBuilder(url, null).build()
@@ -795,7 +788,7 @@ object EhEngine {
code = response.code
headers = response.headers
body = response.body.string()
return getProfileInternal(okHttpClient, ForumsParser.parse(body), url)
return getProfileInternal(ForumsParser.parse(body), url)
}
} catch (e: Throwable) {
ExceptionUtils.throwIfFatal(e)
@@ -805,9 +798,7 @@ object EhEngine {
}
@Throws(Throwable::class)
suspend fun getUConfig(
okHttpClient: OkHttpClient
): Void? {
suspend fun getUConfig(): Void? {
val url = EhUrl.getUConfigUrl()
Log.d(TAG, url)
var request = EhRequestBuilder(url, null).build()
@@ -852,7 +843,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun voteComment(
okHttpClient: OkHttpClient, apiUid: Long,
apiUid: Long,
apiKey: String?, gid: Long, token: String?, commentId: Long, commentVote: Int
): VoteCommentParser.Result {
val json = JSONObject()
@@ -891,7 +882,7 @@ object EhEngine {
@Throws(Throwable::class)
suspend fun voteTag(
okHttpClient: OkHttpClient, apiUid: Long,
apiUid: Long,
apiKey: String?, gid: Long, token: String?, tags: String?, vote: Int
): VoteTagParser.Result {
val json = JSONObject()
@@ -933,7 +924,7 @@ object EhEngine {
*/
@Throws(Throwable::class)
suspend fun imageSearch(
okHttpClient: OkHttpClient, image: File,
image: File,
uss: Boolean, osc: Boolean, se: Boolean
): GalleryListParser.Result {
val builder = MultipartBody.Builder()
@@ -992,14 +983,13 @@ object EhEngine {
transformException(code, headers, body, e)
throw e
}
fillGalleryList(okHttpClient, result.galleryInfoList, url, true)
fillGalleryList(result.galleryInfoList, url, true)
return result
}
@JvmStatic
@Throws(Throwable::class)
fun getGalleryPage(
okHttpClient: OkHttpClient,
url: String?,
gid: Long,
token: String?
@@ -1029,7 +1019,6 @@ object EhEngine {
@JvmStatic
@Throws(Throwable::class)
fun getGalleryPageApi(
okHttpClient: OkHttpClient,
gid: Long,
index: Int,
pToken: String?,
@@ -156,7 +156,7 @@ public final class EhFilter {
}
// Title
String title = info.title;
String title = info.getTitle();
List<Filter> filters = mTitleFilterList;
if (null != title && filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
@@ -175,7 +175,7 @@ public final class EhFilter {
}
// Uploader
String uploader = info.uploader;
String uploader = info.getUploader();
List<Filter> filters = mUploaderFilterList;
if (null != uploader && filters.size() > 0) {
for (int i = 0, n = filters.size(); i < n; i++) {
@@ -227,7 +227,7 @@ public final class EhFilter {
}
// Tag
String[] tags = info.simpleTags;
String[] tags = info.getSimpleTags();
List<Filter> filters = mTagFilterList;
if (null != tags && filters.size() > 0) {
for (String tag : tags) {
@@ -262,7 +262,7 @@ public final class EhFilter {
return false;
}
String[] tags = info.simpleTags;
String[] tags = info.getSimpleTags();
List<Filter> filters = mTagNamespaceFilterList;
if (null != tags && filters.size() > 0) {
for (String tag : tags) {
@@ -62,7 +62,7 @@ class EhRequest {
}
fun enqueue(fragment: Fragment) {
enqueue(fragment.lifecycleScope)
enqueue(fragment.viewLifecycleOwner.lifecycleScope)
}
fun enqueue(activity: Activity) {
@@ -1,75 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser;
import com.hippo.ehviewer.client.parser.GalleryListUrlParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.scene.GalleryDetailScene;
import com.hippo.ehviewer.ui.scene.GalleryListScene;
import com.hippo.ehviewer.ui.scene.ProgressScene;
import com.hippo.scene.Announcer;
public class EhUrlOpener {
private static final String TAG = EhUrlOpener.class.getSimpleName();
@Nullable
public static Announcer parseUrl(String url) {
if (TextUtils.isEmpty(url)) {
return null;
}
ListUrlBuilder listUrlBuilder = GalleryListUrlParser.parse(url);
if (listUrlBuilder != null) {
Bundle args = new Bundle();
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_LIST_URL_BUILDER);
args.putParcelable(GalleryListScene.KEY_LIST_URL_BUILDER, listUrlBuilder);
return new Announcer(GalleryListScene.class).setArgs(args);
}
GalleryDetailUrlParser.Result result1 = GalleryDetailUrlParser.parse(url);
if (result1 != null) {
Bundle args = new Bundle();
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN);
args.putLong(GalleryDetailScene.KEY_GID, result1.gid);
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token);
return new Announcer(GalleryDetailScene.class).setArgs(args);
}
GalleryPageUrlParser.Result result2 = GalleryPageUrlParser.parse(url);
if (result2 != null) {
Bundle args = new Bundle();
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN);
args.putLong(ProgressScene.KEY_GID, result2.gid);
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken);
args.putInt(ProgressScene.KEY_PAGE, result2.page);
return new Announcer(ProgressScene.class).setArgs(args);
}
Log.i(TAG, "Can't parse url: " + url);
return null;
}
}
@@ -133,9 +133,9 @@ public class EhUtils {
public static String getSuitableTitle(GalleryInfo gi) {
if (Settings.getShowJpnTitle()) {
return TextUtils.isEmpty(gi.titleJpn) ? gi.title : gi.titleJpn;
return TextUtils.isEmpty(gi.getTitleJpn()) ? gi.getTitle() : gi.getTitleJpn();
} else {
return TextUtils.isEmpty(gi.title) ? gi.titleJpn : gi.title;
return TextUtils.isEmpty(gi.getTitle()) ? gi.getTitleJpn() : gi.getTitle();
}
}
@@ -0,0 +1,25 @@
package com.hippo.ehviewer.client.data
interface AbstractGalleryInfo {
var gid: Long
var token: String?
var title: String?
var titleJpn: String?
var thumb: String?
var category: Int
var posted: String?
var uploader: String?
var disowned: Boolean
var rating: Float
var rated: Boolean
var simpleTags: Array<String>?
var pages: Int
var thumbWidth: Int
var thumbHeight: Int
var spanSize: Int
var spanIndex: Int
var spanGroupIndex: Int
var simpleLanguage: String?
var favoriteSlot: Int
var favoriteName: String?
}
@@ -0,0 +1,89 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.client.data
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
@Parcelize
open class BaseGalleryInfo(
@PrimaryKey
@ColumnInfo(name = "GID")
override var gid: Long = 0,
@ColumnInfo(name = "TOKEN")
override var token: String? = null,
@ColumnInfo(name = "TITLE")
override var title: String? = null,
@ColumnInfo(name = "TITLE_JPN")
override var titleJpn: String? = null,
@ColumnInfo(name = "THUMB")
override var thumb: String? = null,
@ColumnInfo(name = "CATEGORY")
override var category: Int = 0,
@ColumnInfo(name = "POSTED")
override var posted: String? = null,
@ColumnInfo(name = "UPLOADER")
override var uploader: String? = null,
@Ignore
override var disowned: Boolean = false,
@ColumnInfo(name = "RATING")
override var rating: Float = 0f,
@Ignore
override var rated: Boolean = false,
@Ignore
override var simpleTags: Array<String>? = null,
@Ignore
override var pages: Int = 0,
@Ignore
override var thumbWidth: Int = 0,
@Ignore
override var thumbHeight: Int = 0,
@Ignore
override var spanSize: Int = 0,
@Ignore
override var spanIndex: Int = 0,
@Ignore
override var spanGroupIndex: Int = 0,
@ColumnInfo(name = "SIMPLE_LANGUAGE")
override var simpleLanguage: String? = null,
@Ignore
override var favoriteSlot: Int = -2,
@Ignore
override var favoriteName: String? = null
) : GalleryInfo
@@ -17,8 +17,11 @@ package com.hippo.ehviewer.client.data
import kotlinx.parcelize.Parcelize
@Parcelize // This is wrong Parcelize usage, TODO: use delegate and move galleryinfo to primary constructor
@Parcelize
class GalleryDetail(
@JvmField
val galleryInfo: GalleryInfo = BaseGalleryInfo(),
@JvmField
var apiUid: Long = -1L,
@@ -69,4 +72,4 @@ class GalleryDetail(
@JvmField
var previewSet: PreviewSet? = null,
) : GalleryInfo()
) : AbstractGalleryInfo by galleryInfo, GalleryInfo
@@ -16,102 +16,12 @@
package com.hippo.ehviewer.client.data
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Ignore
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import java.util.regex.Pattern
@Parcelize
open class GalleryInfo(
@JvmField
@PrimaryKey
@ColumnInfo(name = "GID")
var gid: Long = 0,
@JvmField
@ColumnInfo(name = "TOKEN")
var token: String? = null,
@JvmField
@ColumnInfo(name = "TITLE")
var title: String? = null,
@JvmField
@ColumnInfo(name = "TITLE_JPN")
var titleJpn: String? = null,
@JvmField
@ColumnInfo(name = "THUMB")
var thumb: String? = null,
@JvmField
@ColumnInfo(name = "CATEGORY")
var category: Int = 0,
@JvmField
@ColumnInfo(name = "POSTED")
var posted: String? = null,
@JvmField
@ColumnInfo(name = "UPLOADER")
var uploader: String? = null,
@JvmField
@Ignore
var disowned: Boolean = false,
@JvmField
@ColumnInfo(name = "RATING")
var rating: Float = 0f,
@JvmField
@Ignore
var rated: Boolean = false,
@JvmField
@Ignore
var simpleTags: Array<String>? = null,
@JvmField
@Ignore
var pages: Int = 0,
@JvmField
@Ignore
var thumbWidth: Int = 0,
@JvmField
@Ignore
var thumbHeight: Int = 0,
@Ignore
var spanSize: Int = 0,
@Ignore
var spanIndex: Int = 0,
@Ignore
var spanGroupIndex: Int = 0,
/**
* language from title
*/
@JvmField
@ColumnInfo(name = "SIMPLE_LANGUAGE")
var simpleLanguage: String? = null,
@JvmField
@Ignore
var favoriteSlot: Int = -2,
@JvmField
@Ignore
var favoriteName: String? = null
) : Parcelable {
interface GalleryInfo : AbstractGalleryInfo, Parcelable {
fun generateSLang() {
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) } ?: title?.let { generateSLangFromTitle(it) }
simpleLanguage = simpleTags?.let { generateSLangFromTags(it) }
?: title?.let { generateSLangFromTitle(it) }
}
private fun generateSLangFromTags(simpleTags: Array<String>): String? {
@@ -154,7 +64,6 @@ open class GalleryInfo(
const val S_LANG_TH = "TH"
const val S_LANG_VI = "VI"
@JvmField
val S_LANGS = arrayOf(
S_LANG_EN,
S_LANG_ZH,
@@ -32,7 +32,7 @@ import java.io.UnsupportedEncodingException
import java.net.URLDecoder
@Parcelize
open class ListUrlBuilder(
data class ListUrlBuilder(
@get:Mode @Mode var mode: Int = MODE_NORMAL,
private var mPrev: String? = null,
private var mNext: String? = null,
@@ -48,7 +48,7 @@ open class ListUrlBuilder(
var isUseSimilarityScan: Boolean = false,
var isOnlySearchCovers: Boolean = false,
var isShowExpunged: Boolean = false,
) : Cloneable, Parcelable {
) : Parcelable {
fun reset() {
mode = MODE_NORMAL
@@ -83,29 +83,6 @@ open class ListUrlBuilder(
mKeyword = keyword
}
/**
* Make them the same
*
* @param lub The template
*/
fun set(lub: ListUrlBuilder) {
mode = lub.mode
mPrev = lub.mPrev
mNext = lub.mNext
mJumpTo = lub.mJumpTo
this.category = lub.category
mKeyword = lub.mKeyword
advanceSearch = lub.advanceSearch
minRating = lub.minRating
pageFrom = lub.pageFrom
pageTo = lub.pageTo
imagePath = lub.imagePath
isUseSimilarityScan = lub.isUseSimilarityScan
isOnlySearchCovers = lub.isOnlySearchCovers
isShowExpunged = lub.isShowExpunged
mSHash = lub.mSHash
}
fun set(q: QuickSearch) {
mode = q.mode
this.category = q.category
@@ -39,13 +39,13 @@ public class GalleryApiParser {
if (gi == null) {
continue;
}
gi.title = ParserUtils.trim(g.getString("title"));
gi.titleJpn = ParserUtils.trim(g.getString("title_jpn"));
gi.category = EhUtils.getCategory(g.getString("category"));
gi.thumb = EhUtils.handleThumbUrlResolution(g.getString("thumb"));
gi.uploader = g.getString("uploader");
gi.posted = ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000);
gi.rating = NumberUtils.parseFloatSafely(g.getString("rating"), 0.0f);
gi.setTitle(ParserUtils.trim(g.getString("title")));
gi.setTitleJpn(ParserUtils.trim(g.getString("title_jpn")));
gi.setCategory(EhUtils.getCategory(g.getString("category")));
gi.setThumb(EhUtils.handleThumbUrlResolution(g.getString("thumb")));
gi.setUploader(g.getString("uploader"));
gi.setPosted(ParserUtils.formatDate(ParserUtils.parseLong(g.getString("posted"), 0) * 1000));
gi.setRating(NumberUtils.parseFloatSafely(g.getString("rating"), 0.0f));
// tags
JSONArray tagJa = g.getJSONArray("tags");
int tagLength = tagJa.length();
@@ -53,8 +53,8 @@ public class GalleryApiParser {
for (int j = 0; j < tagLength; j++) {
tags[j] = tagJa.getString(j);
}
gi.simpleTags = tags;
gi.pages = NumberUtils.parseIntSafely(g.getString("filecount"), 0);
gi.setSimpleTags(tags);
gi.setPages(NumberUtils.parseIntSafely(g.getString("filecount"), 0));
gi.generateSLang();
}
}
@@ -62,7 +62,7 @@ public class GalleryApiParser {
private static GalleryInfo getGalleryInfoByGid(List<GalleryInfo> galleryInfoList, long gid) {
for (int i = 0, size = galleryInfoList.size(); i < size; i++) {
GalleryInfo gi = galleryInfoList.get(i);
if (gi.gid == gid) {
if (gi.getGid() == gid) {
return gi;
}
}
@@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
import com.hippo.ehviewer.client.data.GalleryDetail;
@@ -124,7 +125,7 @@ public class GalleryDetailParser {
// Generate simpleLanguage for local favorites
for (int i = 0; i < LANGUAGES.length; i++) {
if (galleryDetail.language.equals(LANGUAGES[i])) {
galleryDetail.simpleLanguage = GalleryInfo.S_LANGS[i];
galleryDetail.setSimpleLanguage(GalleryInfo.Companion.getS_LANGS()[i]);
break;
}
}
@@ -136,14 +137,14 @@ public class GalleryDetailParser {
private static void parseDetail(GalleryDetail gd, Document d, String body) throws ParseException {
Matcher matcher = PATTERN_DETAIL.matcher(body);
if (matcher.find()) {
gd.gid = NumberUtils.parseLongSafely(matcher.group(1), -1L);
gd.token = matcher.group(2);
gd.setGid(NumberUtils.parseLongSafely(matcher.group(1), -1L));
gd.setToken(matcher.group(2));
gd.apiUid = NumberUtils.parseLongSafely(matcher.group(3), -1L);
gd.apiKey = matcher.group(4);
} else {
throw new ParseException("Can't parse gallery detail", body);
}
if (gd.gid == -1L) {
if (gd.getGid() == -1L) {
throw new ParseException("Can't parse gallery detail", body);
}
@@ -169,26 +170,26 @@ public class GalleryDetailParser {
// Thumb url
Element gd1 = gm.getElementById("gd1");
try {
gd.thumb = parseCoverStyle(StringUtils.trim(gd1.child(0).attr("style")));
gd.setThumb(parseCoverStyle(StringUtils.trim(gd1.child(0).attr("style"))));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
gd.thumb = "";
gd.setThumb("");
}
// Title
Element gn = gm.getElementById("gn");
if (null != gn) {
gd.title = StringUtils.trim(gn.text());
gd.setTitle(StringUtils.trim(gn.text()));
} else {
gd.title = "";
gd.setTitle("");
}
// Jpn title
Element gj = gm.getElementById("gj");
if (null != gj) {
gd.titleJpn = StringUtils.trim(gj.text());
gd.setTitleJpn(StringUtils.trim(gj.text()));
} else {
gd.titleJpn = "";
gd.setTitleJpn("");
}
// Category
@@ -198,27 +199,27 @@ public class GalleryDetailParser {
if (ce == null) {
ce = JsoupUtils.getElementByClass(gdc, "cs");
}
gd.category = EhUtils.getCategory(ce.text());
gd.setCategory(EhUtils.getCategory(ce.text()));
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
gd.category = EhUtils.UNKNOWN;
gd.setCategory(EhUtils.UNKNOWN);
}
// Uploader
Element gdn = gm.getElementById("gdn");
if (null != gdn) {
gd.disowned = gdn.attr("style").contains("opacity:0.5");
gd.uploader = StringUtils.trim(gdn.text());
gd.setDisowned(gdn.attr("style").contains("opacity:0.5"));
gd.setUploader(StringUtils.trim(gdn.text()));
} else {
gd.uploader = "";
gd.setUploader("");
}
Element gdd = gm.getElementById("gdd");
gd.posted = "";
gd.setPosted("");
gd.parent = "";
gd.visible = "";
gd.size = "";
gd.pages = 0;
gd.setPages(0);
gd.favoriteCount = 0;
Elements es = gdd.child(0).child(0).children();
for (int i = 0, n = es.size(); i < n; i++) {
@@ -239,17 +240,17 @@ public class GalleryDetailParser {
if (null != rating_label) {
String ratingStr = StringUtils.trim(rating_label.text());
if ("Not Yet Rated".equals(ratingStr)) {
gd.rating = -1.0f;
gd.setRating(-1.0f);
} else {
int index = ratingStr.indexOf(' ');
if (index == -1 || index >= ratingStr.length()) {
gd.rating = 0f;
gd.setRating(0f);
} else {
gd.rating = NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f);
gd.setRating(NumberUtils.parseFloatSafely(ratingStr.substring(index + 1), 0f));
}
}
} else {
gd.rating = -1.0f;
gd.setRating(-1.0f);
}
// isFavorited
@@ -258,19 +259,19 @@ public class GalleryDetailParser {
if (gdf != null) {
final String favoriteName = StringUtils.trim(gdf.text());
if (favoriteName.equals("Add to Favorites")) {
gd.favoriteName = null;
gd.setFavoriteName(null);
} else {
gd.isFavorited = true;
gd.favoriteName = StringUtils.trim(gdf.text());
gd.setFavoriteName(StringUtils.trim(gdf.text()));
matcher = PATTERN_FAVORITE_SLOT.matcher(body);
if (matcher.find()) {
gd.favoriteSlot = (NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19;
gd.setFavoriteSlot((NumberUtils.parseIntSafely(matcher.group(1), 2) - 2) / 19);
}
}
}
if (gd.favoriteSlot == -2 && EhDB.containLocalFavorites(gd.gid)) {
gd.favoriteSlot = -1;
if (gd.getFavoriteSlot() == -2 && EhDB.containLocalFavorites(gd.getGid())) {
gd.setFavoriteSlot(-1);
}
} catch (Throwable e) {
ExceptionUtils.throwIfFatal(e);
@@ -289,13 +290,13 @@ public class GalleryDetailParser {
Elements elements = gnd.select("a");
for (int i = 0; i < elements.size(); i++) {
Element element = elements.get(i);
GalleryInfo gi = new GalleryInfo();
GalleryInfo gi = new BaseGalleryInfo();
GalleryDetailUrlParser.Result result = GalleryDetailUrlParser.parse(element.attr("href"));
if (result != null) {
gi.gid = result.gid;
gi.token = result.token;
gi.title = StringUtils.trim(element.text());
gi.posted = dates.get(i);
gi.setGid(result.gid);
gi.setToken(result.token);
gi.setTitle(StringUtils.trim(element.text()));
gi.setPosted(dates.get(i));
gd.newerVersions.add(gi);
}
}
@@ -324,7 +325,7 @@ public class GalleryDetailParser {
String key = StringUtils.trim(es.get(0).text());
String value = StringUtils.trim(es.get(1).ownText());
if (key.startsWith("Posted")) {
gd.posted = value;
gd.setPosted(value);
} else if (key.startsWith("Parent")) {
Element a = es.get(1).children().first();
if (a != null) {
@@ -339,9 +340,9 @@ public class GalleryDetailParser {
} else if (key.startsWith("Length")) {
int index = value.indexOf(' ');
if (index >= 0) {
gd.pages = NumberUtils.parseIntSafely(value.substring(0, index), 1);
gd.setPages(NumberUtils.parseIntSafely(value.substring(0, index), 1));
} else {
gd.pages = 1;
gd.setPages(1);
}
} else if (key.startsWith("Favorited")) {
switch (value) {
@@ -23,6 +23,7 @@ import androidx.annotation.NonNull;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.util.ExceptionUtils;
@@ -110,7 +111,7 @@ public class GalleryListParser {
}
private static GalleryInfo parseGalleryInfo(Element e) {
GalleryInfo gi = new GalleryInfo();
GalleryInfo gi = new BaseGalleryInfo();
// Title, gid, token (required), tags
Element glname = JsoupUtils.getElementByClass(e, "glname");
@@ -125,8 +126,8 @@ public class GalleryListParser {
if (a != null) {
GalleryDetailUrlParser.Result result = GalleryDetailUrlParser.parse(a.attr("href"));
if (result != null) {
gi.gid = result.gid;
gi.token = result.token;
gi.setGid(result.gid);
gi.setToken(result.token);
}
}
@@ -136,9 +137,9 @@ public class GalleryListParser {
child = children.get(0);
children = child.children();
}
gi.title = child.text().trim();
gi.setTitle(child.text().trim());
}
if (gi.title == null) {
if (gi.getTitle() == null) {
return null;
}
@@ -149,17 +150,17 @@ public class GalleryListParser {
for (Element gt : gts) {
tags.add(gt.attr("title"));
}
gi.simpleTags = tags.toArray(new String[0]);
gi.setSimpleTags(tags.toArray(new String[0]));
}
// Category
gi.category = EhUtils.UNKNOWN;
gi.setCategory(EhUtils.UNKNOWN);
Element ce = JsoupUtils.getElementByClass(e, "cn");
if (ce == null) {
ce = JsoupUtils.getElementByClass(e, "cs");
}
if (ce != null) {
gi.category = EhUtils.getCategory(ce.text());
gi.setCategory(EhUtils.getCategory(ce.text()));
}
// Thumb
@@ -170,12 +171,12 @@ public class GalleryListParser {
// Thumb size
Matcher m = PATTERN_THUMB_SIZE.matcher(img.attr("style"));
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0);
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0);
gi.setThumbWidth(NumberUtils.parseIntSafely(m.group(2), 0));
gi.setThumbHeight(NumberUtils.parseIntSafely(m.group(1), 0));
} else {
Log.w(TAG, "Can't parse gallery info thumb size");
gi.thumbWidth = 0;
gi.thumbHeight = 0;
gi.setThumbWidth(0);
gi.setThumbHeight(0);
}
// Thumb url
String url = img.attr("data-src");
@@ -185,7 +186,7 @@ public class GalleryListParser {
if (TextUtils.isEmpty(url)) {
url = null;
}
gi.thumb = EhUtils.handleThumbUrlResolution(url);
gi.setThumb(EhUtils.handleThumbUrlResolution(url));
}
// Pages
@@ -193,12 +194,12 @@ public class GalleryListParser {
if (div != null) {
Matcher matcher = PATTERN_PAGES.matcher(div.text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
// Try extended and thumbnail version
if (gi.thumb == null) {
if (gi.getThumb() == null) {
Element gl = JsoupUtils.getElementByClass(e, "gl1e");
if (gl == null) {
gl = JsoupUtils.getElementByClass(e, "gl3t");
@@ -209,35 +210,35 @@ public class GalleryListParser {
// Thumb size
Matcher m = PATTERN_THUMB_SIZE.matcher(img.attr("style"));
if (m.find()) {
gi.thumbWidth = NumberUtils.parseIntSafely(m.group(2), 0);
gi.thumbHeight = NumberUtils.parseIntSafely(m.group(1), 0);
gi.setThumbWidth(NumberUtils.parseIntSafely(m.group(2), 0));
gi.setThumbHeight(NumberUtils.parseIntSafely(m.group(1), 0));
} else {
Log.w(TAG, "Can't parse gallery info thumb size");
gi.thumbWidth = 0;
gi.thumbHeight = 0;
gi.setThumbWidth(0);
gi.setThumbHeight(0);
}
gi.thumb = EhUtils.handleThumbUrlResolution(img.attr("src"));
gi.setThumb(EhUtils.handleThumbUrlResolution(img.attr("src")));
}
}
}
// Posted
gi.favoriteSlot = -2;
Element posted = e.getElementById("posted_" + gi.gid);
gi.setFavoriteSlot(-2);
Element posted = e.getElementById("posted_" + gi.getGid());
if (posted != null) {
gi.posted = posted.text().trim();
gi.favoriteSlot = parseFavoriteSlot(posted.attr("style"));
gi.setPosted(posted.text().trim());
gi.setFavoriteSlot(parseFavoriteSlot(posted.attr("style")));
}
if (gi.favoriteSlot == -2) {
gi.favoriteSlot = EhDB.containLocalFavorites(gi.gid) ? -1 : -2;
if (gi.getFavoriteSlot() == -2) {
gi.setFavoriteSlot(EhDB.containLocalFavorites(gi.getGid()) ? -1 : -2);
}
// Rating
Element ir = JsoupUtils.getElementByClass(e, "ir");
if (ir != null) {
gi.rating = NumberUtils.parseFloatSafely(parseRating(ir.attr("style")), -1.0f);
gi.setRating(NumberUtils.parseFloatSafely(parseRating(ir.attr("style")), -1.0f));
// TODO The gallery may be rated even if it doesn't has one of these classes
gi.rated = ir.hasClass("irr") || ir.hasClass("irg") || ir.hasClass("irb");
gi.setRated(ir.hasClass("irr") || ir.hasClass("irg") || ir.hasClass("irb"));
}
// Uploader and pages
@@ -255,15 +256,15 @@ public class GalleryListParser {
if (children.size() > uploaderIndex) {
Element div = children.get(uploaderIndex);
if (div != null) {
gi.disowned = div.attr("style").contains("opacity:0.5");
gi.setDisowned(div.attr("style").contains("opacity:0.5"));
Element a = div.children().first();
gi.uploader = a == null ? div.text().trim() : a.text().trim();
gi.setUploader(a == null ? div.text().trim() : a.text().trim());
}
}
if (children.size() > pagesIndex) {
Matcher matcher = PATTERN_PAGES.matcher(children.get(pagesIndex).text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
@@ -274,7 +275,7 @@ public class GalleryListParser {
if (div != null) {
Matcher matcher = PATTERN_PAGES.matcher(div.text());
if (matcher.find()) {
gi.pages = NumberUtils.parseIntSafely(matcher.group(1), 0);
gi.setPages(NumberUtils.parseIntSafely(matcher.group(1), 0));
}
}
}
@@ -2,10 +2,10 @@ package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.BaseGalleryInfo
@Entity(tableName = "BOOKMARKS")
class BookmarkInfo : GalleryInfo() {
class BookmarkInfo : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "PAGE")
var page = 0
@@ -3,10 +3,11 @@ package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Ignore
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "DOWNLOADS")
class DownloadInfo() : GalleryInfo() {
class DownloadInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "STATE")
var state = 0
@@ -11,14 +11,20 @@ interface DownloadsDao : BasicDao<DownloadInfo> {
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC")
override fun list(): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS ORDER BY TIME DESC LIMIT :limit OFFSET :offset")
fun list(offset: Int, limit: Int): List<DownloadInfo>
@Query("SELECT * FROM DOWNLOADS WHERE GID = :gid")
fun load(gid: Long): DownloadInfo?
@Update
fun update(downloadInfos: List<DownloadInfo>)
@Update
fun update(downloadInfo: DownloadInfo)
@Insert
override fun insert(downloadInfo: DownloadInfo): Long
override fun insert(t: DownloadInfo): Long
@Delete
fun delete(downloadInfo: DownloadInfo)
@@ -1,6 +1,8 @@
package com.hippo.ehviewer.dao
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
@@ -18,3 +20,9 @@ abstract class EhDatabase : RoomDatabase() {
abstract fun localFavoritesDao(): LocalFavoritesDao
abstract fun quickSearchDao(): QuickSearchDao
}
fun buildMainDB(context: Context): EhDatabase {
// TODO: Remove allowMainThreadQueries
return Room.databaseBuilder(context, EhDatabase::class.java, "eh.db").allowMainThreadQueries()
.build()
}
@@ -2,10 +2,11 @@ package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "HISTORY")
class HistoryInfo() : GalleryInfo() {
class HistoryInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
@@ -2,10 +2,11 @@ package com.hippo.ehviewer.dao
import androidx.room.ColumnInfo
import androidx.room.Entity
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.client.data.GalleryInfo
@Entity(tableName = "LOCAL_FAVORITES")
class LocalFavoriteInfo() : GalleryInfo() {
class LocalFavoriteInfo() : BaseGalleryInfo() {
@JvmField
@ColumnInfo(name = "TIME")
var time: Long = 0
@@ -24,6 +24,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.dao.DownloadInfo;
import com.hippo.ehviewer.dao.DownloadLabel;
@@ -53,7 +54,7 @@ import java.util.Map;
public class DownloadManager implements SpiderQueen.OnSpiderListener {
private static final String TAG = DownloadManager.class.getSimpleName();
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> (int) (rhs.time - lhs.time);
private static final Comparator<DownloadInfo> DATE_DESC_COMPARATOR = (lhs, rhs) -> lhs.time == rhs.time ? 0 : lhs.time - rhs.time > 0 ? -1 : 1;
// All download info list
private final LinkedList<DownloadInfo> mAllInfoList;
// All download info map
@@ -104,7 +105,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
DownloadInfo info = allInfoList.get(i);
// Add to all info map
allInfoMap.put(info.gid, info);
allInfoMap.put(info.getGid(), info);
// Add to each label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
@@ -232,13 +233,13 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
void startDownload(GalleryInfo galleryInfo, @Nullable String label) {
if (mCurrentTask != null && mCurrentTask.gid == galleryInfo.gid) {
if (mCurrentTask != null && mCurrentTask.getGid() == galleryInfo.getGid()) {
// It is current task
return;
}
// Check in download list
DownloadInfo info = mAllInfoMap.get(galleryInfo.gid);
DownloadInfo info = mAllInfoMap.get(galleryInfo.getGid());
if (info != null) { // Get it in download list
if (info.state != DownloadInfo.STATE_WAIT) {
// Set state DownloadInfo.STATE_WAIT
@@ -274,7 +275,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Add to all download list and map
mAllInfoList.addFirst(info);
mAllInfoMap.put(galleryInfo.gid, info);
mAllInfoMap.put(galleryInfo.getGid(), info);
// Add to wait list
mWaitList.add(info);
@@ -359,7 +360,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
public void addDownload(List<DownloadInfo> downloadInfoList, boolean notify) {
for (DownloadInfo info : downloadInfoList) {
if (containDownloadInfo(info.gid)) {
if (containDownloadInfo(info.getGid())) {
// Contain
return;
}
@@ -387,7 +388,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Add to all download list and map
mAllInfoList.add(info);
mAllInfoMap.put(info.gid, info);
mAllInfoMap.put(info.getGid(), info);
// Save to
EhDB.putDownloadInfo(info);
@@ -415,7 +416,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
public void addDownload(GalleryInfo galleryInfo, @Nullable String label) {
if (containDownloadInfo(galleryInfo.gid)) {
if (containDownloadInfo(galleryInfo.getGid())) {
// Contain
return;
}
@@ -436,7 +437,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Add to all download list and map
mAllInfoList.addFirst(info);
mAllInfoMap.put(galleryInfo.gid, info);
mAllInfoMap.put(galleryInfo.getGid(), info);
// Save to
EhDB.putDownloadInfo(info);
@@ -516,7 +517,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Remove all list and map
mAllInfoList.remove(info);
mAllInfoMap.remove(info.gid);
mAllInfoMap.remove(info.getGid());
// Remove label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
@@ -552,7 +553,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Remove from all info map
mAllInfoList.remove(info);
mAllInfoMap.remove(info.gid);
mAllInfoMap.remove(info.getGid());
// Remove from label list
LinkedList<DownloadInfo> list = getInfoListForLabel(info.label);
@@ -577,18 +578,18 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
GalleryInfo galleryInfo = new GalleryInfo();
GalleryInfo galleryInfo = new BaseGalleryInfo();
for (DownloadInfo downloadInfo : list) {
galleryInfo.gid = downloadInfo.gid;
galleryInfo.token = downloadInfo.token;
galleryInfo.title = downloadInfo.title;
galleryInfo.thumb = downloadInfo.thumb;
galleryInfo.category = downloadInfo.category;
galleryInfo.posted = downloadInfo.posted;
galleryInfo.uploader = downloadInfo.uploader;
galleryInfo.rating = downloadInfo.rating;
galleryInfo.setGid(downloadInfo.getGid());
galleryInfo.setToken(downloadInfo.getToken());
galleryInfo.setTitle(downloadInfo.getTitle());
galleryInfo.setThumb(downloadInfo.getThumb());
galleryInfo.setCategory(downloadInfo.getCategory());
galleryInfo.setPosted(downloadInfo.getPosted());
galleryInfo.setUploader(downloadInfo.getUploader());
galleryInfo.setRating(downloadInfo.getRating());
UniFile downloadDir = SpiderDen.getGalleryDownloadDir(galleryInfo.gid);
UniFile downloadDir = SpiderDen.getGalleryDownloadDir(galleryInfo.getGid());
if (downloadDir == null) {
continue;
}
@@ -618,14 +619,14 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// No ensureDownload
private DownloadInfo stopDownloadInternal(long gid) {
// Check current task
if (mCurrentTask != null && mCurrentTask.gid == gid) {
if (mCurrentTask != null && mCurrentTask.getGid() == gid) {
// Stop current
return stopCurrentDownloadInternal();
}
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext(); ) {
DownloadInfo info = iterator.next();
if (info.gid == gid) {
if (info.getGid() == gid) {
// Remove from wait list
iterator.remove();
// Update state
@@ -677,7 +678,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
}
} else {
// Check current task
if (mCurrentTask != null && gidList.contains(mCurrentTask.gid)) {
if (mCurrentTask != null && gidList.contains(mCurrentTask.getGid())) {
// Stop current
stopCurrentDownloadInternal();
}
@@ -685,7 +686,7 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
// Check all in wait list
for (Iterator<DownloadInfo> iterator = mWaitList.iterator(); iterator.hasNext(); ) {
DownloadInfo info = iterator.next();
if (gidList.contains(info.gid)) {
if (gidList.contains(info.getGid())) {
// Remove from wait list
iterator.remove();
// Update state
@@ -36,7 +36,6 @@ import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.dao.DownloadInfo
import com.hippo.ehviewer.ui.MainActivity
import com.hippo.ehviewer.ui.scene.DownloadsScene
import com.hippo.scene.StageActivity
import com.hippo.util.ReadableTime
import com.hippo.yorozuya.FileUtils
import com.hippo.yorozuya.SimpleHandler
@@ -186,9 +185,8 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
val bundle = Bundle()
bundle.putString(DownloadsScene.KEY_ACTION, DownloadsScene.ACTION_CLEAR_DOWNLOAD_SERVICE)
val activityIntent = Intent(this, MainActivity::class.java)
activityIntent.action = StageActivity.ACTION_START_SCENE
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
activityIntent.action = ACTION_START_DOWNLOADSCENE
activityIntent.putExtra(ACTION_START_DOWNLOADSCENE_ARGS, bundle)
val piActivity = PendingIntent.getActivity(
this@DownloadService, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -241,9 +239,8 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
val bundle = Bundle()
bundle.putLong(DownloadsScene.KEY_GID, info.gid)
val activityIntent = Intent(this, MainActivity::class.java)
activityIntent.action = StageActivity.ACTION_START_SCENE
activityIntent.putExtra(StageActivity.KEY_SCENE_NAME, DownloadsScene::class.java.name)
activityIntent.putExtra(StageActivity.KEY_SCENE_ARGS, bundle)
activityIntent.action = ACTION_START_DOWNLOADSCENE
activityIntent.putExtra(ACTION_START_DOWNLOADSCENE_ARGS, bundle)
val piActivity = PendingIntent.getActivity(
this@DownloadService, 0,
activityIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
@@ -516,6 +513,9 @@ class DownloadService : Service(), DownloadManager.DownloadListener {
}
companion object {
const val ACTION_START_DOWNLOADSCENE = "start_download_scene"
const val ACTION_START_DOWNLOADSCENE_ARGS = "start_download_scene_args"
const val ACTION_START = "start"
const val ACTION_START_RANGE = "start_range"
const val ACTION_START_ALL = "start_all"
@@ -71,7 +71,7 @@ public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderLis
@NonNull
@Override
public String getImageFilename(int index) {
return String.format(Locale.US, "%d-%s-%08d", mGalleryInfo.gid, mGalleryInfo.token, index + 1);
return String.format(Locale.US, "%d-%s-%08d", mGalleryInfo.getGid(), mGalleryInfo.getToken(), index + 1);
}
@NonNull
@@ -80,10 +80,10 @@ public class EhPageLoader extends PageLoader2 implements SpiderQueen.OnSpiderLis
if (null != mSpiderQueen) {
String extension = mSpiderQueen.getExtension(index);
if (extension != null) {
return String.format(Locale.US, "%d-%s-%08d.%s", mGalleryInfo.gid, mGalleryInfo.token, index + 1, extension);
return String.format(Locale.US, "%d-%s-%08d.%s", mGalleryInfo.getGid(), mGalleryInfo.getToken(), index + 1, extension);
}
}
return String.format(Locale.US, "%d-%s-%08d", mGalleryInfo.gid, mGalleryInfo.token, index + 1);
return String.format(Locale.US, "%d-%s-%08d", mGalleryInfo.getGid(), mGalleryInfo.getToken(), index + 1);
}
@Override
@@ -20,13 +20,12 @@ import android.content.Context
import android.util.AttributeSet
import androidx.preference.Preference
import com.hippo.ehviewer.EhApplication.Companion.downloadManager
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhEngine.fillGalleryListByApi
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.client.data.BaseGalleryInfo
import com.hippo.ehviewer.download.DownloadManager
import com.hippo.ehviewer.spider.SpiderInfo
import com.hippo.ehviewer.spider.SpiderQueen
@@ -35,7 +34,6 @@ import com.hippo.unifile.UniFile
import com.hippo.util.ExceptionUtils
import com.hippo.yorozuya.IOUtils
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import java.io.IOException
import java.io.InputStream
import java.util.Collections
@@ -49,7 +47,6 @@ class RestoreDownloadPreference constructor(
private class RestoreTask(context: Context) : Task(context) {
private val mManager: DownloadManager = downloadManager
private val mHttpClient: OkHttpClient = okHttpClient
private var restoreDirCount = 0
private fun getRestoreItem(file: UniFile?): RestoreItem? {
if (null == file || !file.isDirectory) {
@@ -99,7 +96,6 @@ class RestoreDownloadPreference constructor(
} else try {
runBlocking {
fillGalleryListByApi(
mHttpClient,
ArrayList(restoreItemList),
EhUrl.getReferer()
)
@@ -163,7 +159,7 @@ class RestoreDownloadPreference constructor(
}
}
private class RestoreItem : GalleryInfo() {
private class RestoreItem : BaseGalleryInfo() {
var dirname: String? = null
}
}
@@ -56,8 +56,8 @@ public final class SpiderDen {
public SpiderDen(GalleryInfo galleryInfo) {
mGalleryInfo = galleryInfo;
mGid = galleryInfo.gid;
mDownloadDir = getGalleryDownloadDir(galleryInfo.gid);
mGid = galleryInfo.getGid();
mDownloadDir = getGalleryDownloadDir(galleryInfo.getGid());
}
public static void initialize(Context context) {
@@ -179,10 +179,10 @@ public final class SpiderQueen implements Runnable {
public static SpiderQueen obtainSpiderQueen(@NonNull GalleryInfo galleryInfo, @Mode int mode) {
OSUtils.checkMainLoop();
SpiderQueen queen = sQueenMap.get(galleryInfo.gid);
SpiderQueen queen = sQueenMap.get(galleryInfo.getGid());
if (queen == null) {
queen = new SpiderQueen(galleryInfo);
sQueenMap.put(galleryInfo.gid, queen);
sQueenMap.put(galleryInfo.getGid(), queen);
// Set mode
queen.setMode(mode);
queen.start();
@@ -203,7 +203,7 @@ public final class SpiderQueen implements Runnable {
if (queen.mReadReference == 0 && queen.mDownloadReference == 0) {
// Stop and remove if there is no reference
queen.stop();
sQueenMap.remove(queen.mGalleryInfo.gid);
sQueenMap.remove(queen.mGalleryInfo.getGid());
}
}
@@ -697,19 +697,19 @@ public final class SpiderQueen implements Runnable {
if (downloadDir != null) {
UniFile file = downloadDir.findFile(SPIDER_INFO_FILENAME);
spiderInfo = SpiderInfo.read(file);
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.getGid() &&
spiderInfo.token.equals(mGalleryInfo.getToken())) {
return spiderInfo;
}
}
// Read from cache
var entry = mSpiderInfoCache.get(Long.toString(mGalleryInfo.gid));
var entry = mSpiderInfoCache.get(Long.toString(mGalleryInfo.getGid()));
if (entry == null) return null;
try (entry; InputStream is = new FileInputStream(entry.getData().toFile())) {
spiderInfo = SpiderInfo.read(is);
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.gid &&
spiderInfo.token.equals(mGalleryInfo.token)) {
if (spiderInfo != null && spiderInfo.gid == mGalleryInfo.getGid() &&
spiderInfo.token.equals(mGalleryInfo.getToken())) {
return spiderInfo;
}
} catch (IOException e) {
@@ -744,11 +744,11 @@ public final class SpiderQueen implements Runnable {
private SpiderInfo readSpiderInfoFromInternet() {
SpiderInfo spiderInfo = new SpiderInfo();
spiderInfo.gid = mGalleryInfo.gid;
spiderInfo.token = mGalleryInfo.token;
spiderInfo.gid = mGalleryInfo.getGid();
spiderInfo.token = mGalleryInfo.getToken();
Request request = new EhRequestBuilder(EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, 0, false), EhUrl.getReferer()).build();
mGalleryInfo.getGid(), mGalleryInfo.getToken(), 0, false), EhUrl.getReferer()).build();
try (Response response = mHttpClient.newCall(request).execute()) {
String body = response.body().string();
@@ -769,7 +769,7 @@ public final class SpiderQueen implements Runnable {
}
String url = EhUrl.getGalleryMultiPageViewerUrl(
mGalleryInfo.gid, mGalleryInfo.token);
mGalleryInfo.getGid(), mGalleryInfo.getToken());
if (DEBUG_PTOKEN) {
Log.d(TAG, "getPTokenFromMultiPageViewer index " + index + ", url " + url);
}
@@ -815,7 +815,7 @@ public final class SpiderQueen implements Runnable {
}
String url = EhUrl.getGalleryDetailUrl(
mGalleryInfo.gid, mGalleryInfo.token, previewIndex, false);
mGalleryInfo.getGid(), mGalleryInfo.getToken(), previewIndex, false);
String referer = EhUrl.getReferer();
if (DEBUG_PTOKEN) {
Log.d(TAG, "index " + index + ", previewIndex " + previewIndex +
@@ -861,7 +861,7 @@ public final class SpiderQueen implements Runnable {
}
// Write to cache
var editor = mSpiderInfoCache.edit(Long.toString(mGalleryInfo.gid));
var editor = mSpiderInfoCache.edit(Long.toString(mGalleryInfo.getGid()));
if (editor == null) return;
try (OutputStream os = new FileOutputStream(editor.getData().toFile())) {
spiderInfo.write(os);
@@ -1097,7 +1097,7 @@ public final class SpiderQueen implements Runnable {
private final long mGid;
public SpiderWorker() {
mGid = mGalleryInfo.gid;
mGid = mGalleryInfo.getGid();
}
private String getPageUrl(long gid, int index, String pToken,
@@ -1115,7 +1115,7 @@ public final class SpiderQueen implements Runnable {
}
private GalleryPageParser.Result fetchPageResultFromHtml(int index, String pageUrl) throws Throwable {
GalleryPageParser.Result result = EhEngine.getGalleryPage(mHttpClient, pageUrl, mGalleryInfo.gid, mGalleryInfo.token);
GalleryPageParser.Result result = EhEngine.getGalleryPage(pageUrl, mGalleryInfo.getGid(), mGalleryInfo.getToken());
if (StringUtils.endsWith(result.imageUrl, URL_509_SUFFIX_ARRAY)) {
// Get 509
// Notify listeners
@@ -1127,7 +1127,7 @@ public final class SpiderQueen implements Runnable {
}
private GalleryPageApiParser.Result fetchPageResultFromApi(long gid, int index, String pToken, String showKey, String previousPToken) throws Throwable {
GalleryPageApiParser.Result result = EhEngine.getGalleryPageApi(mHttpClient, gid, index, pToken, showKey, previousPToken);
GalleryPageApiParser.Result result = EhEngine.getGalleryPageApi(gid, index, pToken, showKey, previousPToken);
if (StringUtils.endsWith(result.imageUrl, URL_509_SUFFIX_ARRAY)) {
// Get 509
// Notify listeners
@@ -1395,11 +1395,13 @@ public final class SpiderQueen implements Runnable {
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.sleep(0); // to make allow to catch InterruptedException
return true;
} catch (IOException e) {
e.printStackTrace();
error = GetText.getString(R.string.error_socket);
forceHtml = true;
} catch (InterruptedException ignored) {
} finally {
IOUtils.closeQuietly(is);
@@ -56,7 +56,7 @@ public final class CommonOperations {
EhClient client = EhClient.INSTANCE;
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
request.setArgs(galleryInfo.gid, galleryInfo.token, slot, "");
request.setArgs(galleryInfo.getGid(), galleryInfo.getToken(), slot, "");
request.setCallback(listener);
request.enqueue(activity);
} else {
@@ -99,11 +99,11 @@ public final class CommonOperations {
public static void removeFromFavorites(Activity activity, GalleryInfo galleryInfo,
final EhClient.Callback<Void> listener) {
EhDB.removeLocalFavorites(galleryInfo.gid);
EhDB.removeLocalFavorites(galleryInfo.getGid());
EhClient client = EhClient.INSTANCE;
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_ADD_FAVORITES);
request.setArgs(galleryInfo.gid, galleryInfo.token, -1, "");
request.setArgs(galleryInfo.getGid(), galleryInfo.getToken(), -1, "");
request.setCallback(new DelegateFavoriteCallback(listener, galleryInfo, null, -2));
request.enqueue(activity);
}
@@ -125,8 +125,8 @@ public final class CommonOperations {
LongList toStart = new LongList();
List<GalleryInfo> toAdd = new ArrayList<>();
for (GalleryInfo gi : galleryInfos) {
if (dm.containDownloadInfo(gi.gid)) {
toStart.add(gi.gid);
if (dm.containDownloadInfo(gi.getGid())) {
toStart.add(gi.getGid());
} else {
toAdd.add(gi);
}
@@ -259,10 +259,10 @@ public final class CommonOperations {
@Override
public void onSuccess(Void result) {
info.favoriteName = newFavoriteName;
info.favoriteSlot = slot;
info.setFavoriteName(newFavoriteName);
info.setFavoriteSlot(slot);
delegate.onSuccess(result);
EhApplication.getFavouriteStatusRouter().modifyFavourites(info.gid, slot);
EhApplication.getFavouriteStatusRouter().modifyFavourites(info.getGid(), slot);
}
@Override
@@ -29,7 +29,7 @@ import com.hippo.ehviewer.UrlOpener
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryDetail
import com.hippo.util.tellClipboard
import com.hippo.util.tellClipboardWithToast
class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment() {
private var mKeys: ArrayList<String> = arrayListOf()
@@ -117,7 +117,7 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
if (position == INDEX_PARENT) {
UrlOpener.openUrl(context, mValues[position], true)
} else {
requireActivity() tellClipboard mValues[position]
requireActivity() tellClipboardWithToast mValues[position]
if (position == INDEX_URL) {
// Save it to avoid detect the gallery
Settings.putClipboardTextHashCode(mValues[position].hashCode())
@@ -0,0 +1,57 @@
package com.hippo.ehviewer.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.navigation.ActivityNavigator
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.accompanist.themeadapter.material3.Mdc3Theme
import com.hippo.ehviewer.ui.compose.CookieSignInScene
import com.hippo.ehviewer.ui.compose.SelectSiteScreen
import com.hippo.ehviewer.ui.compose.SignInScreen
import com.hippo.ehviewer.ui.compose.WebviewSignInScreen
class LoginActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Mdc3Theme {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = SIGN_IN_ROUTE_NAME) {
composable(SIGN_IN_ROUTE_NAME) {
SignInScreen(navController)
}
composable(WEBVIEW_SIGN_IN_ROUTE_NAME) {
WebviewSignInScreen(navController)
}
composable(COOKIE_SIGN_IN_ROUTE_NAME) {
CookieSignInScene(navController)
}
composable(SELECT_SITE_ROUTE_NAME) {
SelectSiteScreen {
finish()
}
}
}
}
}
}
override fun finish() {
super.finish()
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}
companion object {
const val SIGN_IN_ROUTE_NAME = "SignIn"
const val WEBVIEW_SIGN_IN_ROUTE_NAME = "WebViewSignIn"
const val COOKIE_SIGN_IN_ROUTE_NAME = "CookieSignIn"
const val SELECT_SITE_ROUTE_NAME = "SelectSite"
}
}
@@ -16,6 +16,7 @@
package com.hippo.ehviewer.ui
import android.annotation.SuppressLint
import android.app.assist.AssistContent
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
@@ -26,113 +27,56 @@ import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import android.view.MenuItem
import android.text.TextUtils
import android.view.View
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.IdRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
import com.google.android.material.navigation.NavigationView
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.google.android.material.snackbar.Snackbar
import com.hippo.app.BaseDialogBuilder
import com.hippo.app.EditTextDialogBuilder
import com.hippo.ehviewer.AppConfig
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhUrlOpener
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
import com.hippo.ehviewer.client.parser.GalleryListUrlParser
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.hippo.ehviewer.databinding.ActivityMainBinding
import com.hippo.ehviewer.download.DownloadService
import com.hippo.ehviewer.ui.scene.BaseScene
import com.hippo.ehviewer.ui.scene.CookieSignInScene
import com.hippo.ehviewer.ui.scene.DownloadsScene
import com.hippo.ehviewer.ui.scene.FavoritesScene
import com.hippo.ehviewer.ui.scene.GalleryCommentsScene
import com.hippo.ehviewer.ui.scene.GalleryDetailScene
import com.hippo.ehviewer.ui.scene.GalleryListScene
import com.hippo.ehviewer.ui.scene.GalleryPreviewsScene
import com.hippo.ehviewer.ui.scene.HistoryScene
import com.hippo.ehviewer.ui.scene.ProgressScene
import com.hippo.ehviewer.ui.scene.SelectSiteScene
import com.hippo.ehviewer.ui.scene.SignInScene
import com.hippo.ehviewer.ui.scene.SolidScene
import com.hippo.ehviewer.ui.scene.WebViewSignInScene
import com.hippo.ehviewer.widget.EhStageLayout
import com.hippo.io.UniFileInputStreamPipe
import com.hippo.scene.Announcer
import com.hippo.scene.SceneFragment
import com.hippo.scene.StageActivity
import com.hippo.unifile.UniFile
import com.hippo.util.BitmapUtils
import com.hippo.util.addTextToClipboard
import com.hippo.util.getClipboardManager
import com.hippo.util.getUrlFromClipboard
import com.hippo.widget.DrawerView
import com.hippo.yorozuya.IOUtils
import com.hippo.yorozuya.SimpleHandler
import com.hippo.yorozuya.ViewUtils
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedListener {
private var settingsLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) refreshTopScene()
}
/*---------------
Whole life cycle
---------------*/
private var mDrawerLayout: DrawerLayout? = null
private var mStageLayout: EhStageLayout? = null
private var mNavView: NavigationView? = null
private var mRightDrawer: DrawerView? = null
private var mNavCheckedItem = 0
override fun getContainerViewId(): Int {
return R.id.fragment_container
}
override fun getLaunchAnnouncer(): Announcer {
return if (EhUtils.needSignedIn()) {
Announcer(SignInScene::class.java)
} else if (Settings.getSelectSite()) {
Announcer(SelectSiteScene::class.java)
} else {
val args = Bundle()
args.putString(
GalleryListScene.KEY_ACTION,
Settings.getLaunchPageGalleryListSceneAction()
)
Announcer(GalleryListScene::class.java).setArgs(args)
}
}
// Sometimes scene can't show directly
private fun processAnnouncer(announcer: Announcer): Announcer {
if (0 == sceneCount) {
if (EhUtils.needSignedIn()) {
val newArgs = Bundle()
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.clazz.name)
newArgs.putBundle(SignInScene.KEY_TARGET_ARGS, announcer.args)
return Announcer(SignInScene::class.java).setArgs(newArgs)
} else if (Settings.getSelectSite()) {
val newArgs = Bundle()
newArgs.putString(SelectSiteScene.KEY_TARGET_SCENE, announcer.clazz.name)
newArgs.putBundle(SelectSiteScene.KEY_TARGET_ARGS, announcer.args)
return Announcer(SelectSiteScene::class.java).setArgs(newArgs)
}
}
return announcer
}
class MainActivity : EhActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController
private fun saveImageToTempFile(file: UniFile?): File? {
file ?: return null
@@ -166,16 +110,68 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
}
}
private fun handleIntent(intent: Intent?): Boolean {
if (intent == null) {
return false
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
lifecycleScope.launchUI {
if (!handleIntent(intent)) {
if (intent != null && Intent.ACTION_VIEW == intent.action) {
if (intent.data != null) {
val url = intent.data.toString()
EditTextDialogBuilder(this@MainActivity, url, "")
.setTitle(R.string.error_cannot_parse_the_url)
.setPositiveButton(android.R.string.copy) { _: DialogInterface?, _: Int ->
this@MainActivity.addTextToClipboard(
url,
false
)
}
.show()
}
}
}
}
}
private fun handleIntent(intent: Intent?): Boolean {
intent ?: return false
val action = intent.action
if (Intent.ACTION_VIEW == action) {
val uri = intent.data ?: return false
val announcer = EhUrlOpener.parseUrl(uri.toString())
if (announcer != null) {
startScene(processAnnouncer(announcer))
val url = uri.toString()
if (TextUtils.isEmpty(url)) {
return false
}
val listUrlBuilder = GalleryListUrlParser.parse(url)
if (listUrlBuilder != null) {
val args = Bundle()
args.putString(
GalleryListScene.KEY_ACTION,
GalleryListScene.ACTION_LIST_URL_BUILDER
)
args.putParcelable(GalleryListScene.KEY_LIST_URL_BUILDER, listUrlBuilder)
navController.navigate(R.id.galleryListScene, args)
return true
}
val result1 = GalleryDetailUrlParser.parse(url)
if (result1 != null) {
val args = Bundle()
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
args.putLong(GalleryDetailScene.KEY_GID, result1.gid)
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token)
navController.navigate(R.id.galleryDetailScene, args)
return true
}
val result2 = GalleryPageUrlParser.parse(url)
if (result2 != null) {
val args = Bundle()
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
args.putLong(ProgressScene.KEY_GID, result2.gid)
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken)
args.putInt(ProgressScene.KEY_PAGE, result2.page)
navController.navigate(R.id.progressScene, args)
return true
}
} else if (Intent.ACTION_SEND == action) {
@@ -183,7 +179,10 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
if ("text/plain" == type) {
val builder = ListUrlBuilder()
builder.keyword = intent.getStringExtra(Intent.EXTRA_TEXT)
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)))
navController.navigate(
R.id.galleryListScene,
GalleryListScene.getStartArgs(builder)
)
return true
} else if (type != null && type.startsWith("image/")) {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
@@ -195,69 +194,47 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
builder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
builder.imagePath = temp.path
builder.isUseSimilarityScan = true
startScene(processAnnouncer(GalleryListScene.getStartAnnouncer(builder)))
navController.navigate(
R.id.galleryListScene,
GalleryListScene.getStartArgs(builder)
)
return true
}
}
}
} else if (action == DownloadService.ACTION_START_DOWNLOADSCENE) {
val args = intent.getBundleExtra(DownloadService.ACTION_START_DOWNLOADSCENE_ARGS)
navController.navigate(R.id.nav_downloads, args)
}
return false
}
override fun onUnrecognizedIntent(intent: Intent?) {
val clazz = topSceneClass
if (clazz != null && SolidScene::class.java.isAssignableFrom(clazz)) {
// TODO the intent lost
return
}
if (!handleIntent(intent)) {
var handleUrl = false
if (intent != null && Intent.ACTION_VIEW == intent.action) {
handleUrl = true
if (intent.data != null) {
val url = intent.data.toString()
EditTextDialogBuilder(this, url, "")
.setTitle(R.string.error_cannot_parse_the_url)
.setPositiveButton(android.R.string.copy) { _: DialogInterface?, _: Int ->
this.addTextToClipboard(
url,
false
)
}
.show()
}
}
if (0 == sceneCount) {
if (handleUrl) {
finish()
} else {
val args = Bundle()
args.putString(
GalleryListScene.KEY_ACTION,
Settings.getLaunchPageGalleryListSceneAction()
)
startScene(processAnnouncer(Announcer(GalleryListScene::class.java).setArgs(args)))
override fun onCreate(savedInstanceState: Bundle?) {
if (EhUtils.needSignedIn())
startActivity(Intent(this, LoginActivity::class.java))
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
navController = navHostFragment.navController
navController.apply {
graph = navInflater.inflate(R.navigation.nav_graph).apply {
when (Settings.getLaunchPageGalleryListSceneAction()) {
GalleryListScene.ACTION_HOMEPAGE -> setStartDestination(R.id.nav_homepage)
GalleryListScene.ACTION_SUBSCRIPTION -> setStartDestination(R.id.nav_subscription)
GalleryListScene.ACTION_WHATS_HOT -> setStartDestination(R.id.nav_whats_hot)
GalleryListScene.ACTION_TOP_LIST -> setStartDestination(R.id.nav_toplist)
}
}
}
}
override fun onStartSceneFromIntent(clazz: Class<*>, args: Bundle?): Announcer {
return processAnnouncer(Announcer(clazz).setArgs(args))
}
override fun onCreate2(savedInstanceState: Bundle?) {
setContentView(R.layout.activity_main)
mStageLayout = ViewUtils.`$$`(this, R.id.fragment_container) as EhStageLayout
mDrawerLayout = ViewUtils.`$$`(this, R.id.draw_view) as DrawerLayout
mNavView = ViewUtils.`$$`(this, R.id.nav_view) as NavigationView
mRightDrawer = ViewUtils.`$$`(this, R.id.right_drawer) as DrawerView
mDrawerLayout?.addDrawerListener(mDrawerOnBackPressedCallback)
onBackPressedDispatcher.addCallback(mDrawerOnBackPressedCallback)
if (mNavView != null) {
mNavView!!.setNavigationItemSelectedListener(this)
}
binding.drawView.addDrawerListener(mDrawerOnBackPressedCallback)
binding.navView.setupWithNavController(navController)
if (savedInstanceState == null) {
if (intent.action != Intent.ACTION_MAIN) {
onNewIntent(intent)
}
checkDownloadLocation()
if (Settings.getMeteredNetworkWarning()) {
checkMeteredNetwork()
@@ -270,8 +247,6 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
}
}
}
} else {
onRestore(savedInstanceState)
}
}
@@ -335,9 +310,9 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
private fun checkMeteredNetwork() {
val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
if (cm.isActiveNetworkMetered) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && mDrawerLayout != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Snackbar.make(
mDrawerLayout!!,
binding.drawView,
R.string.metered_network_warning,
Snackbar.LENGTH_LONG
)
@@ -353,110 +328,72 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
}
}
private fun onRestore(savedInstanceState: Bundle) {
mNavCheckedItem = savedInstanceState.getInt(KEY_NAV_CHECKED_ITEM)
}
override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {
super.onSaveInstanceState(outState, outPersistentState)
outState.putInt(KEY_NAV_CHECKED_ITEM, mNavCheckedItem)
}
override fun onDestroy() {
super.onDestroy()
mDrawerLayout = null
mNavView = null
mRightDrawer = null
}
override fun onResume() {
super.onResume()
setNavCheckedItem(mNavCheckedItem)
checkClipboardUrl()
}
override fun onTransactScene() {
super.onTransactScene()
checkClipboardUrl()
}
private fun checkClipboardUrl() {
SimpleHandler.getInstance().postDelayed({
if (!isSolid) {
checkClipboardUrlInternal()
}
}, 300)
}
private val isSolid: Boolean
get() {
val topClass = topSceneClass
return topClass == null || SolidScene::class.java.isAssignableFrom(topClass)
lifecycleScope.launch {
delay(100)
// delay 100ms to make sure it take precedence over androidx navigation's callback
mDrawerOnBackPressedCallback.remove()
onBackPressedDispatcher.addCallback(mDrawerOnBackPressedCallback)
delay(200)
checkClipboardUrl()
}
private fun createAnnouncerFromClipboardUrl(url: String): Announcer? {
val result1 = GalleryDetailUrlParser.parse(url, false)
if (result1 != null) {
val args = Bundle()
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
args.putLong(GalleryDetailScene.KEY_GID, result1.gid)
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token)
return Announcer(GalleryDetailScene::class.java).setArgs(args)
}
val result2 = GalleryPageUrlParser.parse(url, false)
if (result2 != null) {
val args = Bundle()
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
args.putLong(ProgressScene.KEY_GID, result2.gid)
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken)
args.putInt(ProgressScene.KEY_PAGE, result2.page)
return Announcer(ProgressScene::class.java).setArgs(args)
}
return null
}
private fun checkClipboardUrlInternal() {
private suspend fun checkClipboardUrl() {
val text = this.getClipboardManager().getUrlFromClipboard(this)
val hashCode = text?.hashCode() ?: 0
if (text != null && hashCode != 0 && Settings.getClipboardTextHashCode() != hashCode) {
val announcer = createAnnouncerFromClipboardUrl(text)
if (announcer != null && mDrawerLayout != null) {
val snackbar = Snackbar.make(
mDrawerLayout!!,
R.string.clipboard_gallery_url_snack_message,
Snackbar.LENGTH_SHORT
)
snackbar.setAction(R.string.clipboard_gallery_url_snack_action) {
startScene(
announcer
val result1 = GalleryDetailUrlParser.parse(text, false)
var launch: (() -> Unit)? = null
if (result1 != null) {
val args = Bundle()
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
args.putLong(GalleryDetailScene.KEY_GID, result1.gid)
args.putString(GalleryDetailScene.KEY_TOKEN, result1.token)
launch = { navController.navigate(R.id.galleryDetailScene, args) }
}
val result2 = GalleryPageUrlParser.parse(text, false)
if (result2 != null) {
val args = Bundle()
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
args.putLong(ProgressScene.KEY_GID, result2.gid)
args.putString(ProgressScene.KEY_PTOKEN, result2.pToken)
args.putInt(ProgressScene.KEY_PAGE, result2.page)
launch = { navController.navigate(R.id.progressScene, args) }
}
launch?.let {
withUIContext {
val snackbar = Snackbar.make(
binding.drawView,
R.string.clipboard_gallery_url_snack_message,
Snackbar.LENGTH_SHORT
)
snackbar.setAction(R.string.clipboard_gallery_url_snack_action) {
it()
}
snackbar.show()
}
snackbar.show()
}
}
Settings.putClipboardTextHashCode(hashCode)
}
override fun onSceneViewCreated(scene: SceneFragment, savedInstanceState: Bundle?) {
super.onSceneViewCreated(scene, savedInstanceState)
createDrawerView(scene)
}
@SuppressLint("RtlHardcoded")
fun createDrawerView(scene: SceneFragment?) {
if (scene is BaseScene && mRightDrawer != null && mDrawerLayout != null) {
mRightDrawer!!.removeAllViews()
fun createDrawerView(scene: Fragment?) {
if (scene is BaseScene) {
binding.rightDrawer.removeAllViews()
val drawerView = scene.createDrawerView(
scene.layoutInflater, mRightDrawer, null
scene.layoutInflater, binding.rightDrawer, null
)
if (drawerView != null) {
mRightDrawer!!.addView(drawerView)
mDrawerLayout!!.setDrawerLockMode(
binding.rightDrawer.addView(drawerView)
binding.drawView.setDrawerLockMode(
DrawerLayout.LOCK_MODE_UNLOCKED,
GravityCompat.END
)
} else {
mDrawerLayout!!.setDrawerLockMode(
binding.drawView.setDrawerLockMode(
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
GravityCompat.END
)
@@ -464,39 +401,32 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
}
}
override fun onSceneViewDestroyed(scene: SceneFragment) {
super.onSceneViewDestroyed(scene)
if (scene is BaseScene) {
scene.destroyDrawerView()
}
}
fun addAboveSnackView(view: View?) {
mStageLayout?.addAboveSnackView(view)
binding.absnacker.addAboveSnackView(view)
}
fun removeAboveSnackView(view: View?) {
mStageLayout?.removeAboveSnackView(view)
binding.absnacker.removeAboveSnackView(view)
}
fun setDrawerLockMode(lockMode: Int, edgeGravity: Int) {
mDrawerLayout?.setDrawerLockMode(lockMode, edgeGravity)
binding.drawView.setDrawerLockMode(lockMode, edgeGravity)
}
fun getDrawerLockMode(edgeGravity: Int): Int? {
return mDrawerLayout?.getDrawerLockMode(edgeGravity)
fun getDrawerLockMode(edgeGravity: Int): Int {
return binding.drawView.getDrawerLockMode(edgeGravity)
}
fun openDrawer(drawerGravity: Int) {
mDrawerLayout?.openDrawer(drawerGravity)
binding.drawView.openDrawer(drawerGravity)
}
fun closeDrawer(drawerGravity: Int) {
mDrawerLayout?.closeDrawer(drawerGravity)
binding.drawView.closeDrawer(drawerGravity)
}
fun toggleDrawer(drawerGravity: Int) {
mDrawerLayout?.run {
binding.drawView.run {
if (isDrawerOpen(drawerGravity)) {
closeDrawer(drawerGravity)
} else {
@@ -505,26 +435,17 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
}
}
fun setNavCheckedItem(@IdRes resId: Int) {
mNavCheckedItem = resId
mNavView?.run {
if (resId == 0) {
setCheckedItem(R.id.nav_stub)
} else {
setCheckedItem(resId)
}
}
}
fun showTip(@StringRes id: Int, length: Int) {
showTip(getString(id), length)
@JvmOverloads
fun showTip(@StringRes id: Int, length: Int, useToast: Boolean = false) {
showTip(getString(id), length, useToast)
}
/**
* If activity is running, show snack bar, otherwise show toast
*/
fun showTip(message: CharSequence, length: Int) {
findViewById<View>(R.id.snackbar)?.apply {
@JvmOverloads
fun showTip(message: CharSequence, length: Int, useToast: Boolean = false) {
findViewById<View>(R.id.snackbar)?.takeUnless { useToast }?.apply {
Snackbar.make(
this, message,
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT
@@ -535,77 +456,17 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
).show()
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
// Don't select twice
if (item.isChecked) {
return false
}
val id = item.itemId
when (id) {
R.id.nav_homepage -> {
val args = Bundle()
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_HOMEPAGE)
startSceneFirstly(
Announcer(GalleryListScene::class.java)
.setArgs(args)
)
}
R.id.nav_subscription -> {
val args = Bundle()
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_SUBSCRIPTION)
startSceneFirstly(
Announcer(GalleryListScene::class.java)
.setArgs(args)
)
}
R.id.nav_whats_hot -> {
val args = Bundle()
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_WHATS_HOT)
startSceneFirstly(
Announcer(GalleryListScene::class.java)
.setArgs(args)
)
}
R.id.nav_toplist -> {
val args = Bundle()
args.putString(GalleryListScene.KEY_ACTION, GalleryListScene.ACTION_TOP_LIST)
startSceneFirstly(
Announcer(GalleryListScene::class.java)
.setArgs(args)
)
}
R.id.nav_favourite -> {
startScene(Announcer(FavoritesScene::class.java))
}
R.id.nav_history -> {
startScene(Announcer(HistoryScene::class.java))
}
R.id.nav_downloads -> {
startScene(Announcer(DownloadsScene::class.java))
}
R.id.nav_settings -> {
val intent = Intent(this, SettingsActivity::class.java)
settingsLauncher.launch(intent)
}
}
if (id != R.id.nav_stub) {
mDrawerLayout?.closeDrawers()
}
return true
var mShareUrl: String? = null
override fun onProvideAssistContent(outContent: AssistContent?) {
super.onProvideAssistContent(outContent)
mShareUrl?.let { outContent?.webUri = Uri.parse(mShareUrl) }
}
private val mDrawerOnBackPressedCallback =
object : OnBackPressedCallback(false), DrawerListener {
val slideThreshold = 0.05
override fun handleOnBackPressed() {
mDrawerLayout?.closeDrawers()
binding.drawView.closeDrawers()
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
@@ -616,26 +477,4 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
override fun onDrawerClosed(drawerView: View) {}
override fun onDrawerStateChanged(newState: Int) {}
}
companion object {
private const val KEY_NAV_CHECKED_ITEM = "nav_checked_item"
init {
registerLaunchMode(SignInScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TASK)
registerLaunchMode(
WebViewSignInScene::class.java,
SceneFragment.LAUNCH_MODE_SINGLE_TASK
)
registerLaunchMode(CookieSignInScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TASK)
registerLaunchMode(SelectSiteScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TASK)
registerLaunchMode(GalleryListScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TOP)
registerLaunchMode(GalleryDetailScene::class.java, SceneFragment.LAUNCH_MODE_STANDARD)
registerLaunchMode(GalleryCommentsScene::class.java, SceneFragment.LAUNCH_MODE_STANDARD)
registerLaunchMode(GalleryPreviewsScene::class.java, SceneFragment.LAUNCH_MODE_STANDARD)
registerLaunchMode(DownloadsScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TASK)
registerLaunchMode(FavoritesScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TASK)
registerLaunchMode(HistoryScene::class.java, SceneFragment.LAUNCH_MODE_SINGLE_TOP)
registerLaunchMode(ProgressScene::class.java, SceneFragment.LAUNCH_MODE_STANDARD)
}
}
}
@@ -20,6 +20,7 @@ import android.view.MenuItem
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.fragment.app.FragmentTransaction
import androidx.navigation.ActivityNavigator
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
@@ -78,4 +79,9 @@ class SettingsActivity : EhActivity() {
fun resetAppbarLiftStatus() {
mAppbarLayout?.setExpanded(true)
}
override fun finish() {
super.finish()
ActivityNavigator.applyPopAnimationsToPendingTransition(this)
}
}
@@ -0,0 +1,313 @@
package com.hippo.ehviewer.ui.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhClient.METHOD_GET_PROFILE
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.LoginActivity.Companion.SELECT_SITE_ROUTE_NAME
import com.hippo.util.ExceptionUtils
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import okhttp3.Cookie
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CookieSignInScene(navController: NavController) {
val clipboardManager = LocalClipboardManager.current
val focusManager = LocalFocusManager.current
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
var ipbMemberId by rememberSaveable { mutableStateOf("") }
var ipbPassHash by rememberSaveable { mutableStateOf("") }
var igneous by rememberSaveable { mutableStateOf("") }
var ipbMemberIdErrorState by rememberSaveable { mutableStateOf(false) }
var ipbPassHashErrorState by rememberSaveable { mutableStateOf(false) }
var signInJob by remember { mutableStateOf<Job?>(null) }
var showErrorDialog by rememberSaveable { mutableStateOf(false) }
var loginErrorException by remember { mutableStateOf<Throwable?>(null) }
val noCookies = stringResource(R.string.from_clipboard_error)
fun newCookie(name: String, value: String, domain: String): Cookie {
return Cookie.Builder().name(name).value(value).domain(domain).expiresAt(Long.MAX_VALUE)
.build()
}
fun storeCookie(id: String, hash: String, igneous: String) {
EhUtils.signOut()
val store = EhApplication.ehCookieStore
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E))
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX))
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E))
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_EX))
if (igneous.isBlank()) return
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_E))
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX))
}
fun login() {
signInJob?.cancel()
if (ipbMemberId.isBlank()) {
ipbMemberIdErrorState = true
return
} else {
ipbMemberIdErrorState = false
}
if (ipbPassHash.isBlank()) {
ipbPassHashErrorState = true
return
} else {
ipbPassHashErrorState = false
}
focusManager.clearFocus()
storeCookie(ipbMemberId, ipbPassHash, igneous)
isProgressIndicatorVisible = true
signInJob = coroutineScope.launchIO {
runCatching {
EhClient.execute(METHOD_GET_PROFILE)
}.onSuccess {
withUIContext {
navController.navigate(SELECT_SITE_ROUTE_NAME)
}
}.onFailure {
EhApplication.ehCookieStore.signOut()
if (it !is CancellationException) {
loginErrorException = it
showErrorDialog = true
isProgressIndicatorVisible = false
}
}
}
}
fun fillCookiesFromClipboard() {
focusManager.clearFocus()
val text = clipboardManager.getText()
if (text == null) {
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
return
}
runCatching {
val kvs: Array<String> = if (text.contains(";")) {
text.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
} else if (text.contains("\n")) {
text.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
} else {
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
return
}
if (kvs.size < 3) {
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
return
}
for (s in kvs) {
val kv: Array<String> = if (s.contains("=")) {
s.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
} else if (s.contains(":")) {
s.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
} else {
continue
}
if (kv.size != 2) {
continue
}
when (kv[0].trim { it <= ' ' }.lowercase(Locale.getDefault())) {
"ipb_member_id" -> ipbMemberId = kv[1].trim { it <= ' ' }
"ipb_pass_hash" -> ipbPassHash = kv[1].trim { it <= ' ' }
"igneous" -> igneous = kv[1].trim { it <= ' ' }
}
}
login()
}.onFailure {
it.printStackTrace()
coroutineScope.launch { snackbarHostState.showSnackbar(noCookies) }
}
}
Box(
contentAlignment = Alignment.Center
) {
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { padding ->
Column(
Modifier
.padding(dimensionResource(id = R.dimen.keyline_margin))
.padding(padding)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.v_cookie_brown_x48),
contentDescription = null,
modifier = Modifier.padding(dimensionResource(id = R.dimen.keyline_margin))
)
Text(
text = stringResource(id = R.string.cookie_explain),
modifier = Modifier
.padding(
horizontal = 32.dp,
vertical = dimensionResource(id = R.dimen.keyline_margin)
),
fontSize = 16.sp
)
OutlinedTextField(
value = ipbMemberId,
onValueChange = { ipbMemberId = it.trim { char -> char <= ' ' } },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(text = "ipb_member_id") },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next
),
supportingText = { if (ipbMemberIdErrorState) Text(stringResource(R.string.text_is_empty)) },
trailingIcon = {
if (ipbMemberIdErrorState) Icon(
imageVector = Icons.Filled.Info,
contentDescription = null
)
},
isError = ipbMemberIdErrorState,
singleLine = true
)
OutlinedTextField(
value = ipbPassHash,
onValueChange = { ipbPassHash = it.trim { char -> char <= ' ' } },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(text = "ipb_pass_hash") },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next
),
supportingText = { if (ipbPassHashErrorState) Text(stringResource(R.string.text_is_empty)) },
trailingIcon = {
if (ipbPassHashErrorState) Icon(
imageVector = Icons.Filled.Info,
contentDescription = null
)
},
isError = ipbPassHashErrorState,
singleLine = true
)
OutlinedTextField(
value = igneous,
onValueChange = { igneous = it.trim { char -> char <= ' ' } },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(text = "igneous") },
keyboardActions = KeyboardActions(
onDone = { login() }
),
singleLine = true
)
Spacer(modifier = Modifier.weight(1f))
Button(
onClick = { login() },
Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = android.R.string.ok))
}
TextButton(onClick = { fillCookiesFromClipboard() }) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline)
) {
append(stringResource(id = R.string.from_clipboard))
}
},
modifier = Modifier
.padding(horizontal = 8.dp)
)
}
}
}
if (isProgressIndicatorVisible)
CircularProgressIndicator()
if (showErrorDialog) {
AlertDialog(
onDismissRequest = {
showErrorDialog = false
},
confirmButton = {
TextButton(onClick = {
showErrorDialog = false
}) {
Text(text = stringResource(id = R.string.get_it))
}
},
title = {
Text(text = stringResource(id = R.string.sign_in_failed))
},
text = {
Text(
text =
"""
${ExceptionUtils.getReadableString(loginErrorException!!)}
${stringResource(R.string.wrong_cookie_warning)}
""".trimIndent()
)
}
)
}
}
}
@@ -0,0 +1,143 @@
package com.hippo.ehviewer.ui.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Divider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.parser.ProfileParser
import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.GlobalScope
@Composable
fun SelectSiteScreen(finish: () -> Unit) {
var site by remember { mutableStateOf(EhApplication.ehCookieStore.hasSignedIn()) } // false for e, true for ex
Column(
Modifier
.padding(horizontal = dimensionResource(R.dimen.keyline_margin))
.padding(top = dimensionResource(R.dimen.keyline_margin))
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(id = R.string.select_scene),
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.weight(1f))
// TODO: Replace this dummy impl with official one when available
// See https://m3.material.io/components/segmented-buttons/overview
Row(
modifier = Modifier
.height(IntrinsicSize.Min)
.border(
1.dp,
MaterialTheme.colorScheme.outline,
RoundedCornerShape(90f, 90f, 90f, 90f)
)
.clip(RoundedCornerShape(90f, 90f, 90f, 90f))
) {
Box(
modifier = Modifier
.background(if (!site) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)
.clickable { site = false }
.size(100.dp, 40.dp),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(id = R.string.site_e),
style = MaterialTheme.typography.labelLarge
)
}
Divider(
color = MaterialTheme.colorScheme.outline, modifier = Modifier
.fillMaxHeight()
.width(1.dp)
)
Box(
modifier = Modifier
.background(if (site) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)
.clickable { site = true }
.size(100.dp, 40.dp),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(id = R.string.site_ex),
style = MaterialTheme.typography.labelLarge
)
}
}
// Dummy impl end
Text(
text = stringResource(id = R.string.select_scene_explain),
modifier = Modifier.padding(top = 16.dp)
)
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.padding(horizontal = 12.dp)
.padding(top = 4.dp, bottom = 20.dp)
.fillMaxWidth()
) {
Button(onClick = {
Settings.putSelectSite(false)
Settings.putNeedSignIn(false)
if (!site)
Settings.putGallerySite(EhUrl.SITE_E)
else
Settings.putGallerySite(EhUrl.SITE_EX)
GlobalScope.launchIO {
getProfile()
}
finish()
}, Modifier.fillMaxWidth()) {
Text(text = stringResource(id = android.R.string.ok))
}
}
}
}
suspend fun getProfile() {
runCatching {
(EhClient.execute(EhClient.METHOD_GET_PROFILE) as ProfileParser.Result).run {
Settings.putDisplayName(displayName)
Settings.putAvatar(avatar)
}
}
}
@@ -0,0 +1,300 @@
package com.hippo.ehviewer.ui.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.UrlOpener
import com.hippo.ehviewer.client.EhClient
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.LoginActivity.Companion.COOKIE_SIGN_IN_ROUTE_NAME
import com.hippo.ehviewer.ui.LoginActivity.Companion.SELECT_SITE_ROUTE_NAME
import com.hippo.ehviewer.ui.LoginActivity.Companion.WEBVIEW_SIGN_IN_ROUTE_NAME
import com.hippo.util.ExceptionUtils
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.Job
import rikka.core.util.ContextUtils.requireActivity
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SignInScreen(navController: NavController) {
val coroutineScope = rememberCoroutineScope()
val focusManager = LocalFocusManager.current
var isProgressIndicatorVisible by rememberSaveable { mutableStateOf(false) }
var showUsernameError by rememberSaveable { mutableStateOf(false) }
var showPasswordError by rememberSaveable { mutableStateOf(false) }
var showErrorDialog by rememberSaveable { mutableStateOf(false) }
var loginErrorException by remember { mutableStateOf<Throwable?>(null) }
var username by rememberSaveable { mutableStateOf("") }
var password by rememberSaveable { mutableStateOf("") }
var passwordHidden by rememberSaveable { mutableStateOf(true) }
val context = LocalContext.current
var signInJob by remember { mutableStateOf<Job?>(null) }
// Basic login request
fun signIn() {
if (signInJob?.isActive == true) return
if (username.isEmpty()) {
showUsernameError = true
return
} else {
showUsernameError = false
}
if (password.isEmpty()) {
showPasswordError = true
return
} else {
showPasswordError = false
}
focusManager.clearFocus()
isProgressIndicatorVisible = true
EhUtils.signOut()
signInJob = coroutineScope.launchIO {
runCatching {
(EhClient.execute(EhClient.METHOD_SIGN_IN, username, password) as String).let {
Settings.putDisplayName(it)
}
}.onFailure {
it.printStackTrace()
withUIContext {
focusManager.clearFocus()
isProgressIndicatorVisible = false
loginErrorException = it
showErrorDialog = true
}
}.onSuccess {
withUIContext {
navController.navigate(SELECT_SITE_ROUTE_NAME)
}
}
}
}
Box(
contentAlignment = Alignment.Center
) {
Column(
Modifier
.padding(dimensionResource(id = R.dimen.keyline_margin))
.fillMaxSize()
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = null,
Modifier.padding(dimensionResource(id = R.dimen.keyline_margin))
)
OutlinedTextField(
value = username,
onValueChange = { username = it },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(stringResource(R.string.username)) },
supportingText = { if (showUsernameError) Text(stringResource(R.string.error_username_cannot_empty)) },
trailingIcon = {
if (showUsernameError) Icon(
imageVector = Icons.Filled.Info,
contentDescription = null
)
},
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next
),
singleLine = true,
isError = showUsernameError
)
OutlinedTextField(
value = password,
onValueChange = { password = it },
Modifier.width(dimensionResource(id = R.dimen.single_max_width)),
label = { Text(stringResource(R.string.password)) },
visualTransformation = if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,
supportingText = { if (showPasswordError) Text(stringResource(R.string.error_password_cannot_empty)) },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
keyboardActions = KeyboardActions(
onDone = { signIn() }
),
trailingIcon = {
if (showPasswordError)
Icon(imageVector = Icons.Filled.Info, contentDescription = null)
else
IconButton(onClick = { passwordHidden = !passwordHidden }) {
val visibilityIcon =
if (passwordHidden) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
Icon(imageVector = visibilityIcon, contentDescription = null)
}
},
singleLine = true,
isError = showPasswordError
)
Text(
text = stringResource(id = R.string.app_waring),
Modifier
.widthIn(max = dimensionResource(id = R.dimen.single_max_width))
.padding(top = 24.dp),
style = MaterialTheme.typography.labelLarge
)
Spacer(modifier = Modifier.weight(1f))
Row {
OutlinedButton(
onClick = {
UrlOpener.openUrl(
requireActivity(context),
EhUrl.URL_REGISTER,
false
)
},
Modifier
.weight(1f)
.padding(horizontal = 4.dp)
) {
Text(text = stringResource(id = R.string.register))
}
Button(
onClick = { signIn() },
Modifier
.weight(1f)
.padding(horizontal = 4.dp)
) {
Text(text = stringResource(id = R.string.sign_in))
}
}
Row(
Modifier.padding(horizontal = 4.dp)
) {
TextButton(
onClick = { navController.navigate(WEBVIEW_SIGN_IN_ROUTE_NAME) },
Modifier.padding(horizontal = 8.dp)
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline)
) {
append(stringResource(id = R.string.sign_in_via_webview))
}
}
)
}
TextButton(
onClick = { navController.navigate(COOKIE_SIGN_IN_ROUTE_NAME) },
Modifier.padding(horizontal = 8.dp)
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline)
) {
append(stringResource(id = R.string.sign_in_via_cookies))
}
}
)
}
}
TextButton(
onClick = { navController.navigate(SELECT_SITE_ROUTE_NAME) },
) {
Text(
text = buildAnnotatedString {
withStyle(
style = SpanStyle(textDecoration = TextDecoration.Underline)
) {
append(stringResource(id = R.string.tourist_mode))
}
}
)
}
}
if (isProgressIndicatorVisible) {
CircularProgressIndicator()
}
if (showErrorDialog) {
AlertDialog(
onDismissRequest = {
showErrorDialog = false
},
confirmButton = {
TextButton(onClick = {
showErrorDialog = false
}) {
Text(text = stringResource(id = R.string.get_it))
}
},
title = {
Text(text = stringResource(id = R.string.sign_in_failed))
},
text = {
Text(
text =
"""
${ExceptionUtils.getReadableString(loginErrorException!!)}
${stringResource(R.string.sign_in_failed_tip)}
""".trimIndent()
)
}
)
}
}
}
@@ -0,0 +1,108 @@
package com.hippo.ehviewer.ui.compose
import android.annotation.SuppressLint
import android.graphics.Color
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavController
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.client.EhCookieStore
import com.hippo.ehviewer.client.EhUrl
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.ui.LoginActivity.Companion.SELECT_SITE_ROUTE_NAME
import com.hippo.ehviewer.widget.DialogWebChromeClient
import okhttp3.Cookie
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@SuppressLint("SetJavaScriptEnabled")
@Composable
fun WebviewSignInScreen(navController: NavController) {
val context = LocalContext.current
AndroidView(factory = {
EhUtils.signOut()
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager.getInstance().apply {
flush()
removeAllCookies(null)
removeSessionCookies(null)
}
WebView(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setBackgroundColor(Color.TRANSPARENT)
settings.run {
builtInZoomControls = true
displayZoomControls = true
javaScriptEnabled = true
}
webViewClient = object : WebViewClient() {
private var present = false
fun parseCookies(url: HttpUrl?, cookieStrings: String?): List<Cookie> {
if (cookieStrings == null) {
return emptyList()
}
var cookies: MutableList<Cookie>? = null
val pieces =
cookieStrings.split(";".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
for (piece in pieces) {
val cookie = Cookie.parse(url!!, piece) ?: continue
if (cookies == null) {
cookies = ArrayList()
}
cookies.add(cookie)
}
return cookies ?: emptyList()
}
fun addCookie(domain: String, cookie: Cookie) {
EhApplication.ehCookieStore.addCookie(
EhCookieStore.newCookie(
cookie,
domain,
true,
true,
true
)
)
}
override fun onPageFinished(view: WebView, url: String) {
if (present) {
view.destroy()
return
}
val httpUrl = url.toHttpUrlOrNull() ?: return
val cookieString = CookieManager.getInstance().getCookie(EhUrl.HOST_E)
val cookies = parseCookies(httpUrl, cookieString)
var getId = false
var getHash = false
for (cookie in cookies) {
if (EhCookieStore.KEY_IPD_MEMBER_ID == cookie.name) {
getId = true
} else if (EhCookieStore.KEY_IPD_PASS_HASH == cookie.name) {
getHash = true
}
addCookie(EhUrl.DOMAIN_EX, cookie)
addCookie(EhUrl.DOMAIN_E, cookie)
}
if (getId && getHash) {
present = true
navController.navigate(SELECT_SITE_ROUTE_NAME)
}
}
}
webChromeClient = DialogWebChromeClient(context)
loadUrl(EhUrl.URL_SIGN_IN)
}
})
}
@@ -1,281 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
public abstract class BaseScene extends SceneFragment {
public static final int LENGTH_SHORT = 0;
public static final int LENGTH_LONG = 1;
public static final String KEY_DRAWER_VIEW_STATE =
"com.hippo.ehviewer.ui.scene.BaseScene:DRAWER_VIEW_STATE";
private WindowInsetsControllerCompat insetsController;
@Nullable
private View drawerView;
@Nullable
private SparseArray<Parcelable> drawerViewState;
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).addAboveSnackView(view);
}
}
public void removeAboveSnackView(View view) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).removeAboveSnackView(view);
}
}
public void setDrawerLockMode(int lockMode, int edgeGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).setDrawerLockMode(lockMode, edgeGravity);
}
}
public Integer getDrawerLockMode(int edgeGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
return ((MainActivity) activity).getDrawerLockMode(edgeGravity);
}
return null;
}
public void openDrawer(int drawerGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).openDrawer(drawerGravity);
}
}
public void closeDrawer(int drawerGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).closeDrawer(drawerGravity);
}
}
public void toggleDrawer(int drawerGravity) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).toggleDrawer(drawerGravity);
}
}
public void showTip(CharSequence message, int length) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).showTip(message, length);
}
}
public void showTip(@StringRes int id, int length) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).showTip(id, length);
}
}
public boolean needShowLeftDrawer() {
return true;
}
public int getNavCheckedItem() {
return 0;
}
/**
* @param resId 0 for clear
*/
public void setNavCheckedItem(@IdRes int resId) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).setNavCheckedItem(resId);
}
}
public void recreateDrawerView() {
MainActivity activity = getMainActivity();
if (activity != null) {
activity.createDrawerView(this);
}
}
public final View createDrawerView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
drawerView = onCreateDrawerView(inflater, container, savedInstanceState);
if (drawerView != null) {
SparseArray<Parcelable> saved = drawerViewState;
if (saved == null && savedInstanceState != null) {
saved = savedInstanceState.getSparseParcelableArray(KEY_DRAWER_VIEW_STATE);
}
if (saved != null) {
drawerView.restoreHierarchyState(saved);
}
}
return drawerView;
}
public View onCreateDrawerView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return null;
}
public final void destroyDrawerView() {
if (drawerView != null) {
drawerViewState = new SparseArray<>();
drawerView.saveHierarchyState(drawerViewState);
}
onDestroyDrawerView();
drawerView = null;
}
public void onDestroyDrawerView() {
}
@SuppressLint("RtlHardcoded")
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
// Update left drawer locked state
if (needShowLeftDrawer()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START);
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START);
}
// Update nav checked item
setNavCheckedItem(getNavCheckedItem());
// Hide soft ime
hideSoftInput();
}
@Nullable
public Resources getResourcesOrNull() {
Context context = getContext();
if (context != null) {
return context.getResources();
} else {
return null;
}
}
@Nullable
public MainActivity getMainActivity() {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
return (MainActivity) activity;
} else {
return null;
}
}
public void hideSoftInput() {
var insetsController = getInsetsController();
if (insetsController != null) {
insetsController.hide(WindowInsetsCompat.Type.ime());
}
}
public void showSoftInput(@Nullable View view) {
if (view != null) {
view.requestFocus();
view.post(() -> {
var insetsController = getInsetsController();
if (insetsController != null) {
insetsController.show(WindowInsetsCompat.Type.ime());
}
});
}
}
public WindowInsetsControllerCompat getInsetsController() {
if (insetsController == null) {
var activity = getActivity();
if (activity != null) {
insetsController = ViewCompat.getWindowInsetsController(activity.getWindow().getDecorView());
}
}
return insetsController;
}
@Override
public void onDestroy() {
super.onDestroy();
insetsController = null;
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (drawerView != null) {
drawerViewState = new SparseArray<>();
drawerView.saveHierarchyState(drawerViewState);
outState.putSparseParcelableArray(KEY_DRAWER_VIEW_STATE, drawerViewState);
}
}
public Resources.Theme getTheme() {
return requireActivity().getTheme();
}
}
@@ -0,0 +1,255 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene
import android.annotation.SuppressLint
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.os.Bundle
import android.os.Parcelable
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
import androidx.navigation.NavOptions
import androidx.navigation.fragment.NavHostFragment
import com.hippo.ehviewer.R
import com.hippo.ehviewer.ui.MainActivity
import rikka.core.res.resolveDrawable
abstract class BaseScene : Fragment() {
private var insetsController: WindowInsetsControllerCompat? = null
private var drawerView: View? = null
private var drawerViewState: SparseArray<Parcelable?>? = null
fun addAboveSnackView(view: View?) {
val activity = activity
if (activity is MainActivity) {
activity.addAboveSnackView(view)
}
}
fun removeAboveSnackView(view: View?) {
val activity = activity
if (activity is MainActivity) {
activity.removeAboveSnackView(view)
}
}
fun setDrawerLockMode(lockMode: Int, edgeGravity: Int) {
val activity = activity
if (activity is MainActivity) {
activity.setDrawerLockMode(lockMode, edgeGravity)
}
}
fun getDrawerLockMode(edgeGravity: Int): Int? {
val activity = activity
return if (activity is MainActivity) {
activity.getDrawerLockMode(edgeGravity)
} else null
}
fun openDrawer(drawerGravity: Int) {
val activity = activity
if (activity is MainActivity) {
activity.openDrawer(drawerGravity)
}
}
fun closeDrawer(drawerGravity: Int) {
val activity = activity
if (activity is MainActivity) {
activity.closeDrawer(drawerGravity)
}
}
fun toggleDrawer(drawerGravity: Int) {
val activity = activity
if (activity is MainActivity) {
activity.toggleDrawer(drawerGravity)
}
}
fun showTip(message: CharSequence?, length: Int) {
val activity = activity
if (activity is MainActivity) {
activity.showTip(message!!, length)
}
}
fun showTip(@StringRes id: Int, length: Int) {
val activity = activity
if (activity is MainActivity) {
activity.showTip(id, length)
}
}
open fun needShowLeftDrawer(): Boolean {
return true
}
fun recreateDrawerView() {
val activity = mainActivity
activity?.createDrawerView(this)
}
fun createDrawerView(
inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View? {
drawerView = onCreateDrawerView(inflater, container, savedInstanceState)
if (drawerView != null) {
var saved = drawerViewState
if (saved == null && savedInstanceState != null) {
saved = savedInstanceState.getSparseParcelableArray(KEY_DRAWER_VIEW_STATE)
}
if (saved != null) {
drawerView!!.restoreHierarchyState(saved)
}
}
return drawerView
}
open fun onCreateDrawerView(
inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View? {
return null
}
private fun destroyDrawerView() {
if (drawerView != null) {
drawerViewState = SparseArray()
drawerView!!.saveHierarchyState(drawerViewState)
}
onDestroyDrawerView()
drawerView = null
}
open fun onDestroyDrawerView() {}
@SuppressLint("RtlHardcoded")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.background = requireActivity().theme.resolveDrawable(android.R.attr.windowBackground)
// Update left drawer locked state
if (needShowLeftDrawer()) {
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
} else {
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
}
// Hide soft ime
hideSoftInput()
mainActivity!!.createDrawerView(this)
}
override fun onDestroyView() {
super.onDestroyView()
destroyDrawerView()
}
val resourcesOrNull: Resources?
get() {
val context = context
return context?.resources
}
val mainActivity: MainActivity?
get() {
val activity = activity
return if (activity is MainActivity) {
activity
} else {
null
}
}
fun hideSoftInput() {
val insetsController = getInsetsController()
insetsController?.hide(WindowInsetsCompat.Type.ime())
}
fun showSoftInput(view: View?) {
if (view != null) {
view.requestFocus()
view.post(Runnable {
val insetsController = getInsetsController()
insetsController?.show(WindowInsetsCompat.Type.ime())
})
}
}
private fun getInsetsController(): WindowInsetsControllerCompat? {
if (insetsController == null) {
val activity = activity
if (activity != null) {
insetsController = ViewCompat.getWindowInsetsController(activity.window.decorView)
}
}
return insetsController
}
override fun onDestroy() {
super.onDestroy()
insetsController = null
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (drawerView != null) {
drawerViewState = SparseArray()
drawerView!!.saveHierarchyState(drawerViewState)
outState.putSparseParcelableArray(KEY_DRAWER_VIEW_STATE, drawerViewState)
}
}
val theme: Theme
get() = requireActivity().theme
@JvmOverloads
fun navigate(id: Int, args: Bundle?, singleTop: Boolean = false) {
val options: NavOptions = NavOptions.Builder().setLaunchSingleTop(singleTop)
.setEnterAnim(R.anim.scene_open_enter).setExitAnim(R.anim.scene_open_exit)
.setPopEnterAnim(R.anim.scene_close_enter).setPopExitAnim(R.anim.scene_close_exit)
.build()
NavHostFragment.findNavController(this).navigate(id, args, options)
}
fun navigateToTop() {
val navController = NavHostFragment.findNavController(this)
val id = navController.graph.startDestinationId
val options: NavOptions = NavOptions.Builder()
.setEnterAnim(R.anim.scene_open_enter).setExitAnim(R.anim.scene_open_exit)
.setPopEnterAnim(R.anim.scene_close_enter).setPopExitAnim(R.anim.scene_close_exit)
.setPopUpTo(id, true)
.build()
navController.navigate(id, null, options)
}
companion object {
const val LENGTH_SHORT = 0
const val LENGTH_LONG = 1
const val KEY_DRAWER_VIEW_STATE = "com.hippo.ehviewer.ui.scene.BaseScene:DRAWER_VIEW_STATE"
}
}
@@ -1,305 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.ViewUtils;
import okhttp3.Cookie;
public class CookieSignInScene extends SolidScene implements EditText.OnEditorActionListener,
View.OnClickListener {
/*---------------
View life cycle
---------------*/
@Nullable
private TextInputLayout mIpbMemberIdLayout;
@Nullable
private TextInputLayout mIpbPassHashLayout;
@Nullable
private TextInputLayout mIgneousLayout;
@Nullable
private EditText mIpbMemberId;
@Nullable
private EditText mIpbPassHash;
@Nullable
private EditText mIgneous;
@Nullable
private View mOk;
@Nullable
private TextView mFromClipboard;
private static Cookie newCookie(String name, String value, String domain) {
return new Cookie.Builder().name(name).value(value)
.domain(domain).expiresAt(Long.MAX_VALUE).build();
}
@Override
public boolean needShowLeftDrawer() {
return false;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_cookie_sign_in, container, false);
mIpbMemberIdLayout = (TextInputLayout) ViewUtils.$$(view, R.id.ipb_member_id_layout);
mIpbMemberId = mIpbMemberIdLayout.getEditText();
AssertUtils.assertNotNull(mIpbMemberId);
mIpbPassHashLayout = (TextInputLayout) ViewUtils.$$(view, R.id.ipb_pass_hash_layout);
mIpbPassHash = mIpbPassHashLayout.getEditText();
AssertUtils.assertNotNull(mIpbPassHash);
mIgneousLayout = (TextInputLayout) ViewUtils.$$(view, R.id.igneous_layout);
mIgneous = mIgneousLayout.getEditText();
AssertUtils.assertNotNull(mIgneous);
mOk = ViewUtils.$$(view, R.id.ok);
mFromClipboard = (TextView) ViewUtils.$$(view, R.id.from_clipboard);
mFromClipboard.setPaintFlags(mFromClipboard.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mIpbPassHash.setOnEditorActionListener(this);
mOk.setOnClickListener(this);
mFromClipboard.setOnClickListener(this);
// Try to get old version cookie info
Context context = getContext();
AssertUtils.assertNotNull(context);
SharedPreferences sharedPreferences = context.getSharedPreferences("eh_info", 0);
String ipbMemberId = sharedPreferences.getString("ipb_member_id", null);
String ipbPassHash = sharedPreferences.getString("ipb_pass_hash", null);
String igneous = sharedPreferences.getString("igneous", null);
boolean getIt = false;
if (!TextUtils.isEmpty(ipbMemberId)) {
mIpbMemberId.setText(ipbMemberId);
getIt = true;
}
if (!TextUtils.isEmpty(ipbPassHash)) {
mIpbPassHash.setText(ipbPassHash);
getIt = true;
}
if (!TextUtils.isEmpty(igneous)) {
mIgneous.setText(igneous);
getIt = true;
}
if (getIt) {
showTip(R.string.found_cookies, LENGTH_SHORT);
}
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
showSoftInput(mIpbMemberId);
}
@Override
public void onDestroyView() {
super.onDestroyView();
mIpbMemberIdLayout = null;
mIpbPassHashLayout = null;
mIgneousLayout = null;
mIpbMemberId = null;
mIpbPassHash = null;
mIgneous = null;
}
@Override
public void onClick(View v) {
if (mOk == v) {
enter();
} else if (mFromClipboard == v) {
fillCookiesFromClipboard();
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (mIpbPassHash == v) {
enter();
}
return true;
}
public void enter() {
Context context = getContext();
if (null == context || null == mIpbMemberIdLayout || null == mIpbPassHashLayout ||
null == mIgneousLayout || null == mIpbMemberId || null == mIpbPassHash ||
null == mIgneous) {
return;
}
final String ipbMemberId = mIpbMemberId.getText().toString().trim();
final String ipbPassHash = mIpbPassHash.getText().toString().trim();
final String igneous = mIgneous.getText().toString().trim();
if (TextUtils.isEmpty(ipbMemberId)) {
mIpbMemberIdLayout.setError(getString(R.string.text_is_empty));
return;
} else {
mIpbMemberIdLayout.setError(null);
}
if (TextUtils.isEmpty(ipbPassHash)) {
mIpbPassHashLayout.setError(getString(R.string.text_is_empty));
return;
} else {
mIpbPassHashLayout.setError(null);
}
hideSoftInput();
storeCookie(ipbMemberId, ipbPassHash, igneous);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(new CookieSignInListener());
request.enqueue(this);
}
private void storeCookie(String id, String hash, String igneous) {
Context context = getContext();
if (null == context) {
return;
}
EhUtils.signOut();
EhCookieStore store = EhApplication.getEhCookieStore();
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_E));
store.addCookie(newCookie(EhCookieStore.KEY_IPD_MEMBER_ID, id, EhUrl.DOMAIN_EX));
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_E));
store.addCookie(newCookie(EhCookieStore.KEY_IPD_PASS_HASH, hash, EhUrl.DOMAIN_EX));
if (!igneous.isEmpty()) {
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_E));
store.addCookie(newCookie(EhCookieStore.KEY_IGNEOUS, igneous, EhUrl.DOMAIN_EX));
}
}
private void fillCookiesFromClipboard() {
Context context = requireContext();
hideSoftInput();
String text = ClipboardUtilKt.getTextFromClipboard(
ClipboardUtilKt.getClipboardManager(context), context);
if (text == null) {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
}
try {
String[] kvs;
if (text.contains(";")) {
kvs = text.split(";");
} else if (text.contains("\n")) {
kvs = text.split("\n");
} else {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
}
if (kvs.length < 3) {
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
return;
}
for (String s : kvs) {
String[] kv;
if (s.contains("=")) {
kv = s.split("=");
} else if (s.contains(":")) {
kv = s.split(":");
} else {
continue;
}
if (kv.length != 2) {
continue;
}
switch (kv[0].trim().toLowerCase()) {
case "ipb_member_id":
if (mIpbMemberId != null) {
mIpbMemberId.setText(kv[1].trim());
}
break;
case "ipb_pass_hash":
if (mIpbPassHash != null) {
mIpbPassHash.setText(kv[1].trim());
}
break;
case "igneous":
if (mIgneous != null) {
mIgneous.setText(kv[1].trim());
}
break;
}
}
enter();
} catch (Exception e) {
ExceptionUtils.throwIfFatal(e);
e.printStackTrace();
showTip(R.string.from_clipboard_error, LENGTH_SHORT);
}
}
private class CookieSignInListener implements EhClient.Callback<Object> {
@Override
public void onSuccess(Object result) {
setResult(RESULT_OK, null);
finish();
}
@Override
public void onFailure(Exception e) {
EhApplication.getEhCookieStore().signOut();
new BaseDialogBuilder(requireContext()).setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.wrong_cookie_warning))
.setPositiveButton(R.string.get_it, null).show();
}
@Override
public void onCancel() {
EhApplication.getEhCookieStore().signOut();
}
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -20,29 +20,23 @@ import android.content.Context;
import android.widget.Toast;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.ui.EhActivity;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.scene.StageActivity;
public abstract class EhCallback<E extends SceneFragment, T> implements EhClient.Callback<T> {
public abstract class EhCallback<E extends Fragment, T> implements EhClient.Callback<T> {
private final EhApplication mApplication;
private final int mStageId;
private final String mSceneTag;
public EhCallback(Context context, int stageId, String sceneTag) {
public EhCallback(Context context) {
mApplication = (EhApplication) context.getApplicationContext();
mStageId = stageId;
mSceneTag = sceneTag;
}
public abstract boolean isInstance(SceneFragment scene);
public Context getContent() {
Context context = getStageActivity();
Context context = mApplication.getTopActivity();
if (context == null) {
context = getApplication();
}
@@ -53,26 +47,8 @@ public abstract class EhCallback<E extends SceneFragment, T> implements EhClient
return mApplication;
}
public StageActivity getStageActivity() {
return mApplication.findStageActivityById(mStageId);
}
@SuppressWarnings("unchecked")
public E getScene() {
StageActivity stage = mApplication.findStageActivityById(mStageId);
if (stage == null) {
return null;
}
SceneFragment scene = stage.findSceneByTag(mSceneTag);
if (isInstance(scene)) {
return (E) scene;
} else {
return null;
}
}
public void showTip(@StringRes int id, int length) {
StageActivity activity = getStageActivity();
EhActivity activity = mApplication.getTopActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).showTip(id, length);
} else {
@@ -82,7 +58,7 @@ public abstract class EhCallback<E extends SceneFragment, T> implements EhClient
}
public void showTip(String tip, int length) {
StageActivity activity = getStageActivity();
EhActivity activity = mApplication.getTopActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).showTip(tip, length);
} else {
@@ -65,8 +65,6 @@ import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle;
import com.hippo.ehviewer.ui.annotation.ViewLifeCircle;
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
import com.hippo.ehviewer.widget.GalleryInfoContentHelper;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.FabLayout;
import com.hippo.yorozuya.AssertUtils;
@@ -156,11 +154,6 @@ public class FavoritesScene extends SearchBarScene implements
// For modify action
private boolean mModifyAdd;
@Override
public int getNavCheckedItem() {
return R.id.nav_favourite;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -460,8 +453,7 @@ public class FavoritesScene extends SearchBarScene implements
Bundle args = new Bundle();
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO);
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi);
Announcer announcer = new Announcer(GalleryDetailScene.class).setArgs(args);
startScene(announcer);
navigate(R.id.galleryDetailScene, args);
}
}
return true;
@@ -727,15 +719,26 @@ public class FavoritesScene extends SearchBarScene implements
}
}
private static class AddFavoritesListener extends EhCallback<FavoritesScene, Void> {
private static class FavDrawerHolder extends RecyclerView.ViewHolder {
private final TextView key;
private final TextView value;
private FavDrawerHolder(View itemView) {
super(itemView);
key = (TextView) ViewUtils.$$(itemView, R.id.key);
value = (TextView) ViewUtils.$$(itemView, R.id.value);
}
}
private class AddFavoritesListener extends EhCallback<FavoritesScene, Void> {
private final int mTaskId;
private final String mKeyword;
private final List<GalleryInfo> mBackup;
private AddFavoritesListener(Context context, int stageId,
String sceneTag, int taskId, String keyword, List<GalleryInfo> backup) {
super(context, stageId, sceneTag);
private AddFavoritesListener(Context context, int taskId, String keyword, List<GalleryInfo> backup) {
super(context);
mTaskId = taskId;
mKeyword = keyword;
mBackup = backup;
@@ -743,10 +746,8 @@ public class FavoritesScene extends SearchBarScene implements
@Override
public void onSuccess(Void result) {
FavoritesScene scene = getScene();
if (scene != null) {
scene.onGetFavoritesLocal(mKeyword, mTaskId);
}
FavoritesScene scene = FavoritesScene.this;
scene.onGetFavoritesLocal(mKeyword, mTaskId);
}
@Override
@@ -755,32 +756,24 @@ public class FavoritesScene extends SearchBarScene implements
// But how to known which one is failed?
EhDB.putLocalFavorites(mBackup);
FavoritesScene scene = getScene();
if (scene != null) {
scene.onGetFavoritesLocal(mKeyword, mTaskId);
}
FavoritesScene scene = FavoritesScene.this;
scene.onGetFavoritesLocal(mKeyword, mTaskId);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof FavoritesScene;
}
}
private static class GetFavoritesListener extends EhCallback<FavoritesScene, FavoritesParser.Result> {
private class GetFavoritesListener extends EhCallback<FavoritesScene, FavoritesParser.Result> {
private final int mTaskId;
// Local fav is shown now, but operation need be done for cloud fav
private final boolean mLocal;
private final String mKeyword;
private GetFavoritesListener(Context context, int stageId,
String sceneTag, int taskId, boolean local, String keyword) {
super(context, stageId, sceneTag);
private GetFavoritesListener(Context context, int taskId, boolean local, String keyword) {
super(context);
mTaskId = taskId;
mLocal = local;
mKeyword = keyword;
@@ -791,49 +784,28 @@ public class FavoritesScene extends SearchBarScene implements
// Put fav cat
Settings.putFavCat(result.catArray);
Settings.putFavCount(result.countArray);
FavoritesScene scene = getScene();
if (scene != null) {
if (mLocal) {
scene.onGetFavoritesLocal(mKeyword, mTaskId);
} else {
scene.onGetFavoritesSuccess(result, mTaskId);
}
FavoritesScene scene = FavoritesScene.this;
if (mLocal) {
scene.onGetFavoritesLocal(mKeyword, mTaskId);
} else {
scene.onGetFavoritesSuccess(result, mTaskId);
}
}
@Override
public void onFailure(Exception e) {
FavoritesScene scene = getScene();
if (scene != null) {
if (mLocal) {
e.printStackTrace();
scene.onGetFavoritesLocal(mKeyword, mTaskId);
} else {
scene.onGetFavoritesFailure(e, mTaskId);
}
FavoritesScene scene = FavoritesScene.this;
if (mLocal) {
e.printStackTrace();
scene.onGetFavoritesLocal(mKeyword, mTaskId);
} else {
scene.onGetFavoritesFailure(e, mTaskId);
}
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof FavoritesScene;
}
}
private static class FavDrawerHolder extends RecyclerView.ViewHolder {
private final TextView key;
private final TextView value;
private FavDrawerHolder(View itemView) {
super(itemView);
key = (TextView) ViewUtils.$$(itemView, R.id.key);
value = (TextView) ViewUtils.$$(itemView, R.id.value);
}
}
private class FavDrawerAdapter extends RecyclerView.Adapter<FavDrawerHolder> {
@@ -905,7 +877,7 @@ public class FavoritesScene extends SearchBarScene implements
if (mUrlBuilder.getFavCat() == FavListUrlBuilder.FAV_CAT_LOCAL) { // Delete local fav
long[] gidArray = new long[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
gidArray[i] = mModifyGiList.get(i).gid;
gidArray[i] = mModifyGiList.get(i).getGid();
}
EhDB.removeLocalFavorites(gidArray);
mModifyGiList.clear();
@@ -948,7 +920,7 @@ public class FavoritesScene extends SearchBarScene implements
if (srcCat == FavListUrlBuilder.FAV_CAT_LOCAL) { // Move from local to cloud
long[] gidArray = new long[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
gidArray[i] = mModifyGiList.get(i).gid;
gidArray[i] = mModifyGiList.get(i).getGid();
}
EhDB.removeLocalFavorites(gidArray);
mEnableModify = true;
@@ -1031,22 +1003,20 @@ public class FavoritesScene extends SearchBarScene implements
String[] tokenArray = new String[mModifyGiList.size()];
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
GalleryInfo gi = mModifyGiList.get(i);
gidArray[i] = gi.gid;
tokenArray[i] = gi.token;
gidArray[i] = gi.getGid();
tokenArray[i] = gi.getToken();
}
List<GalleryInfo> modifyGiListBackup = new ArrayList<>(mModifyGiList);
mModifyGiList.clear();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_ADD_FAVORITES_RANGE);
request.setCallback(new AddFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, mUrlBuilder.getKeyword(), modifyGiListBackup));
request.setCallback(new AddFavoritesListener(getContext(), taskId, mUrlBuilder.getKeyword(), modifyGiListBackup));
request.setArgs(gidArray, tokenArray, mModifyFavCat);
request.enqueue(FavoritesScene.this);
} else {
for (int i = 0, n = mModifyGiList.size(); i < n; i++) {
gidArray[i] = mModifyGiList.get(i).gid;
gidArray[i] = mModifyGiList.get(i).getGid();
}
mModifyGiList.clear();
@@ -1062,7 +1032,6 @@ public class FavoritesScene extends SearchBarScene implements
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_MODIFY_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, local, mUrlBuilder.getKeyword()));
request.setArgs(url, gidArray, mModifyFavCat);
request.enqueue(FavoritesScene.this);
@@ -1077,7 +1046,6 @@ public class FavoritesScene extends SearchBarScene implements
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_FAVORITES);
request.setCallback(new GetFavoritesListener(getContext(),
activity.getStageId(), getTag(),
taskId, false, mUrlBuilder.getKeyword()));
request.setArgs(url);
request.enqueue(FavoritesScene.this);
@@ -1115,7 +1083,7 @@ public class FavoritesScene extends SearchBarScene implements
@Override
protected boolean isDuplicate(GalleryInfo d1, GalleryInfo d2) {
return d1.gid == d2.gid;
return d1.getGid() == d2.getGid();
}
@Override
@@ -189,53 +189,53 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
switch (mType) {
case TYPE_LIST -> {
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.getGid()), gi.getThumb());
holder.title.setText(EhUtils.getSuitableTitle(gi));
holder.uploader.setText(gi.uploader);
holder.uploader.setAlpha(gi.disowned ? .5f : 1f);
holder.rating.setRating(gi.rating);
holder.uploader.setText(gi.getUploader());
holder.uploader.setAlpha(gi.getDisowned() ? .5f : 1f);
holder.rating.setRating(gi.getRating());
TextView category = holder.category;
String newCategoryText = EhUtils.getCategory(gi.category);
String newCategoryText = EhUtils.getCategory(gi.getCategory());
if (!newCategoryText.equals(category.getText().toString())) {
category.setText(newCategoryText);
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category));
category.setBackgroundColor(EhUtils.getCategoryColor(gi.getCategory()));
}
holder.posted.setText(gi.posted);
if (gi.pages == 0 || !Settings.getShowGalleryPages()) {
holder.posted.setText(gi.getPosted());
if (gi.getPages() == 0 || !Settings.getShowGalleryPages()) {
holder.pages.setText(null);
holder.pages.setVisibility(View.GONE);
} else {
holder.pages.setText(gi.pages + "P");
holder.pages.setText(gi.getPages() + "P");
holder.pages.setVisibility(View.VISIBLE);
}
if (TextUtils.isEmpty(gi.simpleLanguage)) {
if (TextUtils.isEmpty(gi.getSimpleLanguage())) {
holder.simpleLanguage.setText(null);
holder.simpleLanguage.setVisibility(View.GONE);
} else {
holder.simpleLanguage.setText(gi.simpleLanguage);
holder.simpleLanguage.setText(gi.getSimpleLanguage());
holder.simpleLanguage.setVisibility(View.VISIBLE);
}
holder.favourited.setVisibility((mShowFavourited && gi.favoriteSlot >= -1 && gi.favoriteSlot <= 10) ? View.VISIBLE : View.GONE);
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.gid) ? View.VISIBLE : View.GONE);
holder.favourited.setVisibility((mShowFavourited && gi.getFavoriteSlot() >= -1 && gi.getFavoriteSlot() <= 10) ? View.VISIBLE : View.GONE);
holder.downloaded.setVisibility(mDownloadManager.containDownloadInfo(gi.getGid()) ? View.VISIBLE : View.GONE);
}
case TYPE_GRID -> {
((TileThumb) holder.thumb).setThumbSize(gi.thumbWidth, gi.thumbHeight);
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
((TileThumb) holder.thumb).setThumbSize(gi.getThumbWidth(), gi.getThumbHeight());
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.getGid()), gi.getThumb());
View category = holder.category;
Drawable drawable = category.getBackground();
int color = EhUtils.getCategoryColor(gi.category);
int color = EhUtils.getCategoryColor(gi.getCategory());
if (!(drawable instanceof TriangleDrawable)) {
drawable = new TriangleDrawable(color);
category.setBackground(drawable);
} else {
((TriangleDrawable) drawable).setColor(color);
}
holder.simpleLanguage.setText(gi.simpleLanguage);
holder.simpleLanguage.setText(gi.getSimpleLanguage());
}
}
// Update transition name
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gi.gid));
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gi.getGid()));
}
@IntDef({TYPE_LIST, TYPE_GRID})
@@ -72,7 +72,6 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
import com.hippo.util.ClipboardUtilKt;
import com.hippo.util.ExceptionUtils;
@@ -312,9 +311,8 @@ public final class GalleryCommentsScene extends BaseToolbarScene
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_VOTE_COMMENT)
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, id, vote)
.setCallback(new VoteCommentListener(context,
activity.getStageId(), getTag()));
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.getGid(), mGalleryDetail.getToken(), id, vote)
.setCallback(new VoteCommentListener(context));
request.enqueue(this);
}
@@ -450,7 +448,7 @@ public final class GalleryCommentsScene extends BaseToolbarScene
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
.setCallback(new RefreshCommentListener(activity));
request.enqueue(this);
}
}
@@ -599,8 +597,8 @@ public final class GalleryCommentsScene extends BaseToolbarScene
@Nullable
private String getGalleryDetailUrl() {
if (mGalleryDetail != null && mGalleryDetail.gid != -1 && mGalleryDetail.token != null) {
return EhUrl.getGalleryDetailUrl(mGalleryDetail.gid, mGalleryDetail.token, 0, mShowAllComments);
if (mGalleryDetail != null && mGalleryDetail.getGid() != -1 && mGalleryDetail.getToken() != null) {
return EhUrl.getGalleryDetailUrl(mGalleryDetail.getGid(), mGalleryDetail.getToken(), 0, mShowAllComments);
} else {
return null;
}
@@ -634,8 +632,7 @@ public final class GalleryCommentsScene extends BaseToolbarScene
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_COMMENT_GALLERY)
.setArgs(url, comment, mCommentId != 0 ? Long.toString(mCommentId) : null)
.setCallback(new CommentGalleryListener(context,
activity.getStageId(), getTag(), mCommentId));
.setCallback(new CommentGalleryListener(context, mCommentId));
request.enqueue(this);
hideSoftInput();
hideEditPanel(true);
@@ -654,10 +651,6 @@ public final class GalleryCommentsScene extends BaseToolbarScene
mAdapter.notifyDataSetChanged();
updateView(true);
Bundle re = new Bundle();
re.putParcelable(KEY_COMMENT_LIST, result);
setResult(SceneFragment.RESULT_OK, re);
}
private void onRefreshGalleryFailure() {
@@ -680,9 +673,6 @@ public final class GalleryCommentsScene extends BaseToolbarScene
mGalleryDetail.comments = result;
mAdapter.notifyDataSetChanged();
Bundle re = new Bundle();
re.putParcelable(KEY_COMMENT_LIST, result);
setResult(SceneFragment.RESULT_OK, re);
// Remove text
if (mEditText != null) {
@@ -723,10 +713,6 @@ public final class GalleryCommentsScene extends BaseToolbarScene
}
mAdapter.notifyItemChanged(position);
Bundle re = new Bundle();
re.putParcelable(KEY_COMMENT_LIST, mGalleryDetail.comments);
setResult(SceneFragment.RESULT_OK, re);
}
@Override
@@ -741,112 +727,12 @@ public final class GalleryCommentsScene extends BaseToolbarScene
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
.setArgs(url)
.setCallback(new RefreshCommentListener(activity, activity.getStageId(), getTag()));
.setCallback(new RefreshCommentListener(activity));
request.enqueue(this);
}
}
}
private static class RefreshCommentListener extends EhCallback<GalleryCommentsScene, GalleryDetail> {
public RefreshCommentListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(GalleryDetail result) {
GalleryCommentsScene scene = getScene();
if (scene != null) {
scene.onRefreshGallerySuccess(result.comments);
}
}
@Override
public void onFailure(Exception e) {
GalleryCommentsScene scene = getScene();
if (scene != null) {
scene.onRefreshGalleryFailure();
}
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryCommentsScene;
}
}
private static class CommentGalleryListener extends EhCallback<GalleryCommentsScene, GalleryCommentList> {
private final long mCommentId;
public CommentGalleryListener(Context context, int stageId, String sceneTag, long commentId) {
super(context, stageId, sceneTag);
mCommentId = commentId;
}
@Override
public void onSuccess(GalleryCommentList result) {
showTip(mCommentId != 0 ? R.string.edit_comment_successfully : R.string.comment_successfully, LENGTH_SHORT);
GalleryCommentsScene scene = getScene();
if (scene != null) {
scene.onCommentGallerySuccess(result);
}
}
@Override
public void onFailure(Exception e) {
showTip(getContent().getString(mCommentId != 0 ? R.string.edit_comment_failed : R.string.comment_failed) + "\n" + ExceptionUtils.getReadableString(e), LENGTH_LONG);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryCommentsScene;
}
}
private static class VoteCommentListener extends EhCallback<GalleryCommentsScene, VoteCommentParser.Result> {
public VoteCommentListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(VoteCommentParser.Result result) {
showTip(result.expectVote > 0 ?
(0 != result.vote ? R.string.vote_up_successfully : R.string.cancel_vote_up_successfully) :
(0 != result.vote ? R.string.vote_down_successfully : R.string.cancel_vote_down_successfully),
LENGTH_SHORT);
GalleryCommentsScene scene = getScene();
if (scene != null) {
scene.onVoteCommentSuccess(result);
}
}
@Override
public void onFailure(Exception e) {
showTip(R.string.vote_failed, LENGTH_LONG);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryCommentsScene;
}
}
private static class InfoHolder extends RecyclerView.ViewHolder {
private final TextView key;
@@ -877,6 +763,83 @@ public final class GalleryCommentsScene extends BaseToolbarScene
}
}
private class RefreshCommentListener extends EhCallback<GalleryCommentsScene, GalleryDetail> {
public RefreshCommentListener(Context context) {
super(context);
}
@Override
public void onSuccess(GalleryDetail result) {
GalleryCommentsScene scene = GalleryCommentsScene.this;
scene.onRefreshGallerySuccess(result.comments);
}
@Override
public void onFailure(Exception e) {
GalleryCommentsScene scene = GalleryCommentsScene.this;
scene.onRefreshGalleryFailure();
}
@Override
public void onCancel() {
}
}
private class CommentGalleryListener extends EhCallback<GalleryCommentsScene, GalleryCommentList> {
private final long mCommentId;
public CommentGalleryListener(Context context, long commentId) {
super(context);
mCommentId = commentId;
}
@Override
public void onSuccess(GalleryCommentList result) {
showTip(mCommentId != 0 ? R.string.edit_comment_successfully : R.string.comment_successfully, LENGTH_SHORT);
GalleryCommentsScene scene = GalleryCommentsScene.this;
scene.onCommentGallerySuccess(result);
}
@Override
public void onFailure(Exception e) {
showTip(getContent().getString(mCommentId != 0 ? R.string.edit_comment_failed : R.string.comment_failed) + "\n" + ExceptionUtils.getReadableString(e), LENGTH_LONG);
}
@Override
public void onCancel() {
}
}
private class VoteCommentListener extends EhCallback<GalleryCommentsScene, VoteCommentParser.Result> {
public VoteCommentListener(Context context) {
super(context);
}
@Override
public void onSuccess(VoteCommentParser.Result result) {
showTip(result.expectVote > 0 ?
(0 != result.vote ? R.string.vote_up_successfully : R.string.cancel_vote_up_successfully) :
(0 != result.vote ? R.string.vote_down_successfully : R.string.cancel_vote_down_successfully),
LENGTH_SHORT);
GalleryCommentsScene scene = GalleryCommentsScene.this;
scene.onVoteCommentSuccess(result);
}
@Override
public void onFailure(Exception e) {
showTip(R.string.vote_failed, LENGTH_LONG);
}
@Override
public void onCancel() {
}
}
private class ActualCommentHolder extends CommentHolder {
private final TextView user;
@@ -925,7 +888,7 @@ public final class GalleryCommentsScene extends BaseToolbarScene
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_UPLOADER);
lub.setKeyword(value.user);
GalleryListScene.startScene(GalleryCommentsScene.this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
});
time.setText(ReadableTime.getTimeAgo(value.time));
comment.setText(generateComment(comment.getContext(), comment, value));
@@ -21,7 +21,6 @@ import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.DownloadManager;
import android.app.assist.AssistContent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -76,7 +75,6 @@ import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryComment;
import com.hippo.ehviewer.client.data.GalleryCommentList;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.ehviewer.client.data.GalleryInfo;
import com.hippo.ehviewer.client.data.GalleryTagGroup;
@@ -97,8 +95,6 @@ import com.hippo.ehviewer.ui.GalleryInfoBottomSheet;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
import com.hippo.ehviewer.widget.GalleryRatingBar;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.text.URLImageGetter;
import com.hippo.unifile.UniFile;
import com.hippo.util.AppHelper;
@@ -335,11 +331,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
long gid;
String token;
if (mGalleryDetail != null) {
gid = mGalleryDetail.gid;
token = mGalleryDetail.token;
gid = mGalleryDetail.getGid();
token = mGalleryDetail.getToken();
} else if (mGalleryInfo != null) {
gid = mGalleryInfo.gid;
token = mGalleryInfo.token;
gid = mGalleryInfo.getGid();
token = mGalleryInfo.getToken();
} else if (ACTION_GID_TOKEN.equals(mAction)) {
gid = mGid;
token = mToken;
@@ -352,9 +348,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
// -1 for error
private long getGid() {
if (mGalleryDetail != null) {
return mGalleryDetail.gid;
return mGalleryDetail.getGid();
} else if (mGalleryInfo != null) {
return mGalleryInfo.gid;
return mGalleryInfo.getGid();
} else if (ACTION_GID_TOKEN.equals(mAction)) {
return mGid;
} else {
@@ -364,9 +360,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private String getUploader() {
if (mGalleryDetail != null) {
return mGalleryDetail.uploader;
return mGalleryDetail.getUploader();
} else if (mGalleryInfo != null) {
return mGalleryInfo.uploader;
return mGalleryInfo.getUploader();
} else {
return null;
}
@@ -380,9 +376,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
// -1 for error
private int getCategory() {
if (mGalleryDetail != null) {
return mGalleryDetail.category;
return mGalleryDetail.getCategory();
} else if (mGalleryInfo != null) {
return mGalleryInfo.category;
return mGalleryInfo.getCategory();
} else {
return -1;
}
@@ -624,6 +620,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
EhApplication.getDownloadManager().addDownloadInfoListener(this);
((MainActivity) requireActivity()).setMShareUrl(getGalleryDetailUrl());
return view;
}
@@ -635,6 +632,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
Context context = getContext();
AssertUtils.assertNotNull(context);
EhApplication.getDownloadManager().removeDownloadInfoListener(this);
((MainActivity) requireActivity()).setMShareUrl(null);
mTip = null;
mViewTransition = null;
@@ -720,8 +718,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return false;
}
EhClient.Callback<?> callback = new GetGalleryDetailListener(context,
activity.getStageId(), getTag());
EhClient.Callback<?> callback = new GetGalleryDetailListener(context);
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
@@ -776,12 +773,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (ACTION_GALLERY_INFO.equals(mAction) && mGalleryInfo != null) {
GalleryInfo gi = mGalleryInfo;
mThumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb);
mThumb.load(EhCacheKeyFactory.getThumbKey(gi.getGid()), gi.getThumb());
setTitle(EhUtils.getSuitableTitle(gi));
mUploader.setText(gi.uploader);
mUploader.setAlpha(gi.disowned ? .5f : 1f);
mCategory.setText(EhUtils.getCategory(gi.category));
mCategory.setTextColor(EhUtils.getCategoryColor(gi.category));
mUploader.setText(gi.getUploader());
mUploader.setAlpha(gi.getDisowned() ? .5f : 1f);
mCategory.setText(EhUtils.getCategory(gi.getCategory()));
mCategory.setTextColor(EhUtils.getCategoryColor(gi.getCategory()));
updateDownloadText();
}
}
@@ -795,12 +792,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return;
}
if (gd.isFavorited || EhDB.containLocalFavorites(gd.gid)) {
if (gd.isFavorited || EhDB.containLocalFavorites(gd.getGid())) {
mHeart.setVisibility(View.VISIBLE);
if (gd.favoriteName == null) {
if (gd.getFavoriteName() == null) {
mHeart.setText(R.string.local_favorites);
} else {
mHeart.setText(gd.favoriteName);
mHeart.setText(gd.getFavoriteName());
}
mHeartOutline.setVisibility(View.GONE);
} else {
@@ -833,26 +830,26 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
Resources resources = getResources();
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.gid), gd.thumb, true);
mThumb.load(EhCacheKeyFactory.getThumbKey(gd.getGid()), gd.getThumb(), true);
setTitle(EhUtils.getSuitableTitle(gd));
mUploader.setText(gd.uploader);
mUploader.setAlpha(gd.disowned ? .5f : 1f);
mCategory.setText(EhUtils.getCategory(gd.category));
mCategory.setTextColor(EhUtils.getCategoryColor(gd.category));
mUploader.setText(gd.getUploader());
mUploader.setAlpha(gd.getDisowned() ? .5f : 1f);
mCategory.setText(EhUtils.getCategory(gd.getCategory()));
mCategory.setTextColor(EhUtils.getCategoryColor(gd.getCategory()));
updateDownloadText();
mLanguage.setText(gd.language);
mPages.setText(resources.getQuantityString(
R.plurals.page_count, gd.pages, gd.pages));
R.plurals.page_count, gd.getPages(), gd.getPages()));
mSize.setText(gd.size);
mPosted.setText(gd.posted);
mPosted.setText(gd.getPosted());
mFavoredTimes.setText(resources.getString(R.string.favored_times, gd.favoriteCount));
if (gd.newerVersions != null && gd.newerVersions.size() != 0) {
mNewerVersion.setVisibility(View.VISIBLE);
}
mRatingText.setText(getAllRatingText(gd.rating, gd.ratingCount));
mRating.setRating(gd.rating);
mRatingText.setText(getAllRatingText(gd.getRating(), gd.ratingCount));
mRating.setRating(gd.getRating());
updateFavoriteDrawable();
@@ -985,7 +982,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mGridLayout.addView(view);
LoadImageView image = view.findViewById(R.id.image);
previewSet.load(image, gd.gid, i);
previewSet.load(image, gd.getGid(), i);
image.setTag(R.id.index, i);
image.setOnClickListener(this);
TextView text = view.findViewById(R.id.text);
@@ -1002,12 +999,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (null == gd) {
return;
}
String keyword = EhUtils.extractTitle(gd.title);
String keyword = EhUtils.extractTitle(gd.getTitle());
if (null != keyword) {
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_NORMAL);
lub.setKeyword("\"" + keyword + "\"");
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
return;
}
String artist = getArtist(gd.tags);
@@ -1015,14 +1012,14 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_TAG);
lub.setKeyword("artist:" + artist);
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
return;
}
if (null != gd.uploader) {
if (null != gd.getUploader()) {
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_UPLOADER);
lub.setKeyword(gd.uploader);
GalleryListScene.startScene(this, lub);
lub.setKeyword(gd.getUploader());
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
}
}
@@ -1042,7 +1039,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
lub.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
lub.setImagePath(path.toString());
lub.setUseSimilarityScan(true);
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
} catch (Throwable e) {
e.printStackTrace();
}
@@ -1068,7 +1065,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_UPLOADER);
lub.setKeyword(uploader);
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
} else if (mCategory == v) {
int category = getCategory();
if (category == EhUtils.NONE || category == EhUtils.PRIVATE || category == EhUtils.UNKNOWN) {
@@ -1076,15 +1073,15 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
}
ListUrlBuilder lub = new ListUrlBuilder();
lub.setCategory(category);
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
} else if (mDownload == v) {
GalleryInfo galleryInfo = getGalleryInfo();
if (galleryInfo != null) {
if (EhApplication.getDownloadManager().getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
if (EhApplication.getDownloadManager().getDownloadState(galleryInfo.getGid()) == DownloadInfo.STATE_INVALID) {
CommonOperations.startDownload(activity, galleryInfo, false);
} else {
CheckBoxDialogBuilder builder = new CheckBoxDialogBuilder(context,
getString(R.string.download_remove_dialog_message, galleryInfo.title),
getString(R.string.download_remove_dialog_message, galleryInfo.getTitle()),
getString(R.string.download_remove_dialog_check_text),
Settings.getRemoveImageFiles());
DeleteDialogHelper helper = new DeleteDialogHelper(
@@ -1111,16 +1108,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (mGalleryDetail != null) {
ArrayList<CharSequence> titles = new ArrayList<>();
for (GalleryInfo newerVersion : mGalleryDetail.newerVersions) {
titles.add(getString(R.string.newer_version_title, newerVersion.title, newerVersion.posted));
titles.add(getString(R.string.newer_version_title, newerVersion.getTitle(), newerVersion.getPosted()));
}
new BaseDialogBuilder(requireContext())
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
Bundle args = new Bundle();
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN);
args.putLong(GalleryDetailScene.KEY_GID, newerVersion.gid);
args.putString(GalleryDetailScene.KEY_TOKEN, newerVersion.token);
startScene(new Announcer(GalleryDetailScene.class).setArgs(args));
args.putLong(GalleryDetailScene.KEY_GID, newerVersion.getGid());
args.putString(GalleryDetailScene.KEY_TOKEN, newerVersion.getToken());
navigate(R.id.galleryDetailScene, args);
})
.show();
}
@@ -1131,18 +1128,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
if (EhDB.containLocalFavorites(mGalleryDetail.getGid()) || mGalleryDetail.isFavorited) {
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), true));
new ModifyFavoritesListener(context, true));
remove = true;
}
if (!remove) {
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(context,
activity.getStageId(), getTag(), false));
new ModifyFavoritesListener(context, false));
}
// Update UI
updateFavoriteDrawable();
@@ -1197,7 +1192,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, helper)
.show();
helper.setDialog(dialog, mGalleryDetail.rating);
helper.setDialog(dialog, mGalleryDetail.getRating());
} else if (mSimilar == v) {
showSimilarGalleryList();
} else if (mSearchCover == v) {
@@ -1209,18 +1204,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
Bundle args = new Bundle();
args.putLong(GalleryCommentsScene.KEY_API_UID, mGalleryDetail.apiUid);
args.putString(GalleryCommentsScene.KEY_API_KEY, mGalleryDetail.apiKey);
args.putLong(GalleryCommentsScene.KEY_GID, mGalleryDetail.gid);
args.putString(GalleryCommentsScene.KEY_TOKEN, mGalleryDetail.token);
args.putLong(GalleryCommentsScene.KEY_GID, mGalleryDetail.getGid());
args.putString(GalleryCommentsScene.KEY_TOKEN, mGalleryDetail.getToken());
args.putParcelable(GalleryCommentsScene.KEY_COMMENT_LIST, mGalleryDetail.comments);
args.putParcelable(GalleryCommentsScene.KEY_GALLERY_DETAIL, mGalleryDetail);
startScene(new Announcer(GalleryCommentsScene.class)
.setArgs(args)
.setRequestCode(this, REQUEST_CODE_COMMENT_GALLERY));
navigate(R.id.galleryCommentsScene, args);
} else if (mPreviews == v) {
if (null != mGalleryDetail) {
Bundle args = new Bundle();
args.putParcelable(GalleryPreviewsScene.KEY_GALLERY_INFO, mGalleryDetail);
startScene(new Announcer(GalleryPreviewsScene.class).setArgs(args));
navigate(R.id.galleryPreviewsScene, args);
}
} else {
Object o = v.getTag(R.id.tag);
@@ -1228,7 +1221,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
ListUrlBuilder lub = new ListUrlBuilder();
lub.setMode(ListUrlBuilder.MODE_TAG);
lub.setKeyword(tag);
GalleryListScene.startScene(this, lub);
navigate(R.id.galleryListScene, GalleryListScene.getStartArgs(lub), true);
return;
}
@@ -1353,9 +1346,8 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_VOTE_TAG)
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.gid, mGalleryDetail.token, tag, vote)
.setCallback(new VoteTagListener(context,
activity.getStageId(), getTag()));
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey, mGalleryDetail.getGid(), mGalleryDetail.getToken(), tag, vote)
.setCallback(new VoteTagListener(context));
request.enqueue(this);
}
@@ -1380,18 +1372,16 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
} else if (mHeart == v || mHeartOutline == v) {
if (mGalleryDetail != null && !mModifyingFavorites) {
boolean remove = false;
if (EhDB.containLocalFavorites(mGalleryDetail.gid) || mGalleryDetail.isFavorited) {
if (EhDB.containLocalFavorites(mGalleryDetail.getGid()) || mGalleryDetail.isFavorited) {
mModifyingFavorites = true;
CommonOperations.removeFromFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), true));
new ModifyFavoritesListener(activity, true));
remove = true;
}
if (!remove) {
mModifyingFavorites = true;
CommonOperations.addToFavorites(activity, mGalleryDetail,
new ModifyFavoritesListener(activity,
activity.getStageId(), getTag(), false), true);
new ModifyFavoritesListener(activity, false), true);
}
// Update UI
updateFavoriteDrawable();
@@ -1406,23 +1396,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
return false;
}
@Override
protected void onSceneResult(int requestCode, int resultCode, Bundle data) {
if (requestCode == REQUEST_CODE_COMMENT_GALLERY) {
if (resultCode != RESULT_OK || data == null) {
return;
}
GalleryCommentList comments = data.getParcelable(GalleryCommentsScene.KEY_COMMENT_LIST);
if (mGalleryDetail == null && comments == null) {
return;
}
mGalleryDetail.comments = comments;
bindComments(comments.comments);
} else {
super.onSceneResult(requestCode, resultCode, data);
}
}
private void updateDownloadText() {
if (null == mDownload) {
return;
@@ -1510,7 +1483,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void onRateGallerySuccess(RateGalleryParser.Result result) {
if (mGalleryDetail != null) {
mGalleryDetail.rating = result.rating;
mGalleryDetail.setRating(result.rating);
mGalleryDetail.ratingCount = result.ratingCount;
}
@@ -1524,7 +1497,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private void onModifyFavoritesSuccess(boolean addOrRemove) {
mModifyingFavorites = false;
if (mGalleryDetail != null) {
mGalleryDetail.isFavorited = !addOrRemove && mGalleryDetail.favoriteName != null;
mGalleryDetail.isFavorited = !addOrRemove && mGalleryDetail.getFavoriteName() != null;
updateFavoriteDrawable();
}
}
@@ -1537,68 +1510,15 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
mModifyingFavorites = false;
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl();
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
}
@IntDef({STATE_INIT, STATE_NORMAL, STATE_REFRESH, STATE_REFRESH_HEADER, STATE_FAILED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
}
private static class GetGalleryDetailListener extends EhCallback<GalleryDetailScene, GalleryDetail> {
public GetGalleryDetailListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(GalleryDetail result) {
getApplication().removeGlobalStuff(this);
// Put gallery detail to cache
EhApplication.getGalleryDetailCache().put(result.gid, result);
// Add history
EhDB.putHistoryInfo(result);
// Notify success
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onGetGalleryDetailSuccess(result);
}
}
@Override
public void onFailure(Exception e) {
getApplication().removeGlobalStuff(this);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onGetGalleryDetailFailure(e);
}
}
@Override
public void onCancel() {
getApplication().removeGlobalStuff(this);
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryDetailScene;
}
}
private static class VoteTagListener extends EhCallback<GalleryDetailScene, VoteTagParser.Result> {
public VoteTagListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
public VoteTagListener(Context context) {
super(context);
}
@Override
@@ -1618,99 +1538,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryDetailScene;
}
}
private static class RateGalleryListener extends EhCallback<GalleryDetailScene, RateGalleryParser.Result> {
private final long mGid;
public RateGalleryListener(Context context, int stageId, String sceneTag, long gid) {
super(context, stageId, sceneTag);
mGid = gid;
}
@Override
public void onSuccess(RateGalleryParser.Result result) {
showTip(R.string.rate_successfully, LENGTH_SHORT);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onRateGallerySuccess(result);
} else {
// Update rating in cache
GalleryDetail gd = EhApplication.getGalleryDetailCache().get(mGid);
if (gd != null) {
gd.rating = result.rating;
gd.ratingCount = result.ratingCount;
}
}
}
@Override
public void onFailure(Exception e) {
e.printStackTrace();
showTip(R.string.rate_failed, LENGTH_LONG);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryDetailScene;
}
}
private static class ModifyFavoritesListener extends EhCallback<GalleryDetailScene, Void> {
private final boolean mAddOrRemove;
/**
* @param addOrRemove false for add, true for remove
*/
public ModifyFavoritesListener(Context context, int stageId, String sceneTag, boolean addOrRemove) {
super(context, stageId, sceneTag);
mAddOrRemove = addOrRemove;
}
@Override
public void onSuccess(Void result) {
showTip(mAddOrRemove ? R.string.remove_from_favorite_success :
R.string.add_to_favorite_success, LENGTH_SHORT);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesSuccess(mAddOrRemove);
}
}
@Override
public void onFailure(Exception e) {
showTip(mAddOrRemove ? R.string.remove_from_favorite_failure :
R.string.add_to_favorite_failure, LENGTH_LONG);
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesFailure();
}
}
@Override
public void onCancel() {
GalleryDetailScene scene = getScene();
if (scene != null) {
scene.onModifyFavoritesCancel();
}
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryDetailScene;
}
}
private static class DownloadArchiveListener extends EhCallback<GalleryDetailScene, String> {
@@ -1718,7 +1545,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
private final GalleryInfo mGalleryInfo;
public DownloadArchiveListener(Context context, int stageId, String sceneTag, GalleryInfo galleryInfo) {
super(context, stageId, sceneTag);
super(context);
mGalleryInfo = galleryInfo;
}
@@ -1727,7 +1554,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (result != null) {
// TODO: Don't use buggy system download service
DownloadManager.Request r = new DownloadManager.Request(Uri.parse(result));
var name = mGalleryInfo.gid + "-" + EhUtils.getSuitableTitle(mGalleryInfo) + ".zip";
var name = mGalleryInfo.getGid() + "-" + EhUtils.getSuitableTitle(mGalleryInfo) + ".zip";
r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,
FileUtils.sanitizeFilename(name));
r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
@@ -1756,11 +1583,6 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryDetailScene;
}
}
private static class DeleteDialogHelper implements DialogInterface.OnClickListener {
@@ -1783,20 +1605,117 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
// Delete
if (null != mDownloadManager) {
mDownloadManager.deleteDownload(mGalleryInfo.gid);
mDownloadManager.deleteDownload(mGalleryInfo.getGid());
}
// Delete image files
boolean checked = mBuilder.isChecked();
Settings.putRemoveImageFiles(checked);
if (checked) {
UniFile file = SpiderDen.getGalleryDownloadDir(mGalleryInfo.gid);
EhDB.removeDownloadDirname(mGalleryInfo.gid);
UniFile file = SpiderDen.getGalleryDownloadDir(mGalleryInfo.getGid());
EhDB.removeDownloadDirname(mGalleryInfo.getGid());
deleteFileAsync(file);
}
}
}
private class GetGalleryDetailListener extends EhCallback<GalleryDetailScene, GalleryDetail> {
public GetGalleryDetailListener(Context context) {
super(context);
}
@Override
public void onSuccess(GalleryDetail result) {
getApplication().removeGlobalStuff(this);
// Put gallery detail to cache
EhApplication.getGalleryDetailCache().put(result.getGid(), result);
// Add history
EhDB.putHistoryInfo(result);
// Notify success
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onGetGalleryDetailSuccess(result);
}
@Override
public void onFailure(Exception e) {
getApplication().removeGlobalStuff(this);
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onGetGalleryDetailFailure(e);
}
@Override
public void onCancel() {
getApplication().removeGlobalStuff(this);
}
}
private class RateGalleryListener extends EhCallback<GalleryDetailScene, RateGalleryParser.Result> {
private final long mGid;
public RateGalleryListener(Context context, int stageId, String sceneTag, long gid) {
super(context);
mGid = gid;
}
@Override
public void onSuccess(RateGalleryParser.Result result) {
showTip(R.string.rate_successfully, LENGTH_SHORT);
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onRateGallerySuccess(result);
}
@Override
public void onFailure(Exception e) {
e.printStackTrace();
showTip(R.string.rate_failed, LENGTH_LONG);
}
@Override
public void onCancel() {
}
}
private class ModifyFavoritesListener extends EhCallback<GalleryDetailScene, Void> {
private final boolean mAddOrRemove;
/**
* @param addOrRemove false for add, true for remove
*/
public ModifyFavoritesListener(Context context, boolean addOrRemove) {
super(context);
mAddOrRemove = addOrRemove;
}
@Override
public void onSuccess(Void result) {
showTip(mAddOrRemove ? R.string.remove_from_favorite_success :
R.string.add_to_favorite_success, LENGTH_SHORT);
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onModifyFavoritesSuccess(mAddOrRemove);
}
@Override
public void onFailure(Exception e) {
showTip(mAddOrRemove ? R.string.remove_from_favorite_failure :
R.string.add_to_favorite_failure, LENGTH_LONG);
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onModifyFavoritesFailure();
}
@Override
public void onCancel() {
GalleryDetailScene scene = GalleryDetailScene.this;
scene.onModifyFavoritesCancel();
}
}
private class ArchiveListDialogHelper implements AdapterView.OnItemClickListener,
DialogInterface.OnDismissListener, EhClient.Callback<ArchiveParser.Result> {
@@ -1861,8 +1780,8 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
boolean isHAtH = mArchiveList.get(position).isHAtH();
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_DOWNLOAD_ARCHIVE);
request.setArgs(mGalleryDetail.gid, mGalleryDetail.token, mArchiveFormParamOr, res, isHAtH);
request.setCallback(new DownloadArchiveListener(context, activity.getStageId(), getTag(), mGalleryDetail));
request.setArgs(mGalleryDetail.getGid(), mGalleryDetail.getToken(), mArchiveFormParamOr, res, isHAtH);
request.setCallback(new DownloadArchiveListener(context, 1, getTag(), mGalleryDetail));
request.enqueue(GalleryDetailScene.this);
}
@@ -1984,9 +1903,11 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
if (dm != null) {
try {
dm.enqueue(r);
showTip(R.string.download_torrent_started, LENGTH_SHORT);
} catch (Throwable e) {
e.printStackTrace();
ExceptionUtils.throwIfFatal(e);
showTip(R.string.download_torrent_failure, LENGTH_SHORT);
}
}
}
@@ -2071,9 +1992,9 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_RATE_GALLERY)
.setArgs(mGalleryDetail.apiUid, mGalleryDetail.apiKey,
mGalleryDetail.gid, mGalleryDetail.token, mRatingBar.getRating())
mGalleryDetail.getGid(), mGalleryDetail.getToken(), mRatingBar.getRating())
.setCallback(new RateGalleryListener(context,
activity.getStageId(), getTag(), mGalleryDetail.gid));
1, getTag(), mGalleryDetail.getGid()));
request.enqueue(GalleryDetailScene.this);
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -49,7 +49,6 @@ import com.hippo.ehviewer.client.data.GalleryPreview;
import com.hippo.ehviewer.client.data.PreviewSet;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.recyclerview.AutoGridLayoutManager;
@@ -238,7 +237,7 @@ public class GalleryPreviewsScene extends BaseToolbarScene {
int size = previewSet.size();
ArrayList<GalleryPreview> list = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
list.add(previewSet.getGalleryPreview(mGalleryInfo.gid, i));
list.add(previewSet.getGalleryPreview(mGalleryInfo.getGid(), i));
}
mHelper.onGetPageData(taskId, result.second, 0, null, null, list);
@@ -251,42 +250,6 @@ public class GalleryPreviewsScene extends BaseToolbarScene {
}
}
private static class GetPreviewSetListener extends EhCallback<GalleryPreviewsScene, Pair<PreviewSet, Integer>> {
private final int mTaskId;
public GetPreviewSetListener(Context context, int stageId, String sceneTag, int taskId) {
super(context, stageId, sceneTag);
mTaskId = taskId;
}
@Override
public void onSuccess(Pair<PreviewSet, Integer> result) {
GalleryPreviewsScene scene = getScene();
if (scene != null) {
scene.onGetPreviewSetSuccess(result, mTaskId);
}
}
@Override
public void onFailure(Exception e) {
GalleryPreviewsScene scene = getScene();
if (scene != null) {
scene.onGetPreviewSetFailure(e, mTaskId);
}
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof GalleryPreviewsScene;
}
}
private static class GalleryPreviewHolder extends RecyclerView.ViewHolder {
public LoadImageView image;
@@ -300,6 +263,33 @@ public class GalleryPreviewsScene extends BaseToolbarScene {
}
}
private class GetPreviewSetListener extends EhCallback<GalleryPreviewsScene, Pair<PreviewSet, Integer>> {
private final int mTaskId;
public GetPreviewSetListener(Context context, int stageId, String sceneTag, int taskId) {
super(context);
mTaskId = taskId;
}
@Override
public void onSuccess(Pair<PreviewSet, Integer> result) {
GalleryPreviewsScene scene = GalleryPreviewsScene.this;
scene.onGetPreviewSetSuccess(result, mTaskId);
}
@Override
public void onFailure(Exception e) {
GalleryPreviewsScene scene = GalleryPreviewsScene.this;
scene.onGetPreviewSetFailure(e, mTaskId);
}
@Override
public void onCancel() {
}
}
private class GalleryPreviewAdapter extends RecyclerView.Adapter<GalleryPreviewHolder> {
private final LayoutInflater mInflater;
@@ -344,11 +334,11 @@ public class GalleryPreviewsScene extends BaseToolbarScene {
return;
}
String url = EhUrl.getGalleryDetailUrl(mGalleryInfo.gid, mGalleryInfo.token, page, false);
String url = EhUrl.getGalleryDetailUrl(mGalleryInfo.getGid(), mGalleryInfo.getToken(), page, false);
EhRequest request = new EhRequest();
request.setMethod(EhClient.METHOD_GET_PREVIEW_SET);
request.setCallback(new GetPreviewSetListener(getContext(),
activity.getStageId(), getTag(), taskId));
1, getTag(), taskId));
request.setArgs(url);
request.enqueue(GalleryPreviewsScene.this);
}
@@ -48,13 +48,13 @@ import com.hippo.ehviewer.download.DownloadManager.DownloadInfoListener
import com.hippo.ehviewer.ui.CommonOperations
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
import com.hippo.ehviewer.widget.SimpleRatingView
import com.hippo.scene.Announcer
import com.hippo.scene.SceneFragment
import com.hippo.view.ViewTransition
import com.hippo.widget.LoadImageView
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
import com.hippo.yorozuya.ViewUtils
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import rikka.core.res.resolveColor
@@ -112,10 +112,6 @@ class HistoryScene : BaseToolbarScene() {
mFavouriteStatusRouter.removeListener(mFavouriteStatusRouterListener)
}
override fun getNavCheckedItem(): Int {
return R.id.nav_history
}
@SuppressLint("NotifyDataSetChanged")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -196,9 +192,13 @@ class HistoryScene : BaseToolbarScene() {
if (DialogInterface.BUTTON_POSITIVE != which) {
return@setPositiveButton
}
EhDB.clearHistoryInfo()
mAdapter.refresh()
mAdapter.notifyDataSetChanged()
lifecycleScope.launchIO {
EhDB.clearHistoryInfo()
withUIContext {
mAdapter.refresh()
mAdapter.notifyDataSetChanged()
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.show()
@@ -217,8 +217,7 @@ class HistoryScene : BaseToolbarScene() {
val args = Bundle()
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GALLERY_INFO)
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, gi)
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
startScene(announcer)
navigate(R.id.galleryDetailScene, args)
return true
}
@@ -287,13 +286,13 @@ class HistoryScene : BaseToolbarScene() {
CommonOperations.removeFromFavorites(
activity,
gi,
RemoveFromFavoriteListener(context, activity.stageId, tag)
RemoveFromFavoriteListener(context)
)
} else {
CommonOperations.addToFavorites(
activity,
gi,
AddToFavoriteListener(context, activity.stageId, tag)
AddToFavoriteListener(context)
)
}
@@ -319,8 +318,8 @@ class HistoryScene : BaseToolbarScene() {
return true
}
private class AddToFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
private class AddToFavoriteListener(context: Context?) :
EhCallback<GalleryListScene?, Void?>(context) {
override fun onSuccess(result: Void?) {
showTip(R.string.add_to_favorite_success, LENGTH_SHORT)
}
@@ -330,13 +329,10 @@ class HistoryScene : BaseToolbarScene() {
}
override fun onCancel() {}
override fun isInstance(scene: SceneFragment): Boolean {
return scene is GalleryListScene
}
}
private class RemoveFromFavoriteListener(context: Context?, stageId: Int, sceneTag: String?) :
EhCallback<GalleryListScene?, Void?>(context, stageId, sceneTag) {
private class RemoveFromFavoriteListener(context: Context?) :
EhCallback<GalleryListScene?, Void?>(context) {
override fun onSuccess(result: Void?) {
showTip(R.string.remove_from_favorite_success, LENGTH_SHORT)
}
@@ -346,9 +342,6 @@ class HistoryScene : BaseToolbarScene() {
}
override fun onCancel() {}
override fun isInstance(scene: SceneFragment): Boolean {
return scene is GalleryListScene
}
}
private class HistoryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
@@ -403,7 +396,7 @@ class HistoryScene : BaseToolbarScene() {
override fun onBindViewHolder(holder: HistoryHolder, position: Int) {
val gi: GalleryInfo? = getItem(position)
gi ?: return
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb!!)
gi.thumb?.let { holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), it) }
holder.title.text = EhUtils.getSuitableTitle(gi)
holder.uploader.text = gi.uploader
holder.rating.rating = gi.rating
@@ -450,9 +443,13 @@ class HistoryScene : BaseToolbarScene() {
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
val mPosition = viewHolder.bindingAdapterPosition
val info: HistoryInfo? = mAdapter.peek(mPosition)
info?.let { EhDB.deleteHistoryInfo(info) }
mAdapter.refresh()
lifecycleScope.launchIO {
val info: HistoryInfo? = mAdapter.peek(mPosition)
info?.let { EhDB.deleteHistoryInfo(info) }
withUIContext {
mAdapter.refresh()
}
}
}
}
}
@@ -32,8 +32,6 @@ import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.util.ExceptionUtils;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.AssertUtils;
@@ -97,7 +95,7 @@ public final class ProgressScene extends BaseScene implements View.OnClickListen
.setMethod(EhClient.METHOD_GET_GALLERY_TOKEN)
.setArgs(mGid, mPToken, mPage)
.setCallback(new GetGalleryTokenListener(context,
activity.getStageId(), getTag()));
1, getTag()));
request.enqueue(this);
return true;
}
@@ -209,8 +207,7 @@ public final class ProgressScene extends BaseScene implements View.OnClickListen
arg.putLong(GalleryDetailScene.KEY_GID, mGid);
arg.putString(GalleryDetailScene.KEY_TOKEN, result);
arg.putInt(GalleryDetailScene.KEY_PAGE, mPage);
startScene(new Announcer(GalleryDetailScene.class).setArgs(arg));
finish();
navigate(R.id.galleryDetailScene, arg);
}
private void onGetGalleryTokenFailure(Exception e) {
@@ -226,35 +223,26 @@ public final class ProgressScene extends BaseScene implements View.OnClickListen
}
}
private static class GetGalleryTokenListener extends EhCallback<ProgressScene, String> {
private class GetGalleryTokenListener extends EhCallback<ProgressScene, String> {
public GetGalleryTokenListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
super(context);
}
@Override
public void onSuccess(String result) {
ProgressScene scene = getScene();
if (scene != null) {
scene.onGetGalleryTokenSuccess(result);
}
ProgressScene scene = ProgressScene.this;
scene.onGetGalleryTokenSuccess(result);
}
@Override
public void onFailure(Exception e) {
ProgressScene scene = getScene();
if (scene != null) {
scene.onGetGalleryTokenFailure(e);
}
ProgressScene scene = ProgressScene.this;
scene.onGetGalleryTokenFailure(e);
}
@Override
public void onCancel() {
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof ProgressScene;
}
}
}
@@ -1,97 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.yorozuya.ViewUtils;
public class SelectSiteScene extends SolidScene implements View.OnClickListener {
private MaterialButtonToggleGroup mButtonGroup;
private View mOk;
@Override
public boolean needShowLeftDrawer() {
return false;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_select_site, container, false);
mButtonGroup = (MaterialButtonToggleGroup) ViewUtils.$$(view, R.id.button_group);
if (EhApplication.getEhCookieStore().hasSignedIn()) {
((MaterialButton) view.findViewById(R.id.site_ex)).setChecked(true);
} else {
((MaterialButton) view.findViewById(R.id.site_e)).setChecked(true);
}
mOk = ViewUtils.$$(view, R.id.ok);
mOk.setOnClickListener(this);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mButtonGroup = null;
mOk = null;
}
@Override
public void onClick(View v) {
MainActivity activity = getMainActivity();
if (null == activity || null == mButtonGroup) {
return;
}
if (v == mOk) {
int id = mButtonGroup.getCheckedButtonId();
if (id == R.id.site_e) {
Settings.putSelectSite(false);
Settings.putGallerySite(EhUrl.SITE_E);
startSceneForCheckStep(CHECK_STEP_SELECT_SITE, getArguments());
finish();
} else if (id == R.id.site_ex) {
Settings.putSelectSite(false);
Settings.putGallerySite(EhUrl.SITE_EX);
startSceneForCheckStep(CHECK_STEP_SELECT_SITE, getArguments());
finish();
} else {
showTip(R.string.no_select, LENGTH_SHORT);
}
}
}
}
@@ -1,438 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.content.Context;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.app.BaseDialogBuilder;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.UrlOpener;
import com.hippo.ehviewer.client.EhClient;
import com.hippo.ehviewer.client.EhRequest;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.parser.ProfileParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.util.ExceptionUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.IntIdGenerator;
import com.hippo.yorozuya.ViewUtils;
public final class SignInScene extends SolidScene implements EditText.OnEditorActionListener,
View.OnClickListener {
private static final String KEY_REQUEST_ID = "request_id";
private static final int REQUEST_CODE_WEBVIEW = 0;
private static final int REQUEST_CODE_COOKIE = 0;
/*---------------
View life cycle
---------------*/
@Nullable
private View mProgress;
@Nullable
private TextInputLayout mUsernameLayout;
@Nullable
private TextInputLayout mPasswordLayout;
@Nullable
private EditText mUsername;
@Nullable
private EditText mPassword;
@Nullable
private View mRegister;
@Nullable
private View mSignIn;
@Nullable
private TextView mSignInViaWebView;
@Nullable
private TextView mSignInViaCookies;
@Nullable
private TextView mSkipSigningIn;
private boolean mSigningIn;
private int mRequestId = IntIdGenerator.INVALID_ID;
@Override
public boolean needShowLeftDrawer() {
return false;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
onInit();
} else {
onRestore(savedInstanceState);
}
}
private void onInit() {
}
private void onRestore(Bundle savedInstanceState) {
mRequestId = savedInstanceState.getInt(KEY_REQUEST_ID);
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_REQUEST_ID, mRequestId);
}
@NonNull
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_login, container, false);
View loginForm = ViewUtils.$$(view, R.id.login_form);
mProgress = ViewUtils.$$(view, R.id.progress);
mUsernameLayout = (TextInputLayout) ViewUtils.$$(loginForm, R.id.username_layout);
mUsername = mUsernameLayout.getEditText();
AssertUtils.assertNotNull(mUsername);
mPasswordLayout = (TextInputLayout) ViewUtils.$$(loginForm, R.id.password_layout);
mPassword = mPasswordLayout.getEditText();
AssertUtils.assertNotNull(mPassword);
mRegister = ViewUtils.$$(loginForm, R.id.register);
mSignIn = ViewUtils.$$(loginForm, R.id.sign_in);
mSignInViaWebView = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_webview);
mSignInViaCookies = (TextView) ViewUtils.$$(loginForm, R.id.sign_in_via_cookies);
mSkipSigningIn = (TextView) ViewUtils.$$(loginForm, R.id.tourist_mode);
mSignInViaWebView.setPaintFlags(mSignInViaWebView.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mSignInViaCookies.setPaintFlags(mSignInViaCookies.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mSkipSigningIn.setPaintFlags(mSignInViaCookies.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
mPassword.setOnEditorActionListener(this);
mRegister.setOnClickListener(this);
mSignIn.setOnClickListener(this);
mSignInViaWebView.setOnClickListener(this);
mSignInViaCookies.setOnClickListener(this);
mSkipSigningIn.setOnClickListener(this);
Context context = getContext();
AssertUtils.assertNotNull(context);
EhApplication application = (EhApplication) context.getApplicationContext();
if (application.containGlobalStuff(mRequestId)) {
mSigningIn = true;
// request exist
showProgress(false);
}
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Show IME
if (mProgress != null && View.INVISIBLE != mProgress.getVisibility()) {
showSoftInput(mUsername);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mProgress = null;
mUsernameLayout = null;
mPasswordLayout = null;
mUsername = null;
mPassword = null;
mRegister = null;
mSignIn = null;
mSignInViaWebView = null;
mSignInViaCookies = null;
mSkipSigningIn = null;
}
private void showProgress(boolean animation) {
if (null != mProgress && View.VISIBLE != mProgress.getVisibility()) {
if (animation) {
mProgress.setAlpha(0.0f);
mProgress.setVisibility(View.VISIBLE);
mProgress.animate().alpha(1.0f).setDuration(500).start();
} else {
mProgress.setAlpha(1.0f);
mProgress.setVisibility(View.VISIBLE);
}
}
}
private void hideProgress() {
if (null != mProgress) {
mProgress.setVisibility(View.GONE);
}
}
@Override
protected void onSceneResult(int requestCode, int resultCode, Bundle data) {
if (REQUEST_CODE_WEBVIEW == requestCode) {
if (RESULT_OK == resultCode) {
getProfile();
}
} else {
super.onSceneResult(requestCode, resultCode, data);
}
}
@Override
public void onClick(View v) {
MainActivity activity = getMainActivity();
if (null == activity) {
return;
}
if (mRegister == v) {
UrlOpener.openUrl(activity, EhUrl.URL_REGISTER, false);
} else if (mSignIn == v) {
signIn();
} else if (mSignInViaWebView == v) {
startScene(new Announcer(WebViewSignInScene.class).setRequestCode(this, REQUEST_CODE_WEBVIEW));
} else if (mSignInViaCookies == v) {
startScene(new Announcer(CookieSignInScene.class).setRequestCode(this, REQUEST_CODE_COOKIE));
} else if (mSkipSigningIn == v) {
// Set gallery size SITE_E if skip sign in
Settings.putGallerySite(EhUrl.SITE_E);
redirectTo();
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (v == mPassword) {
if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_NULL) {
signIn();
return true;
}
}
return false;
}
private void signIn() {
if (mSigningIn) {
return;
}
Context context = getContext();
MainActivity activity = getMainActivity();
if (null == context || null == activity || null == mUsername || null == mPassword || null == mUsernameLayout ||
null == mPasswordLayout) {
return;
}
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
if (username.isEmpty()) {
mUsernameLayout.setError(getString(R.string.error_username_cannot_empty));
return;
} else {
mUsernameLayout.setError(null);
}
if (password.isEmpty()) {
mPasswordLayout.setError(getString(R.string.error_password_cannot_empty));
return;
} else {
mPasswordLayout.setError(null);
}
hideSoftInput();
showProgress(true);
// Clean up for sign in
EhUtils.signOut();
EhCallback<?, ?> callback = new SignInListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_SIGN_IN)
.setArgs(username, password)
.setCallback(callback);
request.enqueue(this);
mSigningIn = true;
}
private void getProfile() {
Context context = getContext();
MainActivity activity = getMainActivity();
if (null == context || null == activity) {
return;
}
hideSoftInput();
showProgress(true);
EhCallback<?, ?> callback = new GetProfileListener(context,
activity.getStageId(), getTag());
mRequestId = ((EhApplication) context.getApplicationContext()).putGlobalStuff(callback);
EhRequest request = new EhRequest()
.setMethod(EhClient.METHOD_GET_PROFILE)
.setCallback(callback);
request.enqueue();
new EhRequest().setMethod(EhClient.METHOD_GET_UCONFIG).enqueue();
}
private void redirectTo() {
Settings.putNeedSignIn(false);
MainActivity activity = getMainActivity();
if (null != activity) {
startSceneForCheckStep(CHECK_STEP_SIGN_IN, getArguments());
}
finish();
}
private void whetherToSkip(Exception e) {
Context context = getContext();
if (null == context) {
return;
}
new BaseDialogBuilder(context)
.setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.sign_in_failed_tip))
.setPositiveButton(R.string.get_it, null)
.show();
}
public void onSignInEnd(Exception e) {
Context context = getContext();
if (null == context) {
return;
}
if (EhApplication.getEhCookieStore().hasSignedIn()) {
getProfile();
} else {
mSigningIn = false;
hideProgress();
whetherToSkip(e);
}
}
public void onGetProfileEnd() {
mSigningIn = false;
redirectTo();
}
private static class SignInListener extends EhCallback<SignInScene, String> {
public SignInListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(String result) {
getApplication().removeGlobalStuff(this);
Settings.putDisplayName(result);
SignInScene scene = getScene();
if (scene != null) {
scene.onSignInEnd(null);
}
}
@Override
public void onFailure(Exception e) {
getApplication().removeGlobalStuff(this);
e.printStackTrace();
SignInScene scene = getScene();
if (scene != null) {
scene.onSignInEnd(e);
}
}
@Override
public void onCancel() {
getApplication().removeGlobalStuff(this);
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof SignInScene;
}
}
private static class GetProfileListener extends EhCallback<SignInScene, ProfileParser.Result> {
public GetProfileListener(Context context, int stageId, String sceneTag) {
super(context, stageId, sceneTag);
}
@Override
public void onSuccess(ProfileParser.Result result) {
getApplication().removeGlobalStuff(this);
Settings.putDisplayName(result.displayName);
Settings.putAvatar(result.avatar);
SignInScene scene = getScene();
if (scene != null) {
scene.onGetProfileEnd();
}
}
@Override
public void onFailure(Exception e) {
getApplication().removeGlobalStuff(this);
e.printStackTrace();
SignInScene scene = getScene();
if (scene != null) {
scene.onGetProfileEnd();
}
}
@Override
public void onCancel() {
getApplication().removeGlobalStuff(this);
}
@Override
public boolean isInstance(SceneFragment scene) {
return scene instanceof SignInScene;
}
}
}
@@ -1,70 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.os.Bundle;
import android.util.Log;
import com.hippo.ehviewer.Settings;
import com.hippo.scene.Announcer;
/**
* Scene for safety, can't be covered
*/
public class SolidScene extends BaseScene {
public static final int CHECK_STEP_SIGN_IN = 1;
public static final int CHECK_STEP_SELECT_SITE = 2;
public static final String KEY_TARGET_SCENE = "target_scene";
public static final String KEY_TARGET_ARGS = "target_args";
private static final String TAG = SolidScene.class.getSimpleName();
public void startSceneForCheckStep(int checkStep, Bundle args) {
switch (checkStep) {
case CHECK_STEP_SIGN_IN:
if (Settings.getSelectSite()) {
startScene(new Announcer(SelectSiteScene.class).setArgs(args), true);
break;
}
case CHECK_STEP_SELECT_SITE:
String targetScene = null;
Bundle targetArgs = null;
if (null != args) {
targetScene = args.getString(KEY_TARGET_SCENE);
targetArgs = args.getBundle(KEY_TARGET_ARGS);
}
Class<?> clazz = null;
if (targetScene != null) {
try {
clazz = Class.forName(targetScene);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Can't find class with name: " + targetScene);
}
}
if (clazz != null) {
startScene(new Announcer(clazz).setArgs(targetArgs));
} else {
Bundle newArgs = new Bundle();
newArgs.putString(GalleryListScene.KEY_ACTION, Settings.getLaunchPageGalleryListSceneAction());
startScene(new Announcer(GalleryListScene.class).setArgs(newArgs));
}
break;
}
}
}
@@ -1,155 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.CookieManager;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.client.EhCookieStore;
import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.widget.DialogWebChromeClient;
import com.hippo.yorozuya.AssertUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import rikka.core.res.ResourcesKt;
public class WebViewSignInScene extends SolidScene {
/*---------------
View life cycle
---------------*/
@Nullable
private WebView mWebView;
@Override
public boolean needShowLeftDrawer() {
return false;
}
@Nullable
@Override
@SuppressLint("SetJavaScriptEnabled")
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
Context context = getContext();
AssertUtils.assertNotNull(context);
EhUtils.signOut();
// http://stackoverflow.com/questions/32284642/how-to-handle-an-uncatched-exception
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.flush();
cookieManager.removeAllCookies(null);
cookieManager.removeSessionCookies(null);
mWebView = new WebView(context);
mWebView.setBackgroundColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.colorBackground));
mWebView.getSettings().setBuiltInZoomControls(true);
mWebView.getSettings().setDisplayZoomControls(false);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new LoginWebViewClient());
mWebView.setWebChromeClient(new DialogWebChromeClient(context));
mWebView.loadUrl(EhUrl.URL_SIGN_IN);
return mWebView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (null != mWebView) {
mWebView.destroy();
mWebView = null;
}
}
private class LoginWebViewClient extends WebViewClient {
public List<Cookie> parseCookies(HttpUrl url, String cookieStrings) {
if (cookieStrings == null) {
return Collections.emptyList();
}
List<Cookie> cookies = null;
String[] pieces = cookieStrings.split(";");
for (String piece : pieces) {
Cookie cookie = Cookie.parse(url, piece);
if (cookie == null) {
continue;
}
if (cookies == null) {
cookies = new ArrayList<>();
}
cookies.add(cookie);
}
return cookies != null ? cookies : Collections.emptyList();
}
private void addCookie(String domain, Cookie cookie) {
EhApplication.getEhCookieStore().addCookie(EhCookieStore.newCookie(cookie, domain, true, true, true));
}
@Override
public void onPageFinished(WebView view, String url) {
Context context = getContext();
if (context == null) {
return;
}
HttpUrl httpUrl = HttpUrl.parse(url);
if (httpUrl == null) {
return;
}
String cookieString = CookieManager.getInstance().getCookie(EhUrl.HOST_E);
List<Cookie> cookies = parseCookies(httpUrl, cookieString);
boolean getId = false;
boolean getHash = false;
for (Cookie cookie : cookies) {
if (EhCookieStore.KEY_IPD_MEMBER_ID.equals(cookie.name())) {
getId = true;
} else if (EhCookieStore.KEY_IPD_PASS_HASH.equals(cookie.name())) {
getHash = true;
}
addCookie(EhUrl.DOMAIN_EX, cookie);
addCookie(EhUrl.DOMAIN_E, cookie);
}
if (getId && getHash) {
setResult(RESULT_OK, null);
finish();
}
}
}
}
@@ -0,0 +1,229 @@
package com.hippo.ehviewer.widget;
import static androidx.core.util.ObjectsCompat.requireNonNull;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.math.MathUtils;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.drakeet.drawer.FullDraggableHelper;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.yorozuya.LayoutUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class AboveSnackerLayout extends FrameLayout implements CoordinatorLayout.AttachedBehavior, FullDraggableHelper.Callback {
@NonNull
private final FullDraggableHelper helper;
private DrawerLayout drawerLayout;
private List<View> mAboveSnackViewList;
public AboveSnackerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
helper = new FullDraggableHelper(context, this);
}
public AboveSnackerLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
helper = new FullDraggableHelper(context, this);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ensureDrawerLayout();
}
private void ensureDrawerLayout() {
ViewParent parent = getParent().getParent();
if (!(parent instanceof DrawerLayout)) {
throw new IllegalStateException("This " + this + " must be added to a DrawerLayout");
}
drawerLayout = (DrawerLayout) parent;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return helper.onInterceptTouchEvent(event);
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouchEvent(MotionEvent event) {
return helper.onTouchEvent(event);
}
@NonNull
@Override
public View getDrawerMainContainer() {
return this;
}
@Override
public boolean isDrawerOpen(int gravity) {
return drawerLayout.isDrawerOpen(gravity);
}
@Override
public boolean hasEnabledDrawer(int gravity) {
return drawerLayout.getDrawerLockMode(gravity) == DrawerLayout.LOCK_MODE_UNLOCKED
&& findDrawerWithGravity(gravity) != null;
}
@Override
public void offsetDrawer(int gravity, float offset) {
setDrawerToOffset(gravity, offset);
drawerLayout.invalidate();
}
@Override
public void smoothOpenDrawer(int gravity) {
drawerLayout.openDrawer(gravity, true);
}
@Override
public void smoothCloseDrawer(int gravity) {
drawerLayout.closeDrawer(gravity, true);
}
@Override
public void onDrawerDragging() {
List<DrawerLayout.DrawerListener> drawerListeners = getDrawerListeners();
if (drawerListeners != null) {
int listenerCount = drawerListeners.size();
for (int i = listenerCount - 1; i >= 0; --i) {
drawerListeners.get(i).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
}
}
}
@Nullable
protected List<DrawerLayout.DrawerListener> getDrawerListeners() {
try {
Field field = DrawerLayout.class.getDeclaredField("mListeners");
field.setAccessible(true);
// noinspection unchecked
return (List<DrawerLayout.DrawerListener>) field.get(drawerLayout);
} catch (Exception e) {
// throw to let developer know the api is changed
throw new RuntimeException(e);
}
}
protected void setDrawerToOffset(int gravity, float offset) {
View drawerView = findDrawerWithGravity(gravity);
float slideOffsetPercent = MathUtils.clamp(offset / requireNonNull(drawerView).getWidth(), 0f, 1f);
try {
Method method = DrawerLayout.class.getDeclaredMethod("moveDrawerToOffset", View.class, float.class);
method.setAccessible(true);
method.invoke(drawerLayout, drawerView, slideOffsetPercent);
drawerView.setVisibility(VISIBLE);
} catch (Exception e) {
// throw to let developer know the api is changed
throw new RuntimeException(e);
}
}
// Copied from DrawerLayout
@Nullable
private View findDrawerWithGravity(int gravity) {
final int absHorizontalGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerLayout)) & Gravity.HORIZONTAL_GRAVITY_MASK;
final int childCount = drawerLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = drawerLayout.getChildAt(i);
final int childAbsGravity = getDrawerViewAbsoluteGravity(child);
if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizontalGravity) {
return child;
}
}
return null;
}
// Copied from DrawerLayout
private int getDrawerViewAbsoluteGravity(View drawerView) {
final int gravity = ((DrawerLayout.LayoutParams) drawerView.getLayoutParams()).gravity;
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(drawerLayout));
}
public void addAboveSnackView(View view) {
if (null == mAboveSnackViewList) {
mAboveSnackViewList = new ArrayList<>();
}
mAboveSnackViewList.add(view);
}
public void removeAboveSnackView(View view) {
if (null == mAboveSnackViewList) {
return;
}
mAboveSnackViewList.remove(view);
}
public int getAboveSnackViewCount() {
return null == mAboveSnackViewList ? 0 : mAboveSnackViewList.size();
}
@Nullable
public View getAboveSnackViewAt(int index) {
if (null == mAboveSnackViewList || index < 0 || index >= mAboveSnackViewList.size()) {
return null;
} else {
return mAboveSnackViewList.get(index);
}
}
@NonNull
@Override
public AboveSnackerLayout.Behavior getBehavior() {
return new AboveSnackerLayout.Behavior();
}
public static class Behavior extends CoordinatorLayout.Behavior<AboveSnackerLayout> {
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull AboveSnackerLayout child, @NonNull View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, AboveSnackerLayout child, @NonNull View dependency) {
for (int i = 0, n = child.getAboveSnackViewCount(); i < n; i++) {
View view = child.getAboveSnackViewAt(i);
if (view != null) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight() - LayoutUtils.dp2pix(view.getContext(), 8));
ViewCompat.animate(view).setInterpolator(new FastOutSlowInInterpolator()).translationY(translationY).setDuration(150).start();
}
}
return false;
}
@Override
public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull AboveSnackerLayout child, @NonNull View dependency) {
for (int i = 0, n = child.getAboveSnackViewCount(); i < n; i++) {
View view = child.getAboveSnackViewAt(i);
if (view != null) {
ViewCompat.animate(view).setInterpolator(new FastOutSlowInInterpolator()).translationY(0).setDuration(75).start();
}
}
}
}
}
@@ -1,112 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer 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.
*
* EhViewer 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 EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.view.ViewCompat;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.scene.StageLayout;
import com.hippo.yorozuya.LayoutUtils;
import java.util.ArrayList;
import java.util.List;
public class EhStageLayout extends StageLayout implements CoordinatorLayout.AttachedBehavior {
private List<View> mAboveSnackViewList;
public EhStageLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EhStageLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public void addAboveSnackView(View view) {
if (null == mAboveSnackViewList) {
mAboveSnackViewList = new ArrayList<>();
}
mAboveSnackViewList.add(view);
}
public void removeAboveSnackView(View view) {
if (null == mAboveSnackViewList) {
return;
}
mAboveSnackViewList.remove(view);
}
public int getAboveSnackViewCount() {
return null == mAboveSnackViewList ? 0 : mAboveSnackViewList.size();
}
@Nullable
public View getAboveSnackViewAt(int index) {
if (null == mAboveSnackViewList || index < 0 || index >= mAboveSnackViewList.size()) {
return null;
} else {
return mAboveSnackViewList.get(index);
}
}
@NonNull
@Override
public EhStageLayout.Behavior getBehavior() {
return new EhStageLayout.Behavior();
}
public static class Behavior extends CoordinatorLayout.Behavior<EhStageLayout> {
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull EhStageLayout child, @NonNull View dependency) {
return dependency instanceof Snackbar.SnackbarLayout;
}
@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, EhStageLayout child, @NonNull View dependency) {
for (int i = 0, n = child.getAboveSnackViewCount(); i < n; i++) {
View view = child.getAboveSnackViewAt(i);
if (view != null) {
float translationY = Math.min(0, dependency.getTranslationY() - dependency.getHeight() - LayoutUtils.dp2pix(view.getContext(), 8));
ViewCompat.animate(view).setInterpolator(new FastOutSlowInInterpolator()).translationY(translationY).setDuration(150).start();
}
}
return false;
}
@Override
public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull EhStageLayout child, @NonNull View dependency) {
for (int i = 0, n = child.getAboveSnackViewCount(); i < n; i++) {
View view = child.getAboveSnackViewAt(i);
if (view != null) {
ViewCompat.animate(view).setInterpolator(new FastOutSlowInInterpolator()).translationY(0).setDuration(75).start();
}
}
}
}
}
@@ -1,50 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.scene;
import android.os.Bundle;
public final class Announcer {
Class<?> clazz;
Bundle args;
SceneFragment requestFrom;
int requestCode;
public Announcer(Class<?> clazz) {
this.clazz = clazz;
}
public Class<?> getClazz() {
return clazz;
}
public Bundle getArgs() {
return args;
}
public Announcer setArgs(Bundle args) {
this.args = args;
return this;
}
public Announcer setRequestCode(SceneFragment requestFrom, int requestCode) {
this.requestFrom = requestFrom;
this.requestCode = requestCode;
return this;
}
}
@@ -1,56 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.scene;
import android.app.Application;
import android.util.SparseArray;
import com.hippo.yorozuya.IntIdGenerator;
public abstract class SceneApplication extends Application {
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
private final SparseArray<StageActivity> mStageMap = new SparseArray<>();
void registerStageActivity(StageActivity stage) {
int id = mIdGenerator.nextId();
mStageMap.put(id, stage);
stage.onRegister(id);
}
void registerStageActivity(StageActivity stage, int id) {
if (mStageMap.indexOfKey(id) >= 0) {
throw new IllegalStateException("The id exists: " + id);
}
mStageMap.put(id, stage);
stage.onRegister(id);
}
void unregisterStageActivity(int id) {
int index = mStageMap.indexOfKey(id);
if (index >= 0) {
StageActivity stage = mStageMap.valueAt(index);
mStageMap.remove(id);
stage.onUnregister();
}
}
public StageActivity findStageActivityById(int id) {
return mStageMap.get(id);
}
}
@@ -1,169 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.scene;
import android.app.assist.AssistContent;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.collect.IntList;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class SceneFragment extends Fragment {
public static final int LAUNCH_MODE_STANDARD = 0;
public static final int LAUNCH_MODE_SINGLE_TOP = 1;
public static final int LAUNCH_MODE_SINGLE_TASK = 2;
/**
* Standard scene result: operation canceled.
*/
public static final int RESULT_CANCELED = 0;
/**
* Standard scene result: operation succeeded.
*/
public static final int RESULT_OK = -1;
int resultCode = RESULT_CANCELED;
Bundle result = null;
List<String> mRequestSceneTagList = new ArrayList<>(0);
IntList mRequestCodeList = new IntList(0);
public void onNewArguments(@NonNull Bundle args) {
}
public void startScene(Announcer announcer, boolean horizontal) {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).startScene(announcer, horizontal);
}
}
public void startScene(Announcer announcer) {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).startScene(announcer);
}
}
public void finish() {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).finishScene(this);
}
}
public void finishStage() {
FragmentActivity activity = getActivity();
if (activity != null) {
activity.finish();
}
}
/**
* @return negative for error
*/
public int getStackIndex() {
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
return ((StageActivity) activity).getSceneIndex(this);
} else {
return -1;
}
}
public void onProvideAssistContent(AssistContent outContent) {
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.setTag(R.id.fragment_tag, getTag());
view.setBackground(ResourcesKt.resolveDrawable(requireActivity().getTheme(), android.R.attr.windowBackground));
// Notify
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).onSceneViewCreated(this, savedInstanceState);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Notify
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).onSceneViewDestroyed(this);
}
}
@Override
public void onDestroy() {
super.onDestroy();
FragmentActivity activity = getActivity();
if (activity instanceof StageActivity) {
((StageActivity) activity).onSceneDestroyed(this);
}
}
void addRequest(String requestSceneTag, int requestCode) {
mRequestSceneTagList.add(requestSceneTag);
mRequestCodeList.add(requestCode);
}
void returnResult(StageActivity stage) {
for (int i = 0, size = Math.min(mRequestSceneTagList.size(), mRequestCodeList.size()); i < size; i++) {
String tag = mRequestSceneTagList.get(i);
int code = mRequestCodeList.get(i);
SceneFragment scene = stage.findSceneByTag(tag);
if (scene != null) {
scene.onSceneResult(code, resultCode, result);
}
}
mRequestSceneTagList.clear();
mRequestCodeList.clear();
}
protected void onSceneResult(int requestCode, int resultCode, Bundle data) {
}
public void setResult(int resultCode, Bundle result) {
this.resultCode = resultCode;
this.result = result;
}
@IntDef({LAUNCH_MODE_STANDARD, LAUNCH_MODE_SINGLE_TOP, LAUNCH_MODE_SINGLE_TASK})
@Retention(RetentionPolicy.SOURCE)
public @interface LaunchMode {
}
}
@@ -1,655 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.scene;
import android.app.assist.AssistContent;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.EhActivity;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.IntIdGenerator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class StageActivity extends EhActivity {
public static final String ACTION_START_SCENE = "start_scene";
public static final String KEY_SCENE_NAME = "stage_activity_scene_name";
public static final String KEY_SCENE_ARGS = "stage_activity_scene_args";
private static final String TAG = StageActivity.class.getSimpleName();
private static final String KEY_STAGE_ID = "stage_activity_stage_id";
private static final String KEY_SCENE_TAG_LIST = "stage_activity_scene_tag_list";
private static final String KEY_NEXT_ID = "stage_activity_next_id";
private static final Map<Class<?>, Integer> sLaunchModeMap = new HashMap<>();
private final ArrayList<String> mSceneTagList = new ArrayList<>();
private final ArrayList<String> mDelaySceneTagList = new ArrayList<>();
private final AtomicInteger mIdGenerator = new AtomicInteger();
private final SceneViewComparator mSceneViewComparator = new SceneViewComparator();
private final OnBackPressedCallback callback = new BackPressCallBack();
private int mStageId = IntIdGenerator.INVALID_ID;
public static void registerLaunchMode(Class<?> clazz, @SceneFragment.LaunchMode int launchMode) {
if (launchMode != SceneFragment.LAUNCH_MODE_STANDARD &&
launchMode != SceneFragment.LAUNCH_MODE_SINGLE_TOP &&
launchMode != SceneFragment.LAUNCH_MODE_SINGLE_TASK) {
throw new IllegalStateException("Invalid launch mode: " + launchMode);
}
sLaunchModeMap.put(clazz, launchMode);
}
public abstract int getContainerViewId();
/**
* @return {@code true} for start scene
*/
private boolean startSceneFromIntent(Intent intent) {
String clazzStr = intent.getStringExtra(KEY_SCENE_NAME);
if (null == clazzStr) {
return false;
}
Class<?> clazz;
try {
clazz = Class.forName(clazzStr);
} catch (ClassNotFoundException e) {
Log.e(TAG, "Can't find class " + clazzStr, e);
return false;
}
Bundle args = intent.getBundleExtra(KEY_SCENE_ARGS);
Announcer announcer = onStartSceneFromIntent(clazz, args);
if (announcer == null) {
return false;
}
startScene(announcer);
return true;
}
/**
* Start scene from {@code Intent}, it might be not safe,
* Correct it here.
*
* @return {@code null} for do not start scene
*/
@Nullable
protected Announcer onStartSceneFromIntent(@NonNull Class<?> clazz, @Nullable Bundle args) {
return new Announcer(clazz).setArgs(args);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (intent == null || !ACTION_START_SCENE.equals(intent.getAction()) ||
!startSceneFromIntent(intent)) {
onUnrecognizedIntent(intent);
}
}
/**
* Called when launch with action {@code android.intent.action.MAIN}
*/
@Nullable
protected abstract Announcer getLaunchAnnouncer();
/**
* Can't recognize intent in first time {@code onCreate} and {@code onNewIntent},
* null included.
*/
protected void onUnrecognizedIntent(@Nullable Intent intent) {
}
/**
* Call {@code setContentView} here. Do <b>NOT</b> call {@code startScene} here
*/
protected abstract void onCreate2(@Nullable Bundle savedInstanceState);
@Override
protected final void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getOnBackPressedDispatcher().addCallback(callback);
if (savedInstanceState != null) {
mStageId = savedInstanceState.getInt(KEY_STAGE_ID, IntIdGenerator.INVALID_ID);
ArrayList<String> list = savedInstanceState.getStringArrayList(KEY_SCENE_TAG_LIST);
if (list != null) {
mSceneTagList.addAll(list);
mDelaySceneTagList.addAll(list);
}
mIdGenerator.lazySet(savedInstanceState.getInt(KEY_NEXT_ID));
updateBackPressCallBackStatus();
}
if (mStageId == IntIdGenerator.INVALID_ID) {
((SceneApplication) getApplicationContext()).registerStageActivity(this);
} else {
((SceneApplication) getApplicationContext()).registerStageActivity(this, mStageId);
}
// Create layout
onCreate2(savedInstanceState);
Intent intent = getIntent();
if (savedInstanceState == null) {
if (intent != null) {
String action = intent.getAction();
if (Intent.ACTION_MAIN.equals(action)) {
Announcer announcer = getLaunchAnnouncer();
if (announcer != null) {
startScene(announcer);
return;
}
} else if (ACTION_START_SCENE.equals(action)) {
if (startSceneFromIntent(intent)) {
return;
}
}
}
// Can't recognize intent
onUnrecognizedIntent(intent);
}
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_STAGE_ID, mStageId);
outState.putStringArrayList(KEY_SCENE_TAG_LIST, mSceneTagList);
outState.putInt(KEY_NEXT_ID, mIdGenerator.getAndIncrement());
}
@Override
protected void onDestroy() {
super.onDestroy();
((SceneApplication) getApplicationContext()).unregisterStageActivity(mStageId);
}
public void onSceneViewCreated(SceneFragment scene, Bundle savedInstanceState) {
}
public void onSceneViewDestroyed(SceneFragment scene) {
}
void onSceneDestroyed(SceneFragment scene) {
mDelaySceneTagList.remove(scene.getTag());
}
protected void onRegister(int id) {
mStageId = id;
}
protected void onUnregister() {
}
protected void onTransactScene() {
}
public int getStageId() {
return mStageId;
}
public int getSceneCount() {
return mSceneTagList.size();
}
public int getSceneLaunchMode(Class<?> clazz) {
Integer integer = sLaunchModeMap.get(clazz);
if (integer == null) {
throw new RuntimeException("Not register " + clazz.getName());
} else {
return integer;
}
}
private SceneFragment newSceneInstance(Class<?> clazz) {
try {
return (SceneFragment) clazz.newInstance();
} catch (InstantiationException e) {
throw new IllegalStateException("Can't instance " + clazz.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException("The constructor of " +
clazz.getName() + " is not visible", e);
} catch (ClassCastException e) {
throw new IllegalStateException(clazz.getName() + " can not cast to scene", e);
}
}
public void startScene(Announcer announcer) {
startScene(announcer, false);
}
public void startScene(Announcer announcer, boolean horizontal) {
startSceneInternal(announcer, horizontal);
updateBackPressCallBackStatus();
}
public void startSceneInternal(Announcer announcer, boolean horizontal) {
Class<?> clazz = announcer.clazz;
Bundle args = announcer.args;
FragmentManager fragmentManager = getSupportFragmentManager();
int launchMode = getSceneLaunchMode(clazz);
// Check LAUNCH_MODE_SINGLE_TASK
if (launchMode == SceneFragment.LAUNCH_MODE_SINGLE_TASK) {
for (int i = 0, n = mSceneTagList.size(); i < n; i++) {
String tag = mSceneTagList.get(i);
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) {
Log.e(TAG, "Can't find fragment with tag: " + tag);
continue;
}
if (clazz.isInstance(fragment)) { // Get it
FragmentTransaction transaction = fragmentManager.beginTransaction();
// Use default animation
if (horizontal) {
transaction.setCustomAnimations(R.anim.scene_open_enter_horizontal, R.anim.scene_open_exit);
} else {
transaction.setCustomAnimations(R.anim.scene_open_enter, R.anim.scene_open_exit);
}
// Remove top fragments
for (int j = i + 1; j < n; j++) {
String topTag = mSceneTagList.get(j);
Fragment topFragment = fragmentManager.findFragmentByTag(topTag);
if (null == topFragment) {
Log.e(TAG, "Can't find fragment with tag: " + topTag);
continue;
}
// Clear shared element
topFragment.setSharedElementEnterTransition(null);
topFragment.setSharedElementReturnTransition(null);
topFragment.setEnterTransition(null);
topFragment.setExitTransition(null);
// Remove it
transaction.remove(topFragment);
}
// Remove tag from index i+1
mSceneTagList.subList(i + 1, mSceneTagList.size()).clear();
mDelaySceneTagList.subList(i + 1, mDelaySceneTagList.size()).clear();
// Attach fragment
if (fragment.isDetached()) {
transaction.attach(fragment);
}
// Commit
transaction.commitAllowingStateLoss();
onTransactScene();
// New arguments
if (args != null && fragment instanceof SceneFragment) {
// TODO Call onNewArguments when view created ?
((SceneFragment) fragment).onNewArguments(args);
}
return;
}
}
}
// Get current fragment
SceneFragment currentScene = null;
if (mSceneTagList.size() > 0) {
// Get last tag
String tag = mSceneTagList.get(mSceneTagList.size() - 1);
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
AssertUtils.assertTrue(fragment instanceof SceneFragment);
currentScene = (SceneFragment) fragment;
}
}
// Check LAUNCH_MODE_SINGLE_TASK
if (clazz.isInstance(currentScene) && launchMode == SceneFragment.LAUNCH_MODE_SINGLE_TOP) {
if (args != null) {
currentScene.onNewArguments(args);
}
return;
}
// Create new scene
SceneFragment newScene = newSceneInstance(clazz);
newScene.setArguments(args);
// Create new scene tag
String newTag = Integer.toString(mIdGenerator.getAndIncrement());
// Add new tag to list
mSceneTagList.add(newTag);
mDelaySceneTagList.add(newTag);
FragmentTransaction transaction = fragmentManager.beginTransaction();
// Animation
if (currentScene != null) {
// Clear shared item
currentScene.setSharedElementEnterTransition(null);
currentScene.setSharedElementReturnTransition(null);
currentScene.setEnterTransition(null);
currentScene.setExitTransition(null);
newScene.setSharedElementEnterTransition(null);
newScene.setSharedElementReturnTransition(null);
newScene.setEnterTransition(null);
newScene.setExitTransition(null);
// Set default animation
if (horizontal) {
transaction.setCustomAnimations(R.anim.scene_open_enter_horizontal, R.anim.scene_open_exit);
} else {
transaction.setCustomAnimations(R.anim.scene_open_enter, R.anim.scene_open_exit);
}
// Detach current scene
if (!currentScene.isDetached()) {
transaction.detach(currentScene);
} else {
Log.e(TAG, "Current scene is detached");
}
}
// Add new scene
transaction.add(getContainerViewId(), newScene, newTag);
// Commit
transaction.commitAllowingStateLoss();
onTransactScene();
// Check request
if (announcer.requestFrom != null) {
newScene.addRequest(announcer.requestFrom.getTag(), announcer.requestCode);
}
}
public void startSceneFirstly(Announcer announcer) {
Class<?> clazz = announcer.clazz;
Bundle args = announcer.args;
FragmentManager fragmentManager = getSupportFragmentManager();
int launchMode = getSceneLaunchMode(clazz);
boolean forceNewScene = launchMode == SceneFragment.LAUNCH_MODE_STANDARD;
boolean createNewScene = true;
boolean findScene = false;
SceneFragment scene = null;
FragmentTransaction transaction = fragmentManager.beginTransaction();
// Set default animation
transaction.setCustomAnimations(R.anim.scene_open_enter, R.anim.scene_open_exit);
String findSceneTag = null;
for (int i = 0, n = mSceneTagList.size(); i < n; i++) {
String tag = mSceneTagList.get(i);
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) {
Log.e(TAG, "Can't find fragment with tag: " + tag);
continue;
}
// Clear shared element
fragment.setSharedElementEnterTransition(null);
fragment.setSharedElementReturnTransition(null);
fragment.setEnterTransition(null);
fragment.setExitTransition(null);
// Check is target scene
if (!forceNewScene && !findScene && clazz.isInstance(fragment) &&
(launchMode == SceneFragment.LAUNCH_MODE_SINGLE_TASK || !fragment.isDetached())) {
scene = (SceneFragment) fragment;
findScene = true;
createNewScene = false;
findSceneTag = tag;
if (fragment.isDetached()) {
transaction.attach(fragment);
}
} else {
// Remove it
transaction.remove(fragment);
}
}
// Handle tag list
mSceneTagList.clear();
if (null != findSceneTag) {
mSceneTagList.add(findSceneTag);
}
if (createNewScene) {
scene = newSceneInstance(clazz);
scene.setArguments(args);
// Create scene tag
String tag = Integer.toString(mIdGenerator.getAndIncrement());
// Add tag to list
mSceneTagList.add(tag);
mDelaySceneTagList.add(tag);
// Add scene
transaction.add(getContainerViewId(), scene, tag);
}
// Commit
transaction.commitAllowingStateLoss();
onTransactScene();
if (!createNewScene && args != null) {
// TODO Call onNewArguments when view created ?
scene.onNewArguments(args);
}
updateBackPressCallBackStatus();
}
int getSceneIndex(SceneFragment scene) {
return getTagIndex(scene.getTag());
}
int getTagIndex(String tag) {
return mSceneTagList.indexOf(tag);
}
void sortSceneViews(List<View> views) {
views.sort(mSceneViewComparator);
}
public void finishScene(SceneFragment scene) {
finishScene(scene.getTag());
updateBackPressCallBackStatus();
}
private void finishScene(String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
// Get scene
Fragment scene = fragmentManager.findFragmentByTag(tag);
if (scene == null) {
Log.e(TAG, "finishScene: Can't find scene by tag: " + tag);
return;
}
// Get scene index
int index = mSceneTagList.indexOf(tag);
if (index < 0) {
Log.e(TAG, "finishScene: Can't find the tag in tag list: " + tag);
return;
}
if (mSceneTagList.size() == 1) {
// It is the last fragment, finish Activity now
Log.i(TAG, "finishScene: It is the last scene, finish activity now");
finish();
return;
}
Fragment next = null;
if (index == mSceneTagList.size() - 1) {
// It is first fragment, show the next one
next = fragmentManager.findFragmentByTag(mSceneTagList.get(index - 1));
}
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (next != null) {
// Clear shared item
scene.setSharedElementEnterTransition(null);
scene.setSharedElementReturnTransition(null);
scene.setEnterTransition(null);
scene.setExitTransition(null);
next.setSharedElementEnterTransition(null);
next.setSharedElementReturnTransition(null);
next.setEnterTransition(null);
next.setExitTransition(null);
// Do not show animate if it is not the first fragment
transaction.setCustomAnimations(R.anim.scene_close_enter, R.anim.scene_close_exit);
// Attach fragment
transaction.attach(next);
}
transaction.remove(scene);
transaction.commitAllowingStateLoss();
onTransactScene();
// Remove tag
mSceneTagList.remove(index);
// Return result
if (scene instanceof SceneFragment) {
((SceneFragment) scene).returnResult(this);
}
}
public void refreshTopScene() {
int index = mSceneTagList.size() - 1;
if (index < 0) {
return;
}
String tag = mSceneTagList.get(index);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment == null) {
return;
}
fragmentManager.beginTransaction().detach(fragment).commitAllowingStateLoss();
fragmentManager.beginTransaction().attach(fragment).commitAllowingStateLoss();
updateBackPressCallBackStatus();
}
public void updateBackPressCallBackStatus() {
callback.setEnabled(mSceneTagList.size() > 1);
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
int size = mSceneTagList.size();
String tag = mSceneTagList.get(size - 1);
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
if (fragment == null) {
Log.e(TAG, "onProvideAssistContent: Can't find scene by tag: " + tag);
return;
}
if (fragment instanceof SceneFragment scene) {
scene.onProvideAssistContent(outContent);
} else {
Log.e(TAG, "onProvideAssistContent: The fragment is not SceneFragment");
}
}
public SceneFragment findSceneByTag(String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(tag);
if (fragment != null) {
return (SceneFragment) fragment;
} else {
return null;
}
}
@Nullable
public Class<?> getTopSceneClass() {
int index = mSceneTagList.size() - 1;
if (index < 0) {
return null;
}
String tag = mSceneTagList.get(index);
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
if (null == fragment) {
return null;
}
return fragment.getClass();
}
class BackPressCallBack extends OnBackPressedCallback {
public BackPressCallBack() {
super(false);
}
@Override
public void handleOnBackPressed() {
int size = mSceneTagList.size();
String tag = mSceneTagList.get(size - 1);
SceneFragment scene;
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
if (fragment == null) {
Log.e(TAG, "onBackPressed: Can't find scene by tag: " + tag);
return;
}
if (!(fragment instanceof SceneFragment)) {
Log.e(TAG, "onBackPressed: The fragment is not SceneFragment");
return;
}
scene = (SceneFragment) fragment;
finishScene(scene);
}
}
private final class SceneViewComparator implements Comparator<View> {
private int getIndex(View view) {
Object o = view.getTag(R.id.fragment_tag);
if (o instanceof String) {
return mDelaySceneTagList.indexOf(o);
} else {
return -1;
}
}
@Override
public int compare(View lhs, View rhs) {
return getIndex(lhs) - getIndex(rhs);
}
}
}
@@ -1,156 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.scene;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.hippo.ehviewer.R;
import java.lang.reflect.Field;
import java.util.ArrayList;
public class StageLayout extends FrameLayout {
private Field mDisappearingChildrenField;
private ArrayList<View> mSuperDisappearingChildren;
private ArrayList<View> mSortedScenes;
private View mDumpView;
private boolean mDoTrick;
public StageLayout(Context context) {
super(context);
init(context);
}
public StageLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public StageLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
try {
mDisappearingChildrenField = ViewGroup.class.getDeclaredField("mDisappearingChildren");
mDisappearingChildrenField.setAccessible(true);
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
if (mDisappearingChildrenField != null) {
mDumpView = new View(context);
addView(mDumpView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}
}
@SuppressWarnings("unchecked")
private void getSuperDisappearingChildren() {
if (mDisappearingChildrenField == null || mSuperDisappearingChildren != null) {
return;
}
try {
mSuperDisappearingChildren = (ArrayList<View>) mDisappearingChildrenField.get(this);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private boolean beforeDispatchDraw() {
getSuperDisappearingChildren();
if (mSuperDisappearingChildren == null ||
mSuperDisappearingChildren.size() <= 0 || getChildCount() <= 1) { // only dump view
return false;
}
// Get stage
StageActivity stage = null;
Context context = getContext();
if (context instanceof StageActivity) {
stage = (StageActivity) context;
}
if (stage == null) {
return false;
}
if (null == mSortedScenes) {
mSortedScenes = new ArrayList<>();
}
// Add all scene view to mSortedScenes
ArrayList<View> disappearingChildren = mSuperDisappearingChildren;
ArrayList<View> sortedScenes = mSortedScenes;
for (int i = 1, n = getChildCount(); i < n; i++) { // Skip dump view
View view = getChildAt(i);
if (null != view.getTag(R.id.fragment_tag)) {
sortedScenes.add(view);
}
}
for (int i = 0, n = disappearingChildren.size(); i < n; i++) {
View view = disappearingChildren.get(i);
if (null != view.getTag(R.id.fragment_tag)) {
sortedScenes.add(view);
}
}
stage.sortSceneViews(sortedScenes);
return true;
}
private void afterDispatchDraw() {
if (null != mSortedScenes) {
mSortedScenes.clear();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
mDoTrick = beforeDispatchDraw();
super.dispatchDraw(canvas);
afterDispatchDraw();
mDoTrick = false;
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mDoTrick) {
ArrayList<View> sortedScenes = mSortedScenes;
if (child == mDumpView) {
boolean more = false;
for (int i = 0, n = sortedScenes.size(); i < n; i++) {
more |= super.drawChild(canvas, sortedScenes.get(i), drawingTime);
}
return more;
} else if (sortedScenes.contains(child)) {
// Skip
return false;
}
}
return super.drawChild(canvas, child, drawingTime);
}
}
@@ -38,7 +38,12 @@ infix fun Context.tellClipboard(text: String?) {
addTextToClipboard(text, false)
}
fun Context.addTextToClipboard(text: String?, isSensitive: Boolean) {
infix fun Context.tellClipboardWithToast(text: String?) {
addTextToClipboard(text, false, true)
}
@JvmOverloads
fun Context.addTextToClipboard(text: String?, isSensitive: Boolean, useToast: Boolean = false) {
getClipboardManager().apply {
setPrimaryClip(ClipData.newPlainText(null, text).apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isSensitive)
@@ -50,7 +55,7 @@ fun Context.addTextToClipboard(text: String?, isSensitive: Boolean) {
// Avoid double notify user since system have done that on Tiramisu above
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
if (this is MainActivity) {
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT, useToast)
} else if (this is SettingsActivity) {
showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT)
}
@@ -1,47 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer 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.
*
* EhViewer 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 EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.drakeet.drawer.FullDraggableContainer;
public class IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout extends FullDraggableContainer {
public IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout(@NonNull Context context) {
super(context);
}
public IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout(@NonNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs) {
super(context, attrs);
}
public IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout(@NonNull Context context, @Nullable @org.jetbrains.annotations.Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
return insets;
}
}
@@ -174,7 +174,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
(pageView as? SubsamplingScaleImageView)?.let { view ->
RectF().let {
view.getPanRemaining(it)
return fn(it) > 0
return fn(it) > 1
}
}
return false
@@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<alpha
android:duration="200"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:fromAlpha="0.0"
android:interpolator="@anim/decelerate_quart"
android:toAlpha="1.0" />
<translate
android:duration="350"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:fromXDelta="8%"
android:interpolator="@anim/decelerate_quint"
android:toXDelta="0" />
</set>
@@ -1,166 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~
~ EhViewer 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.
~
~ EhViewer 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 EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:fitsSystemWindowsInsets="top|bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="480dp"
android:layout_height="match_parent"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="80dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:text="@string/cookie_explain"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_little_small"
app:drawableTopCompat="@drawable/v_cookie_brown_x256" />
</LinearLayout>
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<Space
android:layout_width="match_parent"
android:layout_height="80dp" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ipb_member_id_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/keyline_margin"
android:hint="ipb_member_id"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ipb_member_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ipb_pass_hash_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ipb_pass_hash"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ipb_pass_hash"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/igneous_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="igneous"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/igneous"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
<Space
android:layout_width="wrap_content"
android:layout_height="64dp"
android:layout_weight="1" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/ok"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:insetLeft="80dp"
android:insetRight="80dp"
android:text="@android:string/ok" />
<Space
android:layout_width="match_parent"
android:layout_height="40dp" />
<TextView
android:id="@+id/from_clipboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/from_clipboard" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~
~ EhViewer 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.
~
~ EhViewer 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 EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:fitsSystemWindowsInsets="top|bottom">
<LinearLayout
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:padding="@dimen/keyline_margin">
<LinearLayout
android:layout_width="480dp"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="257dp"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
</LinearLayout>
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="vertical"
android:paddingLeft="12dp"
android:paddingTop="100dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/username"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/password"
android:minWidth="@dimen/single_max_width"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
style="@style/TextAppearance.Material3.LabelLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:maxWidth="@dimen/single_max_width"
android:paddingTop="24dp"
android:text="@string/app_waring" />
<Space
android:layout_width="match_parent"
android:layout_height="40dp" />
<Button
android:id="@+id/sign_in"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:insetLeft="80dp"
android:insetRight="80dp"
android:text="@string/sign_in" />
<Button
android:id="@+id/register"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:insetLeft="80dp"
android:insetRight="80dp"
android:text="@string/register" />
<Space
android:layout_width="match_parent"
android:layout_height="40dp" />
<TextView
android:id="@+id/sign_in_via_webview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_webview" />
<TextView
android:id="@+id/sign_in_via_cookies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_cookies" />
<TextView
android:id="@+id/tourist_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/tourist_mode" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:visibility="gone">
<com.google.android.material.progressindicator.CircularProgressIndicator
style="@style/ProgressView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</FrameLayout>
@@ -1,125 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2022 hathlife
~
~ This file is part of EhViewer
~
~ EhViewer 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.
~
~ EhViewer 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 EhViewer.
~ If not, see <https://www.gnu.org/licenses/>.
~
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:fitsSystemWindowsInsets="top|bottom">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<LinearLayout
android:layout_width="480dp"
android:layout_height="match_parent"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="96dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="128dp"
android:paddingTop="100dp"
android:paddingEnd="12dp"
android:text="@string/select_scene"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<Space
android:layout_width="match_parent"
android:layout_height="32dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="128dp"
android:paddingTop="100dp"
android:paddingEnd="12dp"
android:text="@string/select_scene_explain" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="160dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/button_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/keyline_margin"
app:selectionRequired="true"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/site_e"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/site_e" />
<com.google.android.material.button.MaterialButton
android:id="@+id/site_ex"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/site_ex" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<Space
android:layout_width="match_parent"
android:layout_height="77dp"
android:layout_weight="1" />
<Button
android:id="@+id/ok"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:insetLeft="80dp"
android:insetRight="80dp"
android:text="@android:string/ok" />
<Space
android:layout_width="match_parent"
android:layout_height="96dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
+31 -35
View File
@@ -14,8 +14,9 @@
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/draw_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
@@ -23,45 +24,40 @@
app:edgeToEdge="true"
app:fitsSystemWindowsInsets="start|end">
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/draw_view"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
android:layout_height="match_parent">
<com.hippo.widget.IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout
<com.hippo.ehviewer.widget.AboveSnackerLayout
android:id="@+id/absnacker"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
android:layout_height="match_parent">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/snackbar"
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
</com.hippo.ehviewer.widget.AboveSnackerLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.hippo.ehviewer.widget.EhStageLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/nav_drawer_main" />
</com.hippo.widget.IgnoreFitsSystemWindowsFullyDraggableDrawerContentLayout>
<com.hippo.widget.DrawerView
android:id="@+id/right_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="true"
android:maxWidth="@dimen/drawer_max_width" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header_main"
app:menu="@menu/nav_drawer_main" />
<com.hippo.widget.DrawerView
android:id="@+id/right_drawer"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:fitsSystemWindows="false"
android:maxWidth="@dimen/drawer_max_width" />
</androidx.drawerlayout.widget.DrawerLayout>
</FrameLayout>
</androidx.drawerlayout.widget.DrawerLayout>
@@ -1,160 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top|bottom">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/v_cookie_brown_x48" />
<ScrollView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:clipToPadding="false"
android:paddingVertical="@dimen/keyline_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="32dp"
android:text="@string/cookie_explain"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_little_small" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ipb_member_id_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/keyline_margin"
android:hint="ipb_member_id"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ipb_member_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/ipb_pass_hash_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="ipb_pass_hash"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/ipb_pass_hash"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/igneous_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="igneous"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/igneous"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
tools:ignore="HardcodedText" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<Button
android:id="@+id/ok"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@android:string/ok" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom|center_horizontal"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<TextView
android:id="@+id/from_clipboard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="8dp"
android:text="@string/from_clipboard" />
</LinearLayout>
</LinearLayout>
-188
View File
@@ -1,188 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:fitsSystemWindowsInsets="top|bottom">
<ScrollView
android:id="@+id/login_form"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="@dimen/keyline_margin">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="@dimen/keyline_margin"
app:srcCompat="@drawable/ic_launcher_foreground" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/username"
android:minWidth="@dimen/single_max_width">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="username"
android:imeOptions="actionNext"
android:inputType="text"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/password_layout"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:hint="@string/password"
android:minWidth="@dimen/single_max_width"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autofillHints="password"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:maxLines="1"
android:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
style="@style/TextAppearance.Material3.LabelLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:maxWidth="@dimen/single_max_width"
android:paddingTop="24dp"
android:text="@string/app_waring" />
<Space
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<Button
android:id="@+id/register"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/register" />
<Button
android:id="@+id/sign_in"
style="@style/Widget.Material3.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
android:text="@string/sign_in" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="4dp">
<TextView
android:id="@+id/sign_in_via_webview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_webview" />
<TextView
android:id="@+id/sign_in_via_cookies"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/sign_in_via_cookies" />
</LinearLayout>
<TextView
android:id="@+id/tourist_mode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="?selectableItemBackground"
android:paddingHorizontal="8dp"
android:text="@string/tourist_mode" />
</LinearLayout>
</ScrollView>
<FrameLayout
android:id="@+id/progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:visibility="gone">
<com.google.android.material.progressindicator.CircularProgressIndicator
style="@style/ProgressView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
</FrameLayout>
@@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingHorizontal="@dimen/keyline_margin"
android:paddingTop="@dimen/keyline_margin"
app:fitsSystemWindowsInsets="top|bottom">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:layout_weight="1"
android:clipToPadding="false"
android:fillViewport="true"
android:paddingVertical="@dimen/keyline_margin">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_scene"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/button_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="@dimen/keyline_margin"
app:selectionRequired="true"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/site_e"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/site_e" />
<com.google.android.material.button.MaterialButton
android:id="@+id/site_ex"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="@string/site_ex" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_scene_explain" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</ScrollView>
<LinearLayout
android:id="@+id/buttonPanel"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:gravity="bottom"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingTop="4dp"
android:paddingRight="12dp"
android:paddingBottom="20dp">
<Button
android:id="@+id/ok"
style="@style/Widget.Material3.Button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@android:string/ok" />
</LinearLayout>
</LinearLayout>
+79
View File
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_graph"
app:startDestination="@id/galleryListScene">
<fragment
android:id="@+id/galleryListScene"
android:name="com.hippo.ehviewer.ui.scene.GalleryListScene"
android:label="GalleryListScene" />
<fragment
android:id="@+id/nav_homepage"
android:name="com.hippo.ehviewer.ui.scene.GalleryListScene"
android:label="GalleryListScene">
<argument
android:name="action"
android:defaultValue="action_homepage"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/nav_subscription"
android:name="com.hippo.ehviewer.ui.scene.GalleryListScene"
android:label="GalleryListScene">
<argument
android:name="action"
android:defaultValue="action_subscription"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/nav_whats_hot"
android:name="com.hippo.ehviewer.ui.scene.GalleryListScene"
android:label="GalleryListScene">
<argument
android:name="action"
android:defaultValue="action_whats_hot"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/nav_toplist"
android:name="com.hippo.ehviewer.ui.scene.GalleryListScene"
android:label="GalleryListScene">
<argument
android:name="action"
android:defaultValue="action_top_list"
app:argType="string" />
</fragment>
<fragment
android:id="@+id/galleryDetailScene"
android:name="com.hippo.ehviewer.ui.scene.GalleryDetailScene"
android:label="GalleryDetailScene" />
<fragment
android:id="@+id/galleryCommentsScene"
android:name="com.hippo.ehviewer.ui.scene.GalleryCommentsScene"
android:label="GalleryCommentsScene" />
<fragment
android:id="@+id/galleryPreviewsScene"
android:name="com.hippo.ehviewer.ui.scene.GalleryPreviewsScene"
android:label="GalleryPreviewsScene" />
<fragment
android:id="@+id/nav_favourite"
android:name="com.hippo.ehviewer.ui.scene.FavoritesScene"
android:label="FavoritesScene" />
<fragment
android:id="@+id/nav_history"
android:name="com.hippo.ehviewer.ui.scene.HistoryScene"
android:label="HistoryScene" />
<fragment
android:id="@+id/nav_downloads"
android:name="com.hippo.ehviewer.ui.scene.DownloadsScene"
android:label="DownloadsScene" />
<activity
android:id="@+id/nav_settings"
android:name="com.hippo.ehviewer.ui.SettingsActivity"
android:label="SettingsActivity" />
<fragment
android:id="@+id/progressScene"
android:name="com.hippo.ehviewer.ui.scene.ProgressScene"
android:label="ProgressScene" />
</navigation>
-2
View File
@@ -35,7 +35,6 @@
<string name="text_is_empty">Der Text wird nicht eingegeben</string>
<string name="waring">Achtung</string>
<string name="wrong_cookie_warning">Die eingegebenen Cookies sehen korrekt nicht aus. Überprüfe sie.</string>
<string name="found_cookies">Cookies werden gefunden</string>
<string name="invalid_download_location">Der Downloadpfad ist nicht verfügbar. Geh zu die Einstellungen und stelle einen neue Pfad fest.</string>
<string name="error_bad_status_code">Inkorrekter Statuscode: %d</string>
<string name="error_timeout">Timeout</string>
@@ -60,7 +59,6 @@
<string name="get_it">Verstanden</string>
<string name="select_scene">Wähle eine Galerie-Webseite aus.</string>
<string name="select_scene_explain">e-hentai: für jeden verfügbar\nexhentai: nur für diejenigen verfügbar, die sich angemeldet haben</string>
<string name="no_select">Wähle eine Webseite aus.</string>
<string name="gallery_list_search_bar_hint_exhentai">ExHentai durchsuchen</string>
<string name="gallery_list_search_bar_hint_e_hentai">E-Hentai durchsuchen</string>
<string name="gallery_list_empty_hit">Die Welt ist so groß und der Panda sitzt ganz allein</string>
-2
View File
@@ -41,7 +41,6 @@
<string name="text_is_empty">El texto está vacío</string>
<string name="waring">Advertencia</string>
<string name="wrong_cookie_warning">Parece que las cookies que ingresaste son incorrectas. Debe revisarlos.</string>
<string name="found_cookies">¡Cookies encontradas!</string>
<string name="invalid_download_location">Parece que la ubicación de descarga no está disponible. Establece la ubicación en Configuración.</string>
@@ -74,7 +73,6 @@
<!-- Select scene -->
<string name="select_scene">¿Qué galería deseas seleccionar?</string>
<string name="select_scene_explain">e-hentai: Disponible para cualquiera .\nexhentai: Solo para usuarios que han iniciado sesión</string>
<string name="no_select">Por favor seleccione un sitio</string>
<!-- Update -->
-2
View File
@@ -41,7 +41,6 @@
<string name="text_is_empty">Entrez le texte.</string>
<string name="waring">Attention</string>
<string name="wrong_cookie_warning">Les cookies entrés n\'ont pas l\'air corrects. Vérifiez-les.</string>
<string name="found_cookies">Cookies trouvés</string>
<string name="invalid_download_location">La location de téléchargement actuelle n\'est pas disponible. Choissez-en une à nouveau dans les Paramétres.</string>
@@ -76,7 +75,6 @@
<!-- Select scene -->
<string name="select_scene">Sélectionnez un site galerie.</string>
<string name="select_scene_explain">e-hentai : disponible pour tout le monde\nexhentai : disponible pour ce qui s\'est connecté(e)</string>
<string name="no_select">Sélectionnez un site</string>
<!-- Update -->
-2
View File
@@ -39,7 +39,6 @@
<string name="text_is_empty">テキストを入力してください</string>
<string name="waring">警告</string>
<string name="wrong_cookie_warning">入力したCookieは正しくないようです。確認してください。</string>
<string name="found_cookies">Cookieが見つかりました</string>
<string name="invalid_download_location">ダウンロードパスは現在利用できません。ダウンロードパスを設定してください。</string>
@@ -74,7 +73,6 @@
<!-- Select scene -->
<string name="select_scene">ギャラリーサイトを選択してください</string>
<string name="select_scene_explain">e-hentai:誰にも公開です\nexhentai:ログインしないとアクセスできません</string>
<string name="no_select">サイトを選択してください</string>
<!-- Update -->
-2
View File
@@ -40,7 +40,6 @@
<string name="text_is_empty">텍스트가 비어 있습니다.</string>
<string name="waring">경고</string>
<string name="wrong_cookie_warning">입력한 쿠키가 잘못된 것 같습니다. 확인해 보는 게 좋겠어요.</string>
<string name="found_cookies">쿠키를 찾았어요!</string>
<string name="invalid_download_location">다운로드 위치가 잘못되었습니다. 설정을 확인하세요.</string>
@@ -77,7 +76,6 @@
<!-- Select scene -->
<string name="select_scene">어느 갤러리 사이트를 사용하시겠습니까?</string>
<string name="select_scene_explain">e-hentai: 모든 사람들이 사용할 수 있음\nexhentai: 로그인한 사람만 사용할 수 있음</string>
<string name="no_select">두 사이트 중 하나를 선택하세요.</string>
<!-- Update -->
-2
View File
@@ -40,7 +40,6 @@
<string name="text_is_empty">ข้อความห้ามเว้นว่างไว้</string>
<string name="waring">คำเตือน</string>
<string name="wrong_cookie_warning">ดูเหมือนว่าคุกกี้ที่คุณกรอกมานั้นมันผิด คุณลองตรวจสอบใหม่ดูอีกรอบ</string>
<string name="found_cookies">พบคุกกี้แล้ว!</string>
<string name="invalid_download_location">ดูเหมือนที่อยู่ในการดาวน์โหลดนั้นไม่พร้อมใช้งาน โปรดตั้งค่าในเมนูการตั้งค่า</string>
@@ -77,7 +76,6 @@
<!-- Select scene -->
<string name="select_scene">เว็บแกลเลอรี่ไหนที่คุณอยากเข้าชม?</string>
<string name="select_scene_explain">e-hentai: เขตแดนที่ทุกๆคนสามารถเข้าถึงได้\nexhentai: เขตแดนอันแสนศักศิทธิ์สำหรับคนที่ลงชื่อเข้าใช้ระบบแล้วเท่านั้นที่จะเข้าไปได้</string>
<string name="no_select">โปรดเลือกหนึ่งเว็บไซต์</string>
<!-- Update -->
+6 -4
View File
@@ -41,7 +41,6 @@
<string name="text_is_empty">文本为空</string>
<string name="waring">警告</string>
<string name="wrong_cookie_warning">看起来您输入的 Cookie 是错误的,最好检查一下。</string>
<string name="found_cookies">找到旧版 Cookie</string>
<string name="invalid_download_location">似乎下载路径不可用。请到重新设置下载路径。</string>
@@ -83,7 +82,6 @@
<!-- Select scene -->
<string name="select_scene">你想选择哪个画廊站点?</string>
<string name="select_scene_explain">e-hentai:对任何人开放\nexhentai:仅对登录用户开放</string>
<string name="no_select">请选择一个站点</string>
<!-- Update -->
@@ -143,6 +141,7 @@
<string name="duplicate_quick_search">已存在相同的快速搜索,名称为“%s”。</string>
<string name="duplicate_name">已存在相同名称</string>
<string name="save_progress">保存进度</string>
<string name="delete_quick_search">删除快速搜索“%s”?</string>
<string name="go_to_hint">第 %1$d 页,共 %2$d 页</string>
@@ -178,6 +177,8 @@
<string name="rate_successfully">评分成功</string>
<string name="rate_failed">评分失败</string>
<string name="no_torrents">没有种子</string>
<string name="download_torrent_started">开始下载种子</string>
<string name="download_torrent_failure">无法下载种子</string>
<string name="torrents">种子</string>
<string name="favorited">已收藏</string>
<string name="not_favorited">未收藏</string>
@@ -304,6 +305,7 @@
<string name="label_text_is_invalid">“默认”是无效标签</string>
<string name="label_text_exist">标签已存在</string>
<string name="rename_label_title">重命名标签</string>
<string name="delete_label">删除标签“%s”?</string>
<!-- History -->
<string name="no_history">这里是阅读历史</string>
@@ -557,8 +559,8 @@
<string name="from_clipboard">从剪贴板导入</string>
<string name="from_clipboard_error">未在剪贴板中发现 Cookies。</string>
<string name="delete_search_history">从搜索记录中删除“%s”?</string>
<string name="settings_advanced_domain_fronting_title">域名前置</string>
<string name="settings_advanced_domain_fronting_summary">绕过 SNI 封锁</string>
<string name="settings_advanced_domain_fronting_title">域名前置(重启应用后生效)</string>
<string name="settings_advanced_domain_fronting_summary">绕过 SNI 封锁\n启用时会禁用代理</string>
<string name="action_add_tag">添加标签</string>
<string name="action_add_tag_tip">添加新标签,用逗号「,」分割</string>
<string name="comment_user_uploader">%s (上传者)</string>
@@ -41,7 +41,6 @@
<string name="text_is_empty">文本為空</string>
<string name="waring">警告</string>
<string name="wrong_cookie_warning">看起來您輸入的 Cookie 是錯誤的,最好檢查一下。</string>
<string name="found_cookies">找到舊版 Cookie</string>
<string name="invalid_download_location">似乎下載路徑不可用。請到重新設置下載路徑。</string>
@@ -83,7 +82,6 @@
<!-- Select scene -->
<string name="select_scene">你想選擇哪個畫廊站點?</string>
<string name="select_scene_explain">e-hentai:對任何人開放\nexhentai:僅對登錄用户開放</string>
<string name="no_select">請選擇一個站點</string>
<!-- Update -->
@@ -41,7 +41,6 @@
<string name="text_is_empty">文字是空的</string>
<string name="waring">警告</string>
<string name="wrong_cookie_warning">看來您輸入的 Cookie 是錯誤的,最好再檢查一下。</string>
<string name="found_cookies">找到 Cookie了!</string>
<string name="invalid_download_location">看來下載路徑似乎沒辦法使用。請至設定重新設定下載路徑。</string>
@@ -83,7 +82,6 @@
<!-- Select scene -->
<string name="select_scene">你想選擇哪個圖庫站台?</string>
<string name="select_scene_explain">e-hentai:對任何人開放\nexhentai:僅對登入使用者開放</string>
<string name="no_select">請選擇一個站台</string>
<!-- Update -->
+4 -2
View File
@@ -57,7 +57,6 @@
<string name="text_is_empty">Text is empty</string>
<string name="waring">Warning</string>
<string name="wrong_cookie_warning">It seems that the cookies you entered are wrong. You\'d better check them.</string>
<string name="found_cookies">Found cookies!</string>
<string name="invalid_download_location">It seems download location is not available. Please set it in Settings.</string>
@@ -125,7 +124,6 @@
<!-- Select scene -->
<string name="select_scene">Which gallery site do you want to visit?</string>
<string name="select_scene_explain">e-hentai: available for anyone\nexhentai: available for who has signed in</string>
<string name="no_select">Please select one site</string>
<!-- Update -->
@@ -187,6 +185,7 @@
<string name="duplicate_quick_search">A duplicate quick search exists. The name is \"%s\".</string>
<string name="duplicate_name">This name is already in use.</string>
<string name="save_progress">Save progress</string>
<string name="delete_quick_search">Delete quick search \"%s\"?</string>
<string name="go_to_hint">Page %1$d, total %2$d pages</string>
@@ -223,6 +222,8 @@
<string name="rate_successfully">Rate successfully</string>
<string name="rate_failed">Rate failed</string>
<string name="no_torrents">No torrents</string>
<string name="download_torrent_started">Torrent download started</string>
<string name="download_torrent_failure">Failed to download torrent</string>
<string name="torrents">Torrents</string>
<string name="favorited">Favorited</string>
<string name="not_favorited">Not favorited</string>
@@ -350,6 +351,7 @@
<string name="label_text_is_invalid">\"Default\" is an invalid label</string>
<string name="label_text_exist">Label exists</string>
<string name="rename_label_title">Rename label</string>
<string name="delete_label">Delete label \"%s\"?</string>
<!-- History -->
<string name="no_history">Viewed galleries will be shown here</string>