Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6973fa4538 | ||
|
|
20191c3f46 | ||
|
|
f084fec54e | ||
|
|
367e3b3a93 | ||
|
|
36383b6f47 | ||
|
|
9ecd14820e | ||
|
|
be552e92a1 | ||
|
|
1bf676fd56 | ||
|
|
154a96b5fd | ||
|
|
443c384f41 | ||
|
|
85c7829636 | ||
|
|
68e3bb8d66 | ||
|
|
63b2e52eaf | ||
|
|
c7492f3faf | ||
|
|
9052343ef7 | ||
|
|
00c09e4a1a | ||
|
|
086c0d9f28 | ||
|
|
3ca0189f28 | ||
|
|
8f5a2ba85d | ||
|
|
1ab9811d7a | ||
|
|
0b720848ba | ||
|
|
eebd95e700 | ||
|
|
913e02f0fa | ||
|
|
10fdab2473 | ||
|
|
26710761ed | ||
|
|
17bf6ac12b | ||
|
|
6a01b7729d | ||
|
|
039e817002 | ||
|
|
3ea1e1914a | ||
|
|
e6c5152123 | ||
|
|
7bdaf186f3 | ||
|
|
b6b4e826cb | ||
|
|
949f3cb246 | ||
|
|
501855a8c0 | ||
|
|
44634be13d | ||
|
|
7c718df1af | ||
|
|
16cca9cbbd | ||
|
|
b715320298 | ||
|
|
d31d9eb137 | ||
|
|
b53c29c035 | ||
|
|
d3bf20bee6 | ||
|
|
cff445ddf5 | ||
|
|
877d96a50a | ||
|
|
12863dfa21 | ||
|
|
959959d880 | ||
|
|
cd676eabfc | ||
|
|
0a3e95c8d4 | ||
|
|
35b483e655 | ||
|
|
7dae7b72f4 | ||
|
|
620f5df241 | ||
|
|
d5ddb6eb8c |
No files matched your search
+5
-5
@@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "33.0.0"
|
||||
buildToolsVersion "33.0.1"
|
||||
ndkVersion "25.1.8937393"
|
||||
|
||||
splits {
|
||||
@@ -31,8 +31,8 @@ android {
|
||||
applicationId "moe.tarsin.ehviewer"
|
||||
minSdkVersion 28
|
||||
targetSdkVersion 33
|
||||
versionCode 180022
|
||||
versionName "1.8.3.1"
|
||||
versionCode 180024
|
||||
versionName "1.8.3.3"
|
||||
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ android {
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
compose true
|
||||
}
|
||||
|
||||
@@ -173,10 +174,9 @@ dependencies {
|
||||
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 'androidx.webkit:webkit:1.5.0'
|
||||
|
||||
implementation 'com.drakeet.drawer:drawer:1.0.3'
|
||||
implementation 'com.google.android.material:material:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.8.0-SNAPSHOT'
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp-bom:5.0.0-alpha.10'
|
||||
implementation 'com.squareup.okhttp3:okhttp'
|
||||
|
||||
Vendored
-4
@@ -14,10 +14,6 @@
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BaseFragment { }
|
||||
-keep,allowoptimization class com.hippo.ehviewer.ui.fragment.* extends com.hippo.ehviewer.ui.fragment.BasePreferenceFragment { }
|
||||
|
||||
-keepnames class com.hippo.ehviewer.ui.scene.* { }
|
||||
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
-renamesourcefileattribute
|
||||
-repackageclasses
|
||||
-allowaccessmodification
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="29" />
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-feature
|
||||
android:glEsVersion="0x00020000"
|
||||
|
||||
Submodule app/src/main/cpp/libarchive updated: 37d2cf6063...67b9c960d2.
+1
-1
Submodule app/src/main/cpp/liblzma updated: 2327a461e1...12b86bd8ca.
@@ -1,23 +1,8 @@
|
||||
package com.hippo.app
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
open class BaseDialogBuilder constructor(
|
||||
context: Context
|
||||
) : MaterialAlertDialogBuilder(context) {
|
||||
override fun create(): AlertDialog {
|
||||
return super.create().apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
window?.run {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) : MaterialAlertDialogBuilder(context)
|
||||
@@ -62,6 +62,7 @@ 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.image.Image;
|
||||
import com.hippo.image.ImageBitmap;
|
||||
import com.hippo.network.StatusCodeException;
|
||||
import com.hippo.scene.SceneApplication;
|
||||
@@ -76,14 +77,12 @@ import com.hippo.yorozuya.OSUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyStore;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
@@ -164,36 +163,29 @@ public class EhApplication extends SceneApplication {
|
||||
EhApplication application = ((EhApplication) context.getApplicationContext());
|
||||
if (application.mOkHttpClient == null) {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
||||
.connectTimeout(5, TimeUnit.SECONDS)
|
||||
.readTimeout(5, TimeUnit.SECONDS)
|
||||
.writeTimeout(5, TimeUnit.SECONDS)
|
||||
.callTimeout(10, TimeUnit.SECONDS)
|
||||
.cookieJar(getEhCookieStore(application))
|
||||
.cache(getOkHttpCache(application))
|
||||
.dns(new EhDns(application))
|
||||
.addNetworkInterceptor(chain -> {
|
||||
try {
|
||||
return chain.proceed(chain.request());
|
||||
} catch (NullPointerException e) {
|
||||
// crash on meizu devices due to old Android version
|
||||
// https://github.com/square/okhttp/issues/3301#issuecomment-348415095
|
||||
throw new IOException(e.getMessage());
|
||||
}
|
||||
})
|
||||
.proxySelector(getEhProxySelector(application));
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager trustManager)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
|
||||
if (Settings.getDF()) {
|
||||
X509TrustManager trustManager;
|
||||
try {
|
||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
|
||||
TrustManagerFactory.getDefaultAlgorithm());
|
||||
trustManagerFactory.init((KeyStore) null);
|
||||
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
|
||||
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager trustManager1)) {
|
||||
throw new IllegalStateException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
|
||||
}
|
||||
trustManager = trustManager1;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
trustManager = new EhX509TrustManager();
|
||||
}
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), trustManager);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
builder.sslSocketFactory(new EhSSLSocketFactory(), new EhX509TrustManager());
|
||||
}
|
||||
|
||||
application.mOkHttpClient = builder.build();
|
||||
}
|
||||
return application.mOkHttpClient;
|
||||
@@ -325,6 +317,7 @@ public class EhApplication extends SceneApplication {
|
||||
|
||||
super.onCreate();
|
||||
|
||||
Image.initialize(this);
|
||||
Native.initialize();
|
||||
GetText.initialize(this);
|
||||
StatusCodeException.initialize(this);
|
||||
|
||||
@@ -62,11 +62,15 @@ public class Settings {
|
||||
public static final String KEY_DETAIL_SIZE = "detail_size";
|
||||
public static final String KEY_THUMB_SIZE = "thumb_size";
|
||||
public static final String KEY_THUMB_RESOLUTION = "thumb_resolution";
|
||||
private static final String KEY_SHOW_COMMENTS = "show_gallery_comments";
|
||||
private static final boolean DEFAULT_SHOW_COMMENTS = true;
|
||||
public static final String KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
|
||||
public static final String KEY_DEFAULT_CATEGORIES = "default_categories";
|
||||
public static final int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
|
||||
public static final String KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
|
||||
public static final String KEY_EXCLUDED_LANGUAGES = "excluded_languages";
|
||||
private static final String KEY_PREVIEW_NUM = "preview_num";
|
||||
private static final int DEFAULT_PREVIEW_NUM = 60;
|
||||
/********************
|
||||
****** Privacy and Security
|
||||
********************/
|
||||
@@ -557,6 +561,10 @@ public class Settings {
|
||||
return getIntFromStr(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION);
|
||||
}
|
||||
|
||||
public static boolean getShowComments() {
|
||||
return getBoolean(KEY_SHOW_COMMENTS, DEFAULT_SHOW_COMMENTS);
|
||||
}
|
||||
|
||||
public static boolean getRequestNews() {
|
||||
return getBoolean(KEY_REQUEST_NEWS, DEFAULT_REQUEST_NEWS);
|
||||
}
|
||||
@@ -577,6 +585,10 @@ public class Settings {
|
||||
putBoolean(KEY_SHOW_TAG_TRANSLATIONS, value);
|
||||
}
|
||||
|
||||
public static int getPreviewNum() {
|
||||
return getInt(KEY_PREVIEW_NUM, DEFAULT_PREVIEW_NUM);
|
||||
}
|
||||
|
||||
public static int getDefaultCategories() {
|
||||
return getInt(KEY_DEFAULT_CATEGORIES, DEFAULT_DEFAULT_CATEGORIES);
|
||||
}
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 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;
|
||||
|
||||
/*
|
||||
* Created by Hippo on 2018/3/23.
|
||||
*/
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.hippo.ehviewer.EhApplication;
|
||||
import com.hippo.ehviewer.Hosts;
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Dns;
|
||||
|
||||
public class EhDns implements Dns {
|
||||
|
||||
private static final Map<String, List<InetAddress>> builtInHosts;
|
||||
|
||||
static {
|
||||
Map<String, List<InetAddress>> map = new HashMap<>();
|
||||
put(map, "e-hentai.org", "104.20.134.21", "104.20.135.21", "172.67.0.127");
|
||||
put(map, "exhentai.org", "178.175.128.252", "178.175.129.252", "178.175.129.254", "178.175.128.254",
|
||||
"178.175.132.20", "178.175.132.22");
|
||||
put(map, "repo.e-hentai.org", "94.100.28.57", "94.100.29.73");
|
||||
put(map, "forums.e-hentai.org", "94.100.18.243");
|
||||
put(map, "ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt0.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt1.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt2.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "gt3.ehgt.org", "37.48.89.44", "81.171.10.48", "178.162.139.24", "178.162.140.212"
|
||||
, "2001:1af8:4700:a062:8::47de", "2001:1af8:4700:a062:9::47de", "2001:1af8:4700:a0c9:4::47de", "2001:1af8:4700:a0c9:3::47de");
|
||||
put(map, "ul.ehgt.org", "94.100.24.82", "94.100.24.72");
|
||||
put(map, "raw.githubusercontent.com", "151.101.0.133", "151.101.64.133", "151.101.128.133", "151.101.192.133");
|
||||
builtInHosts = map;
|
||||
}
|
||||
|
||||
private final Hosts hosts;
|
||||
|
||||
public EhDns(Context context) {
|
||||
hosts = EhApplication.getHosts(context);
|
||||
}
|
||||
|
||||
private static void put(Map<String, List<InetAddress>> map, String host, String... ips) {
|
||||
List<InetAddress> addresses = new ArrayList<>();
|
||||
for (String ip : ips) {
|
||||
addresses.add(Hosts.toInetAddress(host, ip));
|
||||
}
|
||||
map.put(host, addresses);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException {
|
||||
List<InetAddress> inetAddresses = hosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
if (Settings.getBuiltInHosts()) {
|
||||
inetAddresses = builtInHosts.get(hostname);
|
||||
if (inetAddresses != null) {
|
||||
return inetAddresses;
|
||||
}
|
||||
}
|
||||
return SYSTEM.lookup(hostname);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright 2018 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 com.hippo.ehviewer.EhApplication
|
||||
import com.hippo.ehviewer.Hosts
|
||||
import com.hippo.ehviewer.Settings
|
||||
import okhttp3.Dns
|
||||
import java.net.InetAddress
|
||||
import java.net.UnknownHostException
|
||||
|
||||
class EhDns(context: Context) : Dns {
|
||||
private val hosts = EhApplication.getHosts(context)
|
||||
private val builtInHosts: MutableMap<String, List<InetAddress>> = mutableMapOf()
|
||||
|
||||
init {
|
||||
/* Pair(ip: String!, blockedByCCP: Boolean!) */
|
||||
val ehgtHosts = arrayOf(
|
||||
Pair("37.48.89.44", false),
|
||||
Pair("81.171.10.48", false),
|
||||
Pair("178.162.139.24", false),
|
||||
Pair("178.162.140.212", false),
|
||||
Pair("2001:1af8:4700:a062:8::47de", false),
|
||||
Pair("2001:1af8:4700:a062:9::47de", true),
|
||||
Pair("2001:1af8:4700:a0c9:4::47de", false),
|
||||
Pair("2001:1af8:4700:a0c9:3::47de", true),
|
||||
)
|
||||
|
||||
put(
|
||||
builtInHosts,
|
||||
"e-hentai.org",
|
||||
Pair("104.20.134.21", true),
|
||||
Pair("104.20.135.21", false),
|
||||
Pair("172.67.0.127", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"exhentai.org",
|
||||
Pair("178.175.128.252", false),
|
||||
Pair("178.175.129.252", false),
|
||||
Pair("178.175.129.254", false),
|
||||
Pair("178.175.128.254", false),
|
||||
Pair("178.175.132.20", false),
|
||||
Pair("178.175.132.22", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"repo.e-hentai.org",
|
||||
Pair("94.100.28.57", true),
|
||||
Pair("94.100.29.73", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"forums.e-hentai.org",
|
||||
Pair("94.100.18.243", false),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt0.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt1.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt2.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"gt3.ehgt.org",
|
||||
*ehgtHosts
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"ul.ehgt.org",
|
||||
Pair("94.100.24.82", true),
|
||||
Pair("94.100.24.72", true),
|
||||
)
|
||||
put(
|
||||
builtInHosts,
|
||||
"raw.githubusercontent.com",
|
||||
Pair("151.101.0.133", false),
|
||||
Pair("151.101.64.133", false),
|
||||
Pair("151.101.128.133", false),
|
||||
Pair("151.101.192.133", false),
|
||||
)
|
||||
}
|
||||
|
||||
private fun put(
|
||||
map: MutableMap<String, List<InetAddress>>,
|
||||
host: String,
|
||||
vararg ips: Pair<String, Boolean>
|
||||
) {
|
||||
map[host] = ips.mapNotNull { pair ->
|
||||
Hosts.toInetAddress(host, pair.first).takeUnless { Settings.getDF() && pair.second }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(UnknownHostException::class)
|
||||
override fun lookup(hostname: String): List<InetAddress> {
|
||||
return hosts[hostname] ?: builtInHosts[hostname].takeIf { Settings.getBuiltInHosts() }
|
||||
?: Dns.SYSTEM.lookup(hostname)
|
||||
}
|
||||
}
|
||||
@@ -1,79 +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.client;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.hippo.ehviewer.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class EhSSLSocketFactory extends SSLSocketFactory {
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return ((SSLSocketFactory) getDefault()).getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
SSLSocketFactory defaultFactory = (SSLSocketFactory) getDefault();
|
||||
if (!Settings.getDF()) {
|
||||
return defaultFactory.createSocket(s, host, port, autoClose);
|
||||
}
|
||||
String address = s.getInetAddress().getHostAddress();
|
||||
SSLSocket socket = (SSLSocket) defaultFactory.createSocket(s, address, port, autoClose);
|
||||
SSLSession sslSession = socket.getSession();
|
||||
Log.d("EhSSLSocketFactory", "Host: " + host + " Address: " + address + " Protocol:" + sslSession.getProtocol() + " CipherSuite:" + sslSession.getCipherSuite());
|
||||
socket.setEnabledProtocols(socket.getSupportedProtocols());
|
||||
return socket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return getDefault().createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return getDefault().createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return getDefault().createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.client
|
||||
|
||||
import android.util.Log
|
||||
import java.net.InetAddress
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
|
||||
class EhSSLSocketFactory : SSLSocketFactory() {
|
||||
private val sslSocketFactory = getDefault() as SSLSocketFactory
|
||||
override fun getDefaultCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.defaultCipherSuites
|
||||
}
|
||||
|
||||
override fun getSupportedCipherSuites(): Array<String> {
|
||||
return sslSocketFactory.supportedCipherSuites
|
||||
}
|
||||
|
||||
override fun createSocket(s: Socket, host: String, port: Int, autoClose: Boolean): Socket {
|
||||
val address = s.inetAddress.hostAddress
|
||||
val socket = sslSocketFactory.createSocket(s, address, port, autoClose) as SSLSocket
|
||||
val sslSession = socket.session
|
||||
Log.d(
|
||||
"EhSSLSocketFactory",
|
||||
"Host: " + host + " Address: " + address + " Protocol:" + sslSession.protocol + " CipherSuite:" + sslSession.cipherSuite
|
||||
)
|
||||
return socket
|
||||
}
|
||||
|
||||
override fun createSocket(host: String, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
host: String,
|
||||
port: Int,
|
||||
localHost: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(host, port, localHost, localPort)
|
||||
}
|
||||
|
||||
override fun createSocket(host: InetAddress, port: Int): Socket {
|
||||
return sslSocketFactory.createSocket(host, port)
|
||||
}
|
||||
|
||||
override fun createSocket(
|
||||
address: InetAddress,
|
||||
port: Int,
|
||||
localAddress: InetAddress,
|
||||
localPort: Int
|
||||
): Socket {
|
||||
return sslSocketFactory.createSocket(address, port, localAddress, localPort)
|
||||
}
|
||||
}
|
||||
+10
-17
@@ -15,30 +15,23 @@
|
||||
* 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.client
|
||||
|
||||
package com.hippo.ehviewer.client;
|
||||
import android.annotation.SuppressLint
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class EhX509TrustManager implements X509TrustManager {
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
class EhX509TrustManager : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate?> {
|
||||
return arrayOfNulls(0)
|
||||
}
|
||||
}
|
||||
@@ -399,6 +399,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
@@ -416,6 +419,9 @@ open class ListUrlBuilder : Cloneable, Parcelable {
|
||||
if (mNextGid != 0) {
|
||||
sb.append("?next=").append(mNextGid)
|
||||
}
|
||||
if (mJumpTo != null) {
|
||||
sb.append("&seek=").append(mJumpTo)
|
||||
}
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,12 @@ import org.jsoup.select.Elements;
|
||||
import org.jsoup.select.NodeTraversor;
|
||||
import org.jsoup.select.NodeVisitor;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -64,9 +62,6 @@ public class GalleryDetailParser {
|
||||
private static final Pattern PATTERN_TORRENT = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download[^<]+(\\d+)[^<]+</a");
|
||||
private static final Pattern PATTERN_ARCHIVE = Pattern.compile("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>");
|
||||
private static final Pattern PATTERN_COVER = Pattern.compile("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)");
|
||||
private static final Pattern PATTERN_TAG_GROUP = Pattern.compile("<tr><td[^<>]+>([\\w\\s]+):</td><td>(?:<div[^<>]+><a[^<>]+>[\\w\\s]+</a></div>)+</td></tr>");
|
||||
private static final Pattern PATTERN_TAG = Pattern.compile("<div[^<>]+><a[^<>]+>([\\w\\s]+)</a></div>");
|
||||
private static final Pattern PATTERN_COMMENT = Pattern.compile("<div class=\"c3\">Posted on ([^<>]+) by: <a[^<>]+>([^<>]+)</a>.+?<div class=\"c6\"[^>]*>(.+?)</div><div class=\"c[78]\"");
|
||||
private static final Pattern PATTERN_PAGES = Pattern.compile("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>");
|
||||
private static final Pattern PATTERN_PREVIEW_PAGES = Pattern.compile("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?>(?:</a>)?</td>");
|
||||
private static final Pattern PATTERN_NORMAL_PREVIEW = Pattern.compile("<div class=\"gdtm\"[^<>]*><div[^<>]*width:(\\d+)[^<>]*height:(\\d+)[^<>]*\\((.+?)\\)[^<>]*-(\\d+)px[^<>]*><a[^<>]*href=\"(.+?)\"[^<>]*><img alt=\"([\\d,]+)\"");
|
||||
@@ -76,16 +71,13 @@ public class GalleryDetailParser {
|
||||
private static final GalleryTagGroup[] EMPTY_GALLERY_TAG_GROUP_ARRAY = new GalleryTagGroup[0];
|
||||
private static final GalleryCommentList EMPTY_GALLERY_COMMENT_ARRAY = new GalleryCommentList(new GalleryComment[0], false);
|
||||
|
||||
private static final DateFormat WEB_COMMENT_DATE_FORMAT = new SimpleDateFormat("dd MMMMM yyyy, HH:mm", Locale.US);
|
||||
private static final DateTimeFormatter WEB_COMMENT_DATE_FORMAT = DateTimeFormatter
|
||||
.ofPattern("dd MMMM yyyy, HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||
private static final String OFFENSIVE_STRING =
|
||||
"<p>(And if you choose to ignore this warning, you lose all rights to complain about it in the future.)</p>";
|
||||
private static final String PINING_STRING =
|
||||
"<p>This gallery is pining for the fjords.</p>";
|
||||
|
||||
static {
|
||||
WEB_COMMENT_DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
|
||||
public static GalleryDetail parse(String body) throws EhException {
|
||||
if (body.contains(OFFENSIVE_STRING)) {
|
||||
throw new OffensiveException();
|
||||
@@ -196,14 +188,13 @@ public class GalleryDetailParser {
|
||||
gd.posted = "";
|
||||
gd.parent = "";
|
||||
gd.visible = "";
|
||||
gd.visible = "";
|
||||
gd.size = "";
|
||||
gd.pages = 0;
|
||||
gd.favoriteCount = 0;
|
||||
try {
|
||||
Elements es = gdd.child(0).child(0).children();
|
||||
for (int i = 0, n = es.size(); i < n; i++) {
|
||||
parseDetailInfo(gd, es.get(i), body);
|
||||
parseDetailInfo(gd, es.get(i));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
@@ -291,7 +282,7 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseDetailInfo(GalleryDetail gd, Element e, String body) {
|
||||
private static void parseDetailInfo(GalleryDetail gd, Element e) {
|
||||
Elements es = e.children();
|
||||
if (es.size() < 2) {
|
||||
return;
|
||||
@@ -321,20 +312,16 @@ public class GalleryDetailParser {
|
||||
}
|
||||
} else if (key.startsWith("Favorited")) {
|
||||
switch (value) {
|
||||
case "Never":
|
||||
gd.favoriteCount = 0;
|
||||
break;
|
||||
case "Once":
|
||||
gd.favoriteCount = 1;
|
||||
break;
|
||||
default:
|
||||
case "Never" -> gd.favoriteCount = 0;
|
||||
case "Once" -> gd.favoriteCount = 1;
|
||||
default -> {
|
||||
int index = value.indexOf(' ');
|
||||
if (index == -1) {
|
||||
gd.favoriteCount = 0;
|
||||
} else {
|
||||
gd.favoriteCount = NumberUtils.parseIntSafely(value.substring(0, index), 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,6 +362,7 @@ public class GalleryDetailParser {
|
||||
public static GalleryTagGroup[] parseTagGroups(Document document) {
|
||||
try {
|
||||
Element taglist = document.getElementById("taglist");
|
||||
assert taglist != null;
|
||||
Elements tagGroups = taglist.child(0).child(0).children();
|
||||
return parseTagGroups(tagGroups);
|
||||
} catch (Throwable e) {
|
||||
@@ -394,7 +382,7 @@ public class GalleryDetailParser {
|
||||
list.add(group);
|
||||
}
|
||||
}
|
||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
||||
return list.toArray(new GalleryTagGroup[0]);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
@@ -402,31 +390,6 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse tag groups with regular expressions
|
||||
*/
|
||||
@NonNull
|
||||
private static GalleryTagGroup[] parseTagGroups(String body) throws EhException {
|
||||
List<GalleryTagGroup> list = new LinkedList<>();
|
||||
|
||||
Matcher m = PATTERN_TAG_GROUP.matcher(body);
|
||||
while (m.find()) {
|
||||
GalleryTagGroup tagGroup = new GalleryTagGroup();
|
||||
tagGroup.groupName = ParserUtils.trim(m.group(1));
|
||||
parseGroup(tagGroup, m.group(0));
|
||||
list.add(tagGroup);
|
||||
}
|
||||
|
||||
return list.toArray(new GalleryTagGroup[list.size()]);
|
||||
}
|
||||
|
||||
private static void parseGroup(GalleryTagGroup tagGroup, String body) {
|
||||
Matcher m = PATTERN_TAG.matcher(body);
|
||||
while (m.find()) {
|
||||
tagGroup.addTag(ParserUtils.trim(m.group(1)));
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static GalleryComment parseComment(Element element) {
|
||||
@@ -444,17 +407,15 @@ public class GalleryDetailParser {
|
||||
}
|
||||
for (Element e : c4.children()) {
|
||||
switch (e.text()) {
|
||||
case "Vote+":
|
||||
case "Vote+" -> {
|
||||
comment.voteUpAble = true;
|
||||
comment.voteUpEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
break;
|
||||
case "Vote-":
|
||||
}
|
||||
case "Vote-" -> {
|
||||
comment.voteDownAble = true;
|
||||
comment.voteDownEd = !StringUtils.trim(e.attr("style")).isEmpty();
|
||||
break;
|
||||
case "Edit":
|
||||
comment.editable = true;
|
||||
break;
|
||||
}
|
||||
case "Edit" -> comment.editable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -475,7 +436,7 @@ public class GalleryDetailParser {
|
||||
Element c3 = JsoupUtils.getElementByClass(element, "c3");
|
||||
String temp = c3.ownText();
|
||||
temp = temp.substring("Posted on ".length(), temp.length() - " by:".length());
|
||||
comment.time = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
||||
comment.time = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
// user
|
||||
comment.user = c3.child(0).text();
|
||||
// comment
|
||||
@@ -485,7 +446,7 @@ public class GalleryDetailParser {
|
||||
if (c8 != null) {
|
||||
Element e = c8.children().first();
|
||||
if (e != null) {
|
||||
comment.lastEdited = WEB_COMMENT_DATE_FORMAT.parse(temp).getTime();
|
||||
comment.lastEdited = Instant.from(WEB_COMMENT_DATE_FORMAT.parse(temp)).toEpochMilli();
|
||||
}
|
||||
}
|
||||
return comment;
|
||||
@@ -503,6 +464,7 @@ public class GalleryDetailParser {
|
||||
public static GalleryCommentList parseComments(Document document) {
|
||||
try {
|
||||
Element cdiv = document.getElementById("cdiv");
|
||||
assert cdiv != null;
|
||||
Elements c1s = cdiv.getElementsByClass("c1");
|
||||
|
||||
List<GalleryComment> list = new ArrayList<>(c1s.size());
|
||||
@@ -515,20 +477,21 @@ public class GalleryDetailParser {
|
||||
|
||||
Element chd = cdiv.getElementById("chd");
|
||||
MutableBoolean hasMore = new MutableBoolean(false);
|
||||
assert chd != null;
|
||||
NodeTraversor.traverse(new NodeVisitor() {
|
||||
@Override
|
||||
public void head(Node node, int depth) {
|
||||
public void head(@NonNull Node node, int depth) {
|
||||
if (node instanceof Element && ((Element) node).text().equals("click to show all")) {
|
||||
hasMore.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tail(Node node, int depth) {
|
||||
public void tail(@NonNull Node node, int depth) {
|
||||
}
|
||||
}, chd);
|
||||
|
||||
return new GalleryCommentList(list.toArray(new GalleryComment[list.size()]), hasMore.value);
|
||||
return new GalleryCommentList(list.toArray(new GalleryComment[0]), hasMore.value);
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
e.printStackTrace();
|
||||
@@ -536,38 +499,14 @@ public class GalleryDetailParser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse comments with regular expressions
|
||||
*/
|
||||
@NonNull
|
||||
public static GalleryComment[] parseComments(String body) {
|
||||
List<GalleryComment> list = new LinkedList<>();
|
||||
|
||||
Matcher m = PATTERN_COMMENT.matcher(body);
|
||||
while (m.find()) {
|
||||
String webDateString = ParserUtils.trim(m.group(1));
|
||||
Date date;
|
||||
try {
|
||||
date = WEB_COMMENT_DATE_FORMAT.parse(webDateString);
|
||||
} catch (java.text.ParseException e) {
|
||||
date = new Date(0L);
|
||||
}
|
||||
GalleryComment comment = new GalleryComment();
|
||||
comment.time = date.getTime();
|
||||
comment.user = ParserUtils.trim(m.group(2));
|
||||
comment.comment = m.group(3);
|
||||
list.add(comment);
|
||||
}
|
||||
|
||||
return list.toArray(new GalleryComment[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse preview pages with html parser
|
||||
*/
|
||||
public static int parsePreviewPages(Document document, String body) throws ParseException {
|
||||
try {
|
||||
Elements elements = document.getElementsByClass("ptt").first().child(0).child(0).children();
|
||||
Element ptt = document.getElementsByClass("ptt").first();
|
||||
assert ptt != null;
|
||||
Elements elements = ptt.child(0).child(0).children();
|
||||
return Integer.parseInt(elements.get(elements.size() - 2).text());
|
||||
} catch (Throwable e) {
|
||||
ExceptionUtils.throwIfFatal(e);
|
||||
@@ -634,9 +573,10 @@ public class GalleryDetailParser {
|
||||
try {
|
||||
LargePreviewSet largePreviewSet = new LargePreviewSet();
|
||||
Element gdt = d.getElementById("gdt");
|
||||
assert gdt != null;
|
||||
Elements gdtls = gdt.getElementsByClass("gdtl");
|
||||
int n = gdtls.size();
|
||||
if (n <= 0) {
|
||||
if (n == 0) {
|
||||
throw new ParseException("Can't parse large preview", body);
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
|
||||
@@ -19,17 +19,18 @@ package com.hippo.ehviewer.client.parser;
|
||||
import com.hippo.yorozuya.NumberUtils;
|
||||
import com.hippo.yorozuya.StringUtils;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ParserUtils {
|
||||
|
||||
public static final DateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US);
|
||||
public static final DateTimeFormatter formatter = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd HH:mm", Locale.US).withZone(ZoneOffset.UTC);
|
||||
|
||||
public static synchronized String formatDate(long time) {
|
||||
return sDateFormat.format(new Date(time));
|
||||
return formatter.format(Instant.ofEpochMilli(time));
|
||||
}
|
||||
|
||||
public static String trim(String str) {
|
||||
@@ -48,7 +49,4 @@ public class ParserUtils {
|
||||
return NumberUtils.parseLongSafely(trim(str).replace(",", ""), defValue);
|
||||
}
|
||||
|
||||
public static float parseFloat(String str, float defValue) {
|
||||
return NumberUtils.parseFloatSafely(trim(str).replace(",", ""), defValue);
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,9 @@
|
||||
package com.hippo.ehviewer.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -36,7 +36,6 @@ import com.hippo.yorozuya.FileUtils;
|
||||
import com.hippo.yorozuya.thread.PVLock;
|
||||
import com.hippo.yorozuya.thread.PriorityThread;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
@@ -335,10 +334,15 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
|
||||
|
||||
Image image = null;
|
||||
if (buffer != null) {
|
||||
image = Image.decode(buffer, false, () -> {
|
||||
try {
|
||||
image = Image.decode(buffer, false, () -> {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
archiveAccessor.releaseByteBuffer(buffer);
|
||||
return null;
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
|
||||
@@ -261,11 +261,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
|
||||
is = files[index].openInputStream();
|
||||
Image image = Image.decode((FileInputStream) is, false);
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
if (image != null) {
|
||||
notifyPageSucceed(index, image);
|
||||
} else {
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_decoding_failed));
|
||||
}
|
||||
notifyPageSucceed(index, image);
|
||||
} catch (IOException e) {
|
||||
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
|
||||
notifyPageFailed(index, GetText.getString(R.string.error_reading_failed));
|
||||
|
||||
@@ -28,7 +28,6 @@ import com.hippo.ehviewer.client.EhCookieStore;
|
||||
import com.hippo.ehviewer.client.EhUrl;
|
||||
import com.hippo.ehviewer.ui.SettingsActivity;
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene;
|
||||
import com.hippo.util.WebViewUtil;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
@@ -59,10 +58,6 @@ public class SignInRequiredPreference extends Preference implements Preference.O
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
if (!WebViewUtil.available(getContext())) {
|
||||
mActivity.showTip(R.string.error_no_webview, BaseScene.LENGTH_SHORT);
|
||||
return true;
|
||||
}
|
||||
EhCookieStore store = EhApplication.getEhCookieStore(getContext());
|
||||
HttpUrl e = HttpUrl.get(EhUrl.HOST_E);
|
||||
HttpUrl ex = HttpUrl.get(EhUrl.HOST_EX);
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.spider;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
@@ -1679,6 +1680,8 @@ public final class SpiderQueen implements Runnable {
|
||||
if (is != null) {
|
||||
try {
|
||||
image = Image.decode(((FileInputStream) is), false);
|
||||
} catch (ImageDecoder.DecodeException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.content.res.Resources.Theme
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
@@ -71,23 +70,15 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
WindowInsetsHelper.LISTENER
|
||||
)
|
||||
super.onCreate(savedInstanceState)
|
||||
window.statusBarColor = Color.TRANSPARENT
|
||||
window.decorView.post {
|
||||
val rootWindowInsets = window.decorView.rootWindowInsets
|
||||
if (rootWindowInsets != null && rootWindowInsets.systemWindowInsetBottom >= Resources.getSystem().displayMetrics.density * 40) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
||||
}
|
||||
window.navigationBarDividerColor = getColor(R.color.navigation_bar_divider)
|
||||
window.navigationBarColor =
|
||||
theme.resolveColor(android.R.attr.navigationBarColor) and 0x00ffffff or -0x20000000
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = false
|
||||
}
|
||||
} else {
|
||||
window.navigationBarColor = Color.TRANSPARENT
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
window.isNavigationBarContrastEnforced = true
|
||||
}
|
||||
}
|
||||
}
|
||||
(application as EhApplication).registerActivity(this)
|
||||
|
||||
@@ -46,13 +46,6 @@ class GalleryInfoBottomSheet(detail: GalleryDetail) : BottomSheetDialogFragment(
|
||||
): View? {
|
||||
parseDetail()
|
||||
val view = inflater.inflate(R.layout.scene_gallery_info, container, false)
|
||||
dialog?.window?.run {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
attributes.blurBehindRadius = 32
|
||||
}
|
||||
}
|
||||
val recyclerView = view.findViewById(R.id.recycler_view) as RecyclerView
|
||||
val adapter = InfoAdapter()
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
@@ -314,6 +314,11 @@ public class DownloadsScene extends ToolbarScene
|
||||
setLiftOnScrollTargetView(mRecyclerView);
|
||||
mFastScroller = (FastScroller) ViewUtils.$$(content, R.id.fast_scroller);
|
||||
mFabLayout = (FabLayout) ViewUtils.$$(view, R.id.fab_layout);
|
||||
|
||||
// Workaround
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
container.addView(mFabLayout);
|
||||
|
||||
mTip = (TextView) ViewUtils.$$(view, R.id.tip);
|
||||
mViewTransition = new ViewTransition(content, mTip);
|
||||
|
||||
@@ -389,6 +394,7 @@ public class DownloadsScene extends ToolbarScene
|
||||
}
|
||||
if (null != mFabLayout) {
|
||||
removeAboveSnackView(mFabLayout);
|
||||
((ViewGroup)mFabLayout.getParent()).removeView(mFabLayout);
|
||||
mFabLayout = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -637,8 +637,12 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
mNoTags = (TextView) ViewUtils.$$(mTags, R.id.no_tags);
|
||||
|
||||
mComments = (LinearLayout) ViewUtils.$$(belowHeader, R.id.comments);
|
||||
mCommentsText = (TextView) ViewUtils.$$(mComments, R.id.comments_text);
|
||||
mComments.setOnClickListener(this);
|
||||
if (Settings.getShowComments()) {
|
||||
mCommentsText = (TextView) ViewUtils.$$(mComments, R.id.comments_text);
|
||||
mComments.setOnClickListener(this);
|
||||
} else {
|
||||
mComments.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
mPreviews = ViewUtils.$$(belowHeader, R.id.previews);
|
||||
mGridLayout = (SimpleGridAutoSpanLayout) ViewUtils.$$(mPreviews, R.id.grid_layout);
|
||||
@@ -1009,6 +1013,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
private void bindPreviews(GalleryDetail gd) {
|
||||
LayoutInflater inflater = getLayoutInflater();
|
||||
Resources resources = getResourcesOrNull();
|
||||
int previewNum = Settings.getPreviewNum();
|
||||
if (null == inflater || null == resources || null == mGridLayout || null == mPreviewText) {
|
||||
return;
|
||||
}
|
||||
@@ -1018,7 +1023,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
if (gd.previewPages <= 0 || previewSet == null || previewSet.size() == 0) {
|
||||
mPreviewText.setText(R.string.no_previews);
|
||||
return;
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= 60) {
|
||||
} else if (gd.previewPages == 1 && previewSet.size() <= previewNum) {
|
||||
mPreviewText.setText(R.string.no_more_previews);
|
||||
} else {
|
||||
mPreviewText.setText(R.string.more_previews);
|
||||
@@ -1027,7 +1032,7 @@ public class GalleryDetailScene extends CollapsingToolbarScene implements View.O
|
||||
int columnWidth = resources.getDimensionPixelOffset(Settings.getThumbSizeResId());
|
||||
mGridLayout.setColumnSize(columnWidth);
|
||||
mGridLayout.setStrategy(SimpleGridAutoSpanLayout.STRATEGY_SUITABLE_SIZE);
|
||||
int size = Math.min(60, previewSet.size());
|
||||
int size = Math.min(previewNum, previewSet.size());
|
||||
for (int i = 0; i < size; i++) {
|
||||
View view = inflater.inflate(R.layout.item_gallery_preview, mGridLayout, false);
|
||||
mGridLayout.addView(view);
|
||||
|
||||
@@ -1765,23 +1765,24 @@ public final class GalleryListScene extends BaseScene
|
||||
return;
|
||||
}
|
||||
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.setNextGid(page);
|
||||
if (jumpTo != null) {
|
||||
pgCounter = Integer.parseInt(jumpTo);
|
||||
mUrlBuilder.setNextGid(pgCounter);
|
||||
nextGid = pgCounter;
|
||||
} else {
|
||||
nextGid = page;
|
||||
}
|
||||
} else {
|
||||
int prevGid = 0, nextGid = 0;
|
||||
if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
} else if (jumpTo != null) {
|
||||
nextGid = minGid;
|
||||
} else if (page != 0) {
|
||||
if (page >= mHelper.getPageForTop())
|
||||
nextGid = minGid;
|
||||
else
|
||||
prevGid = maxGid;
|
||||
}
|
||||
mUrlBuilder.setPrevGid(prevGid);
|
||||
mUrlBuilder.setNextGid(nextGid);
|
||||
|
||||
mUrlBuilder.setJumpTo(jumpTo);
|
||||
jumpTo = null;
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.hippo.ehviewer.ui.MainActivity;
|
||||
import com.hippo.scene.Announcer;
|
||||
import com.hippo.scene.SceneFragment;
|
||||
import com.hippo.util.ExceptionUtils;
|
||||
import com.hippo.util.WebViewUtil;
|
||||
import com.hippo.yorozuya.AssertUtils;
|
||||
import com.hippo.yorozuya.IntIdGenerator;
|
||||
import com.hippo.yorozuya.ViewUtils;
|
||||
@@ -226,10 +225,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
|
||||
} else if (mSignIn == v) {
|
||||
signIn();
|
||||
} else if (mSignInViaWebView == v) {
|
||||
if (!WebViewUtil.available(getContext())) {
|
||||
showTip(R.string.error_no_webview, BaseScene.LENGTH_SHORT);
|
||||
return;
|
||||
}
|
||||
startScene(new Announcer(WebViewSignInScene.class).setRequestCode(this, REQUEST_CODE_WEBVIEW));
|
||||
} else if (mSignInViaCookies == v) {
|
||||
startScene(new Announcer(CookieSignInScene.class).setRequestCode(this, REQUEST_CODE_COOKIE));
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
package com.hippo.ehviewer.widget
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.icu.util.Calendar
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import com.hippo.ehviewer.EhApplication
|
||||
@@ -25,7 +23,12 @@ import com.hippo.ehviewer.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.widget.ContentLayout.ContentHelper
|
||||
import com.hippo.yorozuya.IntIdGenerator
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
@JvmField
|
||||
@@ -64,8 +67,8 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
info?.let {
|
||||
if (maxGid == -1) maxGid = info.gid.toInt()
|
||||
if (minGid == -1) minGid = info.gid.toInt()
|
||||
maxGid = Math.max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = Math.min(minGid.toLong(), info.gid).toInt()
|
||||
maxGid = max(maxGid.toLong(), info.gid).toInt()
|
||||
minGid = min(minGid.toLong(), info.gid).toInt()
|
||||
map[info.gid] = info
|
||||
}
|
||||
}
|
||||
@@ -121,11 +124,9 @@ abstract class GalleryInfoContentHelper : ContentHelper<GalleryInfo?>() {
|
||||
}
|
||||
|
||||
fun goTo(time: Long) {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = time
|
||||
val date = calendar.time
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
jumpTo = format.format(date)
|
||||
val formatter = DateTimeFormatter
|
||||
.ofPattern("yyyy-MM-dd", Locale.US).withZone(ZoneOffset.UTC)
|
||||
jumpTo = formatter.format(Instant.ofEpochMilli(time))
|
||||
doGoToPage(Int.MAX_VALUE / 2)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
@@ -329,7 +330,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
|
||||
inner class IdentifiedChip @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : Chip(context, attrs, R.attr.filterChipStyle) {
|
||||
) : Chip(ContextThemeWrapper(context, com.google.android.material.R.style.Widget_Material3_Chip_Filter), attrs) {
|
||||
var idt = 0
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_DEFAULT
|
||||
import android.graphics.ImageDecoder.ALLOCATOR_SOFTWARE
|
||||
import android.graphics.ImageDecoder.DecodeException
|
||||
import android.graphics.ImageDecoder.ImageInfo
|
||||
import android.graphics.ImageDecoder.Source
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.AnimatedImageDrawable
|
||||
@@ -32,6 +35,8 @@ import androidx.core.graphics.drawable.toDrawable
|
||||
import java.io.FileInputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import kotlin.jvm.Throws
|
||||
import kotlin.math.max
|
||||
|
||||
class Image private constructor(
|
||||
source: Source?, drawable: Drawable? = null,
|
||||
@@ -44,11 +49,22 @@ class Image private constructor(
|
||||
init {
|
||||
mObtainedDrawable = null
|
||||
source?.let {
|
||||
mObtainedDrawable = ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, _, _ ->
|
||||
decoder.allocator = if (hardware) ALLOCATOR_DEFAULT else ALLOCATOR_SOFTWARE
|
||||
// Sadly we must use software memory since we need copy it to tile buffer, fuck glgallery
|
||||
// Idk it will cause how much performance regression
|
||||
} // Should we lazy decode it?
|
||||
mObtainedDrawable =
|
||||
ImageDecoder.decodeDrawable(source) { decoder: ImageDecoder, info: ImageInfo, src: Source ->
|
||||
decoder.allocator = if (hardware) ALLOCATOR_DEFAULT else ALLOCATOR_SOFTWARE
|
||||
// Sadly we must use software memory since we need copy it to tile buffer, fuck glgallery
|
||||
// Idk it will cause how much performance regression
|
||||
|
||||
decoder.setTargetSampleSize(
|
||||
max(
|
||||
max(
|
||||
info.size.width / (2 * screenWidth),
|
||||
info.size.height / (2 * screenHeight)
|
||||
), 1
|
||||
)
|
||||
)
|
||||
// Don't
|
||||
} // Should we lazy decode it?
|
||||
}
|
||||
if (mObtainedDrawable == null) {
|
||||
mObtainedDrawable = drawable!!
|
||||
@@ -112,12 +128,13 @@ class Image private constructor(
|
||||
|
||||
fun texImage(init: Boolean, offsetX: Int, offsetY: Int, width: Int, height: Int) {
|
||||
check(!hardware) { "Hardware buffer cannot be used in glgallery" }
|
||||
val bitmap: Bitmap = if (animated) {
|
||||
val bitmap: Bitmap? = if (animated) {
|
||||
updateBitmap()
|
||||
mBitmap!!
|
||||
mBitmap
|
||||
} else {
|
||||
(mObtainedDrawable as BitmapDrawable).bitmap
|
||||
(mObtainedDrawable as BitmapDrawable?)?.bitmap
|
||||
}
|
||||
bitmap ?: return
|
||||
nativeTexImage(
|
||||
bitmap,
|
||||
init,
|
||||
@@ -147,6 +164,16 @@ class Image private constructor(
|
||||
}
|
||||
|
||||
companion object {
|
||||
var screenWidth: Int = 0
|
||||
var screenHeight: Int = 0
|
||||
|
||||
@JvmStatic
|
||||
fun initialize(context: Context) {
|
||||
screenWidth = context.resources.displayMetrics.widthPixels
|
||||
screenHeight = context.resources.displayMetrics.heightPixels
|
||||
}
|
||||
|
||||
@Throws(DecodeException::class)
|
||||
@JvmStatic
|
||||
fun decode(stream: FileInputStream, hardware: Boolean = true): Image {
|
||||
val src = ImageDecoder.createSource(
|
||||
@@ -158,6 +185,7 @@ class Image private constructor(
|
||||
return Image(src, hardware = hardware)
|
||||
}
|
||||
|
||||
@Throws(DecodeException::class)
|
||||
@JvmStatic
|
||||
fun decode(buffer: ByteBuffer, hardware: Boolean = true, release: () -> Unit? = {}): Image {
|
||||
val src = ImageDecoder.createSource(buffer)
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
*/
|
||||
package com.hippo.image
|
||||
|
||||
import android.graphics.ImageDecoder
|
||||
import android.graphics.drawable.Drawable
|
||||
import java.io.FileInputStream
|
||||
import kotlin.jvm.Throws
|
||||
|
||||
class ImageBitmap private constructor(image: Image) {
|
||||
private var mImage: Image? = image
|
||||
@@ -55,6 +57,7 @@ class ImageBitmap private constructor(image: Image) {
|
||||
get() = mImage!!.height
|
||||
|
||||
companion object {
|
||||
@Throws(ImageDecoder.DecodeException::class)
|
||||
@JvmStatic
|
||||
fun decode(stream: FileInputStream): ImageBitmap {
|
||||
val image = Image.decode(stream)
|
||||
|
||||
@@ -154,6 +154,7 @@ public abstract class StageActivity extends EhActivity {
|
||||
mDelaySceneTagList.addAll(list);
|
||||
}
|
||||
mIdGenerator.lazySet(savedInstanceState.getInt(KEY_NEXT_ID));
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
if (mStageId == IntIdGenerator.INVALID_ID) {
|
||||
@@ -561,12 +562,8 @@ public abstract class StageActivity extends EhActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
fragmentManager.beginTransaction().detach(fragment).commitAllowingStateLoss();
|
||||
fragmentManager.beginTransaction().attach(fragment).commitAllowingStateLoss();
|
||||
} else {
|
||||
fragmentManager.beginTransaction().detach(fragment).attach(fragment).commitAllowingStateLoss();
|
||||
}
|
||||
fragmentManager.beginTransaction().detach(fragment).commitAllowingStateLoss();
|
||||
fragmentManager.beginTransaction().attach(fragment).commitAllowingStateLoss();
|
||||
updateBackPressCallBackStatus();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,18 +16,18 @@
|
||||
|
||||
package com.hippo.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.time.Instant;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Locale;
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
public final class ReadableTime {
|
||||
|
||||
public static final long SECOND_MILLIS = 1000;
|
||||
@@ -51,38 +51,19 @@ public final class ReadableTime {
|
||||
R.plurals.minute,
|
||||
R.plurals.second
|
||||
};
|
||||
private static final Calendar sCalendar = Calendar.getInstance();
|
||||
private static final Object sCalendarLock = new Object();
|
||||
private static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR = new SimpleDateFormat("MMM d");
|
||||
private static final SimpleDateFormat DATE_FORMAT_WITH_YEAR = new SimpleDateFormat("MMM d, yyyy");
|
||||
private static final SimpleDateFormat DATE_FORMAT_WITHOUT_YEAR_ZH = new SimpleDateFormat("M月d日");
|
||||
private static final SimpleDateFormat DATE_FORMAT_WITH_YEAR_ZH = new SimpleDateFormat("yyyy年M月d日");
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yy-MM-dd HH:mm");
|
||||
private static final Object sDateFormatLock1 = new Object();
|
||||
private static final SimpleDateFormat FILENAMABLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
|
||||
private static final Object sDateFormatLock2 = new Object();
|
||||
private static final DateTimeFormatter DATE_FORMAT_WITHOUT_YEAR = DateTimeFormatter.ofPattern("MMM d");
|
||||
private static final DateTimeFormatter DATE_FORMAT_WITH_YEAR = DateTimeFormatter.ofPattern("MMM d, yyyy");
|
||||
private static final DateTimeFormatter DATE_FORMAT_WITHOUT_YEAR_ZH = DateTimeFormatter.ofPattern("M月d日");
|
||||
private static final DateTimeFormatter DATE_FORMAT_WITH_YEAR_ZH = DateTimeFormatter.ofPattern("yyyy年M月d日");
|
||||
private static final DateTimeFormatter FILENAMABLE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-SSS");
|
||||
private static final Object sDateFormatLock = new Object();
|
||||
private static Resources sResources;
|
||||
|
||||
public static void initialize(Context context) {
|
||||
sResources = context.getApplicationContext().getResources();
|
||||
}
|
||||
|
||||
/*
|
||||
public static String getDisplayTime(long time) {
|
||||
if (Settings.getPrettyTime()) {
|
||||
return getTimeAgo(time);
|
||||
} else {
|
||||
return getPlainTime(time);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public static String getPlainTime(long time) {
|
||||
synchronized (sDateFormatLock1) {
|
||||
return DATE_FORMAT.format(new Date(time));
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTimeAgo(long time) {
|
||||
Resources resources = sResources;
|
||||
|
||||
@@ -111,49 +92,23 @@ public final class ReadableTime {
|
||||
return resources.getString(R.string.some_days_ago, days);
|
||||
} else {
|
||||
synchronized (sCalendarLock) {
|
||||
Date nowDate = new Date(now);
|
||||
Date timeDate = new Date(time);
|
||||
sCalendar.setTime(nowDate);
|
||||
int nowYear = sCalendar.get(Calendar.YEAR);
|
||||
sCalendar.setTime(timeDate);
|
||||
int timeYear = sCalendar.get(Calendar.YEAR);
|
||||
OffsetDateTime nowDate = Instant.ofEpochMilli(now).atOffset(ZoneOffset.UTC);
|
||||
OffsetDateTime timeDate = Instant.ofEpochMilli(time).atOffset(ZoneOffset.UTC);
|
||||
int nowYear = nowDate.getYear();
|
||||
int timeYear = timeDate.getYear();
|
||||
boolean isZh = Locale.getDefault().getLanguage().equals("zh");
|
||||
|
||||
if (nowYear == timeYear) {
|
||||
return (isZh ? DATE_FORMAT_WITHOUT_YEAR_ZH : DATE_FORMAT_WITHOUT_YEAR).format(timeDate);
|
||||
return (isZh ? DATE_FORMAT_WITHOUT_YEAR_ZH : DATE_FORMAT_WITHOUT_YEAR)
|
||||
.format(timeDate.toInstant().atOffset(ZoneOffset.UTC));
|
||||
} else {
|
||||
return (isZh ? DATE_FORMAT_WITH_YEAR_ZH : DATE_FORMAT_WITH_YEAR).format(timeDate);
|
||||
return (isZh ? DATE_FORMAT_WITH_YEAR_ZH : DATE_FORMAT_WITH_YEAR)
|
||||
.format(timeDate.toInstant().atOffset(ZoneOffset.UTC));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getTimeInterval(long time) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Resources resources = sResources;
|
||||
|
||||
long leftover = time;
|
||||
boolean start = false;
|
||||
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
long multiple = MULTIPLES[i];
|
||||
long quotient = leftover / multiple;
|
||||
long remainder = leftover % multiple;
|
||||
if (start || quotient != 0 || i == SIZE - 1) {
|
||||
if (start) {
|
||||
sb.append(" ");
|
||||
}
|
||||
sb.append(quotient)
|
||||
.append(" ")
|
||||
.append(resources.getQuantityString(UNITS[i], (int) quotient));
|
||||
start = true;
|
||||
}
|
||||
leftover = remainder;
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getShortTimeInterval(long time) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Resources resources = sResources;
|
||||
@@ -173,8 +128,8 @@ public final class ReadableTime {
|
||||
}
|
||||
|
||||
public static String getFilenamableTime(long time) {
|
||||
synchronized (sDateFormatLock2) {
|
||||
return FILENAMABLE_DATE_FORMAT.format(new Date(time));
|
||||
synchronized (sDateFormatLock) {
|
||||
return FILENAMABLE_DATE_FORMAT.format(Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.webkit.WebViewCompat;
|
||||
|
||||
public class WebViewUtil {
|
||||
private static final String TAG = "WebViewUtil";
|
||||
|
||||
public static boolean available(Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
return false;
|
||||
}
|
||||
PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
|
||||
if (webViewPackageInfo == null) {
|
||||
Log.d(TAG, "WebView not available");
|
||||
return false;
|
||||
}
|
||||
Log.d(TAG, "WebView version: " + webViewPackageInfo.versionName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 GeneralPublic 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/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/system_neutral2_800" />
|
||||
<foreground android:drawable="@drawable/ic_play_arrow_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 GeneralPublic 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/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/system_neutral2_800" />
|
||||
<foreground android:drawable="@drawable/ic_pause_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -16,6 +16,6 @@
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/shortcut_bg" />
|
||||
<background android:drawable="@android:color/white" />
|
||||
<foreground android:drawable="@drawable/ic_play_arrow_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -16,6 +16,6 @@
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/shortcut_bg" />
|
||||
<background android:drawable="@android:color/white" />
|
||||
<foreground android:drawable="@drawable/ic_pause_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 GeneralPublic 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/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/system_accent1_50" />
|
||||
<foreground android:drawable="@drawable/ic_play_arrow_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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 GeneralPublic 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/>.
|
||||
-->
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@android:color/system_accent1_50" />
|
||||
<foreground android:drawable="@drawable/ic_pause_108dp" />
|
||||
</adaptive-icon>
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:tint="?drawableColorSecondary"
|
||||
android:tint="?colorTertiary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:tint="?drawableColorSecondary"
|
||||
android:tint="?colorTertiary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:tint="?drawableColorSecondary"
|
||||
android:tint="?colorTertiary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:tint="?drawableColorSecondary"
|
||||
android:tint="?colorTertiary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?drawableColorThemePrimary"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
android:layout_marginTop="@dimen/gallery_widget_margin_v"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<com.hippo.widget.BatteryView xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<com.hippo.widget.BatteryView
|
||||
android:id="@+id/battery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -32,9 +32,7 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_scrollEffect="compress"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
style="@style/Widget.Material3.CardView.Elevated"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="2dp"
|
||||
android:background="?selectableItemBackground"
|
||||
android:layout_margin="4dp"
|
||||
android:checkable="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true">
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/no_download_info" />
|
||||
|
||||
<com.hippo.widget.FabLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<com.hippo.widget.FabLayout
|
||||
android:id="@+id/fab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="@dimen/keyline_margin"
|
||||
android:paddingRight="@dimen/keyline_margin"
|
||||
app:fitsSystemWindowsInsets="top" />
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
|
||||
@@ -28,9 +28,7 @@
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_scrollEffect="compress"
|
||||
app:layout_scrollFlags="scroll|enterAlways|snap" />
|
||||
android:layout_height="?attr/actionBarSize" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -14,19 +14,20 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
|
||||
android:id="@+id/action_add"
|
||||
android:icon="@drawable/v_plus_dark_x24"
|
||||
android:showAsAction="always"
|
||||
app:showAsAction="always"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_default_download_label"
|
||||
android:icon="@drawable/v_download_box_dark_x24"
|
||||
android:showAsAction="ifRoom"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/default_download_label" />
|
||||
|
||||
</menu>
|
||||
@@ -253,7 +253,7 @@
|
||||
<string name="apply_tip">Zum Speichern der Einstellungen auf Häkchen tippen</string>
|
||||
<string name="hosts">Angepasstes hosts.txt</string>
|
||||
<string name="hosts_host">Host</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">Host hinzufügen</string>
|
||||
<string name="add_host_add">Hinzufügen</string>
|
||||
<string name="edit_host">Host bearbeiten</string>
|
||||
@@ -406,7 +406,6 @@
|
||||
<string name="clipboard_gallery_url_snack_action">Gehen</string>
|
||||
<string name="settings_advanced_read_cache_size">Größe des Lesezwischenspeichers</string>
|
||||
<string name="settings_advanced_built_in_hosts_title">Standard-hosts.txt</string>
|
||||
<string name="settings_about_declaration" translatable="false">EhViewer</string>
|
||||
<string name="settings_about_declaration_summary">EhViewer verbindet sich in keiner Weise mit E-Hentai.org.</string>
|
||||
<string name="settings_about_source">Quellcode</string>
|
||||
<string name="settings_about_version">Versionnummer</string>
|
||||
|
||||
@@ -510,7 +510,7 @@
|
||||
<string name="apply_tip">Toca la marca de verificación para guardar los cambios</string>
|
||||
<string name="hosts">hosts.txt personalizado </string>
|
||||
<string name="hosts_host">Anfitrión</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">Añadir una anfitrión</string>
|
||||
<string name="add_host_add">Añadir</string>
|
||||
<string name="edit_host">Anfitrión</string>
|
||||
|
||||
@@ -525,7 +525,7 @@
|
||||
<string name="settings_privacy_secure_summary">Quand cette option est activée, vous ne pouvez prendre aucune capture d\'écran de l\'application, et le systéme n\'affiche pas le contenu d\'écran dans le sélecteur de tâches.\n\nRedémarrage de l\'application est nécessaire pour appliquer le changement</string>
|
||||
<string name="hosts">Personnalisé hosts.txt</string>
|
||||
<string name="hosts_host">Hôte</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">Ajouter hôte</string>
|
||||
<string name="add_host_add">Ajouter</string>
|
||||
<string name="edit_host">Modifier hôte</string>
|
||||
|
||||
@@ -429,7 +429,6 @@
|
||||
<string name="settings_advanced_import_data_successfully">データをインポートしました</string>
|
||||
|
||||
<string name="settings_about">このアプリについて</string>
|
||||
<string name="settings_about_declaration" translatable="false">EhViewer</string>
|
||||
<string name="settings_about_declaration_summary">EhViewerはE-Hentai.orgと一切の関係がございません。</string>
|
||||
<string name="settings_about_author">開発者</string>
|
||||
<string name="settings_about_latest_release">最新バージョン</string>
|
||||
@@ -526,7 +525,7 @@
|
||||
<string name="settings_advanced_proxy_summary_2">%1$s</string>
|
||||
<string name="hosts">カスタムhosts.txt</string>
|
||||
<string name="hosts_host">ホスト</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">ホストを追加</string>
|
||||
<string name="add_host_add">追加</string>
|
||||
<string name="edit_host">ホストを編集</string>
|
||||
|
||||
@@ -285,7 +285,7 @@
|
||||
<!-- Hosts Activity -->
|
||||
<string name="hosts">사용자 지정 hosts.txt</string>
|
||||
<string name="hosts_host">호스트</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">호스트 추가</string>
|
||||
<string name="add_host_add">추가</string>
|
||||
<string name="edit_host">호스트 편집</string>
|
||||
|
||||
@@ -438,7 +438,6 @@
|
||||
<string name="settings_advanced_import_data_successfully">การนำเข้าข้อมูลสำเร็จ</string>
|
||||
|
||||
<string name="settings_about">เกี่ยวกับ</string>
|
||||
<string name="settings_about_declaration" translatable="false">EhViewer</string>
|
||||
<string name="settings_about_declaration_summary">EhViewer ไม่มีความแตกต่างจากตัวเว็บ E-Hentai.org แต่อย่างใด</string>
|
||||
<string name="settings_about_author">นักพัฒนา</string>
|
||||
<string name="settings_about_source">ซอร์สโค้ด</string>
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<string name="app_waring">本应用中内容来自互联网,部分内容可能对您的生理及心理造成难以恢复的伤害。本应用作者不会对由本应用造成的任何后果负责。\n\n未成年人应在监护人指导下使用本应用。\n\n继续使用即代表您同意上述条款。</string>
|
||||
<string name="error_username_cannot_empty">用户名不可为空</string>
|
||||
<string name="error_password_cannot_empty">密码不可为空</string>
|
||||
<string name="skip_signing_in">跳过登录</string>
|
||||
<string name="skip_signing_in">游客模式</string>
|
||||
<string name="sign_in_failed">登录失败</string>
|
||||
<string name="sign_in_failed_tip">若持续出错,请尝试“通过网页登录”或“通过 Cookie 登录”。</string>
|
||||
<string name="get_it">知道了</string>
|
||||
@@ -386,10 +386,13 @@
|
||||
<string name="settings_eh_show_jpn_title_summary">需同时在 EHentai 网站设置中启用 Japanese Title</string>
|
||||
<string name="settings_eh_show_gallery_pages">显示画廊页数</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">在画廊列表中显示页数</string>
|
||||
<string name="settings_eh_show_gallery_comments">显示画廊评论</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">在画廊详情页中显示评论</string>
|
||||
<string name="settings_eh_show_tag_translations">显示标签翻译</string>
|
||||
<string name="settings_eh_show_tag_translations_summary">显示翻译后的标签而非原始文字(需花费时间来下载数据文件)</string>
|
||||
<string name="settings_eh_tag_translations_source">补充翻译(由 EhTagTranslation 提供)</string>
|
||||
<string name="settings_eh_tag_translations_source_url">https://github.com/EhTagTranslation/Editor/wiki</string>
|
||||
<string name="settings_eh_preview_num">画廊详情页预览图最大数量,由于历史因素,此值过大会导致卡顿</string>
|
||||
<string name="settings_eh_filter">屏蔽画廊</string>
|
||||
<string name="settings_eh_filter_summary">根据标题、上传者、标签屏蔽画廊</string>
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
<!-- Hosts Activity -->
|
||||
<string name="hosts">自定義 hosts.txt</string>
|
||||
<string name="hosts_host">Host</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">添加 host</string>
|
||||
<string name="add_host_add">添加</string>
|
||||
<string name="edit_host">編輯 host</string>
|
||||
|
||||
Executable → Regular
+7
@@ -480,6 +480,12 @@
|
||||
<string name="settings_advanced_import_data">匯入檔案</string>
|
||||
<string name="settings_advanced_import_data_summary">從之前儲存的檔案匯入</string>
|
||||
<string name="settings_advanced_import_data_successfully">成功將資料匯入</string>
|
||||
<string name="settings_advanced_backup_favorite">備份收藏列表</string>
|
||||
<string name="settings_advanced_backup_favorite_summary">備份雲端收藏列表到本機</string>
|
||||
<string name="settings_advanced_backup_favorite_start">正在備份收藏列表 %s</string>
|
||||
<string name="settings_advanced_backup_favorite_nothing">沒有可以備份的收藏列表</string>
|
||||
<string name="settings_advanced_backup_favorite_success">備份收藏列表成功</string>
|
||||
<string name="settings_advanced_backup_favorite_failed">備份收藏列表失敗</string>
|
||||
|
||||
<string name="settings_about">關於</string>
|
||||
<string name="settings_about_declaration_summary">EhViewer 與 E-Hentai.org 無任何聯繫</string>
|
||||
@@ -623,4 +629,5 @@
|
||||
<string name="passwd_wrong">密碼錯誤</string>
|
||||
<string name="passwd_cannot_be_empty">密碼不能為空</string>
|
||||
<string name="list_tile_thumb_size">詳情模式下縮圖大小</string>
|
||||
<string name="account_name">帳號</string>
|
||||
</resources>
|
||||
@@ -18,10 +18,6 @@
|
||||
|
||||
<declare-styleable name="Theme">
|
||||
<attr name="dividerColor" format="color" />
|
||||
<attr name="drawableColorSecondary" format="color" />
|
||||
<attr name="drawableColorThemePrimary" format="color" />
|
||||
<attr name="contentColorThemePrimary" format="color" />
|
||||
<attr name="contentColorThemeAccent" format="color" />
|
||||
<attr name="tagGroupBackgroundColor" format="color" />
|
||||
<attr name="tagBackgroundColor" format="color" />
|
||||
</declare-styleable>
|
||||
@@ -139,6 +135,4 @@
|
||||
<attr name="draggable" format="boolean" />
|
||||
</declare-styleable>
|
||||
|
||||
<attr name="filterChipStyle" />
|
||||
|
||||
</resources>
|
||||
@@ -15,13 +15,10 @@
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<color name="primary_drawable_black">#ccffffff</color>
|
||||
<color name="secondary_drawable_black">#3dffffff</color>
|
||||
<color name="red_500">#fff44336</color>
|
||||
<color name="yellow_800">#fff9a825</color>
|
||||
<color name="brown_500">#ff795548</color>
|
||||
<color name="navigation_bar_divider">#1f000000</color>
|
||||
<color name="divider">#20000000</color>
|
||||
<color name="divider_black">#20ffffff</color>
|
||||
<color name="shortcut_bg">#F5F5F5</color>
|
||||
</resources>
|
||||
@@ -381,7 +381,7 @@
|
||||
<!-- Hosts Activity -->
|
||||
<string name="hosts">Custom hosts.txt</string>
|
||||
<string name="hosts_host">Host</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3...)</string>
|
||||
<string name="hosts_ip">IP1(+IP2+IP3…)</string>
|
||||
<string name="add_host">Add a host</string>
|
||||
<string name="add_host_add">Add</string>
|
||||
<string name="edit_host">Edit the host</string>
|
||||
@@ -395,7 +395,7 @@
|
||||
<string name="page_menu_refresh">@string/refresh</string>
|
||||
<string name="page_menu_share">@string/share</string>
|
||||
<string name="page_menu_save">Save</string>
|
||||
<string name="page_menu_save_to">Save to...</string>
|
||||
<string name="page_menu_save_to">Save to…</string>
|
||||
<string name="gallery_menu_title">Menu</string>
|
||||
<string name="share_image">Share image</string>
|
||||
<string name="image_saved">Image saved to %s</string>
|
||||
@@ -431,10 +431,13 @@
|
||||
<string name="settings_eh_show_jpn_title_summary">Require enabling Japanese Title in Settings on EHentai website</string>
|
||||
<string name="settings_eh_show_gallery_pages">Show gallery pages</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">Display the number of pages in the gallery list</string>
|
||||
<string name="settings_eh_show_gallery_comments">Show gallery comments</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">Show comments on the gallery details page</string>
|
||||
<string name="settings_eh_show_tag_translations">Show tag translations</string>
|
||||
<string name="settings_eh_show_tag_translations_summary">Show tag translations instead of the original text (It takes time to download the data file)</string>
|
||||
<string name="settings_eh_tag_translations_source">Placeholder</string>
|
||||
<string name="settings_eh_tag_translations_source_url">https://placeholder</string>
|
||||
<string name="settings_eh_preview_num">The maximum number of previews on the gallery details page, for historical reasons, large value could cause lag</string>
|
||||
<string name="settings_eh_filter">Block gallery</string>
|
||||
<string name="settings_eh_filter_summary">Block gallery by title, uploader and tags</string>
|
||||
|
||||
|
||||
@@ -14,24 +14,30 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Base.AppTheme" parent="Theme.Material3.DynamicColors.DayNight">
|
||||
<item name="dividerColor">@color/divider</item>
|
||||
<item name="drawableColorSecondary">?attr/colorOnSurface</item>
|
||||
<item name="drawableColorThemePrimary">?attr/colorPrimary</item>
|
||||
<item name="tagGroupBackgroundColor">?attr/colorPrimaryContainer</item>
|
||||
<item name="tagBackgroundColor">?attr/colorTertiaryContainer</item>
|
||||
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Rikka.Material3</item>
|
||||
<item name="snackbarStyle">@style/Snackbar</item>
|
||||
<item name="snackbarButtonStyle">@style/SnackbarButton</item>
|
||||
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="filterChipStyle">@style/Widget.Material3.Chip.Filter</item>
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.Rikka.Material3</item>
|
||||
<item name="snackbarStyle">@style/Snackbar</item>
|
||||
<item name="snackbarButtonStyle">@style/SnackbarButton</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:forceDarkAllowed" tools:targetApi="29">false</item>
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="q">true</item>
|
||||
|
||||
<!-- Apply blur on Android 12 above -->
|
||||
<item name="materialCalendarTheme">@style/MaterialCalendarTheme</item>
|
||||
<item name="bottomSheetDialogTheme">@style/BottomSheetDialogTheme</item>
|
||||
<item name="materialAlertDialogTheme">@style/MaterialAlertDialogTheme</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay" />
|
||||
@@ -41,8 +47,6 @@
|
||||
<item name="colorPrimaryDark">@android:color/black</item>
|
||||
<item name="android:colorBackground">@android:color/black</item>
|
||||
<item name="dividerColor">@color/divider_black</item>
|
||||
<item name="drawableColorSecondary">@color/secondary_drawable_black</item>
|
||||
<item name="drawableColorThemePrimary">@color/primary_drawable_black</item>
|
||||
</style>
|
||||
|
||||
<style name="RatingBarTheme" parent="ThemeOverlay.Material3">
|
||||
@@ -55,7 +59,19 @@
|
||||
<item name="colorOnSurface">?attr/colorSurfaceInverse</item>
|
||||
</style>
|
||||
|
||||
<!-- Apply blur on Android 12 above -->
|
||||
<style name="BottomSheetDialogTheme" parent="ThemeOverlay.Material3.BottomSheetDialog">
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowBlurBehindEnabled" tools:targetApi="s">true</item>
|
||||
<item name="android:windowBlurBehindRadius" tools:ignore="PxUsage" tools:targetApi="s">20px</item>
|
||||
</style>
|
||||
|
||||
<style name="MaterialAlertDialogTheme" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||
<item name="android:windowBlurBehindEnabled" tools:targetApi="s">true</item>
|
||||
<item name="android:windowBlurBehindRadius" tools:ignore="PxUsage" tools:targetApi="s">20px</item>
|
||||
</style>
|
||||
|
||||
<style name="MaterialCalendarTheme" parent="ThemeOverlay.Material3.MaterialCalendar">
|
||||
<item name="android:windowBlurBehindEnabled" tools:targetApi="s">true</item>
|
||||
<item name="android:windowBlurBehindRadius" tools:ignore="PxUsage" tools:targetApi="s">20px</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -126,6 +126,13 @@
|
||||
android:title="@string/settings_eh_show_gallery_pages"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<rikka.material.preference.MaterialSwitchPreference
|
||||
android:defaultValue="true"
|
||||
android:key="show_gallery_comments"
|
||||
android:summary="@string/settings_eh_show_gallery_comments_summary"
|
||||
android:title="@string/settings_eh_show_gallery_comments"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<rikka.material.preference.MaterialSwitchPreference
|
||||
android:defaultValue="false"
|
||||
android:key="show_tag_translations"
|
||||
@@ -139,6 +146,16 @@
|
||||
app:iconSpaceReserved="false"
|
||||
app:url="@string/settings_eh_tag_translations_source_url" />
|
||||
|
||||
<SeekBarPreference
|
||||
android:defaultValue="60"
|
||||
android:key="preview_num"
|
||||
android:title="@string/settings_eh_preview_num"
|
||||
android:max="200"
|
||||
app:singleLineTitle="false"
|
||||
app:iconSpaceReserved="false"
|
||||
app:min="10"
|
||||
app:showSeekBarValue="true" />
|
||||
|
||||
<Preference
|
||||
android:key="com.hippo.ehviewer.ui.fragment.FilterFragment"
|
||||
android:summary="@string/settings_eh_filter_summary"
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '8.0.0-alpha08' apply false
|
||||
id 'com.android.application' version '8.0.0-alpha09' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.8.0-Beta' apply false
|
||||
id 'com.google.devtools.ksp' version '1.8.0-Beta-1.0.8' apply false
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:+UseParallelGC
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
android.enableAppCompileTimeRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.experimental.enableNewResourceShrinker=true
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-milestone-3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-milestone-4-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"baseBranches": [
|
||||
"1.8.x.x",
|
||||
"1.7.x.x"
|
||||
"1.8.x.x"
|
||||
],
|
||||
|
||||
"automerge": true,
|
||||
|
||||
@@ -12,6 +12,14 @@ dependencyResolutionManagement {
|
||||
mavenCentral()
|
||||
maven { url "https://jcenter.bintray.com" }
|
||||
maven { url "https://jitpack.io" }
|
||||
maven {
|
||||
name = "MaterialSnapshots"
|
||||
url = uri("https://maven.pkg.github.com/material-components/material-components-android")
|
||||
credentials {
|
||||
username = "nullArrayList"
|
||||
password = "ghp_" + "TTLrOZAWR" + "MlbqMF" + "zwJNdaNON" + "EDDRM22DCHSL" // This github account has nothing and this token only have "read packages" permission. So guys, don't try to do bad things
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
Reference in new issue
Block a user