Compare commits

...
80 Commits
Author SHA1 Message Date
tarsin ae941a4955 Bump Version
EhViewer / Build (push) Has been cancelled
2022-05-20 00:39:47 +08:00
tarsin 72da7ebfe4 Fix search crash 2022-05-20 00:39:07 +08:00
tarsin 8e1bacbfac Refactor Search 2022-05-19 23:59:18 +08:00
tarsin 747c19c028 Rename .java to .kt 2022-05-19 23:59:18 +08:00
txyyh aca83fc1ee Cut photo edges 2022-05-19 21:25:36 +08:00
tarsin 35babd089d Image search layout tweaks 2022-05-19 21:06:23 +08:00
tarsin 457ea56aab Reformat code 2022-05-19 20:18:59 +08:00
txyyh ed22de2360 Cut photo background 2022-05-19 20:17:13 +08:00
tarsin 8d386b44bc Kill redundant things in navmain 2022-05-19 20:16:35 +08:00
tarsin c4c88c40c5 Surround Image with round corner cardview 2022-05-19 20:07:37 +08:00
tarsin e637ad6acb Honor uploader with Chip 2022-05-19 19:48:27 +08:00
tarsin 3b9e08e1c7 Don't disturb nekochan 2022-05-19 18:30:34 +08:00
tarsin 4a2aa8a975 Update README.md 2022-05-19 14:14:42 +08:00
txyyh 9c08eda38a Rewrite README commit
Rewrite commit 713d972d16 to make it look normal
2022-05-19 13:18:01 +08:00
tarsin 713d972d16 Update README.md 2022-05-19 00:46:49 +08:00
tarsin d4b3705a9e Bump version
EhViewer / Build (push) Has been cancelled
2022-05-19 00:30:50 +08:00
tarsin c543d1540c Drop unused res
* Use dyn color as much as possible, try avoid hardcode color schemes
2022-05-19 00:28:35 +08:00
tarsin 2956ff08bd Fix black theme 2022-05-19 00:16:01 +08:00
tarsin ff09dec4e4 Refine color schemes 2022-05-19 00:00:10 +08:00
tarsin 5e02479aa5 inline a blank class 2022-05-18 22:33:06 +08:00
tarsin 6157b9cf82 Drop e-ink mode 2022-05-18 22:26:21 +08:00
tarsin 9260633e26 Merge pull request #27 from txyyh/master
Change download section in README
2022-05-18 21:23:41 +08:00
txyyh 1a2ae6b1a8 Redirect download link 2022-05-18 17:42:49 +08:00
txyyh 282b4adaff Move download link below introduce 2022-05-18 17:40:47 +08:00
tarsin 8519b5a096 New screenshot 2022-05-18 16:10:52 +08:00
tarsin 4a00be922e Merge pull request #25 from asuka-mio/renovate/actions-checkout-3.x
Update actions/checkout action to v3
2022-05-18 15:54:45 +08:00
tarsin 31f30cf1b1 Merge pull request #26 from asuka-mio/renovate/dev.rikka.rikkax.material-material-2.x
Update dependency dev.rikka.rikkax.material:material to v2
2022-05-18 15:54:34 +08:00
Renovate Bot 62737cc32f Update dependency dev.rikka.rikkax.material:material to v2 2022-05-18 07:08:08 +00:00
Renovate Bot c7306b2358 Update actions/checkout action to v3 2022-05-18 07:08:05 +00:00
tarsin 779958dfeb Merge pull request #20 from asuka-mio/renovate/com.squareup.okhttp3-okhttp-dnsoverhttps-4.x
Update dependency com.squareup.okhttp3:okhttp-dnsoverhttps to v4.9.3
2022-05-18 14:56:35 +08:00
tarsin d0ca6872b0 Merge pull request #23 from asuka-mio/renovate/actions-setup-java-3.x
Update actions/setup-java action to v3
2022-05-18 14:56:11 +08:00
tarsin 161f191f3d Merge pull request #21 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-api-2.x
Update dependency org.jboss.forge.roaster:roaster-api to v2.24.0.Final
2022-05-18 14:55:59 +08:00
tarsin 2cfa46ed05 Revert "Update dependency com.github.seven332:yorozuya-thread to v0.1.2"
This reverts commit e19759a7e4.
2022-05-18 14:52:42 +08:00
tarsin 005e2b3fa1 Revert "Update dependency com.github.seven332:glview to v0.1.4"
This reverts commit 2fd688ff9e.
2022-05-18 14:49:02 +08:00
tarsin 3826dfb1d4 Revert "Update dependency com.github.seven332:beerbelly to v0.2.1"
This reverts commit b0274946bf.
2022-05-18 14:43:58 +08:00
Renovate Bot 81bb9a852f Update actions/setup-java action to v3 2022-05-18 06:22:35 +00:00
Renovate Bot 37e60673d7 Update dependency org.jboss.forge.roaster:roaster-api to v2.24.0.Final 2022-05-18 06:22:29 +00:00
Renovate Bot 9b618f7ecc Update dependency com.squareup.okhttp3:okhttp-dnsoverhttps to v4.9.3 2022-05-18 06:22:26 +00:00
tarsin 0ce3ceb900 Merge pull request #14 from asuka-mio/renovate/org.jboss.forge.roaster-roaster-jdt-2.x
Update dependency org.jboss.forge.roaster:roaster-jdt to v2.24.0.Final
2022-05-18 14:20:56 +08:00
tarsin 4833226241 Merge pull request #12 from asuka-mio/renovate/dev.rikka.rikkax.core-core-1.x
Update dependency dev.rikka.rikkax.core:core to v1.4.0
2022-05-18 14:20:45 +08:00
tarsin 5cae964a1e Merge pull request #10 from asuka-mio/renovate/com.github.seven332-beerbelly-0.x
Update dependency com.github.seven332:beerbelly to v0.2.1
2022-05-18 14:20:34 +08:00
tarsin 70eaa35bc9 Merge pull request #11 from asuka-mio/renovate/com.github.seven332-glview-0.x
Update dependency com.github.seven332:glview to v0.1.4
2022-05-18 14:20:21 +08:00
tarsin 38bd3fede1 Merge pull request #15 from asuka-mio/renovate/org.jsoup-jsoup-1.x
Update dependency org.jsoup:jsoup to v1.15.1
2022-05-18 14:20:09 +08:00
tarsin e1e32a7f5e Merge pull request #9 from asuka-mio/renovate/gradle-7.x
Update dependency gradle to v7.4.2
2022-05-18 14:19:57 +08:00
tarsin b77d0fb47a Update android.yml 2022-05-18 14:02:02 +08:00
Renovate Bot 4076b2f900 Update dependency org.jsoup:jsoup to v1.15.1 2022-05-18 05:53:38 +00:00
Renovate Bot 1bc409b54a Update dependency org.jboss.forge.roaster:roaster-jdt to v2.24.0.Final 2022-05-18 05:53:35 +00:00
Renovate Bot 1247a0ade9 Update dependency dev.rikka.rikkax.core:core to v1.4.0 2022-05-18 05:53:29 +00:00
Renovate Bot 2fd688ff9e Update dependency com.github.seven332:glview to v0.1.4 2022-05-18 05:53:26 +00:00
Renovate Bot b0274946bf Update dependency com.github.seven332:beerbelly to v0.2.1 2022-05-18 05:53:24 +00:00
Renovate Bot b78c132742 Update dependency gradle to v7.4.2 2022-05-18 05:53:20 +00:00
tarsin b33c46de82 Merge pull request #7 from asuka-mio/renovate/com.squareup.okhttp3-okhttp-4.x
Update dependency com.squareup.okhttp3:okhttp to v4.9.3
2022-05-18 13:48:56 +08:00
Renovate Bot 25c8bba1b0 Update dependency com.squareup.okhttp3:okhttp to v4.9.3 2022-05-18 05:48:23 +00:00
tarsin 089f5cb7ec Merge pull request #4 from asuka-mio/renovate/com.github.seven332-glview-image-0.x
Update dependency com.github.seven332:glview-image to v0.1.3
2022-05-18 13:45:16 +08:00
tarsin 95a6a9dca6 Merge pull request #5 from asuka-mio/renovate/com.github.seven332-yorozuya-thread-0.x
Update dependency com.github.seven332:yorozuya-thread to v0.1.2
2022-05-18 13:45:02 +08:00
Renovate Bot e19759a7e4 Update dependency com.github.seven332:yorozuya-thread to v0.1.2 2022-05-18 05:44:21 +00:00
Renovate Bot 8f95ffeebf Update dependency com.github.seven332:glview-image to v0.1.3 2022-05-18 05:44:19 +00:00
tarsin 98f08feb16 Merge pull request #3 from asuka-mio/renovate/configure
Configure Renovate
2022-05-18 13:43:53 +08:00
Renovate Bot 50baaffc36 Add renovate.json 2022-05-18 05:39:46 +00:00
tarsin 66e9f43bba Revert "Revert minsdk to sdk29"
* No dynamic color support, crashed
* Won't fix
This reverts commit 0cb13bb6a3.
2022-05-18 12:02:21 +08:00
tarsin 0cb13bb6a3 Revert minsdk to sdk29
* Sadly my HW-SCM09 is still Android Q and seems it will never upgrade, damn

* Huawei sucks
2022-05-18 11:59:03 +08:00
tarsin 4278bf4631 Version Bump 2022-05-18 11:48:23 +08:00
tarsin 7fa2dd1b74 Update Screenshot 2022-05-18 11:45:41 +08:00
tarsin afcd5b0d6e Drop unused res 2022-05-18 11:42:47 +08:00
tarsin 2f49748a3b Do Code cleanup 2022-05-18 11:40:29 +08:00
tarsin 4dffa25819 Drop unused lib 2022-05-18 11:30:24 +08:00
tarsin fcce3a04a6 Convert to dynamic color
* Drop color hardcode
2022-05-18 11:17:47 +08:00
tarsin bc38c8c135 Daily version bump 2022-05-18 00:57:27 +08:00
tarsin 4e462a1903 Refine color schemes
* Don't always be green
2022-05-18 00:55:16 +08:00
tarsin 43bc5ab50c Inline a class 2022-05-17 23:24:22 +08:00
tarsin fed04784db Use kotlin 2022-05-17 22:48:54 +08:00
tarsin 4dbf5a45e8 Build 64 abi only 2022-05-17 21:00:21 +08:00
tarsin 5d22e70ded Go minsdk 31 2022-05-17 20:39:06 +08:00
tarsin a3cae20478 Kill TOolbar color
* Don't be green plz
2022-05-17 13:54:51 +08:00
tarsin f7cfbf1b34 Bump a small version 2022-05-17 13:20:00 +08:00
tarsin 4d4358f806 Drop ms center analyze
* We must be libre with no suspense
2022-05-17 13:12:35 +08:00
tarsin 6ca3c3261e Convert more scene to Md3 Alert Dialog 2022-05-17 12:35:13 +08:00
tarsin 635a0b3af1 Revert "Use alertdialog.builder"
This reverts commit 97fbe73edf.
2022-05-17 12:26:04 +08:00
tarsin 4288bc790d Remove ShakeDetector
* It makes no sense to do that in app
2022-05-17 12:12:16 +08:00
tarsin c52c91e1c0 Drop test and debug addons 2022-05-17 09:46:12 +08:00
262 changed files with 1762 additions and 5665 deletions

No files matched your search

