Compare commits
115
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f695c4598a | ||
|
|
eada1bc023 | ||
|
|
0ebb55551c | ||
|
|
20bce419c9 | ||
|
|
ca3fc370c3 | ||
|
|
94a8b78d1a | ||
|
|
06e9ff6fcf | ||
|
|
699e74656d | ||
|
|
b3ad6ef8b8 | ||
|
|
673c8e25f5 | ||
|
|
333c8e812d | ||
|
|
f0f7a88cfb | ||
|
|
0661d78bce | ||
|
|
dc4aece272 | ||
|
|
57a312b0b4 | ||
|
|
b9897ed6ac | ||
|
|
733230f037 | ||
|
|
fc6ba3e6ad | ||
|
|
2d7c539c5f | ||
|
|
7f34965026 | ||
|
|
de8b915cff | ||
|
|
d56040581d | ||
|
|
9869b7bbb5 | ||
|
|
7df97c28c0 | ||
|
|
018a559f78 | ||
|
|
3b32ad5fed | ||
|
|
f796d2a842 | ||
|
|
2f7e84e126 | ||
|
|
996ddbf3db | ||
|
|
7779ba2ac5 | ||
|
|
4dadeab762 | ||
|
|
f4d3131ef3 | ||
|
|
efa2ac803e | ||
|
|
e0be9d46bd | ||
|
|
106b27dbec | ||
|
|
f764567523 | ||
|
|
245f8669bc | ||
|
|
b265d9bba6 | ||
|
|
72228574f9 | ||
|
|
fba1ac3d90 | ||
|
|
4a73fb565e | ||
|
|
13011ed4af | ||
|
|
b2eccdefd3 | ||
|
|
dd2d6a977b | ||
|
|
38cd1b6040 | ||
|
|
1a28bbcde8 | ||
|
|
91013daadc | ||
|
|
1ad435e79d | ||
|
|
fd5c300c53 | ||
|
|
aa7d9cf29c | ||
|
|
8db1c3400e | ||
|
|
e723336706 | ||
|
|
b5cd4c36ab | ||
|
|
d1928f2647 | ||
|
|
61621ae00b | ||
|
|
ac20755c45 | ||
|
|
610202bbbd | ||
|
|
58ce7134e1 | ||
|
|
71f91cd79e | ||
|
|
8c821ecad3 | ||
|
|
c704940462 | ||
|
|
6a7cd1b89a | ||
|
|
e29da0ba13 | ||
|
|
7791fa10f5 | ||
|
|
48ed3c8f31 | ||
|
|
7ad7a9b94e | ||
|
|
54b929ba24 | ||
|
|
7b655c6260 | ||
|
|
4e8d41b145 | ||
|
|
f036774e07 | ||
|
|
97d1bd7685 | ||
|
|
8e39449856 | ||
|
|
30f1a5a6eb | ||
|
|
627abb047c | ||
|
|
9284008afe | ||
|
|
9159f4616f | ||
|
|
73296b1430 | ||
|
|
c74e622d69 | ||
|
|
f48eb55516 | ||
|
|
b01f6ef8d3 | ||
|
|
167185ef45 | ||
|
|
777a8ffeaa | ||
|
|
b2b3e6c485 | ||
|
|
613d502fc1 | ||
|
|
79da457db4 | ||
|
|
b1a431ed82 | ||
|
|
b0f2776a23 | ||
|
|
900c346ee8 | ||
|
|
7541a87b21 | ||
|
|
2547c67ebd | ||
|
|
5ae6a0b287 | ||
|
|
f718fc0c89 | ||
|
|
57e69ba7be | ||
|
|
6168b3e047 | ||
|
|
3e500500e0 | ||
|
|
42f06ff84a | ||
|
|
a7824fc473 | ||
|
|
67e7fd6928 | ||
|
|
450e8badba | ||
|
|
704c211e11 | ||
|
|
0ad871cb5b | ||
|
|
9920605570 | ||
|
|
b1e8678dbf | ||
|
|
e2cd2ddc0d | ||
|
|
a3be0dfddf | ||
|
|
4b615ffc0b | ||
|
|
64cbd189ad | ||
|
|
0fae5a128f | ||
|
|
518b44216f | ||
|
|
9ef4512ef9 | ||
|
|
8bb5f1e955 | ||
|
|
8f8932f617 | ||
|
|
23fd1d75c4 | ||
|
|
593dcef550 | ||
|
|
d769fe9074 |
No files matched your search
+23
-62
@@ -33,7 +33,7 @@ android {
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180030
|
||||
versionName "1.8.5.3-alpha03"
|
||||
versionName "1.8.5.3-alpha05"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -52,9 +52,7 @@ android {
|
||||
jvmTarget = '19'
|
||||
freeCompilerArgs += [
|
||||
// https://kotlinlang.org/docs/compiler-reference.html#progressive
|
||||
"-progressive",
|
||||
"-P",
|
||||
"plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"
|
||||
"-progressive"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -95,109 +93,72 @@ android {
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.0-alpha02"
|
||||
kotlinCompilerExtensionVersion = "1.4.0-dev-k1.8.0-33c0ad36f83"
|
||||
}
|
||||
|
||||
namespace 'com.hippo.ehviewer'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def activity_version = "1.6.1"
|
||||
implementation "androidx.activity:activity-ktx:$activity_version"
|
||||
|
||||
implementation "androidx.annotation:annotation:1.5.0"
|
||||
implementation "androidx.annotation:annotation-experimental:1.3.0"
|
||||
implementation "androidx.appcompat:appcompat:1.7.0-alpha01"
|
||||
|
||||
def arch_core_version = "2.1.0"
|
||||
implementation "androidx.arch.core:core-common:$arch_core_version"
|
||||
implementation "androidx.arch.core:core-runtime:$arch_core_version"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/activity
|
||||
implementation "androidx.activity:activity-compose:1.6.1"
|
||||
implementation "androidx.appcompat:appcompat:1.6.0"
|
||||
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha05"
|
||||
implementation "androidx.browser:browser:1.4.0"
|
||||
|
||||
def collection_version = "1.3.0-alpha02"
|
||||
implementation "androidx.collection:collection:$collection_version"
|
||||
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"
|
||||
// https://developer.android.com/jetpack/androidx/releases/compose-material3
|
||||
implementation platform("androidx.compose:compose-bom:2023.01.00")
|
||||
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.constraintlayout:constraintlayout:2.2.0-alpha05"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
|
||||
// https://developer.android.com/jetpack/androidx/releases/lifecycle
|
||||
implementation "androidx.lifecycle:lifecycle-process:2.5.1"
|
||||
|
||||
def core_version = "1.9.0"
|
||||
implementation "androidx.core:core-ktx:$core_version"
|
||||
|
||||
implementation "androidx.customview:customview:1.2.0-alpha02"
|
||||
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
|
||||
|
||||
def fragment_version = "1.6.0-alpha04"
|
||||
implementation "androidx.fragment:fragment-ktx:$fragment_version"
|
||||
|
||||
def lifecycle_version = "2.6.0-alpha03"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||
|
||||
implementation "androidx.loader:loader:1.1.0"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/navigation
|
||||
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"
|
||||
|
||||
// https://developer.android.com/jetpack/androidx/releases/paging
|
||||
implementation "androidx.paging:paging-runtime-ktx:3.2.0-alpha03"
|
||||
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0-rc01'
|
||||
|
||||
def room_version = "2.5.0-rc01"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
// https://developer.android.com/jetpack/androidx/releases/room
|
||||
def room_version = "2.5.0"
|
||||
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.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||
implementation "androidx.transition:transition-ktx:1.4.1"
|
||||
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
|
||||
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
|
||||
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0' // Dead Dependency
|
||||
implementation 'com.github.tachiyomiorg:DirectionalViewPager:1.0.0' // Dead Dependency
|
||||
// https://github.com/google/accompanist/releases
|
||||
implementation "com.google.accompanist:accompanist-themeadapter-material3:0.28.0"
|
||||
implementation 'com.google.android.material:material:1.8.0-rc01'
|
||||
|
||||
// https://square.github.io/okhttp/changelogs/changelog/
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.11'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
implementation 'com.squareup.okhttp3:okhttp-coroutines'
|
||||
|
||||
implementation 'dev.chrisbanes.insetter:insetter:0.6.1' // Dead Dependency
|
||||
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
|
||||
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
|
||||
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
|
||||
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
|
||||
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
|
||||
// https://coil-kt.github.io/coil/changelog/
|
||||
implementation 'io.coil-kt:coil:2.2.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
|
||||
implementation 'org.jsoup:jsoup:1.15.3'
|
||||
|
||||
// Tachiyomi Reader
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
|
||||
}
|
||||
|
||||
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'
|
||||
|
||||
@@ -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>
|
||||
@@ -45,6 +45,6 @@ SET(LIBARCHIVE_CUSTOM_LIBS "liblzma" "nettle")
|
||||
add_subdirectory(libarchive)
|
||||
|
||||
# Build and link our app's native lib
|
||||
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c)
|
||||
add_library(${PROJECT_NAME} SHARED archive.c fdutils.c gifUtils.c)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC libarchive/libarchive)
|
||||
target_link_libraries(${PROJECT_NAME} archive_static log -Wl,--gc-sections)
|
||||
@@ -0,0 +1,11 @@
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define GIF_HEADER_87A "GIF87a"
|
||||
#define GIF_HEADER_89A "GIF89a"
|
||||
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_hippo_Native_isGif(JNIEnv *env, jobject thiz, jobject buffer) {
|
||||
void *addr = (*env)->GetDirectBufferAddress(env, buffer);
|
||||
return !memcmp(addr, GIF_HEADER_87A, 6) || !memcmp(addr, GIF_HEADER_89A, 6);
|
||||
}
|
||||
+1
-1
Submodule app/src/main/cpp/liblzma updated: b35079d589...fc9fb0ea24.
@@ -0,0 +1,49 @@
|
||||
package androidx.navigation.ui
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavDestination
|
||||
import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavOptions
|
||||
|
||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
||||
if (navController.currentDestination?.matchDestination(item.itemId) == true) return false
|
||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
||||
if (
|
||||
navController.currentDestination!!.parent!!.findNode(item.itemId)
|
||||
is ActivityNavigator.Destination
|
||||
) {
|
||||
builder.setEnterAnim(R.anim.nav_default_enter_anim)
|
||||
.setExitAnim(R.anim.nav_default_exit_anim)
|
||||
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
|
||||
.setPopExitAnim(R.anim.nav_default_pop_exit_anim)
|
||||
} else {
|
||||
builder.setEnterAnim(com.hippo.ehviewer.R.anim.scene_open_enter)
|
||||
.setExitAnim(com.hippo.ehviewer.R.anim.scene_open_exit)
|
||||
.setPopEnterAnim(com.hippo.ehviewer.R.anim.scene_close_enter)
|
||||
.setPopExitAnim(com.hippo.ehviewer.R.anim.scene_close_exit)
|
||||
}
|
||||
if (item.order and Menu.CATEGORY_SECONDARY == 0) {
|
||||
builder.setPopUpTo(
|
||||
navController.graph.findStartDestination().id,
|
||||
inclusive = false,
|
||||
saveState = false
|
||||
)
|
||||
}
|
||||
val options = builder.build()
|
||||
return try {
|
||||
// TODO provide proper API instead of using Exceptions as Control-Flow.
|
||||
navController.navigate(item.itemId, null, options)
|
||||
// Return true only if the destination we've navigated to matches the MenuItem
|
||||
navController.currentDestination?.matchDestination(item.itemId) == true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
|
||||
hierarchy.any { it.id == destId }
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,88 @@
|
||||
@file:Suppress("SameParameterValue")
|
||||
|
||||
package coil.decode
|
||||
|
||||
import okio.Buffer
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.decodeHex
|
||||
import okio.ForwardingSource
|
||||
import okio.Source
|
||||
|
||||
/**
|
||||
* A [ForwardingSource] that rewrites the GIF frame delay in every graphics control block if it's
|
||||
* below a threshold.
|
||||
*/
|
||||
internal class FrameDelayRewritingSource(delegate: Source) : ForwardingSource(delegate) {
|
||||
|
||||
// An intermediary buffer so we can read and alter the data before it's written to the destination.
|
||||
private val buffer = Buffer()
|
||||
|
||||
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||
// Ensure our buffer has enough bytes to satisfy this read.
|
||||
request(byteCount)
|
||||
|
||||
// Short circuit if there are no bytes in the buffer.
|
||||
if (buffer.size == 0L) {
|
||||
return if (byteCount == 0L) 0L else -1L
|
||||
}
|
||||
|
||||
// Search through the buffer and rewrite any frame delays below the threshold.
|
||||
var bytesWritten = 0L
|
||||
while (true) {
|
||||
val index = indexOf(FRAME_DELAY_START_MARKER)
|
||||
if (index == -1L) break
|
||||
|
||||
// Write up until the end of the frame delay start marker.
|
||||
bytesWritten += write(sink, index + FRAME_DELAY_START_MARKER_SIZE_BYTES)
|
||||
|
||||
// Check for the end of the graphics control extension block.
|
||||
if (!request(5) || buffer[4] != 0.toByte()) continue
|
||||
|
||||
// Rewrite the frame delay if it is below the threshold.
|
||||
// The frame delay is stored as two unsigned bytes in reverse order
|
||||
// (i.e. the most significant bits are in the byte that's read second).
|
||||
val frameDelay = (buffer[2].toUByte().toInt() shl 8) or buffer[1].toUByte().toInt()
|
||||
if (frameDelay < MINIMUM_FRAME_DELAY) {
|
||||
sink.writeByte(buffer[0].toInt())
|
||||
sink.writeByte(DEFAULT_FRAME_DELAY)
|
||||
sink.writeByte(0)
|
||||
buffer.skip(3)
|
||||
}
|
||||
}
|
||||
|
||||
// Write anything left in the source.
|
||||
if (bytesWritten < byteCount) {
|
||||
bytesWritten += write(sink, byteCount - bytesWritten)
|
||||
}
|
||||
return if (bytesWritten == 0L) -1 else bytesWritten
|
||||
}
|
||||
|
||||
private fun indexOf(bytes: ByteString): Long {
|
||||
var index = -1L
|
||||
while (true) {
|
||||
index = buffer.indexOf(bytes[0], index + 1)
|
||||
if (index == -1L) break
|
||||
if (request(bytes.size.toLong()) && buffer.rangeEquals(index, bytes)) break
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun write(sink: Buffer, byteCount: Long): Long {
|
||||
return buffer.read(sink, byteCount).coerceAtLeast(0)
|
||||
}
|
||||
|
||||
private fun request(byteCount: Long): Boolean {
|
||||
if (buffer.size >= byteCount) return true
|
||||
val toRead = byteCount - buffer.size
|
||||
return super.read(buffer, toRead) == toRead
|
||||
}
|
||||
|
||||
private companion object {
|
||||
// https://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
|
||||
// See: "Graphics Control Extension"
|
||||
private val FRAME_DELAY_START_MARKER = "0021F904".decodeHex()
|
||||
private const val FRAME_DELAY_START_MARKER_SIZE_BYTES = 4
|
||||
private const val MINIMUM_FRAME_DELAY = 2
|
||||
private const val DEFAULT_FRAME_DELAY = 10
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.hippo
|
||||
|
||||
import java.io.FileDescriptor
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object Native {
|
||||
fun initialize() {
|
||||
@@ -26,4 +27,6 @@ object Native {
|
||||
|
||||
@JvmStatic
|
||||
external fun getFd(fd: FileDescriptor?): Int
|
||||
|
||||
external fun isGif(buffer: ByteBuffer): Boolean
|
||||
}
|
||||
@@ -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
|
||||
@@ -49,7 +50,6 @@ 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
|
||||
@@ -71,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>()
|
||||
@@ -101,7 +101,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
|
||||
}
|
||||
handler?.uncaughtException(t, e)
|
||||
}
|
||||
super<SceneApplication>.onCreate()
|
||||
super<Application>.onCreate()
|
||||
Native.initialize()
|
||||
GetText.initialize(this)
|
||||
Settings.initialize()
|
||||
@@ -281,7 +281,7 @@ class EhApplication : SceneApplication(), DefaultLifecycleObserver, ImageLoaderF
|
||||
private set
|
||||
|
||||
@JvmStatic
|
||||
val ehCookieStore by lazy { EhCookieStore(application) }
|
||||
val ehCookieStore by lazy { EhCookieStore }
|
||||
|
||||
@JvmStatic
|
||||
val ehProxySelector by lazy { EhProxySelector() }
|
||||
|
||||
@@ -111,6 +111,30 @@ 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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,116 +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.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.network.CookieRepository;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class EhCookieStore extends CookieRepository {
|
||||
|
||||
public static final String KEY_IPD_MEMBER_ID = "ipb_member_id";
|
||||
public static final String KEY_IPD_PASS_HASH = "ipb_pass_hash";
|
||||
public static final String KEY_IGNEOUS = "igneous";
|
||||
|
||||
public static final Cookie sTipsCookie =
|
||||
new Cookie.Builder()
|
||||
.name(EhConfig.KEY_CONTENT_WARNING)
|
||||
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
|
||||
.domain(EhUrl.DOMAIN_E)
|
||||
.path("/")
|
||||
.expiresAt(Long.MAX_VALUE)
|
||||
.build();
|
||||
|
||||
public EhCookieStore(Context context) {
|
||||
super(context, "okhttp3-cookie.db");
|
||||
}
|
||||
|
||||
public static Cookie newCookie(Cookie cookie, String newDomain, boolean forcePersistent,
|
||||
boolean forceLongLive, boolean forceNotHostOnly) {
|
||||
Cookie.Builder builder = new Cookie.Builder();
|
||||
builder.name(cookie.name());
|
||||
builder.value(cookie.value());
|
||||
|
||||
if (forceLongLive) {
|
||||
builder.expiresAt(Long.MAX_VALUE);
|
||||
} else if (cookie.persistent()) {
|
||||
builder.expiresAt(cookie.expiresAt());
|
||||
} else if (forcePersistent) {
|
||||
builder.expiresAt(Long.MAX_VALUE);
|
||||
}
|
||||
if (cookie.hostOnly() && !forceNotHostOnly) {
|
||||
builder.hostOnlyDomain(newDomain);
|
||||
} else {
|
||||
builder.domain(newDomain);
|
||||
}
|
||||
builder.path(cookie.path());
|
||||
if (cookie.secure()) {
|
||||
builder.secure();
|
||||
}
|
||||
if (cookie.httpOnly()) {
|
||||
builder.httpOnly();
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public void signOut() {
|
||||
clear();
|
||||
}
|
||||
|
||||
public boolean hasSignedIn() {
|
||||
HttpUrl url = HttpUrl.parse(EhUrl.HOST_E);
|
||||
return contains(url, KEY_IPD_MEMBER_ID) &&
|
||||
contains(url, KEY_IPD_PASS_HASH);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(@NonNull HttpUrl url) {
|
||||
List<Cookie> cookies = super.loadForRequest(url);
|
||||
|
||||
boolean checkTips = domainMatch(url, EhUrl.DOMAIN_E);
|
||||
|
||||
if (checkTips) {
|
||||
List<Cookie> result = new ArrayList<>(cookies.size() + 1);
|
||||
// Add all but skip some
|
||||
for (Cookie cookie : cookies) {
|
||||
String name = cookie.name();
|
||||
if (EhConfig.KEY_CONTENT_WARNING.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
if (EhConfig.KEY_UCONFIG.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
result.add(cookie);
|
||||
}
|
||||
// Add some
|
||||
result.add(sTipsCookie);
|
||||
return Collections.unmodifiableList(result);
|
||||
} else {
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 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 com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.network.CookieDatabase
|
||||
import com.hippo.network.CookieSet
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import java.util.Collections
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object EhCookieStore : CookieJar {
|
||||
private val db: CookieDatabase = CookieDatabase(EhApplication.application, "okhttp3-cookie.db")
|
||||
private val map: MutableMap<String, CookieSet> = db.allCookies
|
||||
|
||||
fun signOut() {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun hasSignedIn(): Boolean {
|
||||
val url = EhUrl.HOST_E.toHttpUrl()
|
||||
return contains(url, KEY_IPD_MEMBER_ID) &&
|
||||
contains(url, KEY_IPD_PASS_HASH)
|
||||
}
|
||||
|
||||
const val KEY_IPD_MEMBER_ID = "ipb_member_id"
|
||||
const val KEY_IPD_PASS_HASH = "ipb_pass_hash"
|
||||
const val KEY_IGNEOUS = "igneous"
|
||||
private val sTipsCookie: Cookie = Cookie.Builder()
|
||||
.name(EhConfig.KEY_CONTENT_WARNING)
|
||||
.value(EhConfig.CONTENT_WARNING_NOT_SHOW)
|
||||
.domain(EhUrl.DOMAIN_E)
|
||||
.path("/")
|
||||
.expiresAt(Long.MAX_VALUE)
|
||||
.build()
|
||||
|
||||
fun newCookie(
|
||||
cookie: Cookie, newDomain: String, forcePersistent: Boolean,
|
||||
forceLongLive: Boolean, forceNotHostOnly: Boolean
|
||||
): Cookie {
|
||||
val builder = Cookie.Builder()
|
||||
builder.name(cookie.name)
|
||||
builder.value(cookie.value)
|
||||
if (forceLongLive) {
|
||||
builder.expiresAt(Long.MAX_VALUE)
|
||||
} else if (cookie.persistent) {
|
||||
builder.expiresAt(cookie.expiresAt)
|
||||
} else if (forcePersistent) {
|
||||
builder.expiresAt(Long.MAX_VALUE)
|
||||
}
|
||||
if (cookie.hostOnly && !forceNotHostOnly) {
|
||||
builder.hostOnlyDomain(newDomain)
|
||||
} else {
|
||||
builder.domain(newDomain)
|
||||
}
|
||||
builder.path(cookie.path)
|
||||
if (cookie.secure) {
|
||||
builder.secure()
|
||||
}
|
||||
if (cookie.httpOnly) {
|
||||
builder.httpOnly()
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addCookie(cookie: Cookie) {
|
||||
// For cookie database
|
||||
var toAdd: Cookie? = null
|
||||
var toUpdate: Cookie? = null
|
||||
var toRemove: Cookie? = null
|
||||
var set = map[cookie.domain]
|
||||
if (set == null) {
|
||||
set = CookieSet()
|
||||
map[cookie.domain] = set
|
||||
}
|
||||
if (cookie.expiresAt <= System.currentTimeMillis()) {
|
||||
toRemove = set.remove(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (toRemove != null && !toRemove.persistent) {
|
||||
toRemove = null
|
||||
}
|
||||
} else {
|
||||
toAdd = cookie
|
||||
toUpdate = set.add(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (!toAdd.persistent) toAdd = null
|
||||
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
|
||||
// Remove the cookie if it updates to null
|
||||
if (toAdd == null && toUpdate != null) {
|
||||
toRemove = toUpdate
|
||||
toUpdate = null
|
||||
}
|
||||
}
|
||||
if (toRemove != null) {
|
||||
db.remove(toRemove)
|
||||
}
|
||||
if (toAdd != null) {
|
||||
if (toUpdate != null) {
|
||||
db.update(toUpdate, toAdd)
|
||||
} else {
|
||||
db.add(toAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCookieHeader(url: HttpUrl): String {
|
||||
val cookies = getCookies(url)
|
||||
val cookieHeader = StringBuilder()
|
||||
var i = 0
|
||||
val size = cookies.size
|
||||
while (i < size) {
|
||||
if (i > 0) {
|
||||
cookieHeader.append("; ")
|
||||
}
|
||||
val cookie = cookies[i]
|
||||
cookieHeader.append(cookie.name).append('=').append(cookie.value)
|
||||
i++
|
||||
}
|
||||
return cookieHeader.toString()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getCookies(url: HttpUrl): List<Cookie> {
|
||||
val accepted: MutableList<Cookie> = ArrayList()
|
||||
val expired: MutableList<Cookie> = ArrayList()
|
||||
for ((domain, cookieSet) in map) {
|
||||
if (domainMatch(url, domain)) {
|
||||
cookieSet[url, accepted, expired]
|
||||
}
|
||||
}
|
||||
for (cookie in expired) {
|
||||
if (cookie.persistent) {
|
||||
db.remove(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 6265 Section-5.4 step 2, sort the cookie-list
|
||||
// Cookies with longer paths are listed before cookies with shorter paths.
|
||||
// Ignore creation-time, we don't store them.
|
||||
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
|
||||
return accepted
|
||||
}
|
||||
|
||||
fun contains(url: HttpUrl, name: String?): Boolean {
|
||||
for (cookie in getCookies(url)) {
|
||||
if (cookie.name == name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all cookies in this `CookieRepository`.
|
||||
*/
|
||||
@Synchronized
|
||||
fun clear() {
|
||||
map.clear()
|
||||
db.clear()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun close() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
for (cookie in cookies) {
|
||||
addCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
|
||||
* of Android's private InetAddress#isNumeric API.
|
||||
*
|
||||
*
|
||||
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
|
||||
* including dots after the first colon. It matches IPv4 addresses as strings containing only
|
||||
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
|
||||
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
|
||||
* verification).
|
||||
*/
|
||||
private val VERIFY_AS_IP_ADDRESS =
|
||||
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
|
||||
|
||||
/**
|
||||
* Returns true if `host` is not a host name and might be an IP address.
|
||||
*/
|
||||
private fun verifyAsIpAddress(host: String): Boolean {
|
||||
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
|
||||
}
|
||||
|
||||
// okhttp3.Cookie.domainMatch(HttpUrl, String)
|
||||
@JvmStatic
|
||||
fun domainMatch(url: HttpUrl, domain: String?): Boolean {
|
||||
val urlHost = url.host
|
||||
return if (urlHost == domain) {
|
||||
true // As in 'example.com' matching 'example.com'.
|
||||
} else urlHost.endsWith(domain!!)
|
||||
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
|
||||
urlHost
|
||||
)
|
||||
// As in 'example.com' matching 'www.example.com'.
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
val cookies = getCookies(url)
|
||||
val checkTips = domainMatch(url, EhUrl.DOMAIN_E)
|
||||
return if (checkTips) {
|
||||
val result: MutableList<Cookie> = ArrayList(cookies.size + 1)
|
||||
// Add all but skip some
|
||||
for (cookie in cookies) {
|
||||
val name = cookie.name
|
||||
if (EhConfig.KEY_CONTENT_WARNING == name) {
|
||||
continue
|
||||
}
|
||||
if (EhConfig.KEY_UCONFIG == name) {
|
||||
continue
|
||||
}
|
||||
result.add(cookie)
|
||||
}
|
||||
// Add some
|
||||
result.add(sTipsCookie)
|
||||
Collections.unmodifiableList(result)
|
||||
} else {
|
||||
cookies
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -75,7 +75,8 @@ object EhEngine {
|
||||
private const val SAD_PANDA_LENGTH = "9615"
|
||||
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 var sEhFilter = EhFilter
|
||||
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?,
|
||||
|
||||
@@ -1,315 +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.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.dao.Filter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public final class EhFilter {
|
||||
|
||||
public static final int MODE_TITLE = 0;
|
||||
public static final int MODE_UPLOADER = 1;
|
||||
public static final int MODE_TAG = 2;
|
||||
public static final int MODE_TAG_NAMESPACE = 3;
|
||||
public static final int MODE_COMMENTER = 4;
|
||||
public static final int MODE_COMMENT = 5;
|
||||
private static final String TAG = EhFilter.class.getSimpleName();
|
||||
private static EhFilter sInstance;
|
||||
private final List<Filter> mTitleFilterList = new ArrayList<>();
|
||||
private final List<Filter> mUploaderFilterList = new ArrayList<>();
|
||||
private final List<Filter> mTagFilterList = new ArrayList<>();
|
||||
private final List<Filter> mTagNamespaceFilterList = new ArrayList<>();
|
||||
private final List<Filter> mCommenterFilterList = new ArrayList<>();
|
||||
private final List<Filter> mCommentFilterList = new ArrayList<>();
|
||||
|
||||
private EhFilter() {
|
||||
List<Filter> list = EhDB.getAllFilter();
|
||||
for (int i = 0, n = list.size(); i < n; i++) {
|
||||
Filter filter = list.get(i);
|
||||
switch (filter.mode) {
|
||||
case MODE_TITLE -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTitleFilterList.add(filter);
|
||||
}
|
||||
case MODE_TAG -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagFilterList.add(filter);
|
||||
}
|
||||
case MODE_TAG_NAMESPACE -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagNamespaceFilterList.add(filter);
|
||||
}
|
||||
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
|
||||
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
|
||||
case MODE_COMMENT -> mCommentFilterList.add(filter);
|
||||
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull EhFilter getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new EhFilter();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
public List<Filter> getTitleFilterList() {
|
||||
return mTitleFilterList;
|
||||
}
|
||||
|
||||
public List<Filter> getUploaderFilterList() {
|
||||
return mUploaderFilterList;
|
||||
}
|
||||
|
||||
public List<Filter> getTagFilterList() {
|
||||
return mTagFilterList;
|
||||
}
|
||||
|
||||
public List<Filter> getTagNamespaceFilterList() {
|
||||
return mTagNamespaceFilterList;
|
||||
}
|
||||
|
||||
public List<Filter> getCommenterFilterList() {
|
||||
return mCommenterFilterList;
|
||||
}
|
||||
|
||||
public List<Filter> getCommentFilterList() {
|
||||
return mCommentFilterList;
|
||||
}
|
||||
|
||||
public synchronized boolean addFilter(Filter filter) {
|
||||
// enable filter by default before it is added to database
|
||||
filter.enable = true;
|
||||
if (!EhDB.addFilter(filter)) return false;
|
||||
|
||||
switch (filter.mode) {
|
||||
case MODE_TITLE -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTitleFilterList.add(filter);
|
||||
}
|
||||
case MODE_TAG -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagFilterList.add(filter);
|
||||
}
|
||||
case MODE_TAG_NAMESPACE -> {
|
||||
filter.text = filter.text.toLowerCase();
|
||||
mTagNamespaceFilterList.add(filter);
|
||||
}
|
||||
case MODE_UPLOADER -> mUploaderFilterList.add(filter);
|
||||
case MODE_COMMENTER -> mCommenterFilterList.add(filter);
|
||||
case MODE_COMMENT -> mCommentFilterList.add(filter);
|
||||
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized void triggerFilter(Filter filter) {
|
||||
EhDB.triggerFilter(filter);
|
||||
}
|
||||
|
||||
public synchronized void deleteFilter(Filter filter) {
|
||||
EhDB.deleteFilter(filter);
|
||||
|
||||
switch (filter.mode) {
|
||||
case MODE_TITLE -> mTitleFilterList.remove(filter);
|
||||
case MODE_TAG -> mTagFilterList.remove(filter);
|
||||
case MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter);
|
||||
case MODE_UPLOADER -> mUploaderFilterList.remove(filter);
|
||||
case MODE_COMMENTER -> mCommenterFilterList.remove(filter);
|
||||
case MODE_COMMENT -> mCommentFilterList.remove(filter);
|
||||
default -> Log.d(TAG, "Unknown mode: " + filter.mode);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean needTags() {
|
||||
return 0 != mTagFilterList.size() || 0 != mTagNamespaceFilterList.size();
|
||||
}
|
||||
|
||||
public synchronized boolean filterTitle(GalleryInfo info) {
|
||||
if (null == info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
if (filters.get(i).enable && title.toLowerCase().contains(filters.get(i).text)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean filterUploader(GalleryInfo info) {
|
||||
if (null == info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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++) {
|
||||
if (filters.get(i).enable && uploader.equals(filters.get(i).text)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matchTag(String tag, String filter) {
|
||||
if (null == tag || null == filter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String tagNamespace;
|
||||
String tagName;
|
||||
String filterNamespace;
|
||||
String filterName;
|
||||
int index = tag.indexOf(':');
|
||||
if (index < 0) {
|
||||
tagNamespace = null;
|
||||
tagName = tag;
|
||||
} else {
|
||||
tagNamespace = tag.substring(0, index);
|
||||
tagName = tag.substring(index + 1);
|
||||
}
|
||||
index = filter.indexOf(':');
|
||||
if (index < 0) {
|
||||
filterNamespace = null;
|
||||
filterName = filter;
|
||||
} else {
|
||||
filterNamespace = filter.substring(0, index);
|
||||
filterName = filter.substring(index + 1);
|
||||
}
|
||||
|
||||
if (null != tagNamespace && null != filterNamespace &&
|
||||
!tagNamespace.equals(filterNamespace)) {
|
||||
return false;
|
||||
}
|
||||
return tagName.equals(filterName);
|
||||
}
|
||||
|
||||
public synchronized boolean filterTag(GalleryInfo info) {
|
||||
if (null == info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Tag
|
||||
String[] tags = info.getSimpleTags();
|
||||
List<Filter> filters = mTagFilterList;
|
||||
if (null != tags && filters.size() > 0) {
|
||||
for (String tag : tags) {
|
||||
for (int i = 0, n = filters.size(); i < n; i++) {
|
||||
if (filters.get(i).enable && matchTag(tag, filters.get(i).text)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matchTagNamespace(String tag, String filter) {
|
||||
if (null == tag || null == filter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String tagNamespace;
|
||||
int index = tag.indexOf(':');
|
||||
if (index >= 0) {
|
||||
tagNamespace = tag.substring(0, index);
|
||||
return tagNamespace.equals(filter);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized boolean filterTagNamespace(GalleryInfo info) {
|
||||
if (null == info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String[] tags = info.getSimpleTags();
|
||||
List<Filter> filters = mTagNamespaceFilterList;
|
||||
if (null != tags && filters.size() > 0) {
|
||||
for (String tag : tags) {
|
||||
for (int i = 0, n = filters.size(); i < n; i++) {
|
||||
if (filters.get(i).enable && matchTagNamespace(tag, filters.get(i).text)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean filterCommenter(String commenter) {
|
||||
if (null == commenter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Filter> filters = mCommenterFilterList;
|
||||
if (filters.size() > 0) {
|
||||
for (int i = 0, n = filters.size(); i < n; i++) {
|
||||
if (filters.get(i).enable && commenter.equals(filters.get(i).text)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public synchronized boolean filterComment(String comment) {
|
||||
if (null == comment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Filter> filters = mCommentFilterList;
|
||||
if (filters.size() > 0) {
|
||||
for (int i = 0, n = filters.size(); i < n; i++) {
|
||||
if (filters.get(i).enable) {
|
||||
Pattern p = Pattern.compile(filters.get(i).text);
|
||||
Matcher m = p.matcher(comment);
|
||||
if (m.find()) return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.util.Log
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
|
||||
object EhFilter {
|
||||
private val mTitleFilterList: MutableList<Filter> = ArrayList()
|
||||
private val mUploaderFilterList: MutableList<Filter> = ArrayList()
|
||||
private val mTagFilterList: MutableList<Filter> = ArrayList()
|
||||
private val mTagNamespaceFilterList: MutableList<Filter> = ArrayList()
|
||||
private val mCommenterFilterList: MutableList<Filter> = ArrayList()
|
||||
private val mCommentFilterList: MutableList<Filter> = ArrayList()
|
||||
|
||||
const val MODE_TITLE = 0
|
||||
const val MODE_UPLOADER = 1
|
||||
const val MODE_TAG = 2
|
||||
const val MODE_TAG_NAMESPACE = 3
|
||||
const val MODE_COMMENTER = 4
|
||||
const val MODE_COMMENT = 5
|
||||
private val TAG = EhFilter::class.java.simpleName
|
||||
|
||||
init {
|
||||
val list = EhDB.getAllFilter()
|
||||
var i = 0
|
||||
val n = list.size
|
||||
while (i < n) {
|
||||
val filter = list[i]
|
||||
when (filter.mode) {
|
||||
MODE_TITLE -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTitleFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_TAG -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTagFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_TAG_NAMESPACE -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTagNamespaceFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_UPLOADER -> mUploaderFilterList.add(filter)
|
||||
MODE_COMMENTER -> mCommenterFilterList.add(filter)
|
||||
MODE_COMMENT -> mCommentFilterList.add(filter)
|
||||
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
val titleFilterList: List<Filter>
|
||||
get() = mTitleFilterList
|
||||
val uploaderFilterList: List<Filter>
|
||||
get() = mUploaderFilterList
|
||||
val tagFilterList: List<Filter>
|
||||
get() = mTagFilterList
|
||||
val tagNamespaceFilterList: List<Filter>
|
||||
get() = mTagNamespaceFilterList
|
||||
val commenterFilterList: List<Filter>
|
||||
get() = mCommenterFilterList
|
||||
val commentFilterList: List<Filter>
|
||||
get() = mCommentFilterList
|
||||
|
||||
@Synchronized
|
||||
fun addFilter(filter: Filter): Boolean {
|
||||
// enable filter by default before it is added to database
|
||||
filter.enable = true
|
||||
if (!EhDB.addFilter(filter)) return false
|
||||
when (filter.mode) {
|
||||
MODE_TITLE -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTitleFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_TAG -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTagFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_TAG_NAMESPACE -> {
|
||||
filter.text = filter.text!!.lowercase(Locale.getDefault())
|
||||
mTagNamespaceFilterList.add(filter)
|
||||
}
|
||||
|
||||
MODE_UPLOADER -> mUploaderFilterList.add(filter)
|
||||
MODE_COMMENTER -> mCommenterFilterList.add(filter)
|
||||
MODE_COMMENT -> mCommentFilterList.add(filter)
|
||||
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun triggerFilter(filter: Filter?) {
|
||||
EhDB.triggerFilter(filter)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun deleteFilter(filter: Filter) {
|
||||
EhDB.deleteFilter(filter)
|
||||
when (filter.mode) {
|
||||
MODE_TITLE -> mTitleFilterList.remove(filter)
|
||||
MODE_TAG -> mTagFilterList.remove(filter)
|
||||
MODE_TAG_NAMESPACE -> mTagNamespaceFilterList.remove(filter)
|
||||
MODE_UPLOADER -> mUploaderFilterList.remove(filter)
|
||||
MODE_COMMENTER -> mCommenterFilterList.remove(filter)
|
||||
MODE_COMMENT -> mCommentFilterList.remove(filter)
|
||||
else -> Log.d(TAG, "Unknown mode: " + filter.mode)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun needTags(): Boolean {
|
||||
return 0 != mTagFilterList.size || 0 != mTagNamespaceFilterList.size
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterTitle(info: GalleryInfo?): Boolean {
|
||||
if (null == info) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Title
|
||||
val title = info.title
|
||||
val filters: List<Filter> = mTitleFilterList
|
||||
if (null != title && filters.isNotEmpty()) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!! && title.lowercase(Locale.getDefault()).contains(
|
||||
filters[i].text!!
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterUploader(info: GalleryInfo?): Boolean {
|
||||
if (null == info) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Uploader
|
||||
val uploader = info.uploader
|
||||
val filters: List<Filter> = mUploaderFilterList
|
||||
if (null != uploader && filters.isNotEmpty()) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!! && uploader == filters[i].text) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun matchTag(tag: String?, filter: String?): Boolean {
|
||||
if (null == tag || null == filter) {
|
||||
return false
|
||||
}
|
||||
val tagNamespace: String?
|
||||
val tagName: String
|
||||
val filterNamespace: String?
|
||||
val filterName: String
|
||||
var index = tag.indexOf(':')
|
||||
if (index < 0) {
|
||||
tagNamespace = null
|
||||
tagName = tag
|
||||
} else {
|
||||
tagNamespace = tag.substring(0, index)
|
||||
tagName = tag.substring(index + 1)
|
||||
}
|
||||
index = filter.indexOf(':')
|
||||
if (index < 0) {
|
||||
filterNamespace = null
|
||||
filterName = filter
|
||||
} else {
|
||||
filterNamespace = filter.substring(0, index)
|
||||
filterName = filter.substring(index + 1)
|
||||
}
|
||||
return if (null != tagNamespace && null != filterNamespace &&
|
||||
tagNamespace != filterNamespace
|
||||
) {
|
||||
false
|
||||
} else tagName == filterName
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterTag(info: GalleryInfo?): Boolean {
|
||||
if (null == info) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Tag
|
||||
val tags = info.simpleTags
|
||||
val filters: List<Filter> = mTagFilterList
|
||||
if (null != tags && filters.isNotEmpty()) {
|
||||
for (tag in tags) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!! && matchTag(tag, filters[i].text)) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun matchTagNamespace(tag: String?, filter: String?): Boolean {
|
||||
if (null == tag || null == filter) {
|
||||
return false
|
||||
}
|
||||
val tagNamespace: String
|
||||
val index = tag.indexOf(':')
|
||||
return if (index >= 0) {
|
||||
tagNamespace = tag.substring(0, index)
|
||||
tagNamespace == filter
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterTagNamespace(info: GalleryInfo?): Boolean {
|
||||
if (null == info) {
|
||||
return false
|
||||
}
|
||||
val tags = info.simpleTags
|
||||
val filters: List<Filter> = mTagNamespaceFilterList
|
||||
if (null != tags && filters.isNotEmpty()) {
|
||||
for (tag in tags) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!! && matchTagNamespace(tag, filters[i].text)) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterCommenter(commenter: String?): Boolean {
|
||||
if (null == commenter) {
|
||||
return false
|
||||
}
|
||||
val filters: List<Filter> = mCommenterFilterList
|
||||
if (filters.isNotEmpty()) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!! && commenter == filters[i].text) {
|
||||
return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun filterComment(comment: String?): Boolean {
|
||||
if (null == comment) {
|
||||
return false
|
||||
}
|
||||
val filters: List<Filter> = mCommentFilterList
|
||||
if (filters.isNotEmpty()) {
|
||||
var i = 0
|
||||
val n = filters.size
|
||||
while (i < n) {
|
||||
if (filters[i].enable!!) {
|
||||
val p = Pattern.compile(filters[i].text!!)
|
||||
val m = p.matcher(comment)
|
||||
if (m.find()) return false
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class EhRequest {
|
||||
get() = job?.isActive ?: false
|
||||
var method = 0
|
||||
private set
|
||||
var args: Array<out Any>? = null
|
||||
var args: Array<out Any?>? = null
|
||||
private set
|
||||
var callback: EhClient.Callback<Any?>? = null
|
||||
private set
|
||||
@@ -41,7 +41,7 @@ class EhRequest {
|
||||
return this
|
||||
}
|
||||
|
||||
fun setArgs(vararg args: Any): EhRequest {
|
||||
fun setArgs(vararg args: Any?): EhRequest {
|
||||
this.args = args
|
||||
return this
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -22,11 +22,11 @@ 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;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo;
|
||||
import com.hippo.ehviewer.client.data.GalleryTagGroup;
|
||||
import com.hippo.ehviewer.client.data.LargePreviewSet;
|
||||
import com.hippo.ehviewer.client.data.NormalPreviewSet;
|
||||
@@ -477,7 +477,7 @@ public class GalleryDetailParser {
|
||||
comment.comment = JsoupUtils.getElementByClass(element, "c6").html();
|
||||
// filter comment
|
||||
if (!comment.uploader) {
|
||||
EhFilter sEhFilter = EhFilter.getInstance();
|
||||
EhFilter sEhFilter = EhFilter.INSTANCE;
|
||||
if (!sEhFilter.filterCommenter(comment.user) || !sEhFilter.filterComment(comment.comment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
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;
|
||||
import com.hippo.util.JsoupUtils;
|
||||
|
||||
@@ -3,8 +3,8 @@ package com.hippo.ehviewer.dao
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Ignore
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.client.data.BaseGalleryInfo
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
|
||||
@Entity(tableName = "DOWNLOADS")
|
||||
class DownloadInfo() : BaseGalleryInfo() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,8 +2,8 @@ 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
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
|
||||
@Entity(tableName = "HISTORY")
|
||||
class HistoryInfo() : BaseGalleryInfo() {
|
||||
|
||||
@@ -2,8 +2,8 @@ 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
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
|
||||
@Entity(tableName = "LOCAL_FAVORITES")
|
||||
class LocalFavoriteInfo() : BaseGalleryInfo() {
|
||||
|
||||
@@ -24,8 +24,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.hippo.ehviewer.EhDB;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
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;
|
||||
import com.hippo.ehviewer.spider.SpiderDen;
|
||||
@@ -54,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
|
||||
@@ -158,6 +158,11 @@ public class DownloadManager implements SpiderQueen.OnSpiderListener {
|
||||
return mLabelList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<DownloadInfo> getAllDownloadInfoList() {
|
||||
return mAllInfoList;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<DownloadInfo> getDefaultDownloadInfoList() {
|
||||
return mDefaultInfoList;
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -20,7 +20,6 @@ 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
|
||||
@@ -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()
|
||||
)
|
||||
|
||||
@@ -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.getGid(), mGalleryInfo.getToken());
|
||||
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);
|
||||
|
||||
|
||||
@@ -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,61 @@ 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.customview.widget.Openable
|
||||
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.NavigationUI
|
||||
import androidx.navigation.ui.onNavDestinationSelected2
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
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.GalleryListScene.Companion.toStartArgs
|
||||
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 +115,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 +184,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,
|
||||
builder.toStartArgs()
|
||||
)
|
||||
return true
|
||||
} else if (type != null && type.startsWith("image/")) {
|
||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
@@ -195,69 +199,67 @@ 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,
|
||||
builder.toStartArgs()
|
||||
)
|
||||
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()
|
||||
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)
|
||||
}
|
||||
}
|
||||
if (0 == sceneCount) {
|
||||
if (handleUrl) {
|
||||
finish()
|
||||
}
|
||||
binding.drawView.addDrawerListener(mDrawerOnBackPressedCallback)
|
||||
binding.navView.setupWithNavController(navController)
|
||||
|
||||
// Trick: Tweak NavigationUI to disable multiple backstack
|
||||
binding.navView.setNavigationItemSelectedListener {
|
||||
val navigationView = binding.navView
|
||||
val handled = onNavDestinationSelected2(it, navController)
|
||||
if (handled) {
|
||||
val parent = navigationView.parent
|
||||
if (parent is Openable) {
|
||||
parent.close()
|
||||
} else {
|
||||
val args = Bundle()
|
||||
args.putString(
|
||||
GalleryListScene.KEY_ACTION,
|
||||
Settings.getLaunchPageGalleryListSceneAction()
|
||||
)
|
||||
startScene(processAnnouncer(Announcer(GalleryListScene::class.java).setArgs(args)))
|
||||
val bottomSheetBehavior = NavigationUI.findBottomSheetBehavior(navigationView)
|
||||
if (bottomSheetBehavior != null) {
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
}
|
||||
}
|
||||
}
|
||||
handled
|
||||
}
|
||||
}
|
||||
// Trick End
|
||||
|
||||
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)
|
||||
}
|
||||
if (savedInstanceState == null) {
|
||||
if (intent.action != Intent.ACTION_MAIN) {
|
||||
onNewIntent(intent)
|
||||
}
|
||||
checkDownloadLocation()
|
||||
if (Settings.getMeteredNetworkWarning()) {
|
||||
checkMeteredNetwork()
|
||||
@@ -270,8 +272,6 @@ class MainActivity : StageActivity(), NavigationView.OnNavigationItemSelectedLis
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onRestore(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,9 +335,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 +353,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 +426,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,17 +460,6 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun showTip(@StringRes id: Int, length: Int, useToast: Boolean = false) {
|
||||
showTip(getString(id), length, useToast)
|
||||
@@ -537,77 +481,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) {
|
||||
@@ -618,26 +502,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)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +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.annotation;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Created in {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)},
|
||||
* destroyed in {@link Fragment#onDestroyView()}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface DrawerLifeCircle {
|
||||
}
|
||||
@@ -1,37 +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.annotation;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Created in {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)},
|
||||
* destroyed in {@link Fragment#onDestroyView()}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface ViewLifeCircle {
|
||||
}
|
||||
@@ -1,36 +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.annotation;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Created in {@link Fragment#onCreate(Bundle)} or {@link Activity#onCreate(Bundle)},
|
||||
* destroyed in {@link Fragment#onDestroy()} or {@link Activity#onDestroy()}.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target({ElementType.FIELD})
|
||||
public @interface WholeLifeCircle {
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
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.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
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.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() {
|
||||
if (signInJob?.isActive == true) return
|
||||
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 {
|
||||
EhEngine.getProfile()
|
||||
}.onSuccess {
|
||||
withUIContext {
|
||||
navController.navigate(SELECT_SITE_ROUTE_NAME)
|
||||
}
|
||||
}.onFailure {
|
||||
EhApplication.ehCookieStore.signOut()
|
||||
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,142 @@
|
||||
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.EhEngine
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
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 {
|
||||
EhEngine.getProfile().run {
|
||||
Settings.putDisplayName(displayName)
|
||||
Settings.putAvatar(avatar)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
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.EhEngine
|
||||
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 {
|
||||
EhEngine.signIn(username, password)
|
||||
}.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -259,7 +259,7 @@ class FilterFragment : BaseFragment() {
|
||||
}
|
||||
|
||||
private inner class FilterList {
|
||||
private val mEhFilter: EhFilter = EhFilter.getInstance()
|
||||
private val mEhFilter: EhFilter = EhFilter
|
||||
private val mTitleFilterList: List<Filter> = mEhFilter.titleFilterList
|
||||
private val mUploaderFilterList: List<Filter> = mEhFilter.uploaderFilterList
|
||||
private val mTagFilterList: List<Filter> = mEhFilter.tagFilterList
|
||||
@@ -407,11 +407,11 @@ class FilterFragment : BaseFragment() {
|
||||
throw IndexOutOfBoundsException()
|
||||
}
|
||||
|
||||
fun add(filter: Filter?): Boolean {
|
||||
fun add(filter: Filter): Boolean {
|
||||
return mEhFilter.addFilter(filter)
|
||||
}
|
||||
|
||||
fun delete(filter: Filter?) {
|
||||
fun delete(filter: Filter) {
|
||||
mEhFilter.deleteFilter(filter)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,244 @@
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@@ -65,7 +64,6 @@ import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.DownloadService.Companion.clear
|
||||
import com.hippo.ehviewer.spider.SpiderDen
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.scene.Announcer
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.FabLayout
|
||||
@@ -109,15 +107,13 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
private var mLabelAdapter: DownloadLabelAdapter? = null
|
||||
private lateinit var mLabels: MutableList<String>
|
||||
private var mType = -1
|
||||
override fun getNavCheckedItem(): Int {
|
||||
return R.id.nav_downloads
|
||||
}
|
||||
|
||||
private fun initLabels() {
|
||||
context ?: return
|
||||
val listLabel = downloadManager.labelList
|
||||
mLabels = ArrayList(listLabel.size + 1)
|
||||
// Add default label name
|
||||
mLabels = ArrayList(listLabel.size + LABEL_OFFSET)
|
||||
// Add "All" and "Default" label names
|
||||
mLabels.add(getString(R.string.download_all))
|
||||
mLabels.add(getString(R.string.default_download_label_name))
|
||||
for ((_, label) in listLabel) {
|
||||
mLabels.add(label!!)
|
||||
@@ -154,10 +150,6 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onNewArguments(args: Bundle) {
|
||||
handleArguments(args)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val context = context
|
||||
@@ -193,12 +185,14 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
}
|
||||
var list: MutableList<DownloadInfo>?
|
||||
if (mLabel == null) {
|
||||
list = mDownloadManager!!.allDownloadInfoList
|
||||
} else if (mLabel == getString(R.string.default_download_label_name)) {
|
||||
list = mDownloadManager!!.defaultDownloadInfoList
|
||||
} else {
|
||||
list = mDownloadManager!!.getLabelDownloadInfoList(mLabel)
|
||||
if (list == null) {
|
||||
mLabel = null
|
||||
list = mDownloadManager!!.defaultDownloadInfoList
|
||||
list = mDownloadManager!!.allDownloadInfoList
|
||||
}
|
||||
}
|
||||
if (mType != -1) {
|
||||
@@ -221,7 +215,7 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
setTitle(
|
||||
getString(
|
||||
R.string.scene_download_title,
|
||||
if (mLabel != null) mLabel else getString(R.string.default_download_label_name)
|
||||
if (mLabel != null) mLabel else getString(R.string.download_all)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -294,6 +288,7 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
mRecyclerView!!.scrollToPosition(mInitPosition)
|
||||
mInitPosition = -1
|
||||
}
|
||||
itemTouchHelper.attachToRecyclerView(mRecyclerView)
|
||||
mFastScroller!!.attachToRecyclerView(mRecyclerView)
|
||||
val handlerDrawable = HandlerDrawable()
|
||||
handlerDrawable.setColor(theme.resolveColor(com.google.android.material.R.attr.colorPrimary))
|
||||
@@ -640,10 +635,10 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
}
|
||||
|
||||
override fun onUpdate(info: DownloadInfo, list: List<DownloadInfo>) {
|
||||
if (mList !== list) {
|
||||
if (mLabel != null && mList !== list) {
|
||||
return
|
||||
}
|
||||
val index = list.indexOf(info)
|
||||
val index = mList!!.indexOf(info)
|
||||
if (index >= 0 && mAdapter != null) {
|
||||
mAdapter!!.notifyItemChanged(index)
|
||||
}
|
||||
@@ -791,7 +786,36 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
private inner class DownloadLabelAdapter(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<DownloadLabelHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadLabelHolder {
|
||||
return DownloadLabelHolder(mInflater.inflate(R.layout.item_drawer_list, parent, false))
|
||||
val holder =
|
||||
DownloadLabelHolder(mInflater.inflate(R.layout.item_drawer_list, parent, false))
|
||||
holder.itemView.setOnClickListener {
|
||||
val position = holder.bindingAdapterPosition
|
||||
val label1: String? = if (position == 0) {
|
||||
null
|
||||
} else {
|
||||
mLabels[position]
|
||||
}
|
||||
if (!ObjectUtils.equal(label1, mLabel)) {
|
||||
mLabel = label1
|
||||
updateForLabel()
|
||||
updateView()
|
||||
closeDrawer(GravityCompat.END)
|
||||
}
|
||||
}
|
||||
holder.edit.setOnClickListener {
|
||||
val context = context
|
||||
val label = mLabels[holder.bindingAdapterPosition]
|
||||
if (context != null) {
|
||||
val builder = EditTextDialogBuilder(
|
||||
context, label, getString(R.string.download_labels)
|
||||
)
|
||||
builder.setTitle(R.string.rename_label_title)
|
||||
builder.setPositiveButton(android.R.string.ok, null)
|
||||
val dialog = builder.show()
|
||||
RenameLabelDialogHelper(builder, dialog, label)
|
||||
}
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@@ -805,10 +829,18 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
}
|
||||
var list: List<DownloadInfo?>? = null
|
||||
if (mDownloadManager != null) {
|
||||
list = if (position == 0) {
|
||||
mDownloadManager!!.defaultDownloadInfoList
|
||||
} else {
|
||||
mDownloadManager!!.getLabelDownloadInfoList(label)
|
||||
list = when (position) {
|
||||
0 -> {
|
||||
mDownloadManager!!.allDownloadInfoList
|
||||
}
|
||||
|
||||
1 -> {
|
||||
mDownloadManager!!.defaultDownloadInfoList
|
||||
}
|
||||
|
||||
else -> {
|
||||
mDownloadManager!!.getLabelDownloadInfoList(label)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list != null) {
|
||||
@@ -816,39 +848,17 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
} else {
|
||||
holder.label.text = label
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
val label1: String? = if (position == 0) {
|
||||
null
|
||||
} else {
|
||||
mLabels[position]
|
||||
}
|
||||
if (!ObjectUtils.equal(label1, mLabel)) {
|
||||
mLabel = label1
|
||||
updateForLabel()
|
||||
updateView()
|
||||
closeDrawer(GravityCompat.END)
|
||||
}
|
||||
}
|
||||
if (position > 0) {
|
||||
holder.edit.visibility = View.VISIBLE
|
||||
holder.edit.setOnClickListener {
|
||||
if (context != null) {
|
||||
val builder = EditTextDialogBuilder(
|
||||
context, label, getString(R.string.download_labels)
|
||||
)
|
||||
builder.setTitle(R.string.rename_label_title)
|
||||
builder.setPositiveButton(android.R.string.ok, null)
|
||||
val dialog = builder.show()
|
||||
RenameLabelDialogHelper(builder, dialog, label)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (position < LABEL_OFFSET) {
|
||||
holder.edit.visibility = View.GONE
|
||||
holder.option.visibility = View.GONE
|
||||
} else {
|
||||
holder.edit.visibility = View.VISIBLE
|
||||
holder.option.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
return mLabels[position].hashCode().toLong()
|
||||
return (if (position < LABEL_OFFSET) position else mLabels[position].hashCode()).toLong()
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@@ -969,8 +979,7 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
GalleryDetailScene.ACTION_GALLERY_INFO
|
||||
)
|
||||
args.putParcelable(GalleryDetailScene.KEY_GALLERY_INFO, list[index])
|
||||
val announcer = Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
startScene(announcer)
|
||||
navigate(R.id.galleryDetailScene, args)
|
||||
} else if (start === v) {
|
||||
val intent = Intent(activity, DownloadService::class.java)
|
||||
intent.action = DownloadService.ACTION_START
|
||||
@@ -1010,6 +1019,8 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
lp.width = mListThumbWidth
|
||||
lp.height = mListThumbHeight
|
||||
holder.thumb.layoutParams = lp
|
||||
holder.itemView.setOnClickListener { onItemClick(holder.bindingAdapterPosition) }
|
||||
holder.itemView.setOnLongClickListener { onItemLongClick(holder.bindingAdapterPosition) }
|
||||
return holder
|
||||
}
|
||||
|
||||
@@ -1031,14 +1042,6 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(info.category))
|
||||
}
|
||||
bindForState(holder, info)
|
||||
|
||||
// Update transition name
|
||||
ViewCompat.setTransitionName(
|
||||
holder.thumb,
|
||||
TransitionNameFactory.getThumbTransitionName(info.gid)
|
||||
)
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
holder.itemView.setOnLongClickListener { onItemLongClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
@@ -1150,13 +1153,17 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return makeMovementFlags(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.LEFT
|
||||
)
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
return if (position < LABEL_OFFSET) {
|
||||
makeMovementFlags(0, 0)
|
||||
} else {
|
||||
makeMovementFlags(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.LEFT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
@@ -1165,37 +1172,87 @@ class DownloadsScene : BaseToolbarScene(), DownloadInfoListener, OnClickFabListe
|
||||
val fromPosition = viewHolder.bindingAdapterPosition
|
||||
val toPosition = target.bindingAdapterPosition
|
||||
val context = context
|
||||
if (null == context || fromPosition == toPosition || toPosition == 0 || fromPosition == 0) {
|
||||
if (null == context || fromPosition == toPosition || toPosition < LABEL_OFFSET) {
|
||||
return false
|
||||
}
|
||||
downloadManager.moveLabel(fromPosition - 1, toPosition - 1)
|
||||
downloadManager.moveLabel(fromPosition - LABEL_OFFSET, toPosition - LABEL_OFFSET)
|
||||
val item = mLabels.removeAt(fromPosition)
|
||||
mLabels.add(toPosition, item)
|
||||
mLabelAdapter!!.notifyDataSetChanged()
|
||||
mLabelAdapter!!.notifyItemMoved(fromPosition, toPosition)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
if (mDownloadManager == null) return
|
||||
lifecycleScope.launchIO {
|
||||
if (position != 0) {
|
||||
val label = mLabels[position]
|
||||
mDownloadManager!!.deleteLabel(label)
|
||||
}
|
||||
mLabels.removeAt(position)
|
||||
withUIContext {
|
||||
mLabelAdapter!!.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
val label = mLabels[position]
|
||||
BaseDialogBuilder(context!!)
|
||||
.setMessage(getString(R.string.delete_label, label))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
lifecycleScope.launchIO {
|
||||
mDownloadManager!!.deleteLabel(label)
|
||||
mLabels.removeAt(position)
|
||||
withUIContext {
|
||||
mLabelAdapter!!.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
}.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}.setOnCancelListener {
|
||||
mLabelAdapter!!.notifyItemChanged(position)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
private val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.Callback() {
|
||||
override fun getMovementFlags(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder
|
||||
): Int {
|
||||
return makeMovementFlags(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMove(
|
||||
recyclerView: RecyclerView,
|
||||
viewHolder: RecyclerView.ViewHolder,
|
||||
target: RecyclerView.ViewHolder
|
||||
): Boolean {
|
||||
val fromPosition = viewHolder.bindingAdapterPosition
|
||||
val toPosition = target.bindingAdapterPosition
|
||||
if (fromPosition == toPosition) {
|
||||
return false
|
||||
}
|
||||
val allInfoList = mDownloadManager!!.allDownloadInfoList
|
||||
val dbFromPosition = allInfoList.indexOf(mList!![fromPosition])
|
||||
val dbToPosition = allInfoList.indexOf(mList!![toPosition])
|
||||
lifecycleScope.launchIO {
|
||||
EhDB.moveDownloadInfo(dbFromPosition, dbToPosition)
|
||||
val item = allInfoList.removeAt(dbFromPosition)
|
||||
allInfoList.add(dbToPosition, item)
|
||||
if (mLabel != null) {
|
||||
val item1 = mList!!.removeAt(fromPosition)
|
||||
mList!!.add(toPosition, item1)
|
||||
}
|
||||
withUIContext {
|
||||
mAdapter!!.notifyItemMoved(fromPosition, toPosition)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
}
|
||||
})
|
||||
|
||||
companion object {
|
||||
const val KEY_GID = "gid"
|
||||
const val KEY_ACTION = "action"
|
||||
const val ACTION_CLEAR_DOWNLOAD_SERVICE = "clear_download_service"
|
||||
private val TAG = DownloadsScene::class.java.simpleName
|
||||
private const val KEY_LABEL = "label"
|
||||
private const val LABEL_OFFSET = 2
|
||||
}
|
||||
}
|
||||
@@ -1,93 +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.widget.Toast;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
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> {
|
||||
|
||||
private final EhApplication mApplication;
|
||||
private final int mStageId;
|
||||
private final String mSceneTag;
|
||||
|
||||
public EhCallback(Context context, int stageId, String sceneTag) {
|
||||
mApplication = (EhApplication) context.getApplicationContext();
|
||||
mStageId = stageId;
|
||||
mSceneTag = sceneTag;
|
||||
}
|
||||
|
||||
public abstract boolean isInstance(SceneFragment scene);
|
||||
|
||||
public Context getContent() {
|
||||
Context context = getStageActivity();
|
||||
if (context == null) {
|
||||
context = getApplication();
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
public EhApplication getApplication() {
|
||||
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();
|
||||
if (activity instanceof MainActivity) {
|
||||
((MainActivity) activity).showTip(id, length);
|
||||
} else {
|
||||
Toast.makeText(getApplication(), id,
|
||||
length == BaseScene.LENGTH_LONG ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void showTip(String tip, int length) {
|
||||
StageActivity activity = getStageActivity();
|
||||
if (activity instanceof MainActivity) {
|
||||
((MainActivity) activity).showTip(tip, length);
|
||||
} else {
|
||||
Toast.makeText(getApplication(), tip,
|
||||
length == BaseScene.LENGTH_LONG ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.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.MainActivity
|
||||
|
||||
abstract class EhCallback<E : Fragment?, T>(context: Context) : EhClient.Callback<T> {
|
||||
val application: EhApplication
|
||||
|
||||
init {
|
||||
application = context.applicationContext as EhApplication
|
||||
}
|
||||
|
||||
val content: Context
|
||||
get() {
|
||||
var context: Context? = application.topActivity
|
||||
if (context == null) {
|
||||
context = application
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int) {
|
||||
val activity = application.topActivity
|
||||
if (activity is MainActivity) {
|
||||
activity.showTip(id, length)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
application, id,
|
||||
if (length == BaseScene.LENGTH_LONG) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun showTip(tip: String, length: Int) {
|
||||
val activity = application.topActivity
|
||||
if (activity is MainActivity) {
|
||||
activity.showTip(tip, length)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
application, tip,
|
||||
if (length == BaseScene.LENGTH_LONG) Toast.LENGTH_LONG else Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,245 +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.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.hippo.drawable.TriangleDrawable;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory;
|
||||
import com.hippo.ehviewer.client.EhUtils;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
import com.hippo.ehviewer.download.DownloadManager;
|
||||
import com.hippo.ehviewer.widget.TileThumb;
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
|
||||
|
||||
public static final int TYPE_INVALID = -1;
|
||||
public static final int TYPE_LIST = 0;
|
||||
public static final int TYPE_GRID = 1;
|
||||
private final LayoutInflater mInflater;
|
||||
private final Resources mResources;
|
||||
private final RecyclerView mRecyclerView;
|
||||
private final AutoStaggeredGridLayoutManager mLayoutManager;
|
||||
private final int mPaddingTopSB;
|
||||
private final int mListThumbWidth;
|
||||
private final int mListThumbHeight;
|
||||
private final boolean mShowFavourited;
|
||||
private final DownloadManager mDownloadManager;
|
||||
private MarginItemDecoration mListDecoration;
|
||||
private MarginItemDecoration mGirdDecoration;
|
||||
private int mType = TYPE_INVALID;
|
||||
|
||||
public GalleryAdapter(@NonNull LayoutInflater inflater, @NonNull Resources resources,
|
||||
@NonNull RecyclerView recyclerView, int type, boolean showFavourited) {
|
||||
mInflater = inflater;
|
||||
mResources = resources;
|
||||
mRecyclerView = recyclerView;
|
||||
mLayoutManager = new AutoStaggeredGridLayoutManager(0, StaggeredGridLayoutManager.VERTICAL);
|
||||
mPaddingTopSB = resources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar);
|
||||
mShowFavourited = showFavourited;
|
||||
|
||||
mRecyclerView.setAdapter(this);
|
||||
mRecyclerView.setLayoutManager(mLayoutManager);
|
||||
|
||||
@SuppressLint("InflateParams") View calculator = inflater.inflate(R.layout.item_gallery_list_thumb_height, null);
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
mListThumbHeight = calculator.getMeasuredHeight();
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3;
|
||||
|
||||
setType(type);
|
||||
|
||||
mDownloadManager = EhApplication.getDownloadManager();
|
||||
}
|
||||
|
||||
private void adjustPaddings() {
|
||||
RecyclerView recyclerView = mRecyclerView;
|
||||
recyclerView.setPadding(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop() + mPaddingTopSB,
|
||||
recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public void setType(int type) {
|
||||
if (type == mType) {
|
||||
return;
|
||||
}
|
||||
mType = type;
|
||||
|
||||
RecyclerView recyclerView = mRecyclerView;
|
||||
switch (type) {
|
||||
case GalleryAdapter.TYPE_LIST -> {
|
||||
int columnWidth = mResources.getDimensionPixelOffset(Settings.getDetailSizeResId());
|
||||
mLayoutManager.setColumnSize(columnWidth);
|
||||
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE);
|
||||
if (null != mGirdDecoration) {
|
||||
recyclerView.removeItemDecoration(mGirdDecoration);
|
||||
}
|
||||
if (null == mListDecoration) {
|
||||
int interval = mResources.getDimensionPixelOffset(R.dimen.gallery_list_interval);
|
||||
int paddingH = mResources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h);
|
||||
int paddingV = mResources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v);
|
||||
mListDecoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
|
||||
}
|
||||
recyclerView.addItemDecoration(mListDecoration);
|
||||
adjustPaddings();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
case GalleryAdapter.TYPE_GRID -> {
|
||||
int columnWidth = Settings.getThumbSize();
|
||||
mLayoutManager.setColumnSize(columnWidth);
|
||||
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE);
|
||||
if (null != mListDecoration) {
|
||||
recyclerView.removeItemDecoration(mListDecoration);
|
||||
}
|
||||
if (null == mGirdDecoration) {
|
||||
int interval = mResources.getDimensionPixelOffset(R.dimen.gallery_grid_interval);
|
||||
int paddingH = mResources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_h);
|
||||
int paddingV = mResources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_v);
|
||||
mGirdDecoration = new MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV);
|
||||
}
|
||||
recyclerView.addItemDecoration(mGirdDecoration);
|
||||
adjustPaddings();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GalleryHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
int layoutId = switch (viewType) {
|
||||
case TYPE_LIST -> R.layout.item_gallery_list;
|
||||
case TYPE_GRID -> R.layout.item_gallery_grid;
|
||||
default -> throw new IllegalStateException("Unexpected value: " + viewType);
|
||||
};
|
||||
|
||||
GalleryHolder holder = new GalleryHolder(mInflater.inflate(layoutId, parent, false));
|
||||
|
||||
if (viewType == TYPE_LIST) {
|
||||
ViewGroup.LayoutParams lp = holder.thumb.getLayoutParams();
|
||||
lp.width = mListThumbWidth;
|
||||
lp.height = mListThumbHeight;
|
||||
holder.thumb.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
holder.card.setOnClickListener(v -> onItemClick(holder.itemView, holder.getBindingAdapterPosition()));
|
||||
holder.card.setOnLongClickListener(v -> onItemLongClick(holder.itemView, holder.getBindingAdapterPosition()));
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
abstract void onItemClick(View view, int position);
|
||||
|
||||
abstract boolean onItemLongClick(View view, int position);
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return mType;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public abstract GalleryInfo getDataAt(int position);
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull GalleryHolder holder, int position) {
|
||||
GalleryInfo gi = getDataAt(position);
|
||||
if (null == gi) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (mType) {
|
||||
case TYPE_LIST -> {
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.getGid()), gi.getThumb());
|
||||
holder.title.setText(EhUtils.getSuitableTitle(gi));
|
||||
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.getCategory());
|
||||
if (!newCategoryText.equals(category.getText().toString())) {
|
||||
category.setText(newCategoryText);
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.getCategory()));
|
||||
}
|
||||
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.getPages() + "P");
|
||||
holder.pages.setVisibility(View.VISIBLE);
|
||||
}
|
||||
if (TextUtils.isEmpty(gi.getSimpleLanguage())) {
|
||||
holder.simpleLanguage.setText(null);
|
||||
holder.simpleLanguage.setVisibility(View.GONE);
|
||||
} else {
|
||||
holder.simpleLanguage.setText(gi.getSimpleLanguage());
|
||||
holder.simpleLanguage.setVisibility(View.VISIBLE);
|
||||
}
|
||||
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.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.getCategory());
|
||||
if (!(drawable instanceof TriangleDrawable)) {
|
||||
drawable = new TriangleDrawable(color);
|
||||
category.setBackground(drawable);
|
||||
} else {
|
||||
((TriangleDrawable) drawable).setColor(color);
|
||||
}
|
||||
holder.simpleLanguage.setText(gi.getSimpleLanguage());
|
||||
}
|
||||
}
|
||||
|
||||
// Update transition name
|
||||
ViewCompat.setTransitionName(holder.thumb, TransitionNameFactory.getThumbTransitionName(gi.getGid()));
|
||||
}
|
||||
|
||||
@IntDef({TYPE_LIST, TYPE_GRID})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Type {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* 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.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.hippo.drawable.TriangleDrawable
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.ehviewer.EhApplication.Companion.downloadManager
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhCacheKeyFactory
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
import com.hippo.ehviewer.widget.TileThumb
|
||||
import com.hippo.widget.recyclerview.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
internal abstract class GalleryAdapter(
|
||||
private val mInflater: LayoutInflater, private val mResources: Resources,
|
||||
private val mRecyclerView: RecyclerView, type: Int, showFavourited: Boolean
|
||||
) : RecyclerView.Adapter<GalleryHolder>() {
|
||||
private val mLayoutManager: AutoStaggeredGridLayoutManager =
|
||||
AutoStaggeredGridLayoutManager(0, StaggeredGridLayoutManager.VERTICAL)
|
||||
private val mPaddingTopSB: Int =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_padding_top_search_bar)
|
||||
private val mListThumbWidth: Int
|
||||
private val mListThumbHeight: Int
|
||||
private val mShowFavourited: Boolean
|
||||
private val mDownloadManager: DownloadManager
|
||||
private var mListDecoration: MarginItemDecoration? = null
|
||||
private var mGirdDecoration: MarginItemDecoration? = null
|
||||
private var mType = TYPE_INVALID
|
||||
|
||||
var type: Int
|
||||
get() = mType
|
||||
set(type) {
|
||||
if (type == mType) {
|
||||
return
|
||||
}
|
||||
mType = type
|
||||
val recyclerView = mRecyclerView
|
||||
when (type) {
|
||||
TYPE_LIST -> {
|
||||
val columnWidth =
|
||||
mResources.getDimensionPixelOffset(Settings.getDetailSizeResId())
|
||||
mLayoutManager.setColumnSize(columnWidth)
|
||||
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_MIN_SIZE)
|
||||
if (null != mGirdDecoration) {
|
||||
recyclerView.removeItemDecoration(mGirdDecoration!!)
|
||||
}
|
||||
if (null == mListDecoration) {
|
||||
val interval =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_list_interval)
|
||||
val paddingH =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_list_margin_h)
|
||||
val paddingV =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_list_margin_v)
|
||||
mListDecoration =
|
||||
MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
|
||||
}
|
||||
recyclerView.addItemDecoration(mListDecoration!!)
|
||||
adjustPaddings()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
TYPE_GRID -> {
|
||||
val columnWidth = Settings.getThumbSize()
|
||||
mLayoutManager.setColumnSize(columnWidth)
|
||||
mLayoutManager.setStrategy(AutoStaggeredGridLayoutManager.STRATEGY_SUITABLE_SIZE)
|
||||
if (null != mListDecoration) {
|
||||
recyclerView.removeItemDecoration(mListDecoration!!)
|
||||
}
|
||||
if (null == mGirdDecoration) {
|
||||
val interval =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_grid_interval)
|
||||
val paddingH =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_h)
|
||||
val paddingV =
|
||||
mResources.getDimensionPixelOffset(R.dimen.gallery_grid_margin_v)
|
||||
mGirdDecoration =
|
||||
MarginItemDecoration(interval, paddingH, paddingV, paddingH, paddingV)
|
||||
}
|
||||
recyclerView.addItemDecoration(mGirdDecoration!!)
|
||||
adjustPaddings()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
mShowFavourited = showFavourited
|
||||
mRecyclerView.adapter = this
|
||||
mRecyclerView.layoutManager = mLayoutManager
|
||||
@SuppressLint("InflateParams") val calculator =
|
||||
mInflater.inflate(R.layout.item_gallery_list_thumb_height, null)
|
||||
ViewUtils.measureView(calculator, 1024, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
mListThumbHeight = calculator.measuredHeight
|
||||
mListThumbWidth = mListThumbHeight * 2 / 3
|
||||
this.type = type
|
||||
mDownloadManager = downloadManager
|
||||
}
|
||||
|
||||
private fun adjustPaddings() {
|
||||
val recyclerView = mRecyclerView
|
||||
recyclerView.setPadding(
|
||||
recyclerView.paddingLeft, recyclerView.paddingTop + mPaddingTopSB,
|
||||
recyclerView.paddingRight, recyclerView.paddingBottom
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryHolder {
|
||||
val layoutId = when (viewType) {
|
||||
TYPE_LIST -> R.layout.item_gallery_list
|
||||
TYPE_GRID -> R.layout.item_gallery_grid
|
||||
else -> throw IllegalStateException("Unexpected value: $viewType")
|
||||
}
|
||||
val holder = GalleryHolder(mInflater.inflate(layoutId, parent, false))
|
||||
if (viewType == TYPE_LIST) {
|
||||
val lp = holder.thumb.layoutParams
|
||||
lp.width = mListThumbWidth
|
||||
lp.height = mListThumbHeight
|
||||
holder.thumb.layoutParams = lp
|
||||
}
|
||||
holder.card.setOnClickListener {
|
||||
onItemClick(
|
||||
holder.itemView,
|
||||
holder.bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
holder.card.setOnLongClickListener {
|
||||
onItemLongClick(
|
||||
holder.itemView,
|
||||
holder.bindingAdapterPosition
|
||||
)
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
abstract fun onItemClick(view: View, position: Int)
|
||||
abstract fun onItemLongClick(view: View, position: Int): Boolean
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return mType
|
||||
}
|
||||
|
||||
abstract fun getDataAt(position: Int): GalleryInfo?
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: GalleryHolder, position: Int) {
|
||||
val gi = getDataAt(position) ?: return
|
||||
when (mType) {
|
||||
TYPE_LIST -> {
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb!!)
|
||||
holder.title!!.text = EhUtils.getSuitableTitle(gi)
|
||||
holder.uploader!!.text = gi.uploader
|
||||
holder.uploader.alpha = if (gi.disowned) .5f else 1f
|
||||
holder.rating!!.rating = gi.rating
|
||||
val category = holder.category
|
||||
val newCategoryText = EhUtils.getCategory(gi.category)
|
||||
if (newCategoryText != category.text.toString()) {
|
||||
category.text = newCategoryText
|
||||
category.setBackgroundColor(EhUtils.getCategoryColor(gi.category))
|
||||
}
|
||||
holder.posted!!.text = gi.posted
|
||||
if (gi.pages == 0 || !Settings.getShowGalleryPages()) {
|
||||
holder.pages?.text = null
|
||||
holder.pages!!.visibility = View.GONE
|
||||
} else {
|
||||
holder.pages!!.text = gi.pages.toString() + "P"
|
||||
holder.pages.visibility = View.VISIBLE
|
||||
}
|
||||
if (TextUtils.isEmpty(gi.simpleLanguage)) {
|
||||
holder.simpleLanguage.text = null
|
||||
holder.simpleLanguage.visibility = View.GONE
|
||||
} else {
|
||||
holder.simpleLanguage.text = gi.simpleLanguage
|
||||
holder.simpleLanguage.visibility = View.VISIBLE
|
||||
}
|
||||
holder.favourited!!.visibility =
|
||||
if (mShowFavourited && gi.favoriteSlot >= -1 && gi.favoriteSlot <= 10) View.VISIBLE else View.GONE
|
||||
holder.downloaded!!.visibility =
|
||||
if (mDownloadManager.containDownloadInfo(gi.gid)) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
TYPE_GRID -> {
|
||||
(holder.thumb as TileThumb).setThumbSize(gi.thumbWidth, gi.thumbHeight)
|
||||
holder.thumb.load(EhCacheKeyFactory.getThumbKey(gi.gid), gi.thumb!!)
|
||||
val category: View = holder.category
|
||||
var drawable = category.background
|
||||
val color = EhUtils.getCategoryColor(gi.category)
|
||||
if (drawable !is TriangleDrawable) {
|
||||
drawable = TriangleDrawable(color)
|
||||
category.background = drawable
|
||||
} else {
|
||||
drawable.setColor(color)
|
||||
}
|
||||
holder.simpleLanguage.text = gi.simpleLanguage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@IntDef(TYPE_LIST, TYPE_GRID)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Type
|
||||
companion object {
|
||||
const val TYPE_INVALID = -1
|
||||
const val TYPE_LIST = 0
|
||||
const val TYPE_GRID = 1
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,914 @@
|
||||
/*
|
||||
* 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.animation.Animator
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Bundle
|
||||
import android.os.Looper
|
||||
import android.text.Html
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.TextUtils
|
||||
import android.text.style.ForegroundColorSpan
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.text.style.StyleSpan
|
||||
import android.text.style.URLSpan
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewAnimationUtils
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsAnimationCompat
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.UrlOpener
|
||||
import com.hippo.ehviewer.WindowInsetsAnimationHelper
|
||||
import com.hippo.ehviewer.client.EhClient
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
import com.hippo.ehviewer.client.EhRequest
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
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.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.parser.VoteCommentParser
|
||||
import com.hippo.ehviewer.dao.Filter
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene.Companion.toStartArgs
|
||||
import com.hippo.text.URLImageGetter
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.util.ReadableTime
|
||||
import com.hippo.util.TextUrl
|
||||
import com.hippo.util.addTextToClipboard
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.FabLayout
|
||||
import com.hippo.widget.LinkifyTextView
|
||||
import com.hippo.widget.ObservedTextView
|
||||
import com.hippo.yorozuya.AnimationUtils
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener
|
||||
import com.hippo.yorozuya.StringUtils
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
import com.hippo.yorozuya.collect.IntList
|
||||
import rikka.core.res.resolveColor
|
||||
import kotlin.math.hypot
|
||||
|
||||
class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefreshListener {
|
||||
private val mCallback = EditPanelOnBackPressedCallback()
|
||||
private var mGalleryDetail: GalleryDetail? = null
|
||||
private var mRecyclerView: EasyRecyclerView? = null
|
||||
private var mFabLayout: FabLayout? = null
|
||||
private var mFab: FloatingActionButton? = null
|
||||
private var mEditPanel: View? = null
|
||||
private var mSendImage: ImageView? = null
|
||||
private var mEditText: EditText? = null
|
||||
private var mAdapter: CommentAdapter? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
private var mRefreshLayout: SwipeRefreshLayout? = null
|
||||
private var mSendDrawable: Drawable? = null
|
||||
private var mPencilDrawable: Drawable? = null
|
||||
private var mCommentId: Long = 0
|
||||
private var mInAnimation = false
|
||||
private var mShowAllComments = false
|
||||
private var mRefreshingComments = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
requireActivity().onBackPressedDispatcher.addCallback(mCallback)
|
||||
if (savedInstanceState == null) {
|
||||
onInit()
|
||||
} else {
|
||||
onRestore(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleArgs(args: Bundle?) {
|
||||
if (args == null) {
|
||||
return
|
||||
}
|
||||
mGalleryDetail = args.getParcelable(KEY_GALLERY_DETAIL)
|
||||
mShowAllComments =
|
||||
mGalleryDetail != null && mGalleryDetail!!.comments != null && !mGalleryDetail!!.comments!!.hasMore
|
||||
}
|
||||
|
||||
private fun onInit() {
|
||||
handleArgs(arguments)
|
||||
}
|
||||
|
||||
private fun onRestore(savedInstanceState: Bundle) {
|
||||
mGalleryDetail = savedInstanceState.getParcelable(KEY_GALLERY_DETAIL)
|
||||
mShowAllComments =
|
||||
mGalleryDetail != null && mGalleryDetail!!.comments != null && !mGalleryDetail!!.comments!!.hasMore
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putParcelable(KEY_GALLERY_DETAIL, mGalleryDetail)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
val view = inflater.inflate(R.layout.scene_gallery_comments, container, false) as ViewGroup
|
||||
mRecyclerView = ViewUtils.`$$`(view, R.id.recycler_view) as EasyRecyclerView
|
||||
setLiftOnScrollTargetView(mRecyclerView)
|
||||
val tip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
mEditPanel = ViewUtils.`$$`(view, R.id.edit_panel)
|
||||
mSendImage = ViewUtils.`$$`(mEditPanel, R.id.send) as ImageView
|
||||
mEditText = ViewUtils.`$$`(mEditPanel, R.id.edit_text) as EditText
|
||||
mFabLayout = ViewUtils.`$$`(view, R.id.fab_layout) as FabLayout
|
||||
mFab = ViewUtils.`$$`(view, R.id.fab) as FloatingActionButton
|
||||
mRefreshLayout = ViewUtils.`$$`(view, R.id.refresh_layout) as SwipeRefreshLayout
|
||||
|
||||
|
||||
// Workaround for fab and edittext render out of screen
|
||||
view.removeView(mFabLayout)
|
||||
view.removeView(mEditPanel!!.parent as View)
|
||||
assert(container != null)
|
||||
container!!.addView(mFabLayout)
|
||||
container.addView(mEditPanel!!.parent as View)
|
||||
ViewCompat.setWindowInsetsAnimationCallback(
|
||||
view, WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
mEditPanel,
|
||||
mFabLayout
|
||||
)
|
||||
)
|
||||
mRefreshLayout!!.setOnRefreshListener(this)
|
||||
val context = requireContext()
|
||||
val drawable = ContextCompat.getDrawable(context, R.drawable.big_sad_pandroid)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
tip.setCompoundDrawables(null, drawable, null, null)
|
||||
mSendDrawable = ContextCompat.getDrawable(context, R.drawable.v_send_dark_x24)
|
||||
mPencilDrawable = ContextCompat.getDrawable(context, R.drawable.v_pencil_dark_x24)
|
||||
mAdapter = CommentAdapter()
|
||||
mRecyclerView!!.adapter = mAdapter
|
||||
mRecyclerView!!.layoutManager = LinearLayoutManager(
|
||||
context,
|
||||
RecyclerView.VERTICAL, false
|
||||
)
|
||||
mRecyclerView!!.setHasFixedSize(true)
|
||||
// Cancel change animator
|
||||
val itemAnimator = mRecyclerView!!.itemAnimator
|
||||
if (itemAnimator is DefaultItemAnimator) {
|
||||
itemAnimator.supportsChangeAnimations = false
|
||||
}
|
||||
mSendImage!!.setOnClickListener(this)
|
||||
mFab!!.setOnClickListener(this)
|
||||
addAboveSnackView(mEditPanel)
|
||||
addAboveSnackView(mFabLayout)
|
||||
mViewTransition = ViewTransition(mRecyclerView, tip)
|
||||
updateView(false)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mCallback.remove()
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView!!.stopScroll()
|
||||
mRecyclerView = null
|
||||
}
|
||||
if (null != mEditPanel) {
|
||||
removeAboveSnackView(mEditPanel)
|
||||
mEditPanel = null
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
removeAboveSnackView(mFabLayout)
|
||||
mFabLayout = null
|
||||
}
|
||||
mFab = null
|
||||
mSendImage = null
|
||||
mEditText = null
|
||||
mAdapter = null
|
||||
mViewTransition = null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setTitle(R.string.gallery_comments)
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24)
|
||||
}
|
||||
|
||||
private fun showFilterCommenterDialog(commenter: String?, position: Int) {
|
||||
val context = context
|
||||
if (context == null || commenter == null) {
|
||||
return
|
||||
}
|
||||
BaseDialogBuilder(context)
|
||||
.setMessage(getString(R.string.filter_the_commenter, commenter))
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val filter = Filter()
|
||||
filter.mode = EhFilter.MODE_COMMENTER
|
||||
filter.text = commenter
|
||||
EhFilter.addFilter(filter)
|
||||
hideComment(position)
|
||||
showTip(R.string.filter_added, LENGTH_SHORT)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun hideComment(position: Int) {
|
||||
if (mGalleryDetail == null || mGalleryDetail!!.comments == null || mGalleryDetail!!.comments!!.comments == null) {
|
||||
return
|
||||
}
|
||||
val oldCommentsList = mGalleryDetail!!.comments!!.comments
|
||||
val newCommentsList = arrayOfNulls<GalleryComment>(
|
||||
oldCommentsList!!.size - 1
|
||||
)
|
||||
var i = 0
|
||||
var j = 0
|
||||
while (i < oldCommentsList.size) {
|
||||
if (i != position) {
|
||||
newCommentsList[j] = oldCommentsList[i]
|
||||
j++
|
||||
}
|
||||
i++
|
||||
}
|
||||
mGalleryDetail!!.comments!!.comments = newCommentsList.requireNoNulls()
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
updateView(true)
|
||||
}
|
||||
|
||||
private fun voteComment(id: Long, vote: Int) {
|
||||
val context = context
|
||||
val activity = mainActivity
|
||||
if (null == context || null == activity) {
|
||||
return
|
||||
}
|
||||
val request = EhRequest()
|
||||
.setMethod(EhClient.METHOD_VOTE_COMMENT)
|
||||
.setArgs(
|
||||
mGalleryDetail!!.apiUid,
|
||||
mGalleryDetail!!.apiKey,
|
||||
mGalleryDetail!!.gid,
|
||||
mGalleryDetail!!.token,
|
||||
id,
|
||||
vote
|
||||
)
|
||||
.setCallback(VoteCommentListener(context))
|
||||
request.enqueue(this)
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
fun showVoteStatusDialog(context: Context?, voteStatus: String?) {
|
||||
var context = context
|
||||
val temp = StringUtils.split(voteStatus, ',')
|
||||
val length = temp.size
|
||||
val userArray = arrayOfNulls<String>(length)
|
||||
val voteArray = arrayOfNulls<String>(length)
|
||||
for (i in 0 until length) {
|
||||
val str = StringUtils.trim(temp[i])
|
||||
val index = str.lastIndexOf(' ')
|
||||
if (index < 0) {
|
||||
Log.d(TAG, "Something wrong happened about vote state")
|
||||
userArray[i] = str
|
||||
voteArray[i] = ""
|
||||
} else {
|
||||
userArray[i] = StringUtils.trim(str.substring(0, index))
|
||||
voteArray[i] = StringUtils.trim(str.substring(index + 1))
|
||||
}
|
||||
}
|
||||
val builder = BaseDialogBuilder(context!!)
|
||||
context = builder.context
|
||||
val inflater = LayoutInflater.from(context)
|
||||
val rv = inflater.inflate(R.layout.dialog_recycler_view, null) as EasyRecyclerView
|
||||
rv.adapter = object : RecyclerView.Adapter<InfoHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InfoHolder {
|
||||
return InfoHolder(inflater.inflate(R.layout.item_drawer_favorites, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: InfoHolder, position: Int) {
|
||||
holder.key.text = userArray[position]
|
||||
holder.value.text = voteArray[position]
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return length
|
||||
}
|
||||
}
|
||||
rv.layoutManager = LinearLayoutManager(context)
|
||||
rv.clipToPadding = false
|
||||
builder.setView(rv).show()
|
||||
}
|
||||
|
||||
private fun showCommentDialog(position: Int) {
|
||||
val context = context
|
||||
if (context == null || mGalleryDetail == null || mGalleryDetail!!.comments == null || mGalleryDetail!!.comments!!.comments == null || position >= mGalleryDetail!!.comments!!.comments!!.size || position < 0) {
|
||||
return
|
||||
}
|
||||
val comment = mGalleryDetail!!.comments!!.comments!![position]
|
||||
val menu: MutableList<String> = ArrayList()
|
||||
val menuId = IntList()
|
||||
val resources = context.resources
|
||||
menu.add(resources.getString(R.string.copy_comment_text))
|
||||
menuId.add(R.id.copy)
|
||||
if (!comment.uploader && !comment.editable) {
|
||||
menu.add(resources.getString(R.string.block_commenter))
|
||||
menuId.add(R.id.block_commenter)
|
||||
}
|
||||
if (comment.editable) {
|
||||
menu.add(resources.getString(R.string.edit_comment))
|
||||
menuId.add(R.id.edit_comment)
|
||||
}
|
||||
if (comment.voteUpAble) {
|
||||
menu.add(resources.getString(if (comment.voteUpEd) R.string.cancel_vote_up else R.string.vote_up))
|
||||
menuId.add(R.id.vote_up)
|
||||
}
|
||||
if (comment.voteDownAble) {
|
||||
menu.add(resources.getString(if (comment.voteDownEd) R.string.cancel_vote_down else R.string.vote_down))
|
||||
menuId.add(R.id.vote_down)
|
||||
}
|
||||
if (!TextUtils.isEmpty(comment.voteState)) {
|
||||
menu.add(resources.getString(R.string.check_vote_status))
|
||||
menuId.add(R.id.check_vote_status)
|
||||
}
|
||||
BaseDialogBuilder(context)
|
||||
.setItems(menu.toTypedArray()) { _: DialogInterface?, which: Int ->
|
||||
if (which < 0 || which >= menuId.size()) {
|
||||
return@setItems
|
||||
}
|
||||
val id = menuId[which]
|
||||
if (id == R.id.copy) {
|
||||
requireActivity().addTextToClipboard(comment.comment, false)
|
||||
} else if (id == R.id.block_commenter) {
|
||||
showFilterCommenterDialog(comment.user, position)
|
||||
} else if (id == R.id.vote_up) {
|
||||
voteComment(comment.id, 1)
|
||||
} else if (id == R.id.vote_down) {
|
||||
voteComment(comment.id, -1)
|
||||
} else if (id == R.id.check_vote_status) {
|
||||
showVoteStatusDialog(context, comment.voteState)
|
||||
} else if (id == R.id.edit_comment) {
|
||||
prepareEditComment(comment.id)
|
||||
if (!mInAnimation && mEditPanel != null && mEditPanel!!.visibility != View.VISIBLE) {
|
||||
showEditPanel(true)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
fun onItemClick(parent: EasyRecyclerView?, view2: View?, position: Int): Boolean {
|
||||
val activity = mainActivity ?: return false
|
||||
val holder = parent!!.getChildViewHolder(view2!!)
|
||||
if (holder is ActualCommentHolder) {
|
||||
val span = holder.comment.currentSpan
|
||||
holder.comment.clearCurrentSpan()
|
||||
if (span is URLSpan) {
|
||||
UrlOpener.openUrl(activity, span.url, true, mGalleryDetail)
|
||||
} else {
|
||||
showCommentDialog(position)
|
||||
}
|
||||
} else if (holder is MoreCommentHolder && !mRefreshingComments && mAdapter != null) {
|
||||
mRefreshingComments = true
|
||||
mShowAllComments = true
|
||||
mAdapter!!.notifyItemChanged(position)
|
||||
val url = galleryDetailUrl
|
||||
if (url != null) {
|
||||
// Request
|
||||
val request = EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
|
||||
.setArgs(url)
|
||||
.setCallback(RefreshCommentListener(activity))
|
||||
request.enqueue(this)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun updateView(animation: Boolean) {
|
||||
if (null == mViewTransition) {
|
||||
return
|
||||
}
|
||||
if (mGalleryDetail == null || mGalleryDetail!!.comments == null || mGalleryDetail!!.comments!!.comments == null || mGalleryDetail!!.comments!!.comments!!.isEmpty()) {
|
||||
mViewTransition!!.showView(1, animation)
|
||||
} else {
|
||||
mViewTransition!!.showView(0, animation)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareNewComment() {
|
||||
mCommentId = 0
|
||||
if (mSendImage != null) {
|
||||
mSendImage!!.setImageDrawable(mSendDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareEditComment(commentId: Long) {
|
||||
mCommentId = commentId
|
||||
if (mSendImage != null) {
|
||||
mSendImage!!.setImageDrawable(mPencilDrawable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showEditPanelWithAnimation() {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
mInAnimation = true
|
||||
mFab!!.translationX = 0.0f
|
||||
mFab!!.translationY = 0.0f
|
||||
mFab!!.scaleX = 1.0f
|
||||
mFab!!.scaleY = 1.0f
|
||||
val fabEndX = mEditPanel!!.left + mEditPanel!!.width / 2 - mFab!!.width / 2
|
||||
val fabEndY = mEditPanel!!.top + mEditPanel!!.height / 2 - mFab!!.height / 2
|
||||
mFab!!.animate().x(fabEndX.toFloat()).y(fabEndY.toFloat()).scaleX(0.0f).scaleY(0.0f)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_SLOW_INTERPOLATOR)
|
||||
.setDuration(300L).setListener(object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
(mFab as View).visibility = View.INVISIBLE
|
||||
mEditPanel!!.visibility = View.VISIBLE
|
||||
val halfW = mEditPanel!!.width / 2
|
||||
val halfH = mEditPanel!!.height / 2
|
||||
val animator = ViewAnimationUtils.createCircularReveal(
|
||||
mEditPanel,
|
||||
halfW,
|
||||
halfH,
|
||||
0f,
|
||||
hypot(halfW.toDouble(), halfH.toDouble()).toFloat()
|
||||
).setDuration(300L)
|
||||
animator.addListener(object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(a: Animator) {
|
||||
mInAnimation = false
|
||||
}
|
||||
})
|
||||
animator.start()
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
|
||||
private fun showEditPanel(animation: Boolean) {
|
||||
mCallback.isEnabled = true
|
||||
if (animation) {
|
||||
showEditPanelWithAnimation()
|
||||
} else {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
(mFab as View).visibility = View.INVISIBLE
|
||||
mEditPanel!!.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideEditPanelWithAnimation() {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
mInAnimation = true
|
||||
val halfW = mEditPanel!!.width / 2
|
||||
val halfH = mEditPanel!!.height / 2
|
||||
val animator = ViewAnimationUtils.createCircularReveal(
|
||||
mEditPanel,
|
||||
halfW,
|
||||
halfH,
|
||||
hypot(halfW.toDouble(), halfH.toDouble()).toFloat(),
|
||||
0.0f
|
||||
).setDuration(300L)
|
||||
animator.addListener(object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(a: Animator) {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
if (Looper.myLooper() != Looper.getMainLooper()) {
|
||||
// Some devices may run this block in non-UI thread.
|
||||
// It might be a bug of Android OS.
|
||||
// Check it here to avoid crash.
|
||||
return
|
||||
}
|
||||
mEditPanel!!.visibility = View.GONE
|
||||
(mFab as View).visibility = View.VISIBLE
|
||||
val fabStartX = mEditPanel!!.left + mEditPanel!!.width / 2 - mFab!!.width / 2
|
||||
val fabStartY = mEditPanel!!.top + mEditPanel!!.height / 2 - mFab!!.height / 2
|
||||
mFab!!.x = fabStartX.toFloat()
|
||||
mFab!!.y = fabStartY.toFloat()
|
||||
mFab!!.scaleX = 0.0f
|
||||
mFab!!.scaleY = 0.0f
|
||||
mFab!!.rotation = -45.0f
|
||||
mFab!!.animate().translationX(0.0f).translationY(0.0f).scaleX(1.0f).scaleY(1.0f)
|
||||
.rotation(0.0f)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_SLOW_INTERPOLATOR)
|
||||
.setDuration(300L).setListener(object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mInAnimation = false
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
animator.start()
|
||||
}
|
||||
|
||||
private fun hideEditPanel(animation: Boolean) {
|
||||
mCallback.isEnabled = false
|
||||
hideSoftInput()
|
||||
if (animation) {
|
||||
hideEditPanelWithAnimation()
|
||||
} else {
|
||||
if (null == mFab || null == mEditPanel) {
|
||||
return
|
||||
}
|
||||
(mFab as View).visibility = View.VISIBLE
|
||||
mEditPanel!!.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private val galleryDetailUrl: String?
|
||||
get() = if (mGalleryDetail != null && mGalleryDetail!!.gid != -1L && mGalleryDetail!!.token != null) {
|
||||
EhUrl.getGalleryDetailUrl(
|
||||
mGalleryDetail!!.gid,
|
||||
mGalleryDetail!!.token,
|
||||
0,
|
||||
mShowAllComments
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
val context = context
|
||||
val activity = mainActivity
|
||||
if (null == context || null == activity || null == mEditText) {
|
||||
return
|
||||
}
|
||||
if (mFab === v) {
|
||||
if (!mInAnimation) {
|
||||
prepareNewComment()
|
||||
showEditPanel(true)
|
||||
}
|
||||
} else if (mSendImage === v) {
|
||||
if (!mInAnimation) {
|
||||
val comment = mEditText!!.text.toString()
|
||||
if (TextUtils.isEmpty(comment)) {
|
||||
// Comment is empty
|
||||
return
|
||||
}
|
||||
val url = galleryDetailUrl ?: return
|
||||
// Request
|
||||
val request = EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_COMMENT_GALLERY)
|
||||
.setArgs(
|
||||
url,
|
||||
comment,
|
||||
if (mCommentId != 0L) mCommentId.toString() else null
|
||||
)
|
||||
.setCallback(CommentGalleryListener(context, mCommentId))
|
||||
request.enqueue(this)
|
||||
hideSoftInput()
|
||||
hideEditPanel(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRefreshGallerySuccess(result: GalleryCommentList?) {
|
||||
if (mGalleryDetail == null || mAdapter == null) {
|
||||
return
|
||||
}
|
||||
mRefreshLayout!!.isRefreshing = false
|
||||
mRefreshingComments = false
|
||||
mGalleryDetail!!.comments = result
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
updateView(true)
|
||||
}
|
||||
|
||||
private fun onRefreshGalleryFailure() {
|
||||
if (mAdapter == null) {
|
||||
return
|
||||
}
|
||||
mRefreshLayout!!.isRefreshing = false
|
||||
mRefreshingComments = false
|
||||
val position = mAdapter!!.itemCount - 1
|
||||
if (position >= 0) {
|
||||
mAdapter!!.notifyItemChanged(position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCommentGallerySuccess(result: GalleryCommentList) {
|
||||
if (mGalleryDetail == null || mAdapter == null) {
|
||||
return
|
||||
}
|
||||
mGalleryDetail!!.comments = result
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
|
||||
// Remove text
|
||||
if (mEditText != null) {
|
||||
mEditText!!.setText("")
|
||||
}
|
||||
updateView(true)
|
||||
}
|
||||
|
||||
private fun onVoteCommentSuccess(result: VoteCommentParser.Result) {
|
||||
if (mAdapter == null || mGalleryDetail!!.comments == null || mGalleryDetail!!.comments!!.comments == null) {
|
||||
return
|
||||
}
|
||||
var position = -1
|
||||
var i = 0
|
||||
val n = mGalleryDetail!!.comments!!.comments!!.size
|
||||
while (i < n) {
|
||||
val comment = mGalleryDetail!!.comments!!.comments!![i]
|
||||
if (comment.id == result.id) {
|
||||
position = i
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (-1 == position) {
|
||||
Log.d(TAG, "Can't find comment with id " + result.id)
|
||||
return
|
||||
}
|
||||
|
||||
// Update comment
|
||||
val comment = mGalleryDetail!!.comments!!.comments!![position]
|
||||
comment.score = result.score
|
||||
if (result.expectVote > 0) {
|
||||
comment.voteUpEd = 0 != result.vote
|
||||
comment.voteDownEd = false
|
||||
} else {
|
||||
comment.voteDownEd = 0 != result.vote
|
||||
comment.voteUpEd = false
|
||||
}
|
||||
mAdapter!!.notifyItemChanged(position)
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
if (!mRefreshingComments && mAdapter != null) {
|
||||
val activity = requireActivity() as MainActivity
|
||||
mRefreshingComments = true
|
||||
val url = galleryDetailUrl
|
||||
if (url != null) {
|
||||
// Request
|
||||
val request = EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_DETAIL)
|
||||
.setArgs(url)
|
||||
.setCallback(RefreshCommentListener(activity))
|
||||
request.enqueue(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InfoHolder(itemView: View?) : RecyclerView.ViewHolder(
|
||||
itemView!!
|
||||
) {
|
||||
val key: TextView
|
||||
val value: TextView
|
||||
|
||||
init {
|
||||
key = ViewUtils.`$$`(itemView, R.id.key) as TextView
|
||||
value = ViewUtils.`$$`(itemView, R.id.value) as TextView
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class CommentHolder(inflater: LayoutInflater, resId: Int, parent: ViewGroup?) :
|
||||
RecyclerView.ViewHolder(inflater.inflate(resId, parent, false))
|
||||
|
||||
private class MoreCommentHolder(inflater: LayoutInflater, parent: ViewGroup?) :
|
||||
CommentHolder(inflater, R.layout.item_gallery_comment_more, parent)
|
||||
|
||||
private class ProgressCommentHolder(inflater: LayoutInflater, parent: ViewGroup?) :
|
||||
CommentHolder(inflater, R.layout.item_gallery_comment_progress, parent)
|
||||
|
||||
private inner class RefreshCommentListener(context: Context) :
|
||||
EhCallback<GalleryCommentsScene?, GalleryDetail>(context) {
|
||||
override fun onSuccess(result: GalleryDetail) {
|
||||
val scene = this@GalleryCommentsScene
|
||||
scene.onRefreshGallerySuccess(result.comments)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
val scene = this@GalleryCommentsScene
|
||||
scene.onRefreshGalleryFailure()
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
}
|
||||
|
||||
private inner class CommentGalleryListener(context: Context, private val mCommentId: Long) :
|
||||
EhCallback<GalleryCommentsScene?, GalleryCommentList>(context) {
|
||||
override fun onSuccess(result: GalleryCommentList) {
|
||||
showTip(
|
||||
if (mCommentId != 0L) R.string.edit_comment_successfully else R.string.comment_successfully,
|
||||
LENGTH_SHORT
|
||||
)
|
||||
val scene = this@GalleryCommentsScene
|
||||
scene.onCommentGallerySuccess(result)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(
|
||||
"""
|
||||
${content.getString(if (mCommentId != 0L) R.string.edit_comment_failed else R.string.comment_failed)}
|
||||
${ExceptionUtils.getReadableString(e)}
|
||||
""".trimIndent(), LENGTH_LONG
|
||||
)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
}
|
||||
|
||||
private inner class VoteCommentListener(context: Context) :
|
||||
EhCallback<GalleryCommentsScene?, VoteCommentParser.Result>(context) {
|
||||
override fun onSuccess(result: VoteCommentParser.Result) {
|
||||
showTip(
|
||||
if (result.expectVote > 0) (if (0 != result.vote) R.string.vote_up_successfully else R.string.cancel_vote_up_successfully) else if (0 != result.vote) R.string.vote_down_successfully else R.string.cancel_vote_down_successfully,
|
||||
LENGTH_SHORT
|
||||
)
|
||||
val scene = this@GalleryCommentsScene
|
||||
scene.onVoteCommentSuccess(result)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
showTip(R.string.vote_failed, LENGTH_LONG)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
}
|
||||
|
||||
private inner class ActualCommentHolder(inflater: LayoutInflater, parent: ViewGroup?) :
|
||||
CommentHolder(inflater, R.layout.item_gallery_comment, parent) {
|
||||
private val user: TextView = itemView.findViewById(R.id.user)
|
||||
private val time: TextView = itemView.findViewById(R.id.time)
|
||||
val comment: LinkifyTextView = itemView.findViewById(R.id.comment)
|
||||
|
||||
private fun generateComment(
|
||||
context: Context,
|
||||
textView: ObservedTextView,
|
||||
comment: GalleryComment
|
||||
): CharSequence {
|
||||
val sp = Html.fromHtml(
|
||||
comment.comment,
|
||||
Html.FROM_HTML_MODE_LEGACY,
|
||||
URLImageGetter(textView),
|
||||
null
|
||||
)
|
||||
val ssb = SpannableStringBuilder(sp)
|
||||
if (0L != comment.id && 0 != comment.score) {
|
||||
val score = comment.score
|
||||
val scoreString = if (score > 0) "+$score" else score.toString()
|
||||
val ss = SpannableString(scoreString)
|
||||
ss.setSpan(
|
||||
RelativeSizeSpan(0.8f),
|
||||
0,
|
||||
scoreString.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ss.setSpan(
|
||||
StyleSpan(Typeface.BOLD),
|
||||
0,
|
||||
scoreString.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ss.setSpan(
|
||||
ForegroundColorSpan(theme.resolveColor(android.R.attr.textColorSecondary)),
|
||||
0,
|
||||
scoreString.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ssb.append(" ").append(ss)
|
||||
}
|
||||
if (comment.lastEdited != 0L) {
|
||||
val str = context.getString(
|
||||
R.string.last_edited,
|
||||
ReadableTime.getTimeAgo(comment.lastEdited)
|
||||
)
|
||||
val ss = SpannableString(str)
|
||||
ss.setSpan(
|
||||
RelativeSizeSpan(0.8f),
|
||||
0,
|
||||
str.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ss.setSpan(
|
||||
StyleSpan(Typeface.BOLD),
|
||||
0,
|
||||
str.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ss.setSpan(
|
||||
ForegroundColorSpan(theme.resolveColor(android.R.attr.textColorSecondary)),
|
||||
0, str.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
ssb.append("\n\n").append(ss)
|
||||
}
|
||||
return TextUrl.handleTextUrl(ssb)
|
||||
}
|
||||
|
||||
fun bind(value: GalleryComment) {
|
||||
user.text = if (value.uploader) getString(
|
||||
R.string.comment_user_uploader,
|
||||
value.user
|
||||
) else value.user
|
||||
user.setOnClickListener {
|
||||
val lub = ListUrlBuilder()
|
||||
lub.mode = ListUrlBuilder.MODE_UPLOADER
|
||||
lub.keyword = value.user
|
||||
navigate(R.id.galleryListScene, lub.toStartArgs(), true)
|
||||
}
|
||||
time.text = ReadableTime.getTimeAgo(value.time)
|
||||
comment.text = generateComment(comment.context, comment, value)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class CommentAdapter : RecyclerView.Adapter<CommentHolder>() {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentHolder {
|
||||
return when (viewType) {
|
||||
TYPE_COMMENT -> ActualCommentHolder(mInflater, parent)
|
||||
TYPE_MORE -> MoreCommentHolder(mInflater, parent)
|
||||
TYPE_PROGRESS -> ProgressCommentHolder(mInflater, parent)
|
||||
else -> throw IllegalStateException("Invalid view type: $viewType")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: CommentHolder, position: Int) {
|
||||
val context = context
|
||||
if (context == null || mGalleryDetail == null || mGalleryDetail!!.comments == null) {
|
||||
return
|
||||
}
|
||||
holder.itemView.setOnClickListener {
|
||||
onItemClick(
|
||||
mRecyclerView,
|
||||
holder.itemView,
|
||||
position
|
||||
)
|
||||
}
|
||||
holder.itemView.isClickable = true
|
||||
holder.itemView.isFocusable = true
|
||||
if (holder is ActualCommentHolder) {
|
||||
holder.bind(mGalleryDetail!!.comments!!.comments!![position])
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (mGalleryDetail == null || mGalleryDetail!!.comments == null || mGalleryDetail!!.comments!!.comments == null) {
|
||||
0
|
||||
} else if (mGalleryDetail!!.comments!!.hasMore) {
|
||||
mGalleryDetail!!.comments!!.comments!!.size + 1
|
||||
} else {
|
||||
mGalleryDetail!!.comments!!.comments!!.size
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return if (position >= mGalleryDetail!!.comments!!.comments!!.size) {
|
||||
if (mRefreshingComments) TYPE_PROGRESS else TYPE_MORE
|
||||
} else {
|
||||
TYPE_COMMENT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class EditPanelOnBackPressedCallback : OnBackPressedCallback(false) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!mInAnimation && null != mEditPanel && mEditPanel!!.visibility == View.VISIBLE) {
|
||||
hideEditPanel(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG: String = GalleryCommentsScene::class.java.simpleName
|
||||
const val KEY_API_UID = "api_uid"
|
||||
const val KEY_API_KEY = "api_key"
|
||||
const val KEY_GID = "gid"
|
||||
const val KEY_TOKEN = "token"
|
||||
const val KEY_COMMENT_LIST = "comment_list"
|
||||
const val KEY_GALLERY_DETAIL = "gallery_detail"
|
||||
private const val TYPE_COMMENT = 0
|
||||
private const val TYPE_MORE = 1
|
||||
private const val TYPE_PROGRESS = 2
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,59 +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.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView;
|
||||
import com.hippo.widget.LoadImageView;
|
||||
|
||||
class GalleryHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public final LoadImageView thumb;
|
||||
public final TextView title;
|
||||
public final TextView uploader;
|
||||
public final SimpleRatingView rating;
|
||||
public final TextView category;
|
||||
public final TextView posted;
|
||||
public final TextView pages;
|
||||
public final TextView simpleLanguage;
|
||||
public final ImageView favourited;
|
||||
public final ImageView downloaded;
|
||||
public final MaterialCardView card;
|
||||
|
||||
public GalleryHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
thumb = itemView.findViewById(R.id.thumb);
|
||||
title = itemView.findViewById(R.id.title);
|
||||
uploader = itemView.findViewById(R.id.uploader);
|
||||
rating = itemView.findViewById(R.id.rating);
|
||||
category = itemView.findViewById(R.id.category);
|
||||
posted = itemView.findViewById(R.id.posted);
|
||||
pages = itemView.findViewById(R.id.pages);
|
||||
simpleLanguage = itemView.findViewById(R.id.simple_language);
|
||||
favourited = itemView.findViewById(R.id.favourited);
|
||||
downloaded = itemView.findViewById(R.id.downloaded);
|
||||
card = itemView.findViewById(R.id.card);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.widget.SimpleRatingView
|
||||
import com.hippo.widget.LoadImageView
|
||||
|
||||
internal class GalleryHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
@JvmField
|
||||
val thumb: LoadImageView = itemView.findViewById(R.id.thumb)
|
||||
|
||||
@JvmField
|
||||
val title: TextView? = itemView.findViewById(R.id.title)
|
||||
|
||||
@JvmField
|
||||
val uploader: TextView? = itemView.findViewById(R.id.uploader)
|
||||
|
||||
@JvmField
|
||||
val rating: SimpleRatingView? = itemView.findViewById(R.id.rating)
|
||||
|
||||
@JvmField
|
||||
val category: TextView = itemView.findViewById(R.id.category)
|
||||
|
||||
@JvmField
|
||||
val posted: TextView? = itemView.findViewById(R.id.posted)
|
||||
|
||||
@JvmField
|
||||
val pages: TextView? = itemView.findViewById(R.id.pages)
|
||||
|
||||
@JvmField
|
||||
val simpleLanguage: TextView = itemView.findViewById(R.id.simple_language)
|
||||
|
||||
@JvmField
|
||||
val favourited: ImageView? = itemView.findViewById(R.id.favourited)
|
||||
|
||||
@JvmField
|
||||
val downloaded: ImageView? = itemView.findViewById(R.id.downloaded)
|
||||
|
||||
@JvmField
|
||||
val card: MaterialCardView = itemView.findViewById(R.id.card)
|
||||
}
|
||||
@@ -92,8 +92,6 @@ import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter
|
||||
import com.hippo.ehviewer.widget.GalleryInfoContentHelper
|
||||
import com.hippo.ehviewer.widget.SearchLayout
|
||||
import com.hippo.scene.Announcer
|
||||
import com.hippo.scene.SceneFragment
|
||||
import com.hippo.view.BringOutTransition
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.widget.FabLayout
|
||||
@@ -206,32 +204,20 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
return R.menu.scene_gallery_list_searchbar_menu
|
||||
}
|
||||
|
||||
override fun getNavCheckedItem(): Int {
|
||||
return mNavCheckedId
|
||||
}
|
||||
|
||||
private fun handleArgs(args: Bundle?) {
|
||||
args ?: return
|
||||
mUrlBuilder = when (args.getString(KEY_ACTION)) {
|
||||
val action = args?.getString(KEY_ACTION) ?: ACTION_HOMEPAGE
|
||||
mUrlBuilder = when (action) {
|
||||
ACTION_HOMEPAGE -> ListUrlBuilder()
|
||||
ACTION_SUBSCRIPTION -> ListUrlBuilder(MODE_SUBSCRIPTION)
|
||||
ACTION_WHATS_HOT -> ListUrlBuilder(MODE_WHATS_HOT)
|
||||
ACTION_TOP_LIST -> ListUrlBuilder(MODE_TOPLIST, mKeyword = "11")
|
||||
ACTION_LIST_URL_BUILDER -> args.getParcelable<ListUrlBuilder>(KEY_LIST_URL_BUILDER)
|
||||
ACTION_LIST_URL_BUILDER -> args?.getParcelable<ListUrlBuilder>(KEY_LIST_URL_BUILDER)
|
||||
?.copy() ?: ListUrlBuilder()
|
||||
|
||||
else -> throw IllegalStateException("Wrong KEY_ACTION:${args.getString(KEY_ACTION)} when handle args!")
|
||||
else -> throw IllegalStateException("Wrong KEY_ACTION:${args?.getString(KEY_ACTION)} when handle args!")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewArguments(args: Bundle) {
|
||||
handleArgs(args)
|
||||
onUpdateUrlBuilder()
|
||||
mHelper?.refresh()
|
||||
setState(STATE_NORMAL)
|
||||
showSearchBar()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mDownloadManager.addDownloadInfoListener(mDownloadInfoListener)
|
||||
@@ -355,18 +341,6 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
title = resources.getString(R.string.search)
|
||||
}
|
||||
setSearchBarHint(title)
|
||||
|
||||
// Update nav checked item
|
||||
val checkedItemId: Int = when (mode) {
|
||||
ListUrlBuilder.MODE_NORMAL -> if (EhUtils.NONE == category && TextUtils.isEmpty(keyword)) R.id.nav_homepage else 0
|
||||
MODE_SUBSCRIPTION -> R.id.nav_subscription
|
||||
MODE_WHATS_HOT -> R.id.nav_whats_hot
|
||||
MODE_TOPLIST -> R.id.nav_toplist
|
||||
ListUrlBuilder.MODE_TAG, ListUrlBuilder.MODE_UPLOADER, ListUrlBuilder.MODE_IMAGE_SEARCH -> 0
|
||||
else -> throw IllegalStateException("Unexpected value: $mode")
|
||||
}
|
||||
navCheckedItem = checkedItemId
|
||||
mNavCheckedId = checkedItemId
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
@@ -619,8 +593,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
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)
|
||||
}
|
||||
|
||||
override fun onClickPrimaryFab(view: FabLayout, fab: FloatingActionButton) {
|
||||
@@ -805,13 +778,13 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
CommonOperations.removeFromFavorites(
|
||||
activity,
|
||||
gi,
|
||||
RemoveFromFavoriteListener(context, activity.stageId, tag)
|
||||
RemoveFromFavoriteListener(context)
|
||||
)
|
||||
} else {
|
||||
CommonOperations.addToFavorites(
|
||||
activity,
|
||||
gi,
|
||||
AddToFavoriteListener(context, activity.stageId, tag)
|
||||
AddToFavoriteListener(context)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1102,30 +1075,25 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
@IntDef(STATE_NORMAL, STATE_SIMPLE_SEARCH, STATE_SEARCH, STATE_SEARCH_SHOW_LIST)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
private annotation class State
|
||||
private class GetGalleryListListener(
|
||||
private inner class GetGalleryListListener(
|
||||
context: Context,
|
||||
stageId: Int,
|
||||
sceneTag: String?,
|
||||
private val mTaskId: Int
|
||||
) : EhCallback<GalleryListScene, GalleryListParser.Result>(context, stageId, sceneTag) {
|
||||
) : EhCallback<GalleryListScene, GalleryListParser.Result>(context) {
|
||||
override fun onSuccess(result: GalleryListParser.Result) {
|
||||
val scene = scene
|
||||
scene?.onGetGalleryListSuccess(result, mTaskId)
|
||||
val scene = this@GalleryListScene
|
||||
scene.onGetGalleryListSuccess(result, mTaskId)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
val scene = scene
|
||||
scene?.onGetGalleryListFailure(e, mTaskId)
|
||||
val scene = this@GalleryListScene
|
||||
scene.onGetGalleryListFailure(e, mTaskId)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1135,13 +1103,10 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1151,9 +1116,6 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
override fun isInstance(scene: SceneFragment): Boolean {
|
||||
return scene is GalleryListScene
|
||||
}
|
||||
}
|
||||
|
||||
private class QsDrawerHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
@@ -1184,17 +1146,13 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
private inner class QsDrawerAdapter(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<QsDrawerHolder>() {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QsDrawerHolder {
|
||||
return QsDrawerHolder(mInflater.inflate(R.layout.item_drawer_list, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: QsDrawerHolder, position: Int) {
|
||||
val holder = QsDrawerHolder(mInflater.inflate(R.layout.item_drawer_list, parent, false))
|
||||
if (!mIsTopList) {
|
||||
holder.key.text = mQuickSearchList[position].name
|
||||
holder.itemView.setOnClickListener {
|
||||
if (null == mHelper) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
val q = mQuickSearchList[position]
|
||||
val q = mQuickSearchList[holder.bindingAdapterPosition]
|
||||
mUrlBuilder.set(q)
|
||||
onUpdateUrlBuilder()
|
||||
val i = q.name!!.lastIndexOf("@")
|
||||
@@ -1204,6 +1162,24 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
} else {
|
||||
val keywords = intArrayOf(11, 12, 13, 15)
|
||||
holder.itemView.setOnClickListener {
|
||||
if (null == mHelper) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
mUrlBuilder.keyword = keywords[holder.bindingAdapterPosition].toString()
|
||||
onUpdateUrlBuilder()
|
||||
mHelper!!.refresh()
|
||||
setState(STATE_NORMAL)
|
||||
closeDrawer(GravityCompat.END)
|
||||
}
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: QsDrawerHolder, position: Int) {
|
||||
if (!mIsTopList) {
|
||||
holder.key.text = mQuickSearchList[position].name
|
||||
} else {
|
||||
val toplists = intArrayOf(
|
||||
R.string.toplist_alltime,
|
||||
R.string.toplist_pastyear,
|
||||
@@ -1212,16 +1188,6 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
)
|
||||
holder.key.text = getString(toplists[position])
|
||||
holder.option.visibility = View.GONE
|
||||
holder.itemView.setOnClickListener {
|
||||
if (null == mHelper) {
|
||||
return@setOnClickListener
|
||||
}
|
||||
mUrlBuilder.keyword = keywords[position].toString()
|
||||
onUpdateUrlBuilder()
|
||||
mHelper!!.refresh()
|
||||
setState(STATE_NORMAL)
|
||||
closeDrawer(GravityCompat.END)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1256,7 +1222,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
startScene(createAnnouncer())
|
||||
navigate(getDestination(), getArgs())
|
||||
if (mState == STATE_SIMPLE_SEARCH) {
|
||||
setState(STATE_NORMAL)
|
||||
} else if (mState == STATE_SEARCH_SHOW_LIST) {
|
||||
@@ -1264,19 +1230,25 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun createAnnouncer(): Announcer?
|
||||
abstract fun getDestination(): Int
|
||||
|
||||
abstract fun getArgs(): Bundle
|
||||
}
|
||||
|
||||
private inner class GalleryDetailUrlSuggestion(
|
||||
private val mGid: Long,
|
||||
private val mToken: String
|
||||
) : UrlSuggestion() {
|
||||
override fun createAnnouncer(): Announcer? {
|
||||
override fun getDestination(): Int {
|
||||
return R.id.galleryDetailScene
|
||||
}
|
||||
|
||||
override fun getArgs(): Bundle {
|
||||
val args = Bundle()
|
||||
args.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
|
||||
args.putLong(GalleryDetailScene.KEY_GID, mGid)
|
||||
args.putString(GalleryDetailScene.KEY_TOKEN, mToken)
|
||||
return Announcer(GalleryDetailScene::class.java).setArgs(args)
|
||||
return args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,13 +1257,17 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
private val mPToken: String,
|
||||
private val mPage: Int
|
||||
) : UrlSuggestion() {
|
||||
override fun createAnnouncer(): Announcer? {
|
||||
override fun getDestination(): Int {
|
||||
return R.id.progressScene
|
||||
}
|
||||
|
||||
override fun getArgs(): Bundle {
|
||||
val args = Bundle()
|
||||
args.putString(ProgressScene.KEY_ACTION, ProgressScene.ACTION_GALLERY_TOKEN)
|
||||
args.putLong(ProgressScene.KEY_GID, mGid)
|
||||
args.putString(ProgressScene.KEY_PTOKEN, mPToken)
|
||||
args.putInt(ProgressScene.KEY_PAGE, mPage)
|
||||
return Announcer(ProgressScene::class.java).setArgs(args)
|
||||
return args
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1340,7 +1316,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
request.setCallback(
|
||||
GetGalleryListListener(
|
||||
context,
|
||||
activity.stageId, tag, taskId
|
||||
taskId
|
||||
)
|
||||
)
|
||||
request.setArgs(
|
||||
@@ -1356,7 +1332,7 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
request.setCallback(
|
||||
GetGalleryListListener(
|
||||
context,
|
||||
activity.stageId, tag, taskId
|
||||
taskId
|
||||
)
|
||||
)
|
||||
request.setArgs(url)
|
||||
@@ -1434,13 +1410,21 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
|
||||
val position = viewHolder.bindingAdapterPosition
|
||||
val quickSearch = mQuickSearchList[position]
|
||||
lifecycleScope.launchIO {
|
||||
EhDB.deleteQuickSearch(quickSearch)
|
||||
mQuickSearchList.removeAt(position)
|
||||
withUIContext {
|
||||
mAdapter.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
BaseDialogBuilder(context!!)
|
||||
.setMessage(getString(R.string.delete_quick_search, quickSearch.name))
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
lifecycleScope.launchIO {
|
||||
EhDB.deleteQuickSearch(quickSearch)
|
||||
mQuickSearchList.removeAt(position)
|
||||
withUIContext {
|
||||
mAdapter.notifyItemRemoved(position)
|
||||
}
|
||||
}
|
||||
}.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.cancel()
|
||||
}.setOnCancelListener {
|
||||
mAdapter.notifyItemChanged(position)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1491,16 +1475,11 @@ class GalleryListScene : SearchBarScene(), OnDragHandlerListener, SearchLayout.H
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun startScene(scene: SceneFragment, lub: ListUrlBuilder?) {
|
||||
scene.startScene(getStartAnnouncer(lub))
|
||||
}
|
||||
|
||||
fun getStartAnnouncer(lub: ListUrlBuilder?): Announcer {
|
||||
fun ListUrlBuilder.toStartArgs(): Bundle {
|
||||
val args = Bundle()
|
||||
args.putString(KEY_ACTION, ACTION_LIST_URL_BUILDER)
|
||||
args.putParcelable(KEY_LIST_URL_BUILDER, lub)
|
||||
return Announcer(GalleryListScene::class.java).setArgs(args)
|
||||
args.putParcelable(KEY_LIST_URL_BUILDER, this)
|
||||
return args
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,435 +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.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.slider.Slider;
|
||||
import com.hippo.app.BaseDialogBuilder;
|
||||
import com.hippo.easyrecyclerview.EasyRecyclerView;
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration;
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
import com.hippo.ehviewer.client.EhClient;
|
||||
import com.hippo.ehviewer.client.EhRequest;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail;
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo;
|
||||
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;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity;
|
||||
|
||||
public class GalleryPreviewsScene extends BaseToolbarScene {
|
||||
|
||||
public static final String KEY_GALLERY_INFO = "gallery_info";
|
||||
private final static String KEY_HAS_FIRST_REFRESH = "has_first_refresh";
|
||||
|
||||
/*---------------
|
||||
Whole life cycle
|
||||
---------------*/
|
||||
@Nullable
|
||||
private EhClient mClient;
|
||||
@Nullable
|
||||
private GalleryInfo mGalleryInfo;
|
||||
|
||||
@Nullable
|
||||
private EasyRecyclerView mRecyclerView;
|
||||
@Nullable
|
||||
private GalleryPreviewAdapter mAdapter;
|
||||
@Nullable
|
||||
private GalleryPreviewHelper mHelper;
|
||||
|
||||
private boolean mHasFirstRefresh = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
mClient = EhClient.INSTANCE;
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
onRestore(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
private void onInit() {
|
||||
Bundle args = getArguments();
|
||||
if (args == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
mGalleryInfo = args.getParcelable(KEY_GALLERY_INFO);
|
||||
}
|
||||
|
||||
private void onRestore(@NonNull Bundle savedInstanceState) {
|
||||
mGalleryInfo = savedInstanceState.getParcelable(KEY_GALLERY_INFO);
|
||||
mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
boolean hasFirstRefresh;
|
||||
if (mHelper != null && 1 == mHelper.getShownViewIndex()) {
|
||||
hasFirstRefresh = false;
|
||||
} else {
|
||||
hasFirstRefresh = mHasFirstRefresh;
|
||||
}
|
||||
outState.putBoolean(KEY_HAS_FIRST_REFRESH, hasFirstRefresh);
|
||||
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateViewWithToolbar(LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
ContentLayout mContentLayout = (ContentLayout) inflater.inflate(
|
||||
R.layout.scene_gallery_previews, container, false);
|
||||
mContentLayout.hideFastScroll();
|
||||
mRecyclerView = mContentLayout.getRecyclerView();
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
mAdapter = new GalleryPreviewAdapter();
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
int columnWidth = Settings.getThumbSize();
|
||||
AutoGridLayoutManager layoutManager = new AutoGridLayoutManager(context, columnWidth, LayoutUtils.dp2pix(context, 16));
|
||||
layoutManager.setStrategy(AutoGridLayoutManager.STRATEGY_SUITABLE_SIZE);
|
||||
mRecyclerView.setLayoutManager(layoutManager);
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
int padding = LayoutUtils.dp2pix(context, 4);
|
||||
MarginItemDecoration decoration = new MarginItemDecoration(padding, padding, padding, padding, padding);
|
||||
mRecyclerView.addItemDecoration(decoration);
|
||||
|
||||
mHelper = new GalleryPreviewHelper();
|
||||
mContentLayout.setHelper(mHelper);
|
||||
|
||||
// Only refresh for the first time
|
||||
if (!mHasFirstRefresh) {
|
||||
mHasFirstRefresh = true;
|
||||
mHelper.firstRefresh();
|
||||
}
|
||||
|
||||
return mContentLayout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
if (null != mHelper) {
|
||||
if (1 == mHelper.getShownViewIndex()) {
|
||||
mHasFirstRefresh = false;
|
||||
}
|
||||
}
|
||||
if (null != mRecyclerView) {
|
||||
mRecyclerView.stopScroll();
|
||||
mRecyclerView = null;
|
||||
}
|
||||
|
||||
mAdapter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setTitle(R.string.gallery_previews);
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24);
|
||||
if (mGalleryInfo != null) {
|
||||
if (((GalleryDetail) mGalleryInfo).previewPages > 2)
|
||||
showMenu(R.menu.scene_gallery_previews);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(@NonNull MenuItem item) {
|
||||
Context context = getContext();
|
||||
if (null == context) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_go_to) {
|
||||
if (mHelper == null) {
|
||||
return true;
|
||||
}
|
||||
int pages = mHelper.getPages();
|
||||
if (pages > 1 && mHelper.canGoTo()) {
|
||||
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
|
||||
AlertDialog dialog = new BaseDialogBuilder(context).setTitle(R.string.go_to)
|
||||
.setView(R.layout.dialog_go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create();
|
||||
dialog.show();
|
||||
helper.setDialog(dialog);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onItemClick(int position) {
|
||||
Context context = getContext();
|
||||
if (null != context && null != mHelper && null != mGalleryInfo) {
|
||||
GalleryPreview p = mHelper.getDataAtEx(position);
|
||||
if (p != null) {
|
||||
Intent intent = new Intent(context, ReaderActivity.class);
|
||||
intent.setAction(ReaderActivity.ACTION_EH);
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo);
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, p.position);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onGetPreviewSetSuccess(Pair<PreviewSet, Integer> result, int taskId) {
|
||||
if (null != mHelper && mHelper.isCurrentTask(taskId) && null != mGalleryInfo) {
|
||||
PreviewSet previewSet = result.first;
|
||||
int size = previewSet.size();
|
||||
ArrayList<GalleryPreview> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
list.add(previewSet.getGalleryPreview(mGalleryInfo.getGid(), i));
|
||||
}
|
||||
|
||||
mHelper.onGetPageData(taskId, result.second, 0, null, null, list);
|
||||
}
|
||||
}
|
||||
|
||||
private void onGetPreviewSetFailure(Exception e, int taskId) {
|
||||
if (mHelper != null && mHelper.isCurrentTask(taskId)) {
|
||||
mHelper.onGetException(taskId, e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
public TextView text;
|
||||
|
||||
public GalleryPreviewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
image = itemView.findViewById(R.id.image);
|
||||
text = itemView.findViewById(R.id.text);
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryPreviewAdapter extends RecyclerView.Adapter<GalleryPreviewHolder> {
|
||||
|
||||
private final LayoutInflater mInflater;
|
||||
|
||||
public GalleryPreviewAdapter() {
|
||||
mInflater = getLayoutInflater();
|
||||
AssertUtils.assertNotNull(mInflater);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public GalleryPreviewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
return new GalleryPreviewHolder(mInflater.inflate(R.layout.item_gallery_preview, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetTextI18n")
|
||||
public void onBindViewHolder(@NonNull GalleryPreviewHolder holder, int position) {
|
||||
if (null != mHelper) {
|
||||
GalleryPreview preview = mHelper.getDataAtEx(position);
|
||||
if (preview != null) {
|
||||
preview.load(holder.image);
|
||||
holder.text.setText(Integer.toString(preview.position + 1));
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnClickListener(v -> onItemClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mHelper != null ? mHelper.size() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryPreviewHelper extends ContentLayout.ContentHelper<GalleryPreview> {
|
||||
|
||||
@Override
|
||||
protected void getPageData(final int taskId, int type, int page, String index, boolean isNext) {
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == activity || null == mClient || null == mGalleryInfo) {
|
||||
onGetException(taskId, new EhException(getString(R.string.error_cannot_find_gallery)));
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
request.setArgs(url);
|
||||
request.enqueue(GalleryPreviewsScene.this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Context getContext() {
|
||||
return GalleryPreviewsScene.this.getContext();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@Override
|
||||
protected void notifyDataSetChanged() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void notifyItemRangeInserted(int positionStart, int itemCount) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter.notifyItemRangeInserted(positionStart, itemCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDuplicate(GalleryPreview d1, GalleryPreview d2) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class GoToDialogHelper implements View.OnClickListener,
|
||||
DialogInterface.OnDismissListener {
|
||||
|
||||
private final int mPages;
|
||||
private final int mCurrentPage;
|
||||
|
||||
@Nullable
|
||||
private Slider mSlider;
|
||||
@Nullable
|
||||
private Dialog mDialog;
|
||||
|
||||
private GoToDialogHelper(int pages, int currentPage) {
|
||||
mPages = pages;
|
||||
mCurrentPage = currentPage;
|
||||
}
|
||||
|
||||
public void setDialog(@NonNull AlertDialog dialog) {
|
||||
mDialog = dialog;
|
||||
|
||||
((TextView) ViewUtils.$$(dialog, R.id.start)).setText(String.format(Locale.US, "%d", 1));
|
||||
((TextView) ViewUtils.$$(dialog, R.id.end)).setText(String.format(Locale.US, "%d", mPages));
|
||||
mSlider = (Slider) ViewUtils.$$(dialog, R.id.slider);
|
||||
mSlider.setValueTo(mPages);
|
||||
mSlider.setValue(mCurrentPage + 1);
|
||||
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
|
||||
dialog.setOnDismissListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (null == mSlider) {
|
||||
return;
|
||||
}
|
||||
|
||||
int page = (int) (mSlider.getValue() - 1);
|
||||
if (page >= 0 && page < mPages && mHelper != null) {
|
||||
mHelper.goTo(page);
|
||||
if (mDialog != null) {
|
||||
mDialog.dismiss();
|
||||
mDialog = null;
|
||||
}
|
||||
} else {
|
||||
showTip(R.string.error_out_of_range, LENGTH_LONG);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
mDialog = null;
|
||||
mSlider = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
* 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.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Pair
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.hippo.app.BaseDialogBuilder
|
||||
import com.hippo.easyrecyclerview.MarginItemDecoration
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhClient
|
||||
import com.hippo.ehviewer.client.EhRequest
|
||||
import com.hippo.ehviewer.client.EhUrl
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
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.databinding.SceneGalleryPreviewsBinding
|
||||
import com.hippo.widget.ContentLayout.ContentHelper
|
||||
import com.hippo.widget.LoadImageView
|
||||
import com.hippo.widget.recyclerview.AutoGridLayoutManager
|
||||
import com.hippo.yorozuya.LayoutUtils
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import java.util.Locale
|
||||
|
||||
class GalleryPreviewsScene : BaseToolbarScene() {
|
||||
private var _binding: SceneGalleryPreviewsBinding? = null
|
||||
private val binding
|
||||
get() = _binding!!
|
||||
private var mGalleryInfo: GalleryInfo? = null
|
||||
private var mAdapter: GalleryPreviewAdapter? = null
|
||||
private var mHelper: GalleryPreviewHelper? = null
|
||||
private var mHasFirstRefresh = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
onInit()
|
||||
} else {
|
||||
onRestore(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onInit() {
|
||||
val args = arguments ?: return
|
||||
mGalleryInfo = args.getParcelable(KEY_GALLERY_INFO)
|
||||
}
|
||||
|
||||
private fun onRestore(savedInstanceState: Bundle) {
|
||||
mGalleryInfo = savedInstanceState.getParcelable(KEY_GALLERY_INFO)
|
||||
mHasFirstRefresh = savedInstanceState.getBoolean(KEY_HAS_FIRST_REFRESH)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
val hasFirstRefresh: Boolean = if (mHelper != null && 1 == mHelper!!.shownViewIndex) {
|
||||
false
|
||||
} else {
|
||||
mHasFirstRefresh
|
||||
}
|
||||
outState.putBoolean(KEY_HAS_FIRST_REFRESH, hasFirstRefresh)
|
||||
outState.putParcelable(KEY_GALLERY_INFO, mGalleryInfo)
|
||||
}
|
||||
|
||||
override fun onCreateViewWithToolbar(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = SceneGalleryPreviewsBinding.inflate(inflater, container, false)
|
||||
binding.contentLayout.hideFastScroll()
|
||||
setLiftOnScrollTargetView(binding.contentLayout)
|
||||
mAdapter = GalleryPreviewAdapter()
|
||||
binding.contentLayout.recyclerView.adapter = mAdapter
|
||||
val columnWidth = Settings.getThumbSize()
|
||||
val layoutManager =
|
||||
AutoGridLayoutManager(context, columnWidth, LayoutUtils.dp2pix(context, 16f))
|
||||
layoutManager.setStrategy(AutoGridLayoutManager.STRATEGY_SUITABLE_SIZE)
|
||||
binding.contentLayout.recyclerView.layoutManager = layoutManager
|
||||
binding.contentLayout.recyclerView.clipToPadding = false
|
||||
val padding = LayoutUtils.dp2pix(context, 4f)
|
||||
val decoration = MarginItemDecoration(padding, padding, padding, padding, padding)
|
||||
binding.contentLayout.recyclerView.addItemDecoration(decoration)
|
||||
mHelper = GalleryPreviewHelper()
|
||||
binding.contentLayout.setHelper(mHelper)
|
||||
|
||||
// Only refresh for the first time
|
||||
if (!mHasFirstRefresh) {
|
||||
mHasFirstRefresh = true
|
||||
mHelper?.firstRefresh()
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (null != mHelper) {
|
||||
if (1 == mHelper!!.shownViewIndex) {
|
||||
mHasFirstRefresh = false
|
||||
}
|
||||
}
|
||||
binding.contentLayout.recyclerView.stopScroll()
|
||||
_binding = null
|
||||
mAdapter = null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setTitle(R.string.gallery_previews)
|
||||
setNavigationIcon(R.drawable.v_arrow_left_dark_x24)
|
||||
if (mGalleryInfo != null) {
|
||||
if ((mGalleryInfo as GalleryDetail).previewPages > 2) showMenu(R.menu.scene_gallery_previews)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
val context = context ?: return false
|
||||
val id = item.itemId
|
||||
if (id == R.id.action_go_to) {
|
||||
if (mHelper == null) {
|
||||
return true
|
||||
}
|
||||
val pages = mHelper!!.pages
|
||||
if (pages > 1 && mHelper!!.canGoTo()) {
|
||||
val helper = GoToDialogHelper(pages, mHelper!!.pageForTop)
|
||||
val dialog = BaseDialogBuilder(context).setTitle(R.string.go_to)
|
||||
.setView(R.layout.dialog_go_to)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.create()
|
||||
dialog.show()
|
||||
helper.setDialog(dialog)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onItemClick(position: Int): Boolean {
|
||||
val context = context
|
||||
if (null != context && null != mHelper && null != mGalleryInfo) {
|
||||
val p = mHelper!!.getDataAtEx(position)
|
||||
if (p != null) {
|
||||
val intent = Intent(context, ReaderActivity::class.java)
|
||||
intent.action = ReaderActivity.ACTION_EH
|
||||
intent.putExtra(ReaderActivity.KEY_GALLERY_INFO, mGalleryInfo)
|
||||
intent.putExtra(ReaderActivity.KEY_PAGE, p.position)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun onGetPreviewSetSuccess(result: Pair<PreviewSet, Int>, taskId: Int) {
|
||||
if (null != mHelper && mHelper!!.isCurrentTask(taskId) && null != mGalleryInfo) {
|
||||
val previewSet = result.first
|
||||
val size = previewSet.size()
|
||||
val list = ArrayList<GalleryPreview>(size)
|
||||
for (i in 0 until size) {
|
||||
list.add(previewSet.getGalleryPreview(mGalleryInfo!!.gid, i))
|
||||
}
|
||||
mHelper!!.onGetPageData(
|
||||
taskId, result.second, 0, null, null,
|
||||
list as List<GalleryPreview?>?
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onGetPreviewSetFailure(e: Exception, taskId: Int) {
|
||||
if (mHelper != null && mHelper!!.isCurrentTask(taskId)) {
|
||||
mHelper!!.onGetException(taskId, e)
|
||||
}
|
||||
}
|
||||
|
||||
private class GalleryPreviewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var image: LoadImageView
|
||||
var text: TextView
|
||||
|
||||
init {
|
||||
image = itemView.findViewById(R.id.image)
|
||||
text = itemView.findViewById(R.id.text)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GetPreviewSetListener(
|
||||
context: Context,
|
||||
private val mTaskId: Int
|
||||
) : EhCallback<GalleryPreviewsScene, Pair<PreviewSet, Int>>(context) {
|
||||
override fun onSuccess(result: Pair<PreviewSet, Int>) {
|
||||
val scene = this@GalleryPreviewsScene
|
||||
scene.onGetPreviewSetSuccess(result, mTaskId)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
val scene = this@GalleryPreviewsScene
|
||||
scene.onGetPreviewSetFailure(e, mTaskId)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
}
|
||||
|
||||
private inner class GalleryPreviewAdapter : RecyclerView.Adapter<GalleryPreviewHolder>() {
|
||||
private val mInflater: LayoutInflater = layoutInflater
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GalleryPreviewHolder {
|
||||
return GalleryPreviewHolder(
|
||||
mInflater.inflate(
|
||||
R.layout.item_gallery_preview,
|
||||
parent,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: GalleryPreviewHolder, position: Int) {
|
||||
if (null != mHelper) {
|
||||
val preview = mHelper!!.getDataAtEx(position)
|
||||
if (preview != null) {
|
||||
preview.load(holder.image)
|
||||
holder.text.text = (preview.position + 1).toString()
|
||||
}
|
||||
}
|
||||
holder.itemView.setOnClickListener { onItemClick(position) }
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (mHelper != null) mHelper!!.size() else 0
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GalleryPreviewHelper : ContentHelper<GalleryPreview>() {
|
||||
override fun getPageData(
|
||||
taskId: Int,
|
||||
type: Int,
|
||||
page: Int,
|
||||
index: String?,
|
||||
isNext: Boolean
|
||||
) {
|
||||
val activity = mainActivity
|
||||
if (null == activity || null == mGalleryInfo) {
|
||||
onGetException(taskId, EhException(getString(R.string.error_cannot_find_gallery)))
|
||||
return
|
||||
}
|
||||
val url =
|
||||
EhUrl.getGalleryDetailUrl(mGalleryInfo!!.gid, mGalleryInfo!!.token, page, false)
|
||||
val request = EhRequest()
|
||||
request.setMethod(EhClient.METHOD_GET_PREVIEW_SET)
|
||||
request.setCallback(
|
||||
GetPreviewSetListener(
|
||||
context,
|
||||
taskId
|
||||
)
|
||||
)
|
||||
request.setArgs(url)
|
||||
request.enqueue(this@GalleryPreviewsScene)
|
||||
}
|
||||
|
||||
override fun getContext(): Context {
|
||||
return this@GalleryPreviewsScene.requireContext()
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
override fun notifyDataSetChanged() {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun notifyItemRangeInserted(positionStart: Int, itemCount: Int) {
|
||||
if (mAdapter != null) {
|
||||
mAdapter!!.notifyItemRangeInserted(positionStart, itemCount)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isDuplicate(d1: GalleryPreview, d2: GalleryPreview): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GoToDialogHelper(private val mPages: Int, private val mCurrentPage: Int) :
|
||||
View.OnClickListener, DialogInterface.OnDismissListener {
|
||||
private var mSlider: Slider? = null
|
||||
private var mDialog: Dialog? = null
|
||||
fun setDialog(dialog: AlertDialog) {
|
||||
mDialog = dialog
|
||||
(ViewUtils.`$$`(dialog, R.id.start) as TextView).text =
|
||||
String.format(Locale.US, "%d", 1)
|
||||
(ViewUtils.`$$`(dialog, R.id.end) as TextView).text =
|
||||
String.format(Locale.US, "%d", mPages)
|
||||
mSlider = ViewUtils.`$$`(dialog, R.id.slider) as Slider
|
||||
mSlider!!.valueTo = mPages.toFloat()
|
||||
mSlider!!.value = (mCurrentPage + 1).toFloat()
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this)
|
||||
dialog.setOnDismissListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (null == mSlider) {
|
||||
return
|
||||
}
|
||||
val page = (mSlider!!.value - 1).toInt()
|
||||
if (page in 0 until mPages && mHelper != null) {
|
||||
mHelper!!.goTo(page)
|
||||
if (mDialog != null) {
|
||||
mDialog!!.dismiss()
|
||||
mDialog = null
|
||||
}
|
||||
} else {
|
||||
showTip(R.string.error_out_of_range, LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
mDialog = null
|
||||
mSlider = null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_GALLERY_INFO = "gallery_info"
|
||||
private const val KEY_HAS_FIRST_REFRESH = "has_first_refresh"
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,6 @@ 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
|
||||
@@ -114,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)
|
||||
@@ -223,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
|
||||
}
|
||||
|
||||
@@ -293,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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -325,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)
|
||||
}
|
||||
@@ -336,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)
|
||||
}
|
||||
@@ -352,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) {
|
||||
|
||||
@@ -1,260 +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.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
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;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
|
||||
/**
|
||||
* Only show a progress with jobs in background
|
||||
*/
|
||||
public final class ProgressScene extends BaseScene implements View.OnClickListener {
|
||||
|
||||
public static final String KEY_ACTION = "action";
|
||||
public static final String ACTION_GALLERY_TOKEN = "gallery_token";
|
||||
public static final String KEY_GID = "gid";
|
||||
public static final String KEY_PTOKEN = "ptoken";
|
||||
public static final String KEY_PAGE = "page";
|
||||
private static final String KEY_VALID = "valid";
|
||||
private static final String KEY_ERROR = "error";
|
||||
private boolean mValid;
|
||||
private String mError;
|
||||
|
||||
private String mAction;
|
||||
|
||||
private long mGid;
|
||||
private String mPToken;
|
||||
private int mPage;
|
||||
|
||||
@Nullable
|
||||
private TextView mTip;
|
||||
@Nullable
|
||||
private ViewTransition mViewTransition;
|
||||
|
||||
@Override
|
||||
public boolean needShowLeftDrawer() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
onInit();
|
||||
} else {
|
||||
onRestore(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doJobs() {
|
||||
Context context = getContext();
|
||||
MainActivity activity = getMainActivity();
|
||||
if (null == context || null == activity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ACTION_GALLERY_TOKEN.equals(mAction)) {
|
||||
if (mGid == -1 || mPToken == null || mPage == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
EhRequest request = new EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_TOKEN)
|
||||
.setArgs(mGid, mPToken, mPage)
|
||||
.setCallback(new GetGalleryTokenListener(context,
|
||||
activity.getStageId(), getTag()));
|
||||
request.enqueue(this);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleArgs(Bundle args) {
|
||||
if (args == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mAction = args.getString(KEY_ACTION);
|
||||
if (ACTION_GALLERY_TOKEN.equals(mAction)) {
|
||||
mGid = args.getLong(KEY_GID, -1);
|
||||
mPToken = args.getString(KEY_PTOKEN, null);
|
||||
mPage = args.getInt(KEY_PAGE, -1);
|
||||
return mGid != -1 && mPToken != null && mPage != -1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void onInit() {
|
||||
mValid = handleArgs(getArguments());
|
||||
if (mValid) {
|
||||
mValid = doJobs();
|
||||
}
|
||||
if (!mValid) {
|
||||
mError = getString(R.string.error_something_wrong_happened);
|
||||
}
|
||||
}
|
||||
|
||||
private void onRestore(@NonNull Bundle savedInstanceState) {
|
||||
mValid = savedInstanceState.getBoolean(KEY_VALID);
|
||||
mError = savedInstanceState.getString(KEY_ERROR);
|
||||
|
||||
mAction = savedInstanceState.getString(KEY_ACTION);
|
||||
|
||||
mGid = savedInstanceState.getLong(KEY_GID, -1);
|
||||
mPToken = savedInstanceState.getString(KEY_PTOKEN, null);
|
||||
mPage = savedInstanceState.getInt(KEY_PAGE, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(KEY_VALID, mValid);
|
||||
outState.putString(KEY_ERROR, mError);
|
||||
|
||||
outState.putString(KEY_ACTION, mAction);
|
||||
|
||||
outState.putLong(KEY_GID, mGid);
|
||||
outState.putString(KEY_PTOKEN, mPToken);
|
||||
outState.putInt(KEY_PAGE, mPage);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.scene_progress, container, false);
|
||||
View progress = ViewUtils.$$(view, R.id.progress);
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
|
||||
Context context = getContext();
|
||||
AssertUtils.assertNotNull(context);
|
||||
|
||||
Drawable drawable = ContextCompat.getDrawable(context, R.drawable.big_sad_pandroid);
|
||||
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
|
||||
mTip.setCompoundDrawables(null, drawable, null, null);
|
||||
mTip.setOnClickListener(this);
|
||||
mTip.setText(mError);
|
||||
|
||||
mViewTransition = new ViewTransition(progress, mTip);
|
||||
|
||||
if (mValid) {
|
||||
mViewTransition.showView(0, false);
|
||||
} else {
|
||||
mViewTransition.showView(1, false);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
mTip = null;
|
||||
mViewTransition = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mTip == v) {
|
||||
if (doJobs()) {
|
||||
mValid = true;
|
||||
// Show progress
|
||||
if (null != mViewTransition) {
|
||||
mViewTransition.showView(0, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onGetGalleryTokenSuccess(String result) {
|
||||
Bundle arg = new Bundle();
|
||||
arg.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN);
|
||||
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();
|
||||
}
|
||||
|
||||
private void onGetGalleryTokenFailure(Exception e) {
|
||||
mValid = false;
|
||||
|
||||
Context context = getContext();
|
||||
|
||||
if (null != context && null != mViewTransition && null != mTip) {
|
||||
// Show tip
|
||||
mError = ExceptionUtils.getReadableString(e);
|
||||
mViewTransition.showView(1);
|
||||
mTip.setText(mError);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetGalleryTokenListener extends EhCallback<ProgressScene, String> {
|
||||
|
||||
public GetGalleryTokenListener(Context context, int stageId, String sceneTag) {
|
||||
super(context, stageId, sceneTag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(String result) {
|
||||
ProgressScene scene = getScene();
|
||||
if (scene != null) {
|
||||
scene.onGetGalleryTokenSuccess(result);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
ProgressScene scene = getScene();
|
||||
if (scene != null) {
|
||||
scene.onGetGalleryTokenFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancel() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstance(SceneFragment scene) {
|
||||
return scene instanceof ProgressScene;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhClient
|
||||
import com.hippo.ehviewer.client.EhRequest
|
||||
import com.hippo.util.ExceptionUtils
|
||||
import com.hippo.view.ViewTransition
|
||||
import com.hippo.yorozuya.AssertUtils
|
||||
import com.hippo.yorozuya.ViewUtils
|
||||
|
||||
/**
|
||||
* Only show a progress with jobs in background
|
||||
*/
|
||||
class ProgressScene : BaseScene(), View.OnClickListener {
|
||||
private var mValid = false
|
||||
private var mError: String? = null
|
||||
private var mAction: String? = null
|
||||
private var mGid: Long = 0
|
||||
private var mPToken: String? = null
|
||||
private var mPage = 0
|
||||
private var mTip: TextView? = null
|
||||
private var mViewTransition: ViewTransition? = null
|
||||
override fun needShowLeftDrawer(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (savedInstanceState == null) {
|
||||
onInit()
|
||||
} else {
|
||||
onRestore(savedInstanceState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun doJobs(): Boolean {
|
||||
val context = context
|
||||
val activity = mainActivity
|
||||
if (null == context || null == activity) {
|
||||
return false
|
||||
}
|
||||
if (ACTION_GALLERY_TOKEN == mAction) {
|
||||
if (mGid == -1L || mPToken == null || mPage == -1) {
|
||||
return false
|
||||
}
|
||||
val request = EhRequest()
|
||||
.setMethod(EhClient.METHOD_GET_GALLERY_TOKEN)
|
||||
.setArgs(mGid, mPToken!!, mPage)
|
||||
.setCallback(
|
||||
GetGalleryTokenListener(
|
||||
context
|
||||
)
|
||||
)
|
||||
request.enqueue(this)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun handleArgs(args: Bundle?): Boolean {
|
||||
if (args == null) {
|
||||
return false
|
||||
}
|
||||
mAction = args.getString(KEY_ACTION)
|
||||
if (ACTION_GALLERY_TOKEN == mAction) {
|
||||
mGid = args.getLong(KEY_GID, -1)
|
||||
mPToken = args.getString(KEY_PTOKEN, null)
|
||||
mPage = args.getInt(KEY_PAGE, -1)
|
||||
return mGid != -1L && mPToken != null && mPage != -1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun onInit() {
|
||||
mValid = handleArgs(arguments)
|
||||
if (mValid) {
|
||||
mValid = doJobs()
|
||||
}
|
||||
if (!mValid) {
|
||||
mError = getString(R.string.error_something_wrong_happened)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onRestore(savedInstanceState: Bundle) {
|
||||
mValid = savedInstanceState.getBoolean(KEY_VALID)
|
||||
mError = savedInstanceState.getString(KEY_ERROR)
|
||||
mAction = savedInstanceState.getString(KEY_ACTION)
|
||||
mGid = savedInstanceState.getLong(KEY_GID, -1)
|
||||
mPToken = savedInstanceState.getString(KEY_PTOKEN, null)
|
||||
mPage = savedInstanceState.getInt(KEY_PAGE, -1)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putBoolean(KEY_VALID, mValid)
|
||||
outState.putString(KEY_ERROR, mError)
|
||||
outState.putString(KEY_ACTION, mAction)
|
||||
outState.putLong(KEY_GID, mGid)
|
||||
outState.putString(KEY_PTOKEN, mPToken)
|
||||
outState.putInt(KEY_PAGE, mPage)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.scene_progress, container, false)
|
||||
val progress = ViewUtils.`$$`(view, R.id.progress)
|
||||
mTip = ViewUtils.`$$`(view, R.id.tip) as TextView
|
||||
val context = context
|
||||
AssertUtils.assertNotNull(context)
|
||||
val drawable = ContextCompat.getDrawable(context!!, R.drawable.big_sad_pandroid)
|
||||
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
mTip!!.setCompoundDrawables(null, drawable, null, null)
|
||||
mTip!!.setOnClickListener(this)
|
||||
mTip!!.text = mError
|
||||
mViewTransition = ViewTransition(progress, mTip)
|
||||
if (mValid) {
|
||||
mViewTransition!!.showView(0, false)
|
||||
} else {
|
||||
mViewTransition!!.showView(1, false)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
mTip = null
|
||||
mViewTransition = null
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
if (mTip === v) {
|
||||
if (doJobs()) {
|
||||
mValid = true
|
||||
// Show progress
|
||||
if (null != mViewTransition) {
|
||||
mViewTransition!!.showView(0, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onGetGalleryTokenSuccess(result: String) {
|
||||
val arg = Bundle()
|
||||
arg.putString(GalleryDetailScene.KEY_ACTION, GalleryDetailScene.ACTION_GID_TOKEN)
|
||||
arg.putLong(GalleryDetailScene.KEY_GID, mGid)
|
||||
arg.putString(GalleryDetailScene.KEY_TOKEN, result)
|
||||
arg.putInt(GalleryDetailScene.KEY_PAGE, mPage)
|
||||
navigate(R.id.galleryDetailScene, arg)
|
||||
}
|
||||
|
||||
private fun onGetGalleryTokenFailure(e: Exception) {
|
||||
mValid = false
|
||||
val context = context
|
||||
if (null != context && null != mViewTransition && null != mTip) {
|
||||
// Show tip
|
||||
mError = ExceptionUtils.getReadableString(e)
|
||||
mViewTransition!!.showView(1)
|
||||
mTip!!.text = mError
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GetGalleryTokenListener(
|
||||
context: Context
|
||||
) : EhCallback<ProgressScene, String>(context) {
|
||||
override fun onSuccess(result: String) {
|
||||
val scene = this@ProgressScene
|
||||
scene.onGetGalleryTokenSuccess(result)
|
||||
}
|
||||
|
||||
override fun onFailure(e: Exception) {
|
||||
val scene = this@ProgressScene
|
||||
scene.onGetGalleryTokenFailure(e)
|
||||
}
|
||||
|
||||
override fun onCancel() {}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_ACTION = "action"
|
||||
const val ACTION_GALLERY_TOKEN = "gallery_token"
|
||||
const val KEY_GID = "gid"
|
||||
const val KEY_PTOKEN = "ptoken"
|
||||
const val KEY_PAGE = "page"
|
||||
private const val KEY_VALID = "valid"
|
||||
private const val KEY_ERROR = "error"
|
||||
}
|
||||
}
|
||||
@@ -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,36 +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;
|
||||
|
||||
public final class TransitionNameFactory {
|
||||
|
||||
public static String getThumbTransitionName(long gid) {
|
||||
return "thumb:" + gid;
|
||||
}
|
||||
|
||||
public static String getTitleTransitionName(long gid) {
|
||||
return "title:" + gid;
|
||||
}
|
||||
|
||||
public static String getUploaderTransitionName(long gid) {
|
||||
return "uploader:" + gid;
|
||||
}
|
||||
|
||||
public static String getCategoryTransitionName(long gid) {
|
||||
return "category:" + gid;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,14 @@ import android.graphics.ImageDecoder.Source
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import coil.decode.FrameDelayRewritingSource
|
||||
import com.hippo.Native
|
||||
import com.hippo.UriArchiveAccessor
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
import okio.Buffer
|
||||
import okio.BufferedSource
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
@@ -80,20 +86,41 @@ class Image private constructor(source: Source, private val byteBuffer: ByteBuff
|
||||
@Throws(DecodeException::class)
|
||||
@JvmStatic
|
||||
fun decode(stream: FileInputStream): Image {
|
||||
val src = ImageDecoder.createSource(
|
||||
stream.channel.map(
|
||||
FileChannel.MapMode.READ_ONLY, 0,
|
||||
stream.available().toLong()
|
||||
)
|
||||
val buffer = stream.channel.map(
|
||||
FileChannel.MapMode.READ_ONLY, 0,
|
||||
stream.available().toLong()
|
||||
)
|
||||
val source = if (checkIsGif(buffer)) {
|
||||
rewriteSource(stream.source().buffer())
|
||||
} else {
|
||||
buffer
|
||||
}
|
||||
val src = ImageDecoder.createSource(source)
|
||||
return Image(src)
|
||||
}
|
||||
|
||||
@Throws(DecodeException::class)
|
||||
@JvmStatic
|
||||
fun decode(buffer: ByteBuffer): Image {
|
||||
val src = ImageDecoder.createSource(buffer)
|
||||
return Image(src, buffer)
|
||||
return if (checkIsGif(buffer)) {
|
||||
val rewritten = rewriteSource(Buffer().apply {
|
||||
write(buffer)
|
||||
UriArchiveAccessor.releaseByteBuffer(buffer)
|
||||
})
|
||||
Image(ImageDecoder.createSource(rewritten))
|
||||
} else {
|
||||
Image(ImageDecoder.createSource(buffer), buffer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun rewriteSource(source: BufferedSource): ByteBuffer {
|
||||
val bufferedSource = FrameDelayRewritingSource(source).buffer()
|
||||
return ByteBuffer.wrap(bufferedSource.use { it.readByteArray() })
|
||||
}
|
||||
|
||||
private fun checkIsGif(buffer: ByteBuffer): Boolean {
|
||||
check(buffer.isDirect)
|
||||
return Native.isGif(buffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 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.network
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.yorozuya.ObjectUtils
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.HttpUrl
|
||||
import java.util.regex.Pattern
|
||||
|
||||
open class CookieRepository(context: Context, name: String) : CookieJar {
|
||||
private val db: CookieDatabase
|
||||
private val map: MutableMap<String, CookieSet>
|
||||
|
||||
@Synchronized
|
||||
fun addCookie(cookie: Cookie) {
|
||||
// For cookie database
|
||||
var toAdd: Cookie? = null
|
||||
var toUpdate: Cookie? = null
|
||||
var toRemove: Cookie? = null
|
||||
var set = map[cookie.domain]
|
||||
if (set == null) {
|
||||
set = CookieSet()
|
||||
map[cookie.domain] = set
|
||||
}
|
||||
if (cookie.expiresAt <= System.currentTimeMillis()) {
|
||||
toRemove = set.remove(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (toRemove != null && !toRemove.persistent) {
|
||||
toRemove = null
|
||||
}
|
||||
} else {
|
||||
toAdd = cookie
|
||||
toUpdate = set.add(cookie)
|
||||
// If the cookie is not persistent, it's not in database
|
||||
if (!toAdd.persistent) toAdd = null
|
||||
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
|
||||
// Remove the cookie if it updates to null
|
||||
if (toAdd == null && toUpdate != null) {
|
||||
toRemove = toUpdate
|
||||
toUpdate = null
|
||||
}
|
||||
}
|
||||
if (toRemove != null) {
|
||||
db.remove(toRemove)
|
||||
}
|
||||
if (toAdd != null) {
|
||||
if (toUpdate != null) {
|
||||
db.update(toUpdate, toAdd)
|
||||
} else {
|
||||
db.add(toAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCookieHeader(url: HttpUrl): String {
|
||||
val cookies = getCookies(url)
|
||||
val cookieHeader = StringBuilder()
|
||||
var i = 0
|
||||
val size = cookies.size
|
||||
while (i < size) {
|
||||
if (i > 0) {
|
||||
cookieHeader.append("; ")
|
||||
}
|
||||
val cookie = cookies[i]
|
||||
cookieHeader.append(cookie.name).append('=').append(cookie.value)
|
||||
i++
|
||||
}
|
||||
return cookieHeader.toString()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun getCookies(url: HttpUrl): List<Cookie> {
|
||||
val accepted: MutableList<Cookie> = ArrayList()
|
||||
val expired: MutableList<Cookie> = ArrayList()
|
||||
for ((domain, cookieSet) in map) {
|
||||
if (domainMatch(url, domain)) {
|
||||
cookieSet[url, accepted, expired]
|
||||
}
|
||||
}
|
||||
for (cookie in expired) {
|
||||
if (cookie.persistent) {
|
||||
db.remove(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 6265 Section-5.4 step 2, sort the cookie-list
|
||||
// Cookies with longer paths are listed before cookies with shorter paths.
|
||||
// Ignore creation-time, we don't store them.
|
||||
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
|
||||
return accepted
|
||||
}
|
||||
|
||||
fun contains(url: HttpUrl, name: String?): Boolean {
|
||||
for (cookie in getCookies(url)) {
|
||||
if (ObjectUtils.equal(cookie.name, name)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all cookies in this `CookieRepository`.
|
||||
*/
|
||||
@Synchronized
|
||||
fun clear() {
|
||||
map.clear()
|
||||
db.clear()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun close() {
|
||||
db.close()
|
||||
}
|
||||
|
||||
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
|
||||
for (cookie in cookies) {
|
||||
addCookie(cookie)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadForRequest(url: HttpUrl): List<Cookie> {
|
||||
return getCookies(url)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
|
||||
* of Android's private InetAddress#isNumeric API.
|
||||
*
|
||||
*
|
||||
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
|
||||
* including dots after the first colon. It matches IPv4 addresses as strings containing only
|
||||
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
|
||||
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
|
||||
* verification).
|
||||
*/
|
||||
private val VERIFY_AS_IP_ADDRESS =
|
||||
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
|
||||
|
||||
/**
|
||||
* Returns true if `host` is not a host name and might be an IP address.
|
||||
*/
|
||||
private fun verifyAsIpAddress(host: String): Boolean {
|
||||
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
|
||||
}
|
||||
|
||||
// okhttp3.Cookie.domainMatch(HttpUrl, String)
|
||||
@JvmStatic
|
||||
protected fun domainMatch(url: HttpUrl, domain: String?): Boolean {
|
||||
val urlHost = url.host
|
||||
return if (urlHost == domain) {
|
||||
true // As in 'example.com' matching 'example.com'.
|
||||
} else urlHost.endsWith(domain!!)
|
||||
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
|
||||
urlHost
|
||||
)
|
||||
// As in 'example.com' matching 'www.example.com'.
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
db = CookieDatabase(context, name)
|
||||
map = db.allCookies
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
-47
@@ -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>
|
||||
@@ -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>
|
||||
@@ -19,7 +19,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/gallery_detail_header" />
|
||||
<include
|
||||
android:id="@+id/header"
|
||||
layout="@layout/gallery_detail_header" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -31,13 +33,21 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/gallery_detail_actions" />
|
||||
<include
|
||||
android:id="@+id/actions"
|
||||
layout="@layout/gallery_detail_actions" />
|
||||
|
||||
<include layout="@layout/gallery_detail_tags" />
|
||||
<include
|
||||
android:id="@+id/tags"
|
||||
layout="@layout/gallery_detail_tags" />
|
||||
|
||||
<include layout="@layout/gallery_detail_comments" />
|
||||
<include
|
||||
android:id="@+id/comments"
|
||||
layout="@layout/gallery_detail_comments" />
|
||||
|
||||
<include layout="@layout/gallery_detail_previews" />
|
||||
<include
|
||||
android:id="@+id/previews"
|
||||
layout="@layout/gallery_detail_previews" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
style="@style/Widget.Material3.CardView.Filled"
|
||||
style="@style/Widget.Material3.CardView.Outlined"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="6dp"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:cardBackgroundColor="@android:color/transparent">
|
||||
app:strokeColor="@android:color/transparent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -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>
|
||||
@@ -26,7 +26,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/gallery_detail_content" />
|
||||
<include
|
||||
android:id="@+id/content"
|
||||
layout="@layout/gallery_detail_content" />
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user