+17 -46
View File
@@ -1,57 +1,28 @@
name: EhViewer
on:
push:
branches:
- master
on: push
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: set up JDK 1.8
uses: actions/setup-java@v1
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
java-version: 1.8
- uses: actions/cache@v1
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Build
env:
KEYSTORE_PASS: ${{ secrets.KEYSTORE_PASS }}
ALIAS_NAME: ${{ secrets.ALIAS_NAME }}
ALIAS_PASS: ${{ secrets.ALIAS_PASS }}
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
./gradlew assembleRelease
echo ::set-env name=APK_FILE::$(find app/build/outputs/apk -name "*.apk")
- name: Create Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
- uses: actions/upload-artifact@v3
with:
tag_name: 1.7.4_${{ github.run_id }}
release_name: 1.7.4_${{ github.run_id }}
body: ""
draft: false
prerelease: true
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps
asset_path: ${{ env.APK_FILE }}
asset_name: app-release.apk
asset_content_type: application/vnd.android.package-archive
- uses: actions/upload-artifact@master
name: Upload artifact
with:
name: EhViewer
path: app/build/outputs/
name: github-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release-unsigned.apk
+13 -5
View File
@@ -1,15 +1,23 @@
### [下载](https://install.appcenter.ms/users/nekoinverter/apps/ehviewer/distribution_groups/public)
[Download](https://install.appcenter.ms/users/nekoinverter/apps/ehviewer/distribution_groups/public)
# EhViewer
![Icon](art/launcher_icon-web.png)
这是一个 E-Hentai Android 平台的浏览器。
这是一个 E-Hentai Android 平台的浏览器
已针对性适配 Material Design 3 及其动态取色系统
仅供 Android 12 及以上平台使用!
An E-Hentai Application for Android.
with Material Design 3 and Dynamic Color Support.
Only Support Android S and above!
# Download
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
# Screenshot
+17 -19
View File
@@ -1,7 +1,7 @@
import java.nio.file.Paths
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
}
android {
@@ -16,6 +16,8 @@ android {
splits {
abi {
enable = true
reset()
include 'arm64-v8a', 'x86_64'
}
}
@@ -32,12 +34,11 @@ android {
defaultConfig {
applicationId "io.github.nekoinverter.ehviewer"
minSdkVersion 26
minSdkVersion 31
targetSdkVersion 32
versionCode 100011
versionName "1.7.26.4"
versionCode 114518
versionName "1.7.26.9"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
testOptions.unitTests.includeAndroidResources = true
}
compileOptions {
@@ -45,6 +46,10 @@ android {
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
lint {
abortOnError true
checkReleaseBuilds false
@@ -106,40 +111,33 @@ dependencies {
implementation 'androidx.webkit:webkit:1.4.0'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.6.0'
implementation 'com.getkeepsafe.relinker:relinker:1.4.4'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
implementation 'com.github.seven332:beerbelly:0.1.4'
implementation 'com.github.seven332:glgallery:0.1.2'
implementation 'com.github.seven332:glview:0.1.0'
implementation 'com.github.seven332:glview-image:0.1.0'
implementation 'com.github.seven332:glview-image:0.1.3'
implementation 'com.github.seven332:streampipe:0.1.0'
implementation 'com.github.seven332:tuxiang:0.1.6'
implementation 'com.github.seven332:unifile:9ec57bcd8f'
implementation 'com.github.seven332:yorozuya-thread:0.1.1'
implementation 'com.github.seven332:yorozuya-collect:0.1.4'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.microsoft.appcenter:appcenter-analytics:4.4.3'
implementation 'com.microsoft.appcenter:appcenter-crashes:4.4.3'
implementation 'com.microsoft.appcenter:appcenter-distribute:4.4.3'
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.2'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'
implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'com.gitlab.NekoInverter:yorozuya:723dc867a9'
implementation 'dev.rikka.rikkax.appcompat:appcompat:1.4.1'
implementation 'dev.rikka.rikkax.core:core:1.3.4'
implementation 'dev.rikka.rikkax.core:core:1.4.0'
implementation 'dev.rikka.rikkax.insets:insets:1.2.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.material:material:1.6.6'
implementation 'dev.rikka.rikkax.material:material:2.4.0'
implementation 'dev.rikka.rikkax.recyclerview:recyclerview-ktx:1.3.1'
implementation 'dev.rikka.rikkax.widget:borderview:1.1.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jsoup:jsoup:1.14.3'
implementation 'org.jsoup:jsoup:1.15.1'
implementation projects.native
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.robolectric:robolectric:4.2.1'
testImplementation 'org.jooq:joor:0.9.6'
}
configurations.all {
@@ -1,13 +0,0 @@
package com.hippo.ehviewer;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a>
*/
public class ApplicationTest extends ApplicationTestCase<Application> {
public ApplicationTest() {
super(Application.class);
}
}
@@ -1,164 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Environment;
import junit.framework.TestCase;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class CardBgTest extends TestCase {
private static final float[] TOP_ALPHA = {
0f, // Dump
0.12f,
0.16f,
0.19f,
0.25f,
0.30f,
};
private static final float[] TOP_OFFSET = {
0f, // Dump
1f,
3f,
10f,
14f,
19f,
};
private static final float[] TOP_BLUR = {
0f, // Dump
1.5f,
3f,
10f,
14f,
19f,
};
private static final float[] BOTTOM_ALPHA = {
0f, // Dump
0.24f,
0.23f,
0.23f,
0.22f,
0.22f,
};
private static final float[] BOTTOM_OFFSET = {
0f, // Dump
1f,
3f,
6f,
10f,
15f,
};
private static final float[] BOTTOM_BLUR = {
0f, // Dump
1f,
3f,
3f,
5f,
6f,
};
private static final int MAX_SIZE = 200;
public void testGen() throws FileNotFoundException {
genCardBg(Color.WHITE, 2, 2);
}
private Path getPath(float radius, float base) {
RectF rectF = new RectF();
Path path = new Path();
path.moveTo(base / 2, -base / 2 - radius);
rectF.set(base / 2 - radius, -base / 2 - radius, base / 2 + radius, -base / 2 + radius);
path.arcTo(rectF, -90, 90);
path.lineTo(base / 2 + radius, base / 2);
rectF.set(base / 2 - radius, base / 2 - radius, base / 2 + radius, base / 2 + radius);
path.arcTo(rectF, 0, 90);
path.lineTo(-base / 2, base / 2 + radius);
rectF.set(-base / 2 - radius, base / 2 - radius, -base / 2 + radius, base / 2 + radius);
path.arcTo(rectF, 90, 90);
path.lineTo(-base / 2 - radius, -base / 2);
rectF.set(-base / 2 - radius, -base / 2 - radius, -base / 2 + radius, -base / 2 + radius);
path.arcTo(rectF, 180, 90);
path.close();
return path;
}
private void genCardBg(int color, float radius, int elevation) throws FileNotFoundException {
float base = 4;
float topAlpha = TOP_ALPHA[elevation];
float topOffset = TOP_OFFSET[elevation];
float topBlur = TOP_BLUR[elevation];
float bottomAlpha = BOTTOM_ALPHA[elevation];
float bottomOffset = BOTTOM_OFFSET[elevation];
float bottomBlur = BOTTOM_BLUR[elevation];
float ratio = 3;
doGenCardBg(new FileOutputStream(new File(Environment.getExternalStorageDirectory(), "test.png")),
color, radius * ratio, base * ratio,
topAlpha, topOffset * ratio, topBlur * ratio,
bottomAlpha, bottomOffset * ratio, bottomBlur * ratio);
}
private int getColor(float alpha) {
return Color.argb((int) (alpha * 0xff), 0, 0, 0);
}
private void doGenCardBg(OutputStream os, int color, float radius, float base,
float topAlpha, float topOffset, float topBlur,
float bottomAlpha, float bottomOffset, float bottomBlur) throws FileNotFoundException {
Path path = getPath(radius, base);
path.offset(MAX_SIZE / 2, MAX_SIZE / 2);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
paint.setColor(color);
Bitmap bitmap = Bitmap.createBitmap(MAX_SIZE, MAX_SIZE, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// Draw bottom
paint.setShadowLayer(bottomBlur, 0, bottomOffset, getColor(bottomAlpha));
canvas.drawPath(path, paint);
// Draw top
paint.setShadowLayer(topBlur, 0, topOffset, getColor(topAlpha));
canvas.drawPath(path, paint);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
}
}
@@ -1,22 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import junit.framework.TestCase;
public class CookieDBTest extends TestCase {
}
@@ -1,50 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import junit.framework.TestCase;
import java.net.MalformedURLException;
import java.net.URL;
public class DumpTest extends TestCase {
public void testDump() {
String str = "M12.1,18.55L12,18.65L11.89,18.55C7.14,14.24 4,11.39 4,8.5C4,6.5 5.5,5 7.5,5C9.04,5 10.54,6 11.07,7.36H12.93C13.46,6 14.96,5 16.5,5C18.5,5 20,6.5 20,8.5C20,11.39 16.86,14.24 12.1,18.55";
System.out.println(str.replaceAll("\\d+(\\.\\d+)?", "12"));
}
public void testDumpURL() {
String str1 = "http://exhentai.org/?f_doujinshi=1&f_manga=0&f_artistcg=0&f_gamecg=1&f_western=1&f_non-h=1&f_imageset=0&f_cosplay=0&f_asianporn=1&f_misc=1&f_search=&f_apply=Apply+Filter";
String str2 = "http://exhentai.org/3?f_search=hentai&f_apply=Apply+Filter";
try {
URL url = new URL(str2);
System.out.println(url.getHost());
System.out.println(url.getPath());
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<string name="app_name">EhViewer Debug</string>
</resources>
-32
View File
@@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:enabled="true"
android:icon="@drawable/ic_shortcut_start"
android:shortcutDisabledMessage="@string/download_start_all"
android:shortcutId="start_all"
android:shortcutLongLabel="@string/download_start_all"
android:shortcutShortLabel="@string/download_start_all">
<intent
android:action="start_all"
android:targetClass="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
android:targetPackage="io.github.nekoinverter.ehviewer.debug" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
<shortcut
android:enabled="true"
android:icon="@drawable/ic_shortcut_stop"
android:shortcutDisabledMessage="@string/download_stop_all"
android:shortcutId="stop_all"
android:shortcutLongLabel="@string/download_stop_all"
android:shortcutShortLabel="@string/download_stop_all">
<intent
android:action="stop_all"
android:targetClass="com.hippo.ehviewer.shortcuts.ShortcutsActivity"
android:targetPackage="io.github.nekoinverter.ehviewer.debug" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>
+8 -8
View File
@@ -32,23 +32,24 @@
<application
android:name="com.hippo.ehviewer.EhApplication"
android:allowBackup="true"
android:appCategory="image"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_scheme"
android:hasFragileUserData="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:appCategory="image"
android:hasFragileUserData="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute">
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
android:exported="true">
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -89,9 +90,8 @@
<activity
android:name="com.hippo.ehviewer.ui.GalleryActivity"
android:configChanges="screenSize"
android:launchMode="singleTask"
android:theme="@style/AppTheme.Gallery"
android:exported="true">
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -1,45 +0,0 @@
package androidx.recyclerview.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BridgeRecyclerView extends RecyclerView {
public BridgeRecyclerView(@NonNull Context context) {
super(context);
}
public BridgeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public BridgeRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
try {
super.scrollStep(dx, dy, consumed);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
return super.scrollByInternal(x, y, ev, type);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
try {
super.onLayout(changed, l, t, r, b);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@@ -22,11 +22,10 @@ import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
public class CheckBoxDialogBuilder extends AlertDialog.Builder {
public class CheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
@@ -29,10 +29,11 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.R;
public class EditTextDialogBuilder extends AlertDialog.Builder implements EditText.OnEditorActionListener {
public class EditTextDialogBuilder extends MaterialAlertDialogBuilder implements EditText.OnEditorActionListener {
private final TextInputLayout mTextInputLayout;
private final EditText mEditText;
@@ -27,10 +27,11 @@ import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
public class ListCheckBoxDialogBuilder extends AlertDialog.Builder {
public class ListCheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
+6 -10
View File
@@ -39,26 +39,17 @@ import okhttp3.OkHttpClient;
@SuppressWarnings("rawtypes")
public final class Conaco<V> {
private static final String TAG = Conaco.class.getSimpleName();
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
@Retention(RetentionPolicy.SOURCE)
public @interface Source {
}
public static final int SOURCE_MEMORY = 0;
public static final int SOURCE_DISK = 1;
public static final int SOURCE_NETWORK = 2;
private static final String TAG = Conaco.class.getSimpleName();
private final ValueHelper<V> mHelper;
private final ValueCache<V> mCache;
private final OkHttpClient mOkHttpClient;
private final Register<V> mRegister;
private final SerialThreadExecutor mDiskExecutor;
private final ThreadPoolExecutor mNetworkExecutor;
private final AtomicInteger mIdGenerator;
private final boolean mDebug;
private Conaco(Builder<V> builder) {
@@ -197,6 +188,11 @@ public final class Conaco<V> {
onUnregisterConacoTask(task);
}
@IntDef({SOURCE_MEMORY, SOURCE_DISK, SOURCE_NETWORK})
@Retention(RetentionPolicy.SOURCE)
public @interface Source {
}
public static class Builder<T> extends BeerBelly.BeerBellyParams {
/**
* The client to get image from internet
+160 -160
View File
@@ -85,6 +85,42 @@ public final class ConacoTask<V> {
mConaco = builder.mConaco;
}
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
SimpleDiskCache diskCache = cache.getDiskCache();
if (diskCache != null) {
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
if (pipe != null) {
try {
pipe.obtain();
container.save(pipe.open(), -1L, null, null);
} catch (IOException e) {
Log.d(TAG, "Can't save value from disk cache to data container");
e.printStackTrace();
container.remove();
} finally {
pipe.close();
pipe.release();
}
}
}
}
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
InputStreamPipe pipe = container.get();
if (pipe != null) {
try {
pipe.obtain();
cache.putRawToDisk(key, pipe.open());
} catch (IOException e) {
Log.w(TAG, "Can't save value from data container to disk cache", e);
cache.removeFromDisk(key);
} finally {
pipe.close();
pipe.release();
}
}
}
int getId() {
return mId;
}
@@ -176,39 +212,133 @@ public final class ConacoTask<V> {
return mStop || asyncTask.isCancelled() || unikery == null || unikery.getTaskId() != mId;
}
private static void putFromDiskCacheToDataContainer(String key, ValueCache cache, DataContainer container) {
SimpleDiskCache diskCache = cache.getDiskCache();
if (diskCache != null) {
InputStreamPipe pipe = diskCache.getInputStreamPipe(key);
if (pipe != null) {
try {
pipe.obtain();
container.save(pipe.open(), -1L, null, null);
} catch (IOException e) {
Log.d(TAG, "Can't save value from disk cache to data container");
e.printStackTrace();
container.remove();
} finally {
pipe.close();
pipe.release();
}
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
public static class Builder<T> {
private int mId;
private Unikery<T> mUnikery;
private String mKey;
private String mUrl;
private DataContainer mDataContainer;
private boolean mUseMemoryCache = true;
private boolean mUseDiskCache = true;
private boolean mUseNetwork = true;
private ValueHelper<T> mHelper;
private ValueCache<T> mCache;
private OkHttpClient mOkHttpClient;
private Executor mDiskExecutor;
private Executor mNetworkExecutor;
private Conaco<T> mConaco;
public Builder<T> setId(int id) {
mId = id;
return this;
}
public Unikery<T> getUnikery() {
return mUnikery;
}
public Builder<T> setUnikery(Unikery<T> unikery) {
mUnikery = unikery;
return this;
}
public String getKey() {
return mKey;
}
public Builder<T> setKey(String key) {
mKey = key;
return this;
}
public String getUrl() {
return mUrl;
}
public Builder<T> setUrl(String url) {
mUrl = url;
return this;
}
public Builder<T> setDataContainer(DataContainer dataContainer) {
mDataContainer = dataContainer;
return this;
}
boolean isUseMemoryCache() {
return mUseMemoryCache;
}
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
mUseMemoryCache = useMemoryCache;
return this;
}
boolean isUseDiskCache() {
return mUseDiskCache;
}
public Builder<T> setUseDiskCache(boolean useDiskCache) {
mUseDiskCache = useDiskCache;
return this;
}
boolean isUseNetwork() {
return mUseNetwork;
}
public Builder<T> setUseNetwork(boolean useNetwork) {
mUseNetwork = useNetwork;
return this;
}
ValueHelper<T> getHelper() {
return mHelper;
}
Builder<T> setHelper(ValueHelper<T> helper) {
mHelper = helper;
return this;
}
Builder<T> setCache(ValueCache<T> cache) {
mCache = cache;
return this;
}
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
mOkHttpClient = okHttpClient;
return this;
}
Builder<T> setDiskExecutor(Executor diskExecutor) {
mDiskExecutor = diskExecutor;
return this;
}
Builder<T> setNetworkExecutor(Executor networkExecutor) {
mNetworkExecutor = networkExecutor;
return this;
}
Builder<T> setConaco(Conaco<T> conaco) {
mConaco = conaco;
return this;
}
public void isValid() {
if (mUnikery == null) {
throw new IllegalStateException("Must set unikery");
}
if (mKey == null && mUrl == null && mDataContainer == null) {
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
}
}
}
private static void putFromDataContainerToDiskCache(String key, ValueCache cache, DataContainer container) {
InputStreamPipe pipe = container.get();
if (pipe != null) {
try {
pipe.obtain();
cache.putRawToDisk(key, pipe.open());
} catch (IOException e) {
Log.w(TAG, "Can't save value from data container to disk cache", e);
cache.removeFromDisk(key);
} finally {
pipe.close();
pipe.release();
}
public ConacoTask<T> build() {
return new ConacoTask<>(this);
}
}
@@ -455,134 +585,4 @@ public final class ConacoTask<V> {
}
}
}
@SuppressWarnings({"unused", "UnusedReturnValue", "RedundantSuppression"})
public static class Builder<T> {
private int mId;
private Unikery<T> mUnikery;
private String mKey;
private String mUrl;
private DataContainer mDataContainer;
private boolean mUseMemoryCache = true;
private boolean mUseDiskCache = true;
private boolean mUseNetwork = true;
private ValueHelper<T> mHelper;
private ValueCache<T> mCache;
private OkHttpClient mOkHttpClient;
private Executor mDiskExecutor;
private Executor mNetworkExecutor;
private Conaco<T> mConaco;
public Builder<T> setId(int id) {
mId = id;
return this;
}
public Builder<T> setUnikery(Unikery<T> unikery) {
mUnikery = unikery;
return this;
}
public Unikery<T> getUnikery() {
return mUnikery;
}
public Builder<T> setKey(String key) {
mKey = key;
return this;
}
public String getKey() {
return mKey;
}
public Builder<T> setUrl(String url) {
mUrl = url;
return this;
}
public String getUrl() {
return mUrl;
}
public Builder<T> setDataContainer(DataContainer dataContainer) {
mDataContainer = dataContainer;
return this;
}
public Builder<T> setUseMemoryCache(boolean useMemoryCache) {
mUseMemoryCache = useMemoryCache;
return this;
}
boolean isUseMemoryCache() {
return mUseMemoryCache;
}
public Builder<T> setUseDiskCache(boolean useDiskCache) {
mUseDiskCache = useDiskCache;
return this;
}
boolean isUseDiskCache() {
return mUseDiskCache;
}
public Builder<T> setUseNetwork(boolean useNetwork) {
mUseNetwork = useNetwork;
return this;
}
boolean isUseNetwork() {
return mUseNetwork;
}
Builder<T> setHelper(ValueHelper<T> helper) {
mHelper = helper;
return this;
}
ValueHelper<T> getHelper() {
return mHelper;
}
Builder<T> setCache(ValueCache<T> cache) {
mCache = cache;
return this;
}
Builder<T> setOkHttpClient(OkHttpClient okHttpClient) {
mOkHttpClient = okHttpClient;
return this;
}
Builder<T> setDiskExecutor(Executor diskExecutor) {
mDiskExecutor = diskExecutor;
return this;
}
Builder<T> setNetworkExecutor(Executor networkExecutor) {
mNetworkExecutor = networkExecutor;
return this;
}
Builder<T> setConaco(Conaco<T> conaco) {
mConaco = conaco;
return this;
}
public void isValid() {
if (mUnikery == null) {
throw new IllegalStateException("Must set unikery");
}
if (mKey == null && mUrl == null && mDataContainer == null) {
throw new IllegalStateException("At least one of mKey and mUrl and mDataContainer have to not be null");
}
}
public ConacoTask<T> build() {
return new ConacoTask<>(this);
}
}
}
@@ -22,13 +22,6 @@ public interface Unikery<V> {
int INVALID_ID = -1;
/**
* Store the conaco task id
*
* @param id the conaco task id
*/
void setTaskId(int id);
/**
* Get the conaco task id
*
@@ -36,6 +29,13 @@ public interface Unikery<V> {
*/
int getTaskId();
/**
* Store the conaco task id
*
* @param id the conaco task id
*/
void setTaskId(int id);
/**
* On miss in the source
*/
@@ -46,9 +46,9 @@ public class MSQLiteBuilder {
JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT");
}
private final SparseArray<List<String>> statementsMap = new SparseArray<>();
private int version = 0;
private List<String> statements;
private final SparseArray<List<String>> statementsMap = new SparseArray<>();
private static String javaTypeToSQLiteType(Class clazz) {
String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz);
@@ -26,7 +26,6 @@ import com.hippo.conaco.ConacoTask;
import com.hippo.conaco.Unikery;
import com.hippo.image.ImageBitmap;
import com.hippo.image.ImageDrawable;
import com.hippo.image.RecycledException;
import com.hippo.widget.ObservedTextView;
public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap>,
@@ -127,7 +126,7 @@ public class UnikeryDrawable extends WrapDrawable implements Unikery<ImageBitmap
ImageDrawable drawable;
try {
drawable = new ImageDrawable(value);
} catch (RecycledException e) {
} catch (Exception e) {
Log.d(TAG, "The ImageBitmap is recycled", e);
return false;
}
@@ -31,20 +31,13 @@ import androidx.annotation.NonNull;
import androidx.collection.LongSparseArray;
import androidx.recyclerview.widget.RecyclerView;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.NumberUtils;
/**
* Add setChoiceMode for RecyclerView
*/
// Get some code from twoway-view and AbsListView.
public class EasyRecyclerView extends ScrollLessRecyclerView {
/**
* Represents an invalid position. All valid positions are in the range 0 to 1 less than the
* number of items in the current adapter.
*/
public static final int INVALID_POSITION = -1;
public class EasyRecyclerView extends RecyclerView {
/**
* Normal list that does not indicate choices
@@ -70,23 +63,21 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
* The list allows multiple choices in custom action
*/
public static final int CHOICE_MODE_MULTIPLE_CUSTOM = 4;
/**
* Controls if/how the user may choose/check items in the list
*/
private int mChoiceMode = CHOICE_MODE_NONE;
/**
* Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
*/
private ActionMode mChoiceActionMode;
// Fling friction
public float pageScrollThreshold;
/**
* Wrapper for the multiple choice mode callback; AbsListView needs to perform
* a few extra actions around what application code does.
*/
MultiChoiceModeWrapper mMultiChoiceModeCallback;
/**
* Controls if/how the user may choose/check items in the list
*/
private int mChoiceMode = CHOICE_MODE_NONE;
/**
* Controls CHOICE_MODE_MULTIPLE_MODAL. null when inactive.
*/
private ActionMode mChoiceActionMode;
/**
* Listener for custom multiple choices
*/
@@ -122,17 +113,25 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
public EasyRecyclerView(Context context) {
super(context);
setEnableScroll(!Settings.getEInkMode());
EasyRecyclerView.this.init(context);
}
public EasyRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
setEnableScroll(!Settings.getEInkMode());
EasyRecyclerView.this.init(context);
}
public EasyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setEnableScroll(!Settings.getEInkMode());
EasyRecyclerView.this.init(context);
}
public static void setViewChecked(View view, boolean checked) {
if (view instanceof Checkable) {
((Checkable) view).setChecked(checked);
} else {
view.setActivated(checked);
}
}
/**
@@ -172,18 +171,7 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
}
}
/**
* Returns the number of items currently selected. This will only be valid
* if the choice mode is not {@link #CHOICE_MODE_NONE} (default).
*
* <p>To determine the specific items that are currently selected, use one of
* the <code>getChecked*</code> methods.
*
* @return The number of items currently selected
* @see #getCheckedItemPosition()
* @see #getCheckedItemPositions()
* @see #getCheckedItemIds()
*/
public int getCheckedItemCount() {
return mCheckedItemCount;
}
@@ -207,22 +195,6 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
return false;
}
/**
* Returns the currently checked item. The result is only valid if the choice
* mode has been set to {@link #CHOICE_MODE_SINGLE}.
*
* @return The position of the currently checked item or
* {@link #INVALID_POSITION} if nothing is selected
* @see #setChoiceMode(int)
*/
public int getCheckedItemPosition() {
if (mChoiceMode == CHOICE_MODE_SINGLE && mCheckStates != null && mCheckStates.size() == 1) {
return mCheckStates.keyAt(0);
}
return INVALID_POSITION;
}
/**
* Returns the set of checked items in the list. The result is only valid if
* the choice mode has not been set to {@link #CHOICE_MODE_NONE}.
@@ -239,30 +211,6 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
return null;
}
/**
* Returns the set of checked items ids. The result is only valid if the
* choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter
* has stable IDs. ({@link android.widget.ListAdapter#hasStableIds()} == {@code true})
*
* @return A new array which contains the id of each checked item in the
* list.
*/
public long[] getCheckedItemIds() {
if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
return new long[0];
}
final LongSparseArray<Integer> idStates = mCheckedIdStates;
final int count = idStates.size();
final long[] ids = new long[count];
for (int i = 0; i < count; i++) {
ids[i] = idStates.keyAt(i);
}
return ids;
}
public boolean isInCustomChoice() {
return mCustomChoice;
}
@@ -445,14 +393,6 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
updateOnScreenCheckedViews();
}
/**
* @return The current choice mode
* @see #setChoiceMode(int)
*/
public int getChoiceMode() {
return mChoiceMode;
}
/**
* Defines the choice behavior for the List. By default, Lists do not have any choice behavior
* ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the
@@ -488,24 +428,6 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
}
}
/**
* Set a {@link MultiChoiceModeListener} that will manage the lifecycle of the
* selection {@link android.view.ActionMode}. Only used when the choice mode is set to
* {@link #CHOICE_MODE_MULTIPLE_MODAL}.
*
* @param listener Listener that will manage the selection mode
* @see #setChoiceMode(int)
*/
public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
if (mMultiChoiceModeCallback == null) {
mMultiChoiceModeCallback = new MultiChoiceModeWrapper();
}
mMultiChoiceModeCallback.setWrapped(listener);
}
/**
* @param listener
*/
public void setCustomCheckedListener(CustomChoiceListener listener) {
mCustomChoiceListener = listener;
}
@@ -524,12 +446,79 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
}
}
public static void setViewChecked(View view, boolean checked) {
if (view instanceof Checkable) {
((Checkable) view).setChecked(checked);
} else {
view.setActivated(checked);
private void init(Context context) {
float scale = context.getResources().getDisplayMetrics().density;
// g (m/s^2)
// inch/meter
// A context-specific coefficient adjusted to physical values.
// look and feel tuning
pageScrollThreshold = scale * 80;
}
@Override
public Parcelable onSaveInstanceState() {
final SavedState ss = new SavedState(super.onSaveInstanceState());
ss.choiceMode = mChoiceMode;
ss.customChoice = mCustomChoice;
ss.checkedItemCount = mCheckedItemCount;
ss.checkState = mCheckStates;
ss.checkIdState = mCheckedIdStates;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChoiceMode(ss.choiceMode);
mCustomChoice = ss.customChoice;
mCheckedItemCount = ss.checkedItemCount;
if (ss.checkState != null) {
mCheckStates = ss.checkState;
}
if (ss.checkIdState != null) {
mCheckedIdStates = ss.checkIdState;
}
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mCheckedItemCount > 0) {
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
updateOnScreenCheckedViews();
}
/**
* A MultiChoiceModeListener receives events for {@link android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
* It acts as the {@link android.view.ActionMode.Callback} for the selection mode and also receives
* {@link #onItemCheckedStateChanged(android.view.ActionMode, int, long, boolean)} events when the user
* selects and deselects list items.
*/
public interface MultiChoiceModeListener extends ActionMode.Callback {
/**
* Called when an item is checked or unchecked during selection mode.
*
* @param mode The {@link android.view.ActionMode} providing the selection mode
* @param position Adapter position of the item that was checked or unchecked
* @param id Adapter ID of the item that was checked or unchecked
* @param checked <code>true</code> if the item is now checked, <code>false</code>
* if the item is now unchecked.
*/
void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked);
}
/**
* Custom checked
*/
public interface CustomChoiceListener {
void onIntoCustomChoice(EasyRecyclerView view);
void onOutOfCustomChoice(EasyRecyclerView view);
void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked);
}
/**
@@ -549,13 +538,23 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
public static final SavedState EMPTY_STATE = new SavedState() {
};
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
int choiceMode;
boolean customChoice;
int checkedItemCount;
SparseBooleanArray checkState;
LongSparseArray<Integer> checkIdState;
// This keeps the parent(RecyclerView)'s state
Parcelable mSuperState;
@@ -618,81 +617,11 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
public Parcelable getSuperState() {
return mSuperState;
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
@Override
public Parcelable onSaveInstanceState() {
final SavedState ss = new SavedState(super.onSaveInstanceState());
ss.choiceMode = mChoiceMode;
ss.customChoice = mCustomChoice;
ss.checkedItemCount = mCheckedItemCount;
ss.checkState = mCheckStates;
ss.checkIdState = mCheckedIdStates;
return ss;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setChoiceMode(ss.choiceMode);
mCustomChoice = ss.customChoice;
mCheckedItemCount = ss.checkedItemCount;
if (ss.checkState != null) {
mCheckStates = ss.checkState;
}
if (ss.checkIdState != null) {
mCheckedIdStates = ss.checkIdState;
}
if (mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mCheckedItemCount > 0) {
mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
}
updateOnScreenCheckedViews();
}
/**
* A MultiChoiceModeListener receives events for {@link android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.
* It acts as the {@link android.view.ActionMode.Callback} for the selection mode and also receives
* {@link #onItemCheckedStateChanged(android.view.ActionMode, int, long, boolean)} events when the user
* selects and deselects list items.
*/
public interface MultiChoiceModeListener extends ActionMode.Callback {
/**
* Called when an item is checked or unchecked during selection mode.
*
* @param mode The {@link android.view.ActionMode} providing the selection mode
* @param position Adapter position of the item that was checked or unchecked
* @param id Adapter ID of the item that was checked or unchecked
* @param checked <code>true</code> if the item is now checked, <code>false</code>
* if the item is now unchecked.
*/
void onItemCheckedStateChanged(ActionMode mode,
int position, long id, boolean checked);
}
class MultiChoiceModeWrapper implements MultiChoiceModeListener {
private MultiChoiceModeListener mWrapped;
public void setWrapped(MultiChoiceModeListener wrapped) {
mWrapped = wrapped;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean hasWrappedCallback() {
@@ -743,16 +672,4 @@ public class EasyRecyclerView extends ScrollLessRecyclerView {
}
}
}
/**
* Custom checked
*/
public interface CustomChoiceListener {
void onIntoCustomChoice(EasyRecyclerView view);
void onOutOfCustomChoice(EasyRecyclerView view);
void onItemCheckedStateChanged(EasyRecyclerView view, int position, long id, boolean checked);
}
}
@@ -28,9 +28,9 @@ import androidx.annotation.NonNull;
public class HandlerDrawable extends Drawable {
private int mColor = Color.BLACK;
private final Paint mPaint;
private final RectF mTemp = new RectF();
private int mColor = Color.BLACK;
public HandlerDrawable() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -35,13 +35,10 @@ import androidx.recyclerview.widget.RecyclerView;
public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
private final Rect mRect;
private final Paint mPaint;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
private int mOrientation;
private int mThickness;
private int mPaddingStart = 0;
@@ -1,349 +0,0 @@
package com.hippo.easyrecyclerview;
import android.annotation.SuppressLint;
import android.content.Context;
import android.hardware.SensorManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.BridgeRecyclerView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
public class ScrollLessRecyclerView extends BridgeRecyclerView {
private boolean isScrollBefore = false;
private int initialTouchX;
private int initialTouchY;
// A context-specific coefficient adjusted to physical values.
private float mPhysicalCoeff;
// Fling friction
private final float mFlingFriction = ViewConfiguration.getScrollFriction();
private static final float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));
public float pageScrollThreshold;
private int defaultOverflowItemScrollKeep;
private boolean enableScroll = false;
public ScrollLessRecyclerView(@NonNull Context context) {
super(context);
init(context);
}
public ScrollLessRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ScrollLessRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
float scale = context.getResources().getDisplayMetrics().density;
final float ppi = scale * 160.0f;
mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)
* 39.37f // inch/meter
* ppi
* 0.84f; // look and feel tuning
pageScrollThreshold = scale * 80;
}
public void setPageScrollThreshold(float pageScrollThreshold) {
this.pageScrollThreshold = pageScrollThreshold;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent e) {
if (enableScroll) {
return super.onTouchEvent(e);
}
boolean superTouchResult = super.onTouchEvent(e);
if (superTouchResult) {
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
initialTouchX = (int) (e.getX() + 0.5f);
initialTouchY = (int) (e.getY() + 0.5f);
break;
case MotionEvent.ACTION_MOVE:
if (initialTouchX < 0 || initialTouchY < 0) {
initialTouchX = (int) (e.getX() + 0.5f);
initialTouchY = (int) (e.getY() + 0.5f);
}
break;
case MotionEvent.ACTION_UP:
if (isScrollBefore) {
int moveDistanceX = (int) (initialTouchX - e.getX() + flingMoveDistanceX);
int moveDistanceY = (int) (initialTouchY - e.getY() + flingMoveDistanceY);
onTouchScroll(moveDistanceX, moveDistanceY);
initialTouchX = initialTouchY = -1;
}
break;
}
isScrollBefore = getScrollState() == SCROLL_STATE_DRAGGING;
}
return superTouchResult;
}
@Override
public boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
if (enableScroll) {
return super.scrollByInternal(x, y, ev, type);
}
//disable drag scroll effect,e-ink screen don't need it
return false;
}
double flingMoveDistanceX;
double flingMoveDistanceY;
@Override
public boolean fling(int velocityX, int velocityY) {
if (enableScroll) {
return super.fling(velocityX, velocityY);
}
flingMoveDistanceX = getSplineFlingDistance(velocityX) * Math.signum(velocityX);
flingMoveDistanceY = getSplineFlingDistance(velocityY) * Math.signum(velocityY);
//disable fling scroll effect,e-ink screen don't need it
return false;
}
boolean canScrollVertical = true;
boolean canScrollHorizontally = true;
protected void onTouchScroll(int dx, int dy) {
canScrollVertical = true;
canScrollHorizontally = true;
LayoutManager layout = getLayoutManager();
if (layout != null) {
if (dy > pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
canScrollVertical = false;
onScrollDown();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
canScrollHorizontally = false;
onScrollRight();
}
} else if (dy < -pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
canScrollVertical = false;
onScrollUp();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
canScrollHorizontally = false;
onScrollLeft();
}
}
if (dx > pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
onScrollDown();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
onScrollRight();
}
} else if (dx < -pageScrollThreshold) {
if (canScrollVertical && layout.canScrollVertically()) {
onScrollUp();
} else if (canScrollHorizontally && layout.canScrollHorizontally()) {
onScrollLeft();
}
}
}
}
protected void onScrollLeft() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int firstVisiblePosition = realLayout.findFirstVisibleItemPosition();
ViewHolder firstVisibleItem = findViewHolderForAdapterPosition(firstVisiblePosition);
if (firstVisibleItem == null) {
return;
}
//when the item width overscreen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (firstVisibleItem.itemView.getMeasuredWidth() > getMeasuredWidth()) {
super.scrollByInternal(-getMeasuredWidth() + defaultOverflowItemScrollKeep, 0, null, ViewCompat.TYPE_TOUCH);
} else if (firstVisibleItem.itemView.getMeasuredWidth() == getMeasuredWidth()) {
super.scrollByInternal(-getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(firstVisibleItem.itemView.getRight() - getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] firstVisiblePosition = realLayout.findFirstVisibleItemPositions(null);
ViewHolder[] firstVisibleItems = new ViewHolder[firstVisiblePosition.length];
for (int i = 0; i < firstVisiblePosition.length; i++) {
firstVisibleItems[i] = findViewHolderForAdapterPosition(firstVisiblePosition[i]);
}
int scrollDistance = Integer.MAX_VALUE;
for (ViewHolder firstVisibleItem : firstVisibleItems) {
if (firstVisibleItem != null) {
if (firstVisibleItem.itemView.getRight() < scrollDistance) {
scrollDistance = firstVisibleItem.itemView.getRight();
}
}
}
if (scrollDistance == getMeasuredWidth()) {
scrollDistance = 0;
}
super.scrollByInternal(scrollDistance - getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollRight() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int lastVisiblePosition = realLayout.findLastVisibleItemPosition();
ViewHolder lastVisibleItem = findViewHolderForAdapterPosition(lastVisiblePosition);
if (lastVisibleItem == null) {
return;
}
//when the item width overflow screen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (lastVisibleItem.itemView.getMeasuredWidth() > getMeasuredWidth()) {
super.scrollByInternal(getMeasuredWidth() - defaultOverflowItemScrollKeep, 0, null, ViewCompat.TYPE_TOUCH);
} else if (lastVisibleItem.itemView.getMeasuredWidth() == getMeasuredWidth()) {
super.scrollByInternal(getMeasuredWidth(), 0, null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(lastVisibleItem.itemView.getLeft(), 0, null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] lastVisiblePosition = realLayout.findLastVisibleItemPositions(null);
ViewHolder[] lastVisibleItems = new ViewHolder[lastVisiblePosition.length];
for (int i = 0; i < lastVisiblePosition.length; i++) {
lastVisibleItems[i] = findViewHolderForAdapterPosition(lastVisiblePosition[i]);
}
int scrollDistance = 0;
for (ViewHolder lastVisibleItem : lastVisibleItems) {
if (lastVisibleItem != null) {
if (lastVisibleItem.itemView.getLeft() > scrollDistance) {
scrollDistance = lastVisibleItem.itemView.getLeft();
}
}
}
if (scrollDistance == 0) {
scrollDistance = getMeasuredWidth();
}
super.scrollByInternal(scrollDistance, 0, null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollUp() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int firstVisiblePosition = realLayout.findFirstVisibleItemPosition();
ViewHolder firstVisibleItem = findViewHolderForAdapterPosition(firstVisiblePosition);
if (firstVisibleItem == null) {
return;
}
//when the item height overscreen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (firstVisibleItem.itemView.getMeasuredHeight() > getMeasuredHeight()) {
super.scrollByInternal(0, -getMeasuredHeight() + defaultOverflowItemScrollKeep, null, ViewCompat.TYPE_TOUCH);
} else if (firstVisibleItem.itemView.getMeasuredHeight() == getMeasuredHeight()) {
super.scrollByInternal(0, -getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(0, firstVisibleItem.itemView.getBottom() - getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] firstVisiblePosition = realLayout.findFirstVisibleItemPositions(null);
ViewHolder[] firstVisibleItems = new ViewHolder[firstVisiblePosition.length];
for (int i = 0; i < firstVisiblePosition.length; i++) {
firstVisibleItems[i] = findViewHolderForAdapterPosition(firstVisiblePosition[i]);
}
int scrollDistance = Integer.MAX_VALUE;
for (ViewHolder firstVisibleItem : firstVisibleItems) {
if (firstVisibleItem != null) {
if (firstVisibleItem.itemView.getBottom() < scrollDistance) {
scrollDistance = firstVisibleItem.itemView.getBaseline();
}
}
}
if (scrollDistance == getMeasuredWidth()) {
scrollDistance = 0;
}
super.scrollByInternal(0, scrollDistance - getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
}
}
protected void onScrollDown() {
LayoutManager layout = getLayoutManager();
if (layout instanceof LinearLayoutManager) {
LinearLayoutManager realLayout = (LinearLayoutManager) layout;
int lastVisiblePosition = realLayout.findLastVisibleItemPosition();
ViewHolder lastVisibleItem = findViewHolderForAdapterPosition(lastVisiblePosition);
if (lastVisibleItem == null) {
return;
}
//when the item height overflow screen, findLastCompletelyVisibleItemPosition always return NO_POSITION
if (lastVisibleItem.itemView.getMeasuredHeight() > getMeasuredHeight()) {
super.scrollByInternal(0, getMeasuredHeight() - defaultOverflowItemScrollKeep, null, ViewCompat.TYPE_TOUCH);
} else if (lastVisibleItem.itemView.getMeasuredHeight() == getMeasuredHeight()) {
super.scrollByInternal(0, getMeasuredHeight(), null, ViewCompat.TYPE_TOUCH);
} else {
super.scrollByInternal(0, lastVisibleItem.itemView.getTop(), null, ViewCompat.TYPE_TOUCH);
}
} else if (layout instanceof StaggeredGridLayoutManager) {
StaggeredGridLayoutManager realLayout = (StaggeredGridLayoutManager) layout;
int[] lastVisiblePosition = realLayout.findLastVisibleItemPositions(null);
ViewHolder[] lastVisibleItems = new ViewHolder[lastVisiblePosition.length];
for (int i = 0; i < lastVisiblePosition.length; i++) {
lastVisibleItems[i] = findViewHolderForAdapterPosition(lastVisiblePosition[i]);
}
int scrollDistance = 0;
for (ViewHolder lastVisibleItem : lastVisibleItems) {
if (lastVisibleItem != null) {
if (lastVisibleItem.itemView.getTop() > scrollDistance) {
scrollDistance = lastVisibleItem.itemView.getTop();
}
}
}
if (scrollDistance == 0) {
scrollDistance = getMeasuredHeight();
}
super.scrollByInternal(0, scrollDistance, null, ViewCompat.TYPE_TOUCH);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
defaultOverflowItemScrollKeep = (int) (h * 0.05f);
}
private double getSplineDeceleration(int velocity) {
return Math.log(0.35f * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));
}
private double getSplineFlingDistance(int velocity) {
final double l = getSplineDeceleration(velocity);
final double decelMinusOne = DECELERATION_RATE - 1.0;
return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);
}
public void setEnableScroll(boolean enableScroll) {
this.enableScroll = enableScroll;
}
public boolean isEnableScroll() {
return enableScroll;
}
}
@@ -1,55 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer;
import android.app.Application;
import com.hippo.scene.SceneFragment;
import com.microsoft.appcenter.AppCenter;
import com.microsoft.appcenter.crashes.Crashes;
import com.microsoft.appcenter.distribute.Distribute;
import java.util.HashMap;
import java.util.Map;
public final class Analytics {
public static void start(Application application) {
AppCenter.start(application, "1aa06805-037c-4f3c-8aaa-e23d1433986d", com.microsoft.appcenter.analytics.Analytics.class, Crashes.class, Distribute.class);
AppCenter.setUserId(Settings.getUserID());
com.microsoft.appcenter.analytics.Analytics.setEnabled(Settings.getEnableAnalytics());
Crashes.setEnabled(!BuildConfig.DEBUG);
Distribute.setEnabledForDebuggableBuild(false);
}
public static boolean isEnabled() {
return AppCenter.isConfigured() && Settings.getEnableAnalytics();
}
public static void setEnabled(boolean enabled) {
com.microsoft.appcenter.analytics.Analytics.setEnabled(enabled);
}
public static void onSceneView(SceneFragment scene) {
if (isEnabled()) {
Map<String, String> properties = new HashMap<>();
properties.put("scene_simple_class", scene.getClass().getSimpleName());
properties.put("scene_class", scene.getClass().getName());
com.microsoft.appcenter.analytics.Analytics.trackEvent("scene_view", properties);
}
}
}
@@ -37,6 +37,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.collection.LruCache;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.Native;
import com.hippo.beerbelly.SimpleDiskCache;
import com.hippo.conaco.Conaco;
@@ -296,11 +297,6 @@ public class EhApplication extends SceneApplication {
return application.mFavouriteStatusRouter;
}
@NonNull
public static String getDeveloperEmail() {
return "nekoworkshop$protonmail.com".replace('$', '@');
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
@@ -340,8 +336,6 @@ public class EhApplication extends SceneApplication {
EhDB.mergeOldDB(this);
}
Analytics.start(this);
LocaleDelegate.setDefaultLocale(Settings.getLocale());
DayNightDelegate.setApplicationContext(this);
DayNightDelegate.setDefaultNightMode(Settings.getTheme());
@@ -431,7 +425,7 @@ public class EhApplication extends SceneApplication {
Activity activity = getTopActivity();
if (activity != null) {
activity.runOnUiThread(() -> {
AlertDialog dialog = new AlertDialog.Builder(activity)
AlertDialog dialog = new MaterialAlertDialogBuilder(activity)
.setMessage(Html.fromHtml(html))
.setPositiveButton(android.R.string.ok, null)
.create();
@@ -594,14 +594,6 @@ public class Settings {
putString(KEY_EXCLUDED_LANGUAGES, value);
}
public static void putEInkMode(boolean value) {
putBoolean(KEY_E_INK_MODE, value);
}
public static boolean getEInkMode() {
return getBoolean(KEY_E_INK_MODE, DEFAULT_E_INK_MODE);
}
public static boolean getMeteredNetworkWarning() {
return getBoolean(KEY_METERED_NETWORK_WARNING, DEFAULT_METERED_NETWORK_WARNING);
}
@@ -19,13 +19,11 @@ package com.hippo.ehviewer;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.text.TextUtils;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.browser.customtabs.CustomTabColorSchemeParams;
import androidx.browser.customtabs.CustomTabsIntent;
import com.hippo.ehviewer.client.EhUrlOpener;
@@ -36,8 +34,6 @@ import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.Announcer;
import com.hippo.scene.StageActivity;
import rikka.core.res.ResourcesKt;
public final class UrlOpener {
private UrlOpener() {
@@ -92,14 +88,8 @@ public final class UrlOpener {
}
}
boolean isNight = (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0;
CustomTabsIntent.Builder customTabsIntent = new CustomTabsIntent.Builder();
customTabsIntent.setShowTitle(true);
CustomTabColorSchemeParams params = new CustomTabColorSchemeParams.Builder()
.setToolbarColor(ResourcesKt.resolveColor(context.getTheme(), R.attr.toolbarColor))
.build();
customTabsIntent.setDefaultColorSchemeParams(params);
customTabsIntent.setColorScheme(isNight ? CustomTabsIntent.COLOR_SCHEME_DARK : CustomTabsIntent.COLOR_SCHEME_LIGHT);
try {
customTabsIntent.build().launchUrl(context, uri);
} catch (ActivityNotFoundException e) {
@@ -15,14 +15,13 @@ public class WindowInsetsAnimationHelper extends WindowInsetsAnimationCompat.Cal
private final View[] views;
private final HashMap<View, Integer> startPaddings = new HashMap<>();
private final HashMap<View, Integer> endPaddings = new HashMap<>();
WindowInsetsAnimationCompat animation;
public WindowInsetsAnimationHelper(int dispatchMode, View... views) {
super(dispatchMode);
this.views = views;
}
WindowInsetsAnimationCompat animation;
@Override
public void onPrepare(@NonNull WindowInsetsAnimationCompat animation) {
super.onPrepare(animation);
@@ -36,6 +36,7 @@ import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.ehviewer.client.exception.NoHAtHClientException;
import com.hippo.ehviewer.client.exception.ParseException;
import com.hippo.ehviewer.client.parser.ArchiveParser;
import com.hippo.ehviewer.client.parser.EventPaneParser;
import com.hippo.ehviewer.client.parser.FavoritesParser;
import com.hippo.ehviewer.client.parser.ForumsParser;
import com.hippo.ehviewer.client.parser.GalleryApiParser;
@@ -48,7 +49,6 @@ import com.hippo.ehviewer.client.parser.GalleryTokenApiParser;
import com.hippo.ehviewer.client.parser.ProfileParser;
import com.hippo.ehviewer.client.parser.RateGalleryParser;
import com.hippo.ehviewer.client.parser.SignInParser;
import com.hippo.ehviewer.client.parser.EventPaneParser;
import com.hippo.ehviewer.client.parser.TorrentParser;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.client.parser.VoteTagParser;
@@ -971,7 +971,7 @@ public class EhEngine {
try {
Response response = call.execute();
Log.d(TAG, "" + response.request().url().toString());
Log.d(TAG, "" + response.request().url());
code = response.code();
headers = response.headers();
@@ -56,9 +56,9 @@ import okio.Okio;
public class EhTagDatabase {
private static final Map<String, String> NAMESPACE_TO_PREFIX = new HashMap<>();
private static volatile EhTagDatabase instance;
// TODO more lock for different language
private static final Lock lock = new ReentrantLock();
private static volatile EhTagDatabase instance;
static {
NAMESPACE_TO_PREFIX.put("artist", "a:");
@@ -36,40 +36,29 @@ public class EhUrl {
public static final String DOMAIN_LOFI = "lofi.e-hentai.org";
public static final String HOST_EX = "https://" + DOMAIN_EX + "/";
public static final String HOST_E = "https://" + DOMAIN_E + "/";
public static final String API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01";
public static final String API_E = HOST_E + "api.php";
public static final String API_EX = HOST_EX + "api.php";
public static final String URL_FAVORITES_EX = HOST_EX + "favorites.php";
public static final String URL_UCONFIG_EX = HOST_EX + "uconfig.php";
public static final String URL_MY_TAGS_EX = HOST_EX + "mytags";
public static final String URL_WATCHED_EX = HOST_EX + "watched";
public static final String HOST_E = "https://" + DOMAIN_E + "/";
public static final String API_E = HOST_E + "api.php";
public static final String URL_FAVORITES_E = HOST_E + "favorites.php";
public static final String URL_UCONFIG_E = HOST_E + "uconfig.php";
public static final String URL_MY_TAGS_E = HOST_E + "mytags";
public static final String URL_WATCHED_E = HOST_E + "watched";
public static final String API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01";
public static final String URL_POPULAR_E = "https://e-hentai.org/popular";
public static final String URL_POPULAR_EX = "https://exhentai.org/popular";
public static final String URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php";
public static final String URL_IMAGE_SEARCH_EX = "https://exhentai.org/upload/image_lookup.php";
public static final String URL_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login";
public static final String URL_REGISTER = "https://forums.e-hentai.org/index.php?act=Reg&CODE=00";
public static final String URL_FAVORITES_E = HOST_E + "favorites.php";
public static final String URL_FAVORITES_EX = HOST_EX + "favorites.php";
public static final String URL_FORUMS = "https://forums.e-hentai.org/";
public static final String REFERER_EX = "https://" + DOMAIN_EX;
public static final String REFERER_E = "https://" + DOMAIN_E;
public static final String ORIGIN_EX = REFERER_EX;
public static final String REFERER_E = "https://" + DOMAIN_E;
public static final String ORIGIN_E = REFERER_E;
public static final String URL_UCONFIG_E = HOST_E + "uconfig.php";
public static final String URL_UCONFIG_EX = HOST_EX + "uconfig.php";
public static final String URL_MY_TAGS_E = HOST_E + "mytags";
public static final String URL_MY_TAGS_EX = HOST_EX + "mytags";
public static final String URL_WATCHED_E = HOST_E + "watched";
public static final String URL_WATCHED_EX = HOST_EX + "watched";
private static final String URL_PREFIX_THUMB_E = "https://ehgt.org/";
private static final String URL_PREFIX_THUMB_EX = "https://exhentai.org/t/";
@@ -40,9 +40,9 @@ import okhttp3.HttpUrl;
public class IdentityCookiePreference extends MessagePreference {
private String message;
@SuppressLint("StaticFieldLeak")
private final SettingsActivity mActivity;
private String message;
public IdentityCookiePreference(Context context) {
super(context);
@@ -143,6 +143,7 @@ public final class SpiderQueen implements Runnable {
private final int mWorkerMaxCount;
private final int mPreloadNumber;
private final int mDownloadDelay;
private final AtomicReference<String> showKey = new AtomicReference<>();
private int mReadReference = 0;
private int mDownloadReference = 0;
// It mQueenThread is null, failed or stopped
@@ -153,7 +154,6 @@ public final class SpiderQueen implements Runnable {
private volatile int[] mPageStateArray;
// For download, when it go to mPageStateArray.size(), done
private volatile int mDownloadPage = -1;
private final AtomicReference<String> showKey = new AtomicReference<>();
private SpiderQueen(EhApplication application, @NonNull GalleryInfo galleryInfo) {
mHttpClient = EhApplication.getOkHttpClient(application);
@@ -20,7 +20,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.view.Window;
import android.view.WindowInsets;
@@ -47,10 +46,8 @@ public abstract class EhActivity extends MaterialActivity {
}
public static String getTheme(Context context) {
if (isBlackNightTheme()
&& isNightMode(context.getResources().getConfiguration()))
if (isBlackNightTheme() && isNightMode(context.getResources().getConfiguration()))
return THEME_BLACK;
return THEME_DEFAULT;
}
@@ -70,26 +67,21 @@ public abstract class EhActivity extends MaterialActivity {
@StyleRes
public int getThemeStyleRes(Context context) {
switch (getTheme(context)) {
case THEME_BLACK:
return R.style.ThemeOverlay_Black;
case THEME_DEFAULT:
default:
return R.style.ThemeOverlay;
if (THEME_BLACK.equals(getTheme(context))) {
return R.style.ThemeOverlay_Black;
}
return R.style.ThemeOverlay;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
((EhApplication) getApplication()).registerActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
((EhApplication) getApplication()).unregisterActivity(this);
}
@@ -114,14 +106,10 @@ public abstract class EhActivity extends MaterialActivity {
WindowInsets rootWindowInsets = window.getDecorView().getRootWindowInsets();
if (rootWindowInsets != null && rootWindowInsets.getSystemWindowInsetBottom() >= Resources.getSystem().getDisplayMetrics().density * 40) {
window.setNavigationBarColor(ResourcesKt.resolveColor(getTheme(), android.R.attr.navigationBarColor) & 0x00ffffff | -0x20000000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(false);
}
window.setNavigationBarContrastEnforced(false);
} else {
window.setNavigationBarColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
window.setNavigationBarContrastEnforced(true);
}
});
}
@@ -65,6 +65,7 @@ import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.R;
@@ -129,6 +130,36 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private GalleryInfo mGalleryInfo;
private int mPage;
private String mCacheFileName;
ActivityResultLauncher<String> saveImageToLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
uri -> {
if (uri != null) {
String filepath = AppConfig.getExternalTempDir() + File.separator + mCacheFileName;
File cachefile = new File(filepath);
ContentResolver resolver = getContentResolver();
IoThreadPoolExecutor.getInstance().execute(() -> {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(cachefile);
os = resolver.openOutputStream(uri);
IOUtils.copy(is, os);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
runOnUiThread(() -> Toast.makeText(GalleryActivity.this, getString(R.string.image_saved, uri.getPath()), Toast.LENGTH_SHORT).show());
}
//noinspection ResultOfMethodCallIgnored
cachefile.delete();
});
}
});
@Nullable
private GLRootView mGLRootView;
@Nullable
@@ -190,50 +221,8 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private int mLayoutMode;
private int mSize;
private int mCurrentIndex;
private int mSavingPage = -1;
ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
ActivityResultLauncher<String> saveImageToLauncher = registerForActivityResult(
new ActivityResultContracts.CreateDocument(),
uri -> {
if (uri != null) {
String filepath = AppConfig.getExternalTempDir() + File.separator + mCacheFileName;
File cachefile = new File(filepath);
ContentResolver resolver = getContentResolver();
IoThreadPoolExecutor.getInstance().execute(() -> {
InputStream is = null;
OutputStream os = null;
try {
is = new FileInputStream(cachefile);
os = resolver.openOutputStream(uri);
IOUtils.copy(is, os);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
runOnUiThread(() -> Toast.makeText(GalleryActivity.this, getString(R.string.image_saved, uri.getPath()), Toast.LENGTH_SHORT).show());
}
//noinspection ResultOfMethodCallIgnored
cachefile.delete();
});
}
});
private void buildProvider() {
if (mGalleryProvider != null) {
return;
@@ -261,7 +250,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
mGalleryProvider = new ArchiveGalleryProvider(this, mUri);
}
}
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
private void onInit() {
Intent intent = getIntent();
@@ -981,7 +979,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private void showPageDialog(final int page) {
Resources resources = GalleryActivity.this.getResources();
AlertDialog.Builder builder = new AlertDialog.Builder(GalleryActivity.this);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
builder.setTitle(resources.getString(R.string.page_menu_title, page + 1));
final CharSequence[] items;
@@ -1022,6 +1020,29 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
});
}
@Nullable
private String getGalleryDetailUrl() {
long gid;
String token;
if (mGalleryInfo != null) {
gid = mGalleryInfo.gid;
token = mGalleryInfo.token;
} else {
return null;
}
return EhUrl.getGalleryDetailUrl(gid, token, 0, false);
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl();
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
}
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
private final View mView;
@@ -1194,7 +1215,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
private void onTapMenuArea() {
AlertDialog.Builder builder = new AlertDialog.Builder(GalleryActivity.this);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(GalleryActivity.this);
GalleryMenuHelper helper = new GalleryMenuHelper(builder.getContext());
builder.setTitle(R.string.gallery_menu_title)
.setView(helper.getView())
@@ -1282,26 +1303,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
@Nullable
private String getGalleryDetailUrl() {
long gid;
String token;
if (mGalleryInfo != null) {
gid = mGalleryInfo.gid;
token = mGalleryInfo.token;
} else {
return null;
}
return EhUrl.getGalleryDetailUrl(gid, token, 0, false);
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl();
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
}
}
@@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.net.ConnectivityManager;
import android.net.Uri;
@@ -33,7 +32,6 @@ import android.text.TextUtils;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
@@ -43,10 +41,9 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.drawerlayout.widget.DrawerLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.EditTextDialogBuilder;
@@ -59,7 +56,6 @@ import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser;
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser;
import com.hippo.ehviewer.ui.scene.AnalyticsScene;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.ehviewer.ui.scene.CookieSignInScene;
import com.hippo.ehviewer.ui.scene.DownloadsScene;
@@ -86,7 +82,6 @@ import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
import com.hippo.widget.DrawerView;
import com.hippo.widget.LoadImageView;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.SimpleHandler;
import com.hippo.yorozuya.ViewUtils;
@@ -97,8 +92,6 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.Map;
import rikka.material.app.DayNightDelegate;
public final class MainActivity extends StageActivity
implements NavigationView.OnNavigationItemSelectedListener {
@@ -107,7 +100,6 @@ public final class MainActivity extends StageActivity
static {
registerLaunchMode(SecurityScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(WarningScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(AnalyticsScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(SignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(WebViewSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
registerLaunchMode(CookieSignInScene.class, SceneFragment.LAUNCH_MODE_SINGLE_TASK);
@@ -141,10 +133,6 @@ public final class MainActivity extends StageActivity
private NavigationView mNavView;
@Nullable
private DrawerView mRightDrawer;
@Nullable
private LoadImageView mAvatar;
@Nullable
private TextView mDisplayName;
private int mNavCheckedItem = 0;
@Override
@@ -159,8 +147,6 @@ public final class MainActivity extends StageActivity
return new Announcer(SecurityScene.class);
} else if (Settings.getShowWarning()) {
return new Announcer(WarningScene.class);
} else if (Settings.getAskAnalytics()) {
return new Announcer(AnalyticsScene.class);
} else if (EhUtils.needSignedIn(this)) {
return new Announcer(SignInScene.class);
} else if (Settings.getSelectSite()) {
@@ -185,11 +171,6 @@ public final class MainActivity extends StageActivity
newArgs.putString(WarningScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(WarningScene.KEY_TARGET_ARGS, announcer.getArgs());
return new Announcer(WarningScene.class).setArgs(newArgs);
} else if (Settings.getAskAnalytics()) {
Bundle newArgs = new Bundle();
newArgs.putString(AnalyticsScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
newArgs.putBundle(AnalyticsScene.KEY_TARGET_ARGS, announcer.getArgs());
return new Announcer(AnalyticsScene.class).setArgs(newArgs);
} else if (EhUtils.needSignedIn(this)) {
Bundle newArgs = new Bundle();
newArgs.putString(SignInScene.KEY_TARGET_SCENE, announcer.getClazz().getName());
@@ -329,19 +310,6 @@ public final class MainActivity extends StageActivity
mDrawerLayout.setStatusBarBackgroundColor(0);
mNavView = (NavigationView) ViewUtils.$$(this, R.id.nav_view);
mRightDrawer = (DrawerView) ViewUtils.$$(this, R.id.right_drawer);
View headerLayout = mNavView.getHeaderView(0);
mAvatar = (LoadImageView) ViewUtils.$$(headerLayout, R.id.avatar);
mDisplayName = (TextView) ViewUtils.$$(headerLayout, R.id.display_name);
ViewUtils.$$(headerLayout, R.id.night_mode).setOnClickListener(v -> {
int theme = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_YES) > 0 ? AppCompatDelegate.MODE_NIGHT_NO : AppCompatDelegate.MODE_NIGHT_YES;
DayNightDelegate.setDefaultNightMode(theme);
recreate();
if (Settings.getTheme() != AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
Settings.putTheme(theme);
}
});
updateProfile();
if (mNavView != null) {
mNavView.setNavigationItemSelectedListener(this);
@@ -389,7 +357,7 @@ public final class MainActivity extends StageActivity
break;
}
if (hasUnverified) {
new AlertDialog.Builder(this)
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.app_link_not_verified_title)
.setMessage(R.string.app_link_not_verified_message)
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
@@ -409,7 +377,7 @@ public final class MainActivity extends StageActivity
if (null == uniFile || uniFile.ensureDir()) {
return;
}
new AlertDialog.Builder(this)
new MaterialAlertDialogBuilder(this)
.setTitle(R.string.waring)
.setMessage(R.string.invalid_download_location)
.setPositiveButton(R.string.get_it, null)
@@ -449,23 +417,18 @@ public final class MainActivity extends StageActivity
mDrawerLayout = null;
mNavView = null;
mRightDrawer = null;
mAvatar = null;
mDisplayName = null;
}
@Override
protected void onResume() {
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
}
@Override
protected void onTransactScene() {
super.onTransactScene();
checkClipboardUrl();
}
@@ -555,25 +518,6 @@ public final class MainActivity extends StageActivity
}
}
public void updateProfile() {
if (null == mAvatar || null == mDisplayName) {
return;
}
String avatarUrl = Settings.getAvatar();
if (TextUtils.isEmpty(avatarUrl)) {
mAvatar.load(R.drawable.default_avatar);
} else {
mAvatar.load(avatarUrl, avatarUrl);
}
String displayName = Settings.getDisplayName();
if (TextUtils.isEmpty(displayName)) {
displayName = getString(R.string.default_display_name);
}
mDisplayName.setText(displayName);
}
public void addAboveSnackView(View view) {
if (mStageLayout != null) {
mStageLayout.addAboveSnackView(view);
@@ -21,45 +21,19 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
import com.hippo.util.AppHelper;
import com.microsoft.appcenter.distribute.Distribute;
public class AboutFragment extends BasePreferenceFragment {
private static final String KEY_AUTHOR = "author";
//private static final String KEY_DONATE = "donate";
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.about_settings);
Preference author = findPreference(KEY_AUTHOR);
//Preference donate = findPreference(KEY_DONATE);
Preference checkForUpdate = findPreference(KEY_CHECK_FOR_UPDATES);
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
author.setOnPreferenceClickListener(this);
//donate.setOnPreferenceClickListener(this);
checkForUpdate.setOnPreferenceClickListener(this);
checkForUpdate.setVisible(Analytics.isEnabled());
}
@Override
public boolean onPreferenceClick(Preference preference) {
String key = preference.getKey();
if (KEY_AUTHOR.equals(key)) {
AppHelper.sendEmail(requireActivity(), EhApplication.getDeveloperEmail(),
"About EhViewer", null);
} else if (KEY_CHECK_FOR_UPDATES.equals(key)) {
Distribute.checkForUpdate();
}
return true;
}
@Override
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.EhApplication;
@@ -76,7 +77,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
e.printStackTrace();
}
try {
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
.setCancelable(false)
.setView(R.layout.preference_dialog_task)
.show();
@@ -193,7 +194,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
e.printStackTrace();
}
try {
AlertDialog alertDialog = new AlertDialog.Builder(requireActivity())
AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireActivity())
.setCancelable(false)
.setView(R.layout.preference_dialog_task)
.show();
@@ -23,9 +23,9 @@ import android.os.Bundle;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
@@ -99,7 +99,7 @@ public class DownloadFragment extends BasePreferenceFragment {
if (KEY_DOWNLOAD_LOCATION.equals(key)) {
UniFile file = Settings.getDownloadLocation();
if (file != null && !UniFile.isFileUri(Settings.getDownloadLocation().getUri())) {
new AlertDialog.Builder(requireContext())
new MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.settings_download_download_location)
.setMessage(file.getUri().toString())
.setPositiveButton(R.string.pick_new_download_location, (dialogInterface, i) -> openDirPickerL())
@@ -24,6 +24,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
@@ -137,7 +138,7 @@ public class FilterFragment extends BaseFragment {
}
private void showTipDialog() {
new AlertDialog.Builder(requireActivity())
new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
@@ -145,7 +146,7 @@ public class FilterFragment extends BaseFragment {
}
private void showAddFilterDialog() {
AlertDialog dialog = new AlertDialog.Builder(requireActivity())
AlertDialog dialog = new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
@@ -157,7 +158,7 @@ public class FilterFragment extends BaseFragment {
private void showDeleteFilterDialog(final Filter filter) {
String message = getString(R.string.delete_filter, filter.text);
new AlertDialog.Builder(requireActivity())
new MaterialAlertDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete, (dialog, which) -> {
if (DialogInterface.BUTTON_POSITIVE != which || null == mFilterList) {
@@ -173,6 +174,25 @@ public class FilterFragment extends BaseFragment {
.show();
}
@Override
public int getFragmentTitle() {
return R.string.filter;
}
private static class FilterHolder extends RecyclerView.ViewHolder {
private final MaterialCheckBox checkbox;
private final TextView text;
private final ImageView delete;
public FilterHolder(View itemView) {
super(itemView);
checkbox = itemView.findViewById(R.id.checkbox);
text = itemView.findViewById(R.id.text);
delete = itemView.findViewById(R.id.delete);
}
}
private class AddFilterDialogHelper implements View.OnClickListener {
@Nullable
@@ -234,20 +254,6 @@ public class FilterFragment extends BaseFragment {
}
}
private static class FilterHolder extends RecyclerView.ViewHolder {
private final MaterialCheckBox checkbox;
private final TextView text;
private final ImageView delete;
public FilterHolder(View itemView) {
super(itemView);
checkbox = itemView.findViewById(R.id.checkbox);
text = itemView.findViewById(R.id.text);
delete = itemView.findViewById(R.id.delete);
}
}
private class FilterAdapter extends RecyclerView.Adapter<FilterHolder> {
private static final int TYPE_ITEM = 0;
@@ -456,9 +462,4 @@ public class FilterFragment extends BaseFragment {
mEhFilter.triggerFilter(filter);
}
}
@Override
public int getFragmentTitle() {
return R.string.filter;
}
}
@@ -16,6 +16,7 @@ import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
@@ -117,6 +118,11 @@ public class HostsFragment extends BaseFragment
adapter.notifyDataSetChanged();
}
@Override
public int getFragmentTitle() {
return R.string.hosts;
}
public abstract static class HostDialogFragment extends DialogFragment {
private HostsFragment hostsFragment;
@@ -135,7 +141,7 @@ public class HostsFragment extends BaseFragment
ip.setText(arguments.getString(KEY_IP));
}
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()).setView(view);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext()).setView(view);
onCreateDialogBuilder(builder);
AlertDialog dialog = builder.create();
dialog.setOnShowListener(d -> onCreateDialog((AlertDialog) d));
@@ -265,9 +271,4 @@ public class HostsFragment extends BaseFragment
return data.size();
}
}
@Override
public int getFragmentTitle() {
return R.string.hosts;
}
}
@@ -72,6 +72,11 @@ public class MyTagsFragment extends BaseFragment {
}
}
@Override
public int getFragmentTitle() {
return R.string.my_tags;
}
private class MyTagsWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
@@ -89,9 +94,4 @@ public class MyTagsFragment extends BaseFragment {
progress.setVisibility(View.GONE);
}
}
@Override
public int getFragmentTitle() {
return R.string.my_tags;
}
}
@@ -6,7 +6,6 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
@@ -20,9 +19,6 @@ public class PrivacyFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.privacy_settings);
Preference enableAnalytics = findPreference(Settings.KEY_ENABLE_ANALYTICS);
enableAnalytics.setOnPreferenceChangeListener(this);
}
@Override
@@ -34,17 +30,6 @@ public class PrivacyFragment extends BasePreferenceFragment {
R.string.settings_privacy_pattern_protection_set);
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
if (Settings.KEY_ENABLE_ANALYTICS.equals(key)) {
if (newValue instanceof Boolean && (Boolean) newValue) {
Analytics.start(getActivity().getApplication());
}
}
return true;
}
@Override
public int getFragmentTitle() {
return R.string.settings_privacy;
@@ -146,6 +146,16 @@ public class UConfigFragment extends BaseFragment {
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int getFragmentTitle() {
return R.string.u_config;
}
private class UConfigWebViewClient extends WebViewClient {
@Override
@@ -166,14 +176,4 @@ public class UConfigFragment extends BaseFragment {
loaded = true;
}
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public int getFragmentTitle() {
return R.string.u_config;
}
}
@@ -1,96 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.ui.scene;
import android.content.Context;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.text.Html;
import com.hippo.yorozuya.ViewUtils;
public class AnalyticsScene extends SolidScene implements View.OnClickListener {
@Nullable
private View mReject;
@Nullable
private View mAccept;
@Override
public boolean needShowLeftDrawer() {
return false;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.scene_analytics, container, false);
mReject = ViewUtils.$$(view, R.id.reject);
mAccept = ViewUtils.$$(view, R.id.accept);
TextView text = (TextView) ViewUtils.$$(view, R.id.text);
text.setText(Html.fromHtml(getString(R.string.analytics_explain)));
text.setMovementMethod(LinkMovementMethod.getInstance());
mReject.setOnClickListener(this);
mAccept.setOnClickListener(this);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mReject = null;
mAccept = null;
}
@Override
public void onClick(View v) {
Context context = getContext();
MainActivity activity = getMainActivity();
if (context == null || activity == null) {
return;
}
if (mReject == v) {
Settings.putEnableAnalytics(false);
} else if (mAccept == v) {
Settings.putEnableAnalytics(true);
}
Analytics.setEnabled(Settings.getEnableAnalytics());
Settings.putAskAnalytics(false);
// Start new scene and finish it self
startSceneForCheckStep(CHECK_STEP_ANALYTICS, getArguments());
finish();
}
}
@@ -39,10 +39,8 @@ import androidx.core.view.WindowInsetsControllerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentActivity;
import com.hippo.ehviewer.Analytics;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.util.AppHelper;
public abstract class BaseScene extends SceneFragment {
@@ -60,13 +58,6 @@ public abstract class BaseScene extends SceneFragment {
private SparseArray<Parcelable> drawerViewState;
private boolean needWhiteStatusBar = needWhiteStatusBar();
public void updateAvatar() {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
((MainActivity) activity).updateProfile();
}
}
public void addAboveSnackView(View view) {
FragmentActivity activity = getActivity();
if (activity instanceof MainActivity) {
@@ -274,12 +265,6 @@ public abstract class BaseScene extends SceneFragment {
return insetsController;
}
@Override
public void onResume() {
super.onResume();
Analytics.onSceneView(this);
}
@Override
public void onDestroy() {
super.onDestroy();
@@ -30,8 +30,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
@@ -217,7 +217,7 @@ public class CookieSignInScene extends SolidScene implements EditText.OnEditorAc
hideSoftInput();
if (!checkIpbMemberId(ipbMemberId) || !(checkIpbPassHash(ipbPassHash))) {
new AlertDialog.Builder(context).setTitle(R.string.waring)
new MaterialAlertDialogBuilder(context).setTitle(R.string.waring)
.setMessage(R.string.wrong_cookie_warning)
.setPositiveButton(R.string.i_will_check_it, null)
.setNegativeButton(R.string.i_dont_think_so, (dialog, which) -> {
@@ -58,6 +58,7 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
@@ -377,12 +378,12 @@ public class DownloadsScene extends ToolbarScene
mRecyclerView.scrollToPosition(mInitPosition);
mInitPosition = -1;
}
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4,4,4,4, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4, 4, 4, 4, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.fixEdgeEffect(mRecyclerView, false, true);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), R.attr.widgetColorThemeAccent));
handlerDrawable.setColor(com.google.android.material.R.color.material_dynamic_neutral100);
mFastScroller.setHandlerDrawable(handlerDrawable);
mFastScroller.setOnDragHandlerListener(this);
@@ -436,8 +437,8 @@ public class DownloadsScene extends ToolbarScene
TapTargetView.showFor(requireActivity(),
TapTarget.forView(((DownloadHolder) holder).thumb,
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
@@ -465,8 +466,8 @@ public class DownloadsScene extends ToolbarScene
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
.outerCircleColor(R.color.colorPrimary)
.transparentTarget(true),
new TapTargetView.Listener() {
@@ -525,7 +526,7 @@ public class DownloadsScene extends ToolbarScene
int id = item.getItemId();
if (id == R.id.action_filter) {
new AlertDialog.Builder(requireActivity())
new MaterialAlertDialogBuilder(requireActivity())
.setSingleChoiceItems(R.array.download_state, mType + 1, (dialog, which) -> {
mType = which - 1;
updateForLabel();
@@ -548,7 +549,7 @@ public class DownloadsScene extends ToolbarScene
openDrawer(Gravity.RIGHT);
return true;
} else if (id == R.id.action_reset_reading_progress) {
new AlertDialog.Builder(getContext())
new MaterialAlertDialogBuilder(getContext())
.setMessage(R.string.reset_reading_progress_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
@@ -621,7 +622,7 @@ public class DownloadsScene extends ToolbarScene
for (int i = 0, n = list.size(); i < n; i++) {
items[i + 2] = list.get(i).getLabel();
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.default_download_label)
.setItems(items, (dialog1, which) -> {
if (which == 0) {
@@ -821,7 +822,7 @@ public class DownloadsScene extends ToolbarScene
MoveDialogHelper helper = new MoveDialogHelper(labels, downloadInfoList);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
@@ -998,6 +999,73 @@ public class DownloadsScene extends ToolbarScene
}
}
private static class ThumbDataContainer implements DataContainer {
private final DownloadInfo mInfo;
@Nullable
private UniFile mFile;
public ThumbDataContainer(@NonNull DownloadInfo info) {
mInfo = info;
}
private void ensureFile() {
if (mFile == null) {
UniFile dir = SpiderDen.getGalleryDownloadDir(mInfo);
if (dir != null && dir.isDirectory()) {
mFile = dir.createFile(".thumb");
}
}
}
@Override
public boolean isEnabled() {
ensureFile();
return mFile != null;
}
@Override
public void onUrlMoved(String requestUrl, String responseUrl) {
}
@Override
public boolean save(InputStream is, long length, String mediaType, ProgressNotifier notify) {
ensureFile();
if (mFile == null) {
return false;
}
OutputStream os = null;
try {
os = mFile.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
IOUtils.closeQuietly(os);
}
}
@Override
public InputStreamPipe get() {
ensureFile();
if (mFile != null) {
return new UniFileInputStreamPipe(mFile);
} else {
return null;
}
}
@Override
public void remove() {
if (mFile != null) {
mFile.delete();
}
}
}
private class DownloadLabelAdapter extends RecyclerView.Adapter<DownloadLabelHolder> implements DraggableItemAdapter<DownloadLabelHolder> {
private final LayoutInflater mInflater;
@@ -1067,7 +1135,7 @@ public class DownloadsScene extends ToolbarScene
AlertDialog dialog = builder.show();
new RenameLabelDialogHelper(builder, dialog, label);
} else if (itemId == R.id.menu_label_remove) {
new AlertDialog.Builder(requireContext())
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_label_title))
.setMessage(getString(R.string.delete_label_message, label))
.setPositiveButton(R.string.delete, (dialog, which) -> {
@@ -1391,73 +1459,6 @@ public class DownloadsScene extends ToolbarScene
}
}
private static class ThumbDataContainer implements DataContainer {
private final DownloadInfo mInfo;
@Nullable
private UniFile mFile;
public ThumbDataContainer(@NonNull DownloadInfo info) {
mInfo = info;
}
private void ensureFile() {
if (mFile == null) {
UniFile dir = SpiderDen.getGalleryDownloadDir(mInfo);
if (dir != null && dir.isDirectory()) {
mFile = dir.createFile(".thumb");
}
}
}
@Override
public boolean isEnabled() {
ensureFile();
return mFile != null;
}
@Override
public void onUrlMoved(String requestUrl, String responseUrl) {
}
@Override
public boolean save(InputStream is, long length, String mediaType, ProgressNotifier notify) {
ensureFile();
if (mFile == null) {
return false;
}
OutputStream os = null;
try {
os = mFile.openOutputStream();
IOUtils.copy(is, os);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
} finally {
IOUtils.closeQuietly(os);
}
}
@Override
public InputStreamPipe get() {
ensureFile();
if (mFile != null) {
return new UniFileInputStreamPipe(mFile);
} else {
return null;
}
}
@Override
public void remove() {
if (mFile != null) {
mFile.delete();
}
}
}
private class RenameLabelDialogHelper implements View.OnClickListener {
private final EditTextDialogBuilder mBuilder;
@@ -27,7 +27,6 @@ import android.os.Bundle;
import android.text.InputType;
import android.text.TextUtils;
import android.util.SparseBooleanArray;
import android.util.TypedValue;
import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -48,6 +47,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
import com.hippo.app.EditTextDialogBuilder;
@@ -90,7 +90,6 @@ import java.util.Collections;
import java.util.List;
import rikka.core.res.ResourcesKt;
import rikka.recyclerview.RecyclerViewKt;
// TODO Get favorite, modify favorite, add favorite, what a mess!
@SuppressLint("RtlHardcoded")
@@ -120,6 +119,19 @@ public class FavoritesScene extends BaseScene implements
@Nullable
@ViewLifeCircle
private FabLayout mFabLayout;
private final Runnable showNormalFabsRunnable = new Runnable() {
@Override
public void run() {
if (mFabLayout != null) {
mFabLayout.setSecondaryFabVisibilityAt(0, true);
mFabLayout.setSecondaryFabVisibilityAt(1, true);
mFabLayout.setSecondaryFabVisibilityAt(2, true);
mFabLayout.setSecondaryFabVisibilityAt(3, false);
mFabLayout.setSecondaryFabVisibilityAt(4, false);
mFabLayout.setSecondaryFabVisibilityAt(5, false);
}
}
};
@Nullable
@ViewLifeCircle
private FavoritesAdapter mAdapter;
@@ -165,20 +177,6 @@ public class FavoritesScene extends BaseScene implements
// For modify action
private boolean mModifyAdd;
private final Runnable showNormalFabsRunnable = new Runnable() {
@Override
public void run() {
if (mFabLayout != null) {
mFabLayout.setSecondaryFabVisibilityAt(0, true);
mFabLayout.setSecondaryFabVisibilityAt(1, true);
mFabLayout.setSecondaryFabVisibilityAt(2, true);
mFabLayout.setSecondaryFabVisibilityAt(3, false);
mFabLayout.setSecondaryFabVisibilityAt(4, false);
mFabLayout.setSecondaryFabVisibilityAt(5, false);
}
}
};
@Override
public int getNavCheckedItem() {
return R.id.nav_favourite;
@@ -334,8 +332,8 @@ public class FavoritesScene extends BaseScene implements
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
.outerCircleColor(R.color.colorPrimary)
.transparentTarget(true),
new TapTargetView.Listener() {
@@ -438,7 +436,7 @@ public class FavoritesScene extends BaseScene implements
items[1] = getString(R.string.local_favorites);
String[] favCat = Settings.getFavCat();
System.arraycopy(favCat, 0, items, 2, 10);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.default_favorites_collection)
.setItems(items, (dialog, which) -> Settings.putDefaultFavSlot(which - 2)).show();
return true;
@@ -777,7 +775,7 @@ public class FavoritesScene extends BaseScene implements
}
case 4: { // Delete
DeleteDialogHelper helper = new DeleteDialogHelper();
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_favorites_dialog_title)
.setMessage(getString(R.string.delete_favorites_dialog_message, mModifyGiList.size()))
.setPositiveButton(android.R.string.ok, helper)
@@ -791,7 +789,7 @@ public class FavoritesScene extends BaseScene implements
String[] array = new String[11];
array[0] = getString(R.string.local_favorites);
System.arraycopy(Settings.getFavCat(), 0, array, 1, 10);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.move_favorites_dialog_title)
.setItems(array, helper)
.setOnCancelListener(helper)
@@ -60,11 +60,11 @@ abstract class GalleryAdapter extends RecyclerView.Adapter<GalleryHolder> {
private final int mPaddingTopSB;
private final int mListThumbWidth;
private final int mListThumbHeight;
private final boolean mShowFavourited;
private final DownloadManager mDownloadManager;
private MarginItemDecoration mListDecoration;
private MarginItemDecoration mGirdDecoration;
private int mType = TYPE_INVALID;
private final boolean mShowFavourited;
private final DownloadManager mDownloadManager;
public GalleryAdapter(@NonNull LayoutInflater inflater, @NonNull Resources resources,
@NonNull RecyclerView recyclerView, int type, boolean showFavourited) {
@@ -45,7 +45,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
@@ -54,6 +53,7 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
@@ -223,7 +223,7 @@ public final class GalleryCommentsScene extends ToolbarScene
if (itemAnimator instanceof DefaultItemAnimator) {
((DefaultItemAnimator) itemAnimator).setSupportsChangeAnimations(false);
}
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 0,0,0,80, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 0, 0, 0, 80, TypedValue.COMPLEX_UNIT_DIP);
mSendImage.setOnClickListener(this);
mFab.setOnClickListener(this);
@@ -308,7 +308,7 @@ public final class GalleryCommentsScene extends ToolbarScene
}
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
context = builder.getContext();
final LayoutInflater inflater = LayoutInflater.from(context);
EasyRecyclerView rv = (EasyRecyclerView) inflater.inflate(R.layout.dialog_recycler_view, null);
@@ -370,7 +370,7 @@ public final class GalleryCommentsScene extends ToolbarScene
menuId.add(R.id.check_vote_status);
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
if (which < 0 || which >= menuId.size()) {
return;
@@ -471,27 +471,27 @@ public final class GalleryCommentsScene extends ToolbarScene
mFab.animate().x(fabEndX).y(fabEndY).scaleX(0.0f).scaleY(0.0f)
.setInterpolator(AnimationUtils.SLOW_FAST_SLOW_INTERPOLATOR)
.setDuration(300L).setListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (null == mFab || null == mEditPanel) {
return;
}
((View) mFab).setVisibility(View.INVISIBLE);
mEditPanel.setVisibility(View.VISIBLE);
int halfW = mEditPanel.getWidth() / 2;
int halfH = mEditPanel.getHeight() / 2;
Animator animator = ViewAnimationUtils.createCircularReveal(mEditPanel, halfW, halfH, 0,
(float) Math.hypot(halfW, halfH)).setDuration(300L);
animator.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator a) {
mInAnimation = false;
public void onAnimationEnd(Animator animation) {
if (null == mFab || null == mEditPanel) {
return;
}
((View) mFab).setVisibility(View.INVISIBLE);
mEditPanel.setVisibility(View.VISIBLE);
int halfW = mEditPanel.getWidth() / 2;
int halfH = mEditPanel.getHeight() / 2;
Animator animator = ViewAnimationUtils.createCircularReveal(mEditPanel, halfW, halfH, 0,
(float) Math.hypot(halfW, halfH)).setDuration(300L);
animator.addListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator a) {
mInAnimation = false;
}
});
animator.start();
}
});
animator.start();
}
}).start();
}).start();
}
private void showEditPanel(boolean animation) {
@@ -543,11 +543,11 @@ public final class GalleryCommentsScene extends ToolbarScene
mFab.animate().translationX(0.0f).translationY(0.0f).scaleX(1.0f).scaleY(1.0f).rotation(0.0f)
.setInterpolator(AnimationUtils.SLOW_FAST_SLOW_INTERPOLATOR)
.setDuration(300L).setListener(new SimpleAnimatorListener() {
@Override
public void onAnimationEnd(Animator animation) {
mInAnimation = false;
}
}).start();
@Override
public void onAnimationEnd(Animator animation) {
mInAnimation = false;
}
}).start();
}
});
animator.start();
@@ -847,6 +847,18 @@ public final class GalleryCommentsScene extends ToolbarScene
}
}
private static class MoreCommentHolder extends CommentHolder {
public MoreCommentHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater, R.layout.item_gallery_comment_more, parent);
}
}
private static class ProgressCommentHolder extends CommentHolder {
public ProgressCommentHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater, R.layout.item_gallery_comment_progress, parent);
}
}
private class ActualCommentHolder extends CommentHolder {
private final TextView user;
@@ -901,18 +913,6 @@ public final class GalleryCommentsScene extends ToolbarScene
}
}
private static class MoreCommentHolder extends CommentHolder {
public MoreCommentHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater, R.layout.item_gallery_comment_more, parent);
}
}
private static class ProgressCommentHolder extends CommentHolder {
public ProgressCommentHolder(LayoutInflater inflater, ViewGroup parent) {
super(inflater, R.layout.item_gallery_comment_progress, parent);
}
}
private class CommentAdapter extends RecyclerView.Adapter<CommentHolder> {
private final LayoutInflater mInflater;
@@ -66,6 +66,7 @@ import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.transition.TransitionInflater;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.EditTextDialogBuilder;
@@ -264,18 +265,12 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
private GalleryDetail mGalleryDetail;
private int mRequestId = IntIdGenerator.INVALID_ID;
private Pair<String, String>[] mTorrentList;
private String mArchiveFormParamOr;
private Pair<String, String>[] mArchiveList;
@State
private int mState = STATE_INIT;
private boolean mModifingFavorites;
ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mGalleryDetail != null) {
TorrentListDialogHelper helper = new TorrentListDialogHelper();
Dialog dialog = new AlertDialog.Builder(requireActivity())
Dialog dialog = new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.torrents)
.setView(R.layout.dialog_torrent_list)
.setOnDismissListener(helper)
@@ -283,6 +278,24 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
helper.setDialog(dialog, mGalleryDetail.torrentUrl);
}
});
private String mArchiveFormParamOr;
private Pair<String, String>[] mArchiveList;
@State
private int mState = STATE_INIT;
private boolean mModifingFavorites;
@Nullable
private static String getArtist(GalleryTagGroup[] tagGroups) {
if (null == tagGroups) {
return null;
}
for (GalleryTagGroup tagGroup : tagGroups) {
if ("artist".equals(tagGroup.groupName) && tagGroup.size() > 0) {
return tagGroup.getTagAt(0);
}
}
return null;
}
@StringRes
private int getRatingText(float rating) {
@@ -329,19 +342,6 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
return resId;
}
@Nullable
private static String getArtist(GalleryTagGroup[] tagGroups) {
if (null == tagGroups) {
return null;
}
for (GalleryTagGroup tagGroup : tagGroups) {
if ("artist".equals(tagGroup.groupName) && tagGroup.size() > 0) {
return tagGroup.getTagAt(0);
}
}
return null;
}
private void handleArgs(Bundle args) {
if (args == null) {
return;
@@ -1231,7 +1231,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
if (EhApplication.getDownloadManager(context).getDownloadState(galleryInfo.gid) == DownloadInfo.STATE_INVALID) {
CommonOperations.startDownload(activity, galleryInfo, false);
} else {
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, galleryInfo.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> EhApplication.getDownloadManager(context).deleteDownload(galleryInfo.gid))
@@ -1257,7 +1257,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
for (GalleryInfo newerVersion : mGalleryDetail.newerVersions) {
titles.add(getString(R.string.newer_version_title, newerVersion.title, newerVersion.posted));
}
new AlertDialog.Builder(requireContext())
new MaterialAlertDialogBuilder(requireContext())
.setItems(titles.toArray(new CharSequence[0]), (dialog, which) -> {
GalleryInfo newerVersion = mGalleryDetail.newerVersions.get(which);
Bundle args = new Bundle();
@@ -1303,7 +1303,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
requestStoragePermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE);
} else {
TorrentListDialogHelper helper = new TorrentListDialogHelper();
Dialog dialog = new AlertDialog.Builder(context)
Dialog dialog = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.torrents)
.setView(R.layout.dialog_torrent_list)
.setOnDismissListener(helper)
@@ -1320,7 +1320,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
return;
}
ArchiveListDialogHelper helper = new ArchiveListDialogHelper();
Dialog dialog = new AlertDialog.Builder(context)
Dialog dialog = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.dialog_archive_title)
.setView(R.layout.dialog_archive_list)
.setOnDismissListener(helper)
@@ -1335,7 +1335,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
return;
}
RateDialogHelper helper = new RateDialogHelper();
Dialog dialog = new AlertDialog.Builder(context)
Dialog dialog = new MaterialAlertDialogBuilder(context)
.setTitle(R.string.rate)
.setView(R.layout.dialog_rate)
.setNegativeButton(android.R.string.cancel, null)
@@ -1397,7 +1397,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
return;
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setMessage(getString(R.string.filter_the_uploader, uploader))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
@@ -1417,7 +1417,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
return;
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setMessage(getString(R.string.filter_the_tag, tag))
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
Filter filter = new Filter();
@@ -1466,7 +1466,7 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
menuId.add(R.id.vote_down);
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(tag)
.setItems(menu.toArray(new String[0]), (dialog, which) -> {
if (which < 0 || which >= menuId.size()) {
@@ -1709,6 +1709,16 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
mModifingFavorites = false;
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl(false);
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
}
@IntDef({STATE_INIT, STATE_NORMAL, STATE_REFRESH, STATE_REFRESH_HEADER, STATE_FAILED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
@@ -2222,14 +2232,4 @@ public class GalleryDetailScene extends BaseScene implements View.OnClickListene
EhApplication.getEhClient(context).execute(request);
}
}
@Override
public void onProvideAssistContent(AssistContent outContent) {
super.onProvideAssistContent(outContent);
String url = getGalleryDetailUrl(false);
if (url != null) {
outContent.setWebUri(Uri.parse(url));
}
}
}
@@ -61,6 +61,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
@@ -164,6 +165,9 @@ public final class GalleryListScene extends BaseScene
private EasyRecyclerView mRecyclerView;
@Nullable
private SearchLayout mSearchLayout;
ActivityResultLauncher<String[]> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.OpenDocument(),
result -> mSearchLayout.setImageUri(result));
@Nullable
private SearchBar mSearchBar;
@Nullable
@@ -235,10 +239,6 @@ public final class GalleryListScene extends BaseScene
private FavouriteStatusRouter.Listener mFavouriteStatusRouterListener;
private boolean mIsTopList = false;
ActivityResultLauncher<String[]> selectImageLauncher = registerForActivityResult(
new ActivityResultContracts.OpenDocument(),
result -> mSearchLayout.setImageUri(result));
@Nullable
private static String getSuitableTitleForUrlBuilder(
Resources resources, ListUrlBuilder urlBuilder, boolean appName) {
@@ -656,8 +656,8 @@ public final class GalleryListScene extends BaseScene
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
.outerCircleColor(R.color.colorPrimary)
.transparentTarget(true),
new TapTargetView.Listener() {
@@ -1043,7 +1043,7 @@ public final class GalleryListScene extends BaseScene
favourited ? R.drawable.v_heart_broken_x24 : R.drawable.v_heart_x24,
};
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
@@ -1055,7 +1055,7 @@ public final class GalleryListScene extends BaseScene
break;
case 1: // Download
if (downloaded) {
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
@@ -1082,7 +1082,7 @@ public final class GalleryListScene extends BaseScene
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
@@ -1092,39 +1092,6 @@ public final class GalleryListScene extends BaseScene
return true;
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
private final String[] mLabels;
private final GalleryInfo mGi;
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
mLabels = labels;
mGi = gi;
}
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel check mode
Context context = getContext();
if (null == context) {
return;
}
if (null != mRecyclerView) {
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
}
String label = which == 0 ? null : mLabels[which];
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
}
}
private void showActionFab() {
if (null != mFabLayout && STATE_NORMAL == mState && !mShowActionFab) {
mShowActionFab = true;
@@ -1613,6 +1580,39 @@ public final class GalleryListScene extends BaseScene
}
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
private final String[] mLabels;
private final GalleryInfo mGi;
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
mLabels = labels;
mGi = gi;
}
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel check mode
Context context = getContext();
if (null == context) {
return;
}
if (null != mRecyclerView) {
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
}
String label = which == 0 ? null : mLabels[which];
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
}
}
private class QsDrawerAdapter extends RecyclerView.Adapter<QsDrawerHolder> implements DraggableItemAdapter<QsDrawerHolder> {
private final LayoutInflater mInflater;
@@ -1655,7 +1655,7 @@ public final class GalleryListScene extends BaseScene
@Override
public boolean onMenuItemClick(MenuItem item) {
if (item.getItemId() == R.id.menu_qs_remove) {
new AlertDialog.Builder(requireContext())
new MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_quick_search_title))
.setMessage(getString(R.string.delete_quick_search_message, quickSearch.name))
.setPositiveButton(R.string.delete, (dialog, which) -> {
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
@@ -210,7 +211,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
int pages = mHelper.getPages();
if (pages > 0 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new AlertDialog.Builder(context).setTitle(R.string.go_to)
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
.setView(R.layout.dialog_go_to)
.setPositiveButton(android.R.string.ok, null)
.create();
@@ -35,12 +35,12 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.h6ah4i.android.widget.advrecyclerview.animator.GeneralItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator;
import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager;
@@ -86,7 +86,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import rikka.core.res.ResourcesKt;
import rikka.recyclerview.RecyclerViewKt;
public class HistoryScene extends ToolbarScene {
@@ -234,11 +233,11 @@ public class HistoryScene extends ToolbarScene {
guardManager.attachRecyclerView(mRecyclerView);
swipeManager.attachRecyclerView(mRecyclerView);
RecyclerViewKt.fixEdgeEffect(mRecyclerView, false, true);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4,4,4,4, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4, 4, 4, 4, TypedValue.COMPLEX_UNIT_DIP);
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable handlerDrawable = new HandlerDrawable();
handlerDrawable.setColor(ResourcesKt.resolveColor(getTheme(), R.attr.widgetColorThemeAccent));
handlerDrawable.setColor(com.google.android.material.R.color.material_dynamic_neutral100);
mFastScroller.setHandlerDrawable(handlerDrawable);
updateLazyList();
@@ -307,7 +306,7 @@ public class HistoryScene extends ToolbarScene {
}
private void showClearAllDialog() {
new AlertDialog.Builder(requireContext())
new MaterialAlertDialogBuilder(requireContext())
.setMessage(R.string.clear_all_history)
.setPositiveButton(R.string.clear_all, (dialog, which) -> {
if (DialogInterface.BUTTON_POSITIVE != which || null == mAdapter) {
@@ -398,7 +397,7 @@ public class HistoryScene extends ToolbarScene {
R.drawable.v_delete_x24,
};
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(EhUtils.getSuitableTitle(gi))
.setAdapter(new SelectItemWithIconAdapter(context, items, icons), (dialog, which) -> {
switch (which) {
@@ -410,7 +409,7 @@ public class HistoryScene extends ToolbarScene {
break;
case 1: // Download
if (downloaded) {
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_remove_dialog_title)
.setMessage(getString(R.string.download_remove_dialog_message, gi.title))
.setPositiveButton(android.R.string.ok, (dialog1, which1) -> mDownloadManager.deleteDownload(gi.gid))
@@ -448,7 +447,7 @@ public class HistoryScene extends ToolbarScene {
MoveDialogHelper helper = new MoveDialogHelper(labels, gi);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.download_move_dialog_title)
.setItems(labels, helper)
.show();
@@ -458,39 +457,6 @@ public class HistoryScene extends ToolbarScene {
return true;
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
private final String[] mLabels;
private final GalleryInfo mGi;
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
mLabels = labels;
mGi = gi;
}
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel check mode
Context context = getContext();
if (null == context) {
return;
}
if (null != mRecyclerView) {
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
}
String label = which == 0 ? null : mLabels[which];
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
}
}
private static class AddToFavoriteListener extends EhCallback<GalleryListScene, Void> {
public AddToFavoriteListener(Context context, int stageId, String sceneTag) {
@@ -578,6 +544,39 @@ public class HistoryScene extends ToolbarScene {
}
}
private class MoveDialogHelper implements DialogInterface.OnClickListener {
private final String[] mLabels;
private final GalleryInfo mGi;
public MoveDialogHelper(String[] labels, GalleryInfo gi) {
mLabels = labels;
mGi = gi;
}
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel check mode
Context context = getContext();
if (null == context) {
return;
}
if (null != mRecyclerView) {
mRecyclerView.outOfCustomChoiceMode();
}
DownloadManager downloadManager = EhApplication.getDownloadManager(context);
DownloadInfo downloadInfo = downloadManager.getDownloadInfo(mGi.gid);
if (downloadInfo == null) {
return;
}
String label = which == 0 ? null : mLabels[which];
downloadManager.changeLabel(Collections.singletonList(downloadInfo), label);
}
}
private class HistoryAdapter extends RecyclerView.Adapter<HistoryHolder>
implements SwipeableItemAdapter<HistoryHolder> {
@@ -32,7 +32,6 @@ import androidx.biometric.BiometricPrompt;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.hardware.ShakeDetector;
import com.hippo.widget.lockpattern.LockPatternUtils;
import com.hippo.widget.lockpattern.LockPatternView;
import com.hippo.yorozuya.ObjectUtils;
@@ -42,7 +41,7 @@ import java.util.List;
import java.util.concurrent.Executors;
public class SecurityScene extends SolidScene implements
LockPatternView.OnPatternListener, ShakeDetector.OnShakeListener {
LockPatternView.OnPatternListener {
private static final int MAX_RETRY_TIMES = 5;
private static final String KEY_RETRY_TIMES = "retry_times";
@@ -52,7 +51,6 @@ public class SecurityScene extends SolidScene implements
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private ShakeDetector mShakeDetector;
private BiometricPrompt.PromptInfo promptInfo;
private BiometricPrompt biometricPrompt;
private boolean canAuthenticate = false;
@@ -72,10 +70,6 @@ public class SecurityScene extends SolidScene implements
mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
if (null != mSensorManager) {
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (null != mAccelerometer) {
mShakeDetector = new ShakeDetector();
mShakeDetector.setOnShakeListener(this);
}
}
if (null == savedInstanceState) {
@@ -116,17 +110,12 @@ public class SecurityScene extends SolidScene implements
mSensorManager = null;
mAccelerometer = null;
mShakeDetector = null;
}
@Override
public void onResume() {
super.onResume();
if (null != mShakeDetector) {
mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
}
if (canAuthenticate) {
startBiometricPrompt();
}
@@ -139,10 +128,6 @@ public class SecurityScene extends SolidScene implements
@Override
public void onPause() {
super.onPause();
if (null != mShakeDetector) {
mSensorManager.unregisterListener(mShakeDetector);
}
}
@Override
@@ -205,17 +190,4 @@ public class SecurityScene extends SolidScene implements
}
}
}
@Override
public void onShake(int count) {
if (count == 10) {
MainActivity activity = getMainActivity();
if (null == activity) {
return;
}
Settings.putSecurity("");
startSceneForCheckStep(CHECK_STEP_SECURITY, getArguments());
finish();
}
}
}
@@ -29,8 +29,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
@@ -333,7 +333,7 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
return;
}
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setTitle(R.string.sign_in_failed)
.setMessage(ExceptionUtils.getReadableString(e) + "\n\n" + getString(R.string.sign_in_failed_tip))
.setPositiveButton(R.string.get_it, null)
@@ -357,7 +357,6 @@ public final class SignInScene extends SolidScene implements EditText.OnEditorAc
public void onGetProfileEnd() {
mSigningIn = false;
updateAvatar();
redirectTo();
}
@@ -30,9 +30,8 @@ public class SolidScene extends BaseScene {
public static final int CHECK_STEP_SECURITY = 0;
public static final int CHECK_STEP_WARNING = 1;
public static final int CHECK_STEP_ANALYTICS = 2;
public static final int CHECK_STEP_SIGN_IN = 3;
public static final int CHECK_STEP_SELECT_SITE = 4;
public static final int CHECK_STEP_SIGN_IN = 2;
public static final int CHECK_STEP_SELECT_SITE = 3;
public static final String KEY_TARGET_SCENE = "target_scene";
public static final String KEY_TARGET_ARGS = "target_args";
private static final String TAG = SolidScene.class.getSimpleName();
@@ -45,11 +44,6 @@ public class SolidScene extends BaseScene {
break;
}
case CHECK_STEP_WARNING:
if (Settings.getAskAnalytics()) {
startScene(new Announcer(AnalyticsScene.class).setArgs(args), true);
break;
}
case CHECK_STEP_ANALYTICS:
if (EhUtils.needSignedIn(getContext())) {
startScene(new Announcer(SignInScene.class).setArgs(args), true);
break;
@@ -1,163 +0,0 @@
/*
* Copyright (C) 2015 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.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TableLayout;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhConfig;
import com.hippo.widget.CheckTextView;
import com.hippo.yorozuya.NumberUtils;
public class CategoryTable extends TableLayout implements View.OnLongClickListener {
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_CATEGORY = "category";
private CheckTextView mDoujinshi;
private CheckTextView mManga;
private CheckTextView mArtistCG;
private CheckTextView mGameCG;
private CheckTextView mWestern;
private CheckTextView mNonH;
private CheckTextView mImageSets;
private CheckTextView mCosplay;
private CheckTextView mAsianPorn;
private CheckTextView mMisc;
private CheckTextView[] mOptions;
public CategoryTable(Context context) {
super(context);
init();
}
public CategoryTable(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public void init() {
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_category_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mDoujinshi = (CheckTextView) row0.getChildAt(0);
mManga = (CheckTextView) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mArtistCG = (CheckTextView) row1.getChildAt(0);
mGameCG = (CheckTextView) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mWestern = (CheckTextView) row2.getChildAt(0);
mNonH = (CheckTextView) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mImageSets = (CheckTextView) row3.getChildAt(0);
mCosplay = (CheckTextView) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mAsianPorn = (CheckTextView) row4.getChildAt(0);
mMisc = (CheckTextView) row4.getChildAt(1);
mOptions = new CheckTextView[]{
mDoujinshi, mManga, mArtistCG, mGameCG, mWestern,
mNonH, mImageSets, mCosplay, mAsianPorn, mMisc
};
for (CheckTextView option : mOptions) {
option.setOnLongClickListener(this);
}
}
@Override
public boolean onLongClick(View v) {
if (v instanceof CheckTextView) {
boolean checked = ((CheckTextView) v).isChecked();
for (CheckTextView option : mOptions) {
if (option != v) {
option.setChecked(!checked);
}
}
}
return true;
}
/**
* Get category according to button.
*
* @return the category of this view
*/
public int getCategory() {
int category = 0;
if (!mDoujinshi.isChecked()) category |= EhConfig.DOUJINSHI;
if (!mManga.isChecked()) category |= EhConfig.MANGA;
if (!mArtistCG.isChecked()) category |= EhConfig.ARTIST_CG;
if (!mGameCG.isChecked()) category |= EhConfig.GAME_CG;
if (!mWestern.isChecked()) category |= EhConfig.WESTERN;
if (!mNonH.isChecked()) category |= EhConfig.NON_H;
if (!mImageSets.isChecked()) category |= EhConfig.IMAGE_SET;
if (!mCosplay.isChecked()) category |= EhConfig.COSPLAY;
if (!mAsianPorn.isChecked()) category |= EhConfig.ASIAN_PORN;
if (!mMisc.isChecked()) category |= EhConfig.MISC;
return category;
}
/**
* Set each button checked or not according to category.
*
* @param category target category
*/
public void setCategory(int category) {
mDoujinshi.setChecked(!NumberUtils.int2boolean(category & EhConfig.DOUJINSHI));
mManga.setChecked(!NumberUtils.int2boolean(category & EhConfig.MANGA));
mArtistCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.ARTIST_CG));
mGameCG.setChecked(!NumberUtils.int2boolean(category & EhConfig.GAME_CG));
mWestern.setChecked(!NumberUtils.int2boolean(category & EhConfig.WESTERN));
mNonH.setChecked(!NumberUtils.int2boolean(category & EhConfig.NON_H));
mImageSets.setChecked(!NumberUtils.int2boolean(category & EhConfig.IMAGE_SET));
mCosplay.setChecked(!NumberUtils.int2boolean(category & EhConfig.COSPLAY));
mAsianPorn.setChecked(!NumberUtils.int2boolean(category & EhConfig.ASIAN_PORN));
mMisc.setChecked(!NumberUtils.int2boolean(category & EhConfig.MISC));
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_CATEGORY, getCategory());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setCategory(savedState.getInt(STATE_KEY_CATEGORY));
}
}
}
@@ -26,8 +26,7 @@ import android.webkit.WebView;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
public class DialogWebChromeClient extends WebChromeClient {
@@ -40,7 +39,7 @@ public class DialogWebChromeClient extends WebChromeClient {
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
new MaterialAlertDialogBuilder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
.setOnCancelListener(dialog -> result.cancel())
@@ -50,7 +49,7 @@ public class DialogWebChromeClient extends WebChromeClient {
@Override
public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
new AlertDialog.Builder(view.getContext())
new MaterialAlertDialogBuilder(view.getContext())
.setMessage(message)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm())
.setNegativeButton(android.R.string.cancel, (dialog, which) -> result.cancel())
@@ -68,7 +67,7 @@ public class DialogWebChromeClient extends WebChromeClient {
final EditText valueView = promptView.findViewById(R.id.value);
valueView.setText(defaultValue);
new AlertDialog.Builder(context)
new MaterialAlertDialogBuilder(context)
.setView(promptView)
.setPositiveButton(android.R.string.ok, (dialog, which) -> result.confirm(valueView.getText().toString()))
.setOnCancelListener(dialog -> result.cancel())
@@ -32,19 +32,15 @@ import com.hippo.yorozuya.ObjectUtils;
public class GalleryHeader extends ViewGroup {
private DisplayCutoutCompat displayCutout;
private int topInsets = 0;
private View battery;
private View progress;
private View clock;
private final Rect batteryRect = new Rect();
private final Rect progressRect = new Rect();
private final Rect clockRect = new Rect();
private final int[] location = new int[2];
private DisplayCutoutCompat displayCutout;
private int topInsets = 0;
private View battery;
private View progress;
private View clock;
private int lastX = 0;
private int lastY = 0;
@@ -33,10 +33,9 @@ import java.util.Map;
public abstract class GalleryInfoContentHelper extends ContentLayout.ContentHelper<GalleryInfo> {
private static final String KEY_DATA_MAP = "data_map";
private final FavouriteStatusRouter.Listener listener;
@SuppressLint("UseSparseArrays")
private Map<Long, GalleryInfo> map = new HashMap<>();
private final FavouriteStatusRouter.Listener listener;
public GalleryInfoContentHelper() {
listener = (gid, slot) -> {
@@ -141,7 +141,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os);
mImagePath = temp.getPath();
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View)mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
@@ -166,7 +166,7 @@ public final class ImageSearchLayout extends LinearLayout implements View.OnClic
}
mImagePath = imagePath;
mPreview.setImageBitmap(bitmap);
mPreview.setVisibility(VISIBLE);
((View)mPreview.getParent()).setVisibility(VISIBLE);
} catch (FileNotFoundException e) {
// Ignore
} finally {
@@ -40,12 +40,12 @@ import android.widget.TextView;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
@@ -490,6 +490,27 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
}
}
private String wrapTagKeyword(String keyword) {
keyword = keyword.trim();
int index1 = keyword.indexOf(':');
if (index1 == -1 || index1 >= keyword.length() - 1) {
// Can't find :, or : is the last char
return keyword;
}
if (keyword.charAt(index1 + 1) == '"') {
// The char after : is ", the word must be quoted
return keyword;
}
int index2 = keyword.indexOf(' ');
if (index2 <= index1) {
// Can't find space, or space is before :
return keyword;
}
return keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\"";
}
public interface Helper {
void onClickTitle();
@@ -654,7 +675,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
@Override
public boolean onLongClick() {
new AlertDialog.Builder(getContext())
new MaterialAlertDialogBuilder(getContext())
.setMessage(getContext().getString(R.string.delete_search_history, mKeyword))
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.delete, (dialog, which) -> {
@@ -665,25 +686,4 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
return true;
}
}
private String wrapTagKeyword(String keyword) {
keyword = keyword.trim();
int index1 = keyword.indexOf(':');
if (index1 == -1 || index1 >= keyword.length() - 1) {
// Can't find :, or : is the last char
return keyword;
}
if (keyword.charAt(index1 + 1) == '"') {
// The char after : is ", the word must be quoted
return keyword;
}
int index2 = keyword.indexOf(' ');
if (index2 <= index1) {
// Can't find space, or space is before :
return keyword;
}
return keyword.substring(0, index1 + 1) + "\"" + keyword.substring(index1 + 1) + "$\"";
}
}
@@ -1,404 +0,0 @@
/*
* Copyright (C) 2015 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.widget;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.tabs.TabLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.easyrecyclerview.SimpleHolder;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.exception.EhException;
import com.hippo.widget.RadioGridGroup;
import com.hippo.yorozuya.ViewUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
public class SearchLayout extends EasyRecyclerView implements CompoundButton.OnCheckedChangeListener,
View.OnClickListener, ImageSearchLayout.Helper, TabLayout.OnTabSelectedListener {
public static final int SEARCH_MODE_NORMAL = 0;
public static final int SEARCH_MODE_IMAGE = 1;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_SEARCH_MODE = "search_mode";
private static final String STATE_KEY_ENABLE_ADVANCE = "enable_advance";
private static final int ITEM_TYPE_NORMAL = 0;
private static final int ITEM_TYPE_NORMAL_ADVANCE = 1;
private static final int ITEM_TYPE_IMAGE = 2;
private static final int ITEM_TYPE_ACTION = 3;
private static final int[] SEARCH_ITEM_COUNT_ARRAY = {
3, 2
};
private static final int[][] SEARCH_ITEM_TYPE = {
{ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION}, // SEARCH_MODE_NORMAL
{ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION}, // SEARCH_MODE_IMAGE
};
private LayoutInflater mInflater;
@SearchMode
private int mSearchMode = SEARCH_MODE_NORMAL;
private boolean mEnableAdvance = false;
private View mNormalView;
private CategoryTable mCategoryTable;
private RadioGridGroup mNormalSearchMode;
private ImageView mNormalSearchModeHelp;
private Switch mEnableAdvanceSwitch;
private View mAdvanceView;
private AdvanceSearchTable mTableAdvanceSearch;
private ImageSearchLayout mImageView;
private View mActionView;
private TabLayout mAction;
private LinearLayoutManager mLayoutManager;
private SearchAdapter mAdapter;
private Helper mHelper;
public SearchLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public SearchLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@SuppressLint("InflateParams")
private void init(Context context) {
Resources resources = context.getResources();
mInflater = LayoutInflater.from(context);
mLayoutManager = new SearchLayoutManager(context);
mAdapter = new SearchAdapter();
mAdapter.setHasStableIds(true);
setLayoutManager(mLayoutManager);
setAdapter(mAdapter);
setHasFixedSize(true);
setClipToPadding(false);
((DefaultItemAnimator) Objects.requireNonNull(getItemAnimator())).setSupportsChangeAnimations(false);
int interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval);
int paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h);
int paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v);
MarginItemDecoration decoration = new MarginItemDecoration(
interval, paddingH, paddingV, paddingH, paddingV);
addItemDecoration(decoration);
decoration.applyPaddings(this);
// Create normal view
View normalView = mInflater.inflate(R.layout.search_normal, null);
mNormalView = normalView;
mCategoryTable = normalView.findViewById(R.id.search_category_table);
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode);
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help);
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance);
mNormalSearchModeHelp.setOnClickListener(this);
mEnableAdvanceSwitch.setOnCheckedChangeListener(this);
mEnableAdvanceSwitch.setSwitchPadding(resources.getDimensionPixelSize(R.dimen.switch_padding));
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null);
mTableAdvanceSearch = mAdvanceView.findViewById(R.id.search_advance_search_table);
// Create image view
mImageView = (ImageSearchLayout) mInflater.inflate(R.layout.search_image, null);
mImageView.setHelper(this);
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null);
mActionView.setLayoutParams(new RecyclerView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
mAction = mActionView.findViewById(R.id.action);
mAction.addOnTabSelectedListener(this);
}
public void setHelper(Helper helper) {
mHelper = helper;
}
public void scrollSearchContainerToTop() {
mLayoutManager.scrollToPositionWithOffset(0, 0);
}
public void setImageUri(Uri imageUri) {
mImageView.setImageUri(imageUri);
}
public void setNormalSearchMode(int id) {
mNormalSearchMode.check(id);
}
@Override
public void onSelectImage() {
if (mHelper != null) {
mHelper.onSelectImage();
}
}
@Override
protected void dispatchSaveInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchSaveInstanceState(container);
mNormalView.saveHierarchyState(container);
mAdvanceView.saveHierarchyState(container);
mImageView.saveHierarchyState(container);
mActionView.saveHierarchyState(container);
}
@Override
protected void dispatchRestoreInstanceState(@NonNull SparseArray<Parcelable> container) {
super.dispatchRestoreInstanceState(container);
mNormalView.restoreHierarchyState(container);
mAdvanceView.restoreHierarchyState(container);
mImageView.restoreHierarchyState(container);
mActionView.restoreHierarchyState(container);
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode);
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance);
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
mSearchMode = savedState.getInt(STATE_KEY_SEARCH_MODE);
mEnableAdvance = savedState.getBoolean(STATE_KEY_ENABLE_ADVANCE);
}
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (buttonView == mEnableAdvanceSwitch) {
post(() -> {
mEnableAdvance = isChecked;
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter.notifyItemInserted(1);
} else {
mAdapter.notifyItemRemoved(1);
}
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
});
}
}
@SuppressLint("NonConstantResourceId")
public void formatListUrlBuilder(ListUrlBuilder urlBuilder, String query) throws EhException {
urlBuilder.reset();
switch (mSearchMode) {
case SEARCH_MODE_NORMAL:
int nsMode = mNormalSearchMode.getCheckedRadioButtonId();
if (nsMode == R.id.search_subscription_search) {
urlBuilder.setMode(ListUrlBuilder.MODE_SUBSCRIPTION);
} else if (nsMode == R.id.search_specify_uploader) {
urlBuilder.setMode(ListUrlBuilder.MODE_UPLOADER);
} else if (nsMode == R.id.search_specify_tag) {
urlBuilder.setMode(ListUrlBuilder.MODE_TAG);
} else {
urlBuilder.setMode(ListUrlBuilder.MODE_NORMAL);
}
urlBuilder.setKeyword(query);
urlBuilder.setCategory(mCategoryTable.getCategory());
if (mEnableAdvance) {
urlBuilder.setAdvanceSearch(mTableAdvanceSearch.getAdvanceSearch());
urlBuilder.setMinRating(mTableAdvanceSearch.getMinRating());
urlBuilder.setPageFrom(mTableAdvanceSearch.getPageFrom());
urlBuilder.setPageTo(mTableAdvanceSearch.getPageTo());
}
break;
case SEARCH_MODE_IMAGE:
urlBuilder.setMode(ListUrlBuilder.MODE_IMAGE_SEARCH);
mImageView.formatListUrlBuilder(urlBuilder);
break;
}
}
@Override
public void onClick(View v) {
if (mNormalSearchModeHelp == v) {
new AlertDialog.Builder(getContext())
.setMessage(R.string.search_tip)
.show();
}
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
post(() -> setSearchMode(tab.getPosition()));
}
public void setSearchMode(@SearchMode int mode) {
int oldItemCount = mAdapter.getItemCount();
mSearchMode = mode;
int newItemCount = mAdapter.getItemCount();
mAdapter.notifyItemRangeRemoved(0, oldItemCount - 1);
mAdapter.notifyItemRangeInserted(0, newItemCount - 1);
if (mHelper != null) {
mHelper.onChangeSearchMode();
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
@IntDef({SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE})
@Retention(RetentionPolicy.SOURCE)
private @interface SearchMode {
}
public interface Helper {
void onChangeSearchMode();
void onSelectImage();
}
private class SearchAdapter extends EasyRecyclerView.Adapter<SimpleHolder> {
@Override
public int getItemCount() {
int count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode];
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
count--;
}
return count;
}
@Override
public int getItemViewType(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
@NonNull
@Override
public SimpleHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view;
if (viewType == ITEM_TYPE_ACTION) {
ViewUtils.removeFromParent(mActionView);
view = mActionView;
} else {
view = mInflater.inflate(R.layout.search_category, parent, false);
TextView title = view.findViewById(R.id.category_title);
FrameLayout content = view.findViewById(R.id.category_content);
switch (viewType) {
case ITEM_TYPE_NORMAL:
title.setText(R.string.search_normal);
ViewUtils.removeFromParent(mNormalView);
content.addView(mNormalView);
break;
case ITEM_TYPE_NORMAL_ADVANCE:
title.setText(R.string.search_advance);
ViewUtils.removeFromParent(mAdvanceView);
content.addView(mAdvanceView);
break;
case ITEM_TYPE_IMAGE:
title.setText(R.string.search_image);
ViewUtils.removeFromParent(mImageView);
content.addView(mImageView);
break;
}
}
return new SimpleHolder(view);
}
@Override
public void onBindViewHolder(SimpleHolder holder, int position) {
if (holder.getItemViewType() == ITEM_TYPE_ACTION) {
mAction.selectTab(mAction.getTabAt(mSearchMode));
}
}
@Override
public long getItemId(int position) {
int type = SEARCH_ITEM_TYPE[mSearchMode][position];
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION;
}
return type;
}
}
static class SearchLayoutManager extends LinearLayoutManager {
public SearchLayoutManager(Context context) {
super(context);
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
super.onLayoutChildren(recycler, state);
} catch (IndexOutOfBoundsException e) {
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,412 @@
/*
* Copyright (C) 2022 tarsin norbin
*
* 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.widget
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import android.view.LayoutInflater
import com.google.android.material.chip.ChipGroup
import com.hippo.widget.RadioGridGroup
import com.google.android.material.tabs.TabLayout
import androidx.recyclerview.widget.LinearLayoutManager
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import androidx.recyclerview.widget.DefaultItemAnimator
import com.hippo.ehviewer.R
import com.hippo.easyrecyclerview.MarginItemDecoration
import android.view.ViewGroup
import android.util.SparseArray
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.widget.*
import kotlin.Throws
import com.hippo.ehviewer.client.exception.EhException
import com.hippo.ehviewer.client.data.ListUrlBuilder
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.annotation.IntDef
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.google.android.material.chip.Chip
import com.hippo.easyrecyclerview.SimpleHolder
import com.hippo.ehviewer.client.EhConfig
import com.hippo.yorozuya.NumberUtils
import com.hippo.yorozuya.ViewUtils
import java.lang.IndexOutOfBoundsException
class SearchLayout : EasyRecyclerView, CompoundButton.OnCheckedChangeListener, View.OnClickListener,
ImageSearchLayout.Helper, OnTabSelectedListener {
private val mInflater : LayoutInflater
private val mSharePref : SharedPreferences
@SearchMode
private var mSearchMode = SEARCH_MODE_NORMAL
private var mEnableAdvance = false
private var mNormalView: View? = null
private var mNormalSearchMode: RadioGridGroup? = null
private var mNormalSearchModeHelp: ImageView? = null
private var mEnableAdvanceSwitch: Switch? = null
private var mAdvanceView: View? = null
private var mTableAdvanceSearch: AdvanceSearchTable? = null
private var mImageView: ImageSearchLayout? = null
private var mActionView: View? = null
private var mAction: TabLayout? = null
private var mLayoutManager: LinearLayoutManager? = null
private var mAdapter: SearchAdapter? = null
private var mHelper: Helper? = null
private val mCategoryTable : ArrayList<Pair<Int, Int>> = arrayListOf(
Pair(EhConfig.DOUJINSHI, R.string.doujinshi),
Pair(EhConfig.MANGA, R.string.manga),
Pair(EhConfig.ARTIST_CG, R.string.artist_cg),
Pair(EhConfig.GAME_CG, R.string.game_cg),
Pair(EhConfig.WESTERN, R.string.western),
Pair(EhConfig.NON_H, R.string.non_h),
Pair(EhConfig.IMAGE_SET, R.string.image_set),
Pair(EhConfig.COSPLAY, R.string.cosplay),
Pair(EhConfig.ASIAN_PORN, R.string.asian_porn),
Pair(EhConfig.MISC, R.string.misc)
)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
mInflater = LayoutInflater.from(context)
init(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(
context,
attrs,
defStyle
) {
mSharePref = PreferenceManager.getDefaultSharedPreferences(context)
mInflater = LayoutInflater.from(context)
init(context)
}
@SuppressLint("InflateParams")
private fun init(context: Context) {
val resources = context.resources
mLayoutManager = SearchLayoutManager(context)
mAdapter = SearchAdapter()
mAdapter!!.setHasStableIds(true)
layoutManager = mLayoutManager
adapter = mAdapter
setHasFixedSize(true)
clipToPadding = false
(itemAnimator as DefaultItemAnimator?)!!.supportsChangeAnimations = false
val interval = resources.getDimensionPixelOffset(R.dimen.search_layout_interval)
val paddingH = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_h)
val paddingV = resources.getDimensionPixelOffset(R.dimen.search_layout_margin_v)
val decoration = MarginItemDecoration(
interval, paddingH, paddingV, paddingH, paddingV
)
addItemDecoration(decoration)
decoration.applyPaddings(this)
// Create normal view
val normalView = mInflater.inflate(R.layout.search_normal, null)
mNormalView = normalView
val mCategoryStored = mSharePref.getInt(SEARCH_CATEGORY_PREF, EhConfig.ALL_CATEGORY)
val mCategoryGroup = normalView.findViewById(R.id.search_category_chipgroup) as ChipGroup
for (mPair in mCategoryTable) {
val mChip = IdentifiedChip(context)
mChip.isCheckable = true
mChip.setText(mPair.second)
mChip.idt = mPair.first
mChip.isChecked = NumberUtils.int2boolean(mPair.first and mCategoryStored)
mCategoryGroup.addView(mChip)
}
mCategoryGroup.setOnCheckedStateChangeListener { _, checkedIds ->
var mCategory = 0
for (index in checkedIds) {
mCategory = mCategory or findViewById<IdentifiedChip>(index).idt
}
mSharePref.edit { putInt(SEARCH_CATEGORY_PREF, mCategory) }
}
mNormalSearchMode = normalView.findViewById(R.id.normal_search_mode)
mNormalSearchModeHelp = normalView.findViewById(R.id.normal_search_mode_help)
mEnableAdvanceSwitch = normalView.findViewById(R.id.search_enable_advance)
mNormalSearchModeHelp!!.setOnClickListener(this)
mEnableAdvanceSwitch!!.setOnCheckedChangeListener(this)
mEnableAdvanceSwitch!!.switchPadding = resources.getDimensionPixelSize(R.dimen.switch_padding)
// Create advance view
mAdvanceView = mInflater.inflate(R.layout.search_advance, null)
mTableAdvanceSearch = mAdvanceView!!.findViewById(R.id.search_advance_search_table)
// Create image view
mImageView = mInflater.inflate(R.layout.search_image, null) as ImageSearchLayout
mImageView!!.setHelper(this)
// Create action view
mActionView = mInflater.inflate(R.layout.search_action, null)
mActionView!!.layoutParams = LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
mAction = mActionView!!.findViewById(R.id.action)
mAction!!.addOnTabSelectedListener(this)
}
fun setHelper(helper: Helper?) {
mHelper = helper
}
fun scrollSearchContainerToTop() {
mLayoutManager!!.scrollToPositionWithOffset(0, 0)
}
fun setImageUri(imageUri: Uri?) {
mImageView!!.setImageUri(imageUri)
}
fun setNormalSearchMode(id: Int) {
mNormalSearchMode!!.check(id)
}
override fun onSelectImage() {
if (mHelper != null) {
mHelper!!.onSelectImage()
}
}
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
super.dispatchSaveInstanceState(container)
mNormalView!!.saveHierarchyState(container)
mAdvanceView!!.saveHierarchyState(container)
mImageView!!.saveHierarchyState(container)
mActionView!!.saveHierarchyState(container)
}
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
super.dispatchRestoreInstanceState(container)
mNormalView!!.restoreHierarchyState(container)
mAdvanceView!!.restoreHierarchyState(container)
mImageView!!.restoreHierarchyState(container)
mActionView!!.restoreHierarchyState(container)
}
override fun onSaveInstanceState(): Parcelable {
val state = Bundle()
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState())
state.putInt(STATE_KEY_SEARCH_MODE, mSearchMode)
state.putBoolean(STATE_KEY_ENABLE_ADVANCE, mEnableAdvance)
return state
}
override fun onRestoreInstanceState(state: Parcelable) {
if (state is Bundle) {
super.onRestoreInstanceState(state.getParcelable(STATE_KEY_SUPER))
mSearchMode = state.getInt(STATE_KEY_SEARCH_MODE)
mEnableAdvance = state.getBoolean(STATE_KEY_ENABLE_ADVANCE)
}
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
if (buttonView === mEnableAdvanceSwitch) {
post {
mEnableAdvance = isChecked
if (mSearchMode == SEARCH_MODE_NORMAL) {
if (mEnableAdvance) {
mAdapter!!.notifyItemInserted(1)
} else {
mAdapter!!.notifyItemRemoved(1)
}
if (mHelper != null) {
mHelper!!.onChangeSearchMode()
}
}
}
}
}
@SuppressLint("NonConstantResourceId")
@Throws(EhException::class)
fun formatListUrlBuilder(urlBuilder: ListUrlBuilder, query: String?) {
urlBuilder.reset()
when (mSearchMode) {
SEARCH_MODE_NORMAL -> {
when (mNormalSearchMode!!.checkedRadioButtonId) {
R.id.search_subscription_search -> {
urlBuilder.mode = ListUrlBuilder.MODE_SUBSCRIPTION
}
R.id.search_specify_uploader -> {
urlBuilder.mode = ListUrlBuilder.MODE_UPLOADER
}
R.id.search_specify_tag -> {
urlBuilder.mode = ListUrlBuilder.MODE_TAG
}
else -> {
urlBuilder.mode = ListUrlBuilder.MODE_NORMAL
}
}
urlBuilder.keyword = query
urlBuilder.category = PreferenceManager.getDefaultSharedPreferences(context).getInt(
SEARCH_CATEGORY_PREF, 0)
if (mEnableAdvance) {
urlBuilder.advanceSearch = mTableAdvanceSearch!!.advanceSearch
urlBuilder.minRating = mTableAdvanceSearch!!.minRating
urlBuilder.pageFrom = mTableAdvanceSearch!!.pageFrom
urlBuilder.pageTo = mTableAdvanceSearch!!.pageTo
}
}
SEARCH_MODE_IMAGE -> {
urlBuilder.mode = ListUrlBuilder.MODE_IMAGE_SEARCH
mImageView!!.formatListUrlBuilder(urlBuilder)
}
}
}
override fun onClick(v: View) {
if (mNormalSearchModeHelp === v) {
MaterialAlertDialogBuilder(context)
.setMessage(R.string.search_tip)
.show()
}
}
override fun onTabSelected(tab: TabLayout.Tab) {
post { setSearchMode(tab.position) }
}
fun setSearchMode(@SearchMode mode: Int) {
val oldItemCount = mAdapter!!.itemCount
mSearchMode = mode
val newItemCount = mAdapter!!.itemCount
mAdapter!!.notifyItemRangeRemoved(0, oldItemCount - 1)
mAdapter!!.notifyItemRangeInserted(0, newItemCount - 1)
if (mHelper != null) {
mHelper!!.onChangeSearchMode()
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
@IntDef(SEARCH_MODE_NORMAL, SEARCH_MODE_IMAGE)
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
private annotation class SearchMode
interface Helper {
fun onChangeSearchMode()
fun onSelectImage()
}
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context) {
override fun onLayoutChildren(recycler: Recycler, state: State) {
try {
super.onLayoutChildren(recycler, state)
} catch (e: IndexOutOfBoundsException) {
e.printStackTrace()
}
}
}
private inner class SearchAdapter : Adapter<SimpleHolder>() {
override fun getItemCount(): Int {
var count = SEARCH_ITEM_COUNT_ARRAY[mSearchMode]
if (mSearchMode == SEARCH_MODE_NORMAL && !mEnableAdvance) {
count--
}
return count
}
override fun getItemViewType(position: Int): Int {
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION
}
return type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder {
val view: View?
if (viewType == ITEM_TYPE_ACTION) {
ViewUtils.removeFromParent(mActionView)
view = mActionView
} else {
view = mInflater.inflate(R.layout.search_category, parent, false)
val title = view.findViewById<TextView>(R.id.category_title)
val content = view.findViewById<FrameLayout>(R.id.category_content)
when (viewType) {
ITEM_TYPE_NORMAL -> {
title.setText(R.string.search_normal)
ViewUtils.removeFromParent(mNormalView)
content.addView(mNormalView)
}
ITEM_TYPE_NORMAL_ADVANCE -> {
title.setText(R.string.search_advance)
ViewUtils.removeFromParent(mAdvanceView)
content.addView(mAdvanceView)
}
ITEM_TYPE_IMAGE -> {
title.setText(R.string.search_image)
ViewUtils.removeFromParent(mImageView)
content.addView(mImageView)
}
}
}
return SimpleHolder(view)
}
override fun onBindViewHolder(holder: SimpleHolder, position: Int) {
if (holder.itemViewType == ITEM_TYPE_ACTION) {
mAction!!.selectTab(mAction!!.getTabAt(mSearchMode))
}
}
override fun getItemId(position: Int): Long {
var type = SEARCH_ITEM_TYPE[mSearchMode][position]
if (mSearchMode == SEARCH_MODE_NORMAL && position == 1 && !mEnableAdvance) {
type = ITEM_TYPE_ACTION
}
return type.toLong()
}
}
inner class IdentifiedChip @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : Chip(context, attrs) {
var idt = 0
}
companion object {
const val SEARCH_MODE_NORMAL = 0
const val SEARCH_MODE_IMAGE = 1
private const val STATE_KEY_SUPER = "super"
private const val STATE_KEY_SEARCH_MODE = "search_mode"
private const val STATE_KEY_ENABLE_ADVANCE = "enable_advance"
const val SEARCH_CATEGORY_PREF = "search_pref"
private const val ITEM_TYPE_NORMAL = 0
private const val ITEM_TYPE_NORMAL_ADVANCE = 1
private const val ITEM_TYPE_IMAGE = 2
private const val ITEM_TYPE_ACTION = 3
private val SEARCH_ITEM_COUNT_ARRAY = intArrayOf(
3, 2
)
private val SEARCH_ITEM_TYPE = arrayOf(
intArrayOf(ITEM_TYPE_NORMAL, ITEM_TYPE_NORMAL_ADVANCE, ITEM_TYPE_ACTION), intArrayOf(
ITEM_TYPE_IMAGE, ITEM_TYPE_ACTION
)
)
}
}
@@ -31,8 +31,8 @@ import com.hippo.yorozuya.ViewUtils;
public class SeekBarPanel extends LinearLayout {
private SeekBar mSeekBar;
private final int[] mLocation = new int[2];
private SeekBar mSeekBar;
public SeekBarPanel(Context context) {
super(context);
@@ -1,73 +0,0 @@
package com.hippo.hardware;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
// http://jasonmcreynolds.com/?p=388
public class ShakeDetector implements SensorEventListener {
/*
* The gForce that is necessary to register as shake.
* Must be greater than 1G (one earth gravity unit).
* You can install "G-Force", by Blake La Pierre
* from the Google Play Store and run it to see how
* many G's it takes to register a shake
*/
private static final float SHAKE_THRESHOLD_GRAVITY = 2.7F;
private static final int SHAKE_SLOP_TIME_MS = 500;
private static final int SHAKE_COUNT_RESET_TIME_MS = 3000;
private OnShakeListener mListener;
private long mShakeTimestamp;
private int mShakeCount;
public void setOnShakeListener(OnShakeListener listener) {
mListener = listener;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// ignore
}
@Override
public void onSensorChanged(SensorEvent event) {
if (mListener != null) {
float x = event.values[0];
float y = event.values[1];
float z = event.values[2];
float gX = x / SensorManager.GRAVITY_EARTH;
float gY = y / SensorManager.GRAVITY_EARTH;
float gZ = z / SensorManager.GRAVITY_EARTH;
// gForce will be close to 1 when there is no movement.
float gForce = (float) Math.sqrt(gX * gX + gY * gY + gZ * gZ);
if (gForce > SHAKE_THRESHOLD_GRAVITY) {
final long now = System.currentTimeMillis();
// ignore shake events too close to each other (500ms)
if (mShakeTimestamp + SHAKE_SLOP_TIME_MS > now) {
return;
}
// reset the shake count after 3 seconds of no shakes
if (mShakeTimestamp + SHAKE_COUNT_RESET_TIME_MS < now) {
mShakeCount = 0;
}
mShakeTimestamp = now;
mShakeCount++;
mListener.onShake(mShakeCount);
}
}
}
public interface OnShakeListener {
void onShake(int count);
}
}
@@ -37,6 +37,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
/**
@@ -254,7 +255,7 @@ public abstract class DialogPreference extends Preference implements
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
mBuilder = new AlertDialog.Builder(context)
mBuilder = new MaterialAlertDialogBuilder(context)
.setTitle(mDialogTitle)
.setIcon(mDialogIcon)
.setPositiveButton(mPositiveButtonText, this)
@@ -36,7 +36,6 @@ public final class ReadableTime {
public static final long DAY_MILLIS = 24 * HOUR_MILLIS;
public static final long WEEK_MILLIS = 7 * DAY_MILLIS;
public static final long YEAR_MILLIS = 365 * DAY_MILLIS;
public static final int SIZE = 5;
public static final long[] MULTIPLES = {
YEAR_MILLIS,
DAY_MILLIS,
@@ -44,6 +43,7 @@ public final class ReadableTime {
MINUTE_MILLIS,
SECOND_MILLIS
};
public static final int SIZE = 5;
public static final int[] UNITS = {
R.plurals.year,
R.plurals.day,
@@ -26,12 +26,9 @@ public class ViewTransition {
protected static final long ANIMATE_TIME = 300L;
private final View[] mViews;
private int mShownView = -1;
protected ViewPropertyAnimator mAnimator1;
protected ViewPropertyAnimator mAnimator2;
private int mShownView = -1;
private OnShowViewListener mOnShowViewListener;
public ViewTransition(View... views) {
@@ -167,8 +167,8 @@ public class AutoWrapLayout extends ViewGroup {
// If it is first item in line, try to show it all
if (lineEndIndex == lineStartIndex) {
child.measure(MeasureSpec.makeMeasureSpec(
maxWidth - paddingLeft - paddingRight
- lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST),
maxWidth - paddingLeft - paddingRight
- lp.leftMargin - lp.rightMargin, MeasureSpec.AT_MOST),
MeasureSpec.UNSPECIFIED);
childWidth = child.getMeasuredWidth();
childHeight = child.getMeasuredHeight();
@@ -41,7 +41,8 @@ public class BatteryView extends AppCompatTextView {
private boolean mCharging = false;
private BatteryDrawable mDrawable;
private final Runnable mCharger = new Runnable() {
private boolean mAttached = false;
private boolean mIsChargerWorking = false; private final Runnable mCharger = new Runnable() {
private int level = 0;
@@ -55,39 +56,6 @@ public class BatteryView extends AppCompatTextView {
getHandler().postDelayed(mCharger, 200);
}
};
private boolean mAttached = false;
private boolean mIsChargerWorking = false;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@SuppressLint("SetTextI18n")
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
== BatteryManager.BATTERY_STATUS_CHARGING);
if (mLevel != level || mCharging != charging) {
mLevel = level;
mCharging = charging;
if (mCharging && mLevel != 100) {
startCharger();
} else {
stopCharger();
mDrawable.setElect(mLevel);
}
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
setTextColor(mWarningColor);
} else {
setTextColor(mColor);
}
setText(mLevel + "%");
}
}
};
public BatteryView(Context context) {
super(context);
init();
@@ -117,7 +85,36 @@ public class BatteryView extends AppCompatTextView {
int height = (int) getTextSize();
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
setCompoundDrawables(mDrawable, null, null, null);
}
} private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@SuppressLint("SetTextI18n")
public void onReceive(Context context, Intent intent) {
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
* 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
boolean charging = (intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
== BatteryManager.BATTERY_STATUS_CHARGING);
if (mLevel != level || mCharging != charging) {
mLevel = level;
mCharging = charging;
if (mCharging && mLevel != 100) {
startCharger();
} else {
stopCharger();
mDrawable.setElect(mLevel);
}
if (level <= BatteryDrawable.WARN_LIMIT && !charging) {
setTextColor(mWarningColor);
} else {
setTextColor(mColor);
}
setText(mLevel + "%");
}
}
};
@Override
public void setTextColor(int color) {
@@ -173,4 +170,10 @@ public class BatteryView extends AppCompatTextView {
private void unregisterReceiver() {
getContext().unregisterReceiver(mIntentReceiver);
}
}
@@ -1,241 +0,0 @@
/*
* Copyright (C) 2015 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.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatCheckedTextView;
import com.hippo.ehviewer.R;
public class CheckTextView extends AppCompatCheckedTextView implements View.OnClickListener {
private Drawable mForeground;
private final Rect mSelfBounds = new Rect();
private final Rect mOverlayBounds = new Rect();
private int mForegroundGravity = Gravity.FILL;
protected boolean mForegroundInPadding = true;
boolean mForegroundBoundsChanged = false;
public CheckTextView(Context context) {
this(context, null);
}
public CheckTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckTextView,
defStyleAttr, 0);
mForegroundGravity = a.getInt(
R.styleable.CheckTextView_android_foregroundGravity, mForegroundGravity);
Drawable d = a.getDrawable(R.styleable.CheckTextView_android_foreground);
if (d != null) {
setForeground(d);
}
mForegroundInPadding = a.getBoolean(
R.styleable.CheckTextView_foregroundInsidePadding, true);
a.recycle();
setOnClickListener(this);
}
/**
* Describes how the foreground is positioned.
*
* @return foreground gravity.
* @see #setForegroundGravity(int)
*/
public int getForegroundGravity() {
return mForegroundGravity;
}
/**
* Describes how the foreground is positioned. Defaults to START and TOP.
*
* @param foregroundGravity See {@link android.view.Gravity}
* @see #getForegroundGravity()
*/
public void setForegroundGravity(int foregroundGravity) {
if (mForegroundGravity != foregroundGravity) {
if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.START;
}
if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
foregroundGravity |= Gravity.TOP;
}
mForegroundGravity = foregroundGravity;
if (mForegroundGravity == Gravity.FILL && mForeground != null) {
Rect padding = new Rect();
mForeground.getPadding(padding);
}
requestLayout();
}
}
@Override
protected boolean verifyDrawable(@NonNull Drawable who) {
return super.verifyDrawable(who) || (who == mForeground);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mForeground != null) {
mForeground.jumpToCurrentState();
}
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mForeground != null && mForeground.isStateful()) {
mForeground.setState(getDrawableState());
}
}
/**
* Supply a Drawable that is to be rendered on top of all of the child
* views in the frame layout. Any padding in the Drawable will be taken
* into account by ensuring that the children are inset to be placed
* inside of the padding area.
*
* @param drawable The Drawable to be drawn on top of the children.
*/
public void setForeground(Drawable drawable) {
if (mForeground != drawable) {
if (mForeground != null) {
mForeground.setCallback(null);
unscheduleDrawable(mForeground);
}
mForeground = drawable;
if (drawable != null) {
setWillNotDraw(false);
drawable.setCallback(this);
if (drawable.isStateful()) {
drawable.setState(getDrawableState());
}
if (mForegroundGravity == Gravity.FILL) {
Rect padding = new Rect();
drawable.getPadding(padding);
}
} else {
setWillNotDraw(true);
}
requestLayout();
invalidate();
}
}
/**
* Returns the drawable used as the foreground of this FrameLayout. The
* foreground drawable, if non-null, is always drawn on top of the children.
*
* @return A Drawable or null if no foreground was set.
*/
public Drawable getForeground() {
return mForeground;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mForegroundBoundsChanged |= changed;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mForegroundBoundsChanged = true;
}
@Override
public void draw(@NonNull Canvas canvas) {
super.draw(canvas);
if (mForeground != null) {
final Drawable foreground = mForeground;
if (mForegroundBoundsChanged) {
mForegroundBoundsChanged = false;
final Rect selfBounds = mSelfBounds;
final Rect overlayBounds = mOverlayBounds;
final int w = getRight() - getLeft();
final int h = getBottom() - getTop();
if (mForegroundInPadding) {
selfBounds.set(0, 0, w, h);
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
w - getPaddingRight(), h - getPaddingBottom());
}
Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds);
foreground.setBounds(overlayBounds);
}
foreground.draw(canvas);
}
}
@Override
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
if (mForeground != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mForeground.setHotspot(x, y);
}
}
}
@Override
public void onClick(View v) {
setChecked(!isChecked());
}
}
@@ -56,7 +56,6 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import rikka.core.res.ResourcesKt;
import rikka.recyclerview.RecyclerViewKt;
public class ContentLayout extends FrameLayout {
@@ -106,7 +105,7 @@ public class ContentLayout extends FrameLayout {
mFastScroller.attachToRecyclerView(mRecyclerView);
HandlerDrawable drawable = new HandlerDrawable();
drawable.setColor(ResourcesKt.resolveColor(context.getTheme(), R.attr.widgetColorThemeAccent));
drawable.setColor(com.google.android.material.R.color.material_dynamic_neutral100);
mFastScroller.setHandlerDrawable(drawable);
mRefreshLayout.setColorSchemeResources(
@@ -125,7 +124,7 @@ public class ContentLayout extends FrameLayout {
resources.getColor(R.color.loading_indicator_orange));
mBottomProgress.setIndeterminateAnimationType(LinearProgressIndicator.INDETERMINATE_ANIMATION_TYPE_CONTIGUOUS);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4,4,4,4, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.addEdgeSpacing(mRecyclerView, 4, 4, 4, 4, TypedValue.COMPLEX_UNIT_DIP);
RecyclerViewKt.fixEdgeEffect(mRecyclerView, false, true);
mRecyclerViewOriginBottom = mRecyclerView.getPaddingBottom();
@@ -263,8 +262,6 @@ public class ContentLayout extends FrameLayout {
private int mCurrentTaskId;
private int mCurrentTaskType;
private int mCurrentTaskPage;
private int mNextPageScrollSize;
private String mEmptyString = "No hint";
private final SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
@@ -278,6 +275,8 @@ public class ContentLayout extends FrameLayout {
}
}
};
private int mNextPageScrollSize;
private String mEmptyString = "No hint";
private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
@@ -41,7 +41,6 @@ import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.image.ImageBitmap;
import com.hippo.image.ImageDrawable;
import com.hippo.image.RecycledException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -285,7 +284,7 @@ public class LoadImageView extends FixedAspectImageView implements Unikery<Image
Drawable drawable;
try {
drawable = new ImageDrawable(value);
} catch (RecycledException e) {
} catch (Exception e) {
// The image might be recycled because it is removed from memory cache.
Log.d(TAG, "The image is recycled", e);
return false;
@@ -134,10 +134,8 @@ public class Slider extends View {
setProgress(a.getInteger(R.styleable.Slider_slider_progress, 0));
mThickness = a.getDimension(R.styleable.Slider_thickness, 2);
mRadius = a.getDimension(R.styleable.Slider_radius, 6);
setColor(a.getColor(R.styleable.Slider_color, Color.BLACK));
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mBgPaint.setColor(a.getBoolean(R.styleable.Slider_dark, false) ? 0x4dffffff : 0x42000000);
a.recycle();
@@ -55,7 +55,17 @@ public class TextClock extends AppCompatTextView {
private boolean mAttached;
private Calendar mTime;
private final Runnable mTicker = new Runnable() {
private String mTimeZone;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
}; private final Runnable mTicker = new Runnable() {
@Override
public void run() {
onTimeChanged();
@@ -66,7 +76,12 @@ public class TextClock extends AppCompatTextView {
getHandler().postAtTime(mTicker, next);
}
};
private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
chooseFormat();
@@ -79,25 +94,6 @@ public class TextClock extends AppCompatTextView {
onTimeChanged();
}
};
private String mTimeZone;
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (mTimeZone == null && Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction())) {
final String timeZone = intent.getStringExtra("time-zone");
createTime(timeZone);
}
onTimeChanged();
}
};
public TextClock(Context context) {
this(context, null);
}
public TextClock(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TextClock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
@@ -261,4 +257,10 @@ public class TextClock extends AppCompatTextView {
mTime.setTimeInMillis(System.currentTimeMillis());
setText(DateFormat.format(mFormat, mTime));
}
}
@@ -58,9 +58,6 @@ public class LockPatternView extends View {
private static final int ASPECT_SQUARE = 0; // View will be the minimum of width/height
private static final int ASPECT_LOCK_WIDTH = 1; // Fixed width; height will be minimum of (w,h)
private static final int ASPECT_LOCK_HEIGHT = 2; // Fixed height; width will be minimum of (w,h)
private final Cell[][] mCells;
private static final boolean PROFILE_DRAWING = false;
/**
* How many milliseconds we spend animating each circle of a lock pattern
@@ -73,6 +70,7 @@ public class LockPatternView extends View {
* It didn't seem to have much impact on the devices tested, so currently set to 0.
*/
private static final float DRAG_THRESHOLD = 0.0f;
private final Cell[][] mCells;
private final CellState[][] mCellStates;
private final int mDotSize;
private final int mDotSizeActivated;
@@ -181,6 +179,66 @@ public class LockPatternView extends View {
mLinearOutSlowInInterpolator = new LinearOutSlowInInterpolator();
}
/**
* Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if
* {@code a == 0 && b == 0}.
*
* @throws IllegalArgumentException if {@code a < 0} or {@code b < 0}
*/
private static int gcd(int a, int b) {
/*
* The reason we require both arguments to be >= 0 is because otherwise, what do you return
* on gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive
* 2^31 isn't an int.
*/
//checkNonNegative("a", a);
if (a < 0) {
throw new IllegalArgumentException("a (" + a + ") must be >= 0");
}
//checkNonNegative("b", b);
if (b < 0) {
throw new IllegalArgumentException("b (" + b + ") must be >= 0");
}
if (a == 0) {
// 0 % b == 0, so b divides a, but the converse doesn't hold.
// BigInteger.gcd is consistent with this decision.
return b;
} else if (b == 0) {
return a; // similar logic
}
/*
* Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm.
* This is >40% faster than the Euclidean algorithm in benchmarks.
*/
int aTwos = Integer.numberOfTrailingZeros(a);
a >>= aTwos; // divide out all 2s
int bTwos = Integer.numberOfTrailingZeros(b);
b >>= bTwos; // divide out all 2s
while (a != b) { // both a, b are odd
// The key to the binary GCD algorithm is as follows:
// Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b).
// But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two.
// We bend over backwards to avoid branching, adapting a technique from
// http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
int delta = a - b; // can't overflow, since a and b are nonnegative
int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1));
// equivalent to Math.min(delta, 0)
a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b)
// a is now nonnegative and even
b += minDeltaOrZero; // sets b to min(old a, b)
a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b
}
return a << Math.min(aTwos, bTwos);
}
public int getCellSize() {
return mPattern.size();
}
@@ -333,6 +391,9 @@ public class LockPatternView extends View {
setMeasuredDimension(viewWidth, viewHeight);
}
// From
// https://github.com/google/guava/blob/c462d69329709f72a17a64cb229d15e76e72199c/guava/src/com/google/common/math/IntMath.java
/**
* Determines whether the point x, y will add a new point to the current
* pattern (in addition to finding the cell, also makes heuristic choices
@@ -376,69 +437,6 @@ public class LockPatternView extends View {
return null;
}
// From
// https://github.com/google/guava/blob/c462d69329709f72a17a64cb229d15e76e72199c/guava/src/com/google/common/math/IntMath.java
/**
* Returns the greatest common divisor of {@code a, b}. Returns {@code 0} if
* {@code a == 0 && b == 0}.
*
* @throws IllegalArgumentException if {@code a < 0} or {@code b < 0}
*/
private static int gcd(int a, int b) {
/*
* The reason we require both arguments to be >= 0 is because otherwise, what do you return
* on gcd(0, Integer.MIN_VALUE)? BigInteger.gcd would return positive 2^31, but positive
* 2^31 isn't an int.
*/
//checkNonNegative("a", a);
if (a < 0) {
throw new IllegalArgumentException("a (" + a + ") must be >= 0");
}
//checkNonNegative("b", b);
if (b < 0) {
throw new IllegalArgumentException("b (" + b + ") must be >= 0");
}
if (a == 0) {
// 0 % b == 0, so b divides a, but the converse doesn't hold.
// BigInteger.gcd is consistent with this decision.
return b;
} else if (b == 0) {
return a; // similar logic
}
/*
* Uses the binary GCD algorithm; see http://en.wikipedia.org/wiki/Binary_GCD_algorithm.
* This is >40% faster than the Euclidean algorithm in benchmarks.
*/
int aTwos = Integer.numberOfTrailingZeros(a);
a >>= aTwos; // divide out all 2s
int bTwos = Integer.numberOfTrailingZeros(b);
b >>= bTwos; // divide out all 2s
while (a != b) { // both a, b are odd
// The key to the binary GCD algorithm is as follows:
// Both a and b are odd. Assume a > b; then gcd(a - b, b) = gcd(a, b).
// But in gcd(a - b, b), a - b is even and b is odd, so we can divide out powers of two.
// We bend over backwards to avoid branching, adapting a technique from
// http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
int delta = a - b; // can't overflow, since a and b are nonnegative
int minDeltaOrZero = delta & (delta >> (Integer.SIZE - 1));
// equivalent to Math.min(delta, 0)
a = delta - minDeltaOrZero - minDeltaOrZero; // sets a to Math.abs(a - b)
// a is now nonnegative and even
b += minDeltaOrZero; // sets b to min(old a, b)
a >>= Integer.numberOfTrailingZeros(a); // divide out all 2s, since 2 doesn't divide b
}
return a << Math.min(aTwos, bTwos);
}
private void addCellToPattern(Cell newCell) {
mPatternDrawLookup[newCell.getRow()][newCell.getColumn()] = true;
mPattern.add(newCell);
@@ -1040,14 +1038,14 @@ public class LockPatternView extends View {
}
public static class CellState {
public float lineEndX = Float.MIN_VALUE;
public float lineEndY = Float.MIN_VALUE;
public ValueAnimator lineAnimator;
int row;
int col;
float radius;
float translationY;
float alpha = 1f;
public float lineEndX = Float.MIN_VALUE;
public float lineEndY = Float.MIN_VALUE;
public ValueAnimator lineAnimator;
}
/**
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/content_activated" android:state_activated="true" />
<item android:color="@color/content" />
</selector>
@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/content_activated_black" android:state_activated="true" />
<item android:color="@color/content_black" />
</selector>
@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/primary_text_disabled_material_black" android:state_enabled="false" />
<item android:color="@color/primary_text_default_material_black" />
</selector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 9.2 KiB

+2 -2
View File
@@ -17,9 +17,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:tint="?drawableColorSecondary"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?drawableColorSecondary">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
+2 -2
View File
@@ -17,9 +17,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:tint="?drawableColorSecondary"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?drawableColorSecondary">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
+2 -2
View File
@@ -17,9 +17,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:tint="?drawableColorSecondary"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?drawableColorSecondary">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
@@ -17,9 +17,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="120dp"
android:height="120dp"
android:tint="?drawableColorSecondary"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?drawableColorSecondary">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@android:color/white" />
<color android:color="@android:color/system_neutral1_50" />
</item>
<item android:drawable="?android:attr/selectableItemBackground" />
</layer-list>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2016 Hippo Seven
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/divider" />
<size android:height="1dp" />
</shape>
@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/divider_black" />
<size android:height="1dp" />
</shape>
@@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z"/>
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z" />
</vector>
@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z" />
Loaded 100 of 262 files, more files were not shown because too many files have changed in this diff. Show more