Initial commit: Favorites - Android bookmark navigator

This commit is contained in:
personal82555 committed 2026-06-15 23:38:00 +08:00
commit 461778d763
33 files changed
+2050

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
# Gradle
.gradle/
build/
app/build/
local.properties
# IDE
.idea/
*.iml
.DS_Store
# Android
*.apk
*.aab
*.keystore
signing.properties
# Local state
.local/
# Temp files
icon_preview.html
+1
View File
@@ -0,0 +1 @@
46
+64
View File
@@ -0,0 +1,64 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.hermes.app"
compileSdk = 35
defaultConfig {
applicationId = "com.hermes.app"
minSdk = 24
targetSdk = 35
versionCode = 46
versionName = "1.0.46"
}
signingConfigs {
create("release") {
storeFile = rootProject.file("hermes.keystore")
storePassword = "hermes123"
keyAlias = "hermes"
keyPassword = "hermes123"
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
signingConfig = signingConfigs.getByName("release")
}
debug {
isMinifyEnabled = false
applicationIdSuffix = ".debug"
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.webkit:webkit:1.9.0")
}
+23
View File
@@ -0,0 +1,23 @@
# Keep WebView JS interface
-keepclassmembers class * {
@android.webkit.JavascriptInterface <methods>;
}
# Keep Hermes voice bridge class explicitly (inner classes get renamed by R8)
-keep class com.hermes.app.MainActivity$HermesVoiceBridge { *; }
# Keep Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# Keep Serializable
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.HermesApp">
<activity
android:name=".MainActivity"
android:exported="true"
android:hardwareAccelerated="true"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|locale|layoutDirection"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#80000000" />
<size android:width="36dp" android:height="36dp" />
</shape>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Background -->
<item>
<shape android:shape="rectangle">
<solid android:color="#F0F6FF" />
<corners android:radius="10dp" />
</shape>
</item>
<!-- Left accent bar -->
<item android:left="0dp" android:top="4dp" android:bottom="4dp" android:width="4dp">
<shape android:shape="rectangle">
<solid android:color="#1565C0" />
<corners android:topLeftRadius="2dp" android:bottomLeftRadius="2dp" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z" />
</vector>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1H8v-2h2c0.55,0 1,-0.45 1,-1V7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Darker blue background -->
<path
android:fillColor="#0D47A1"
android:pathData="M0,0h108v108H0z" />
</vector>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Golden five-pointed star -->
<path
android:fillColor="#FFD54F"
android:pathData="M54,12L66,40L96,40L72,58L82,88L54,68L26,88L36,58L12,40L42,40Z" />
<!-- Subtle inner highlight -->
<path
android:fillColor="#FFC107"
android:pathData="M54,22L60,36L74,36L64,46L68,60L54,50L40,60L44,46L34,36L48,36Z" />
</vector>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#FFFFFF">
<!-- Three vertical dots (⋮) overflow menu icon -->
<path
android:fillColor="#FFFFFF"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z" />
</vector>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android-color="#CC1A1A1A" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="#44FFFFFF" />
</shape>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Shadow layer -->
<item android:left="0dp" android:top="1dp" android:right="0dp" android:bottom="2dp">
<shape android:shape="rectangle">
<solid android:color="#08000000" />
<corners android:radius="12dp" />
</shape>
</item>
<!-- Card background -->
<item android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp">
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="12dp" />
</shape>
</item>
</layer-list>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#F0F8FF" />
<corners android:radius="10dp" />
<stroke android:width="1dp" android:color="#1565C0" />
</shape>
+174
View File
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<!-- ===== Navigation Page ===== -->
<LinearLayout
android:id="@+id/navPage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#F2F4F7"
android:visibility="visible">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:paddingTop="20dp"
android:paddingBottom="14dp"
android:background="@color/hermes_primary"
android:gravity="center_vertical">
<!-- App icon -->
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_globe"
android:contentDescription="图标"
android:layout_marginEnd="10dp" />
<!-- Title + subtitle -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Favorites"
android:textColor="@android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headerSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="书签导航"
android:textColor="#B0D4F1"
android:textSize="12sp"
android:layout_marginTop="1dp" />
</LinearLayout>
<!-- Overflow menu button -->
<ImageButton
android:id="@+id/overflowBtn"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_overflow"
android:scaleType="centerInside"
android:padding="8dp"
android:contentDescription="更多"
android:tint="#FFFFFF" />
</LinearLayout>
<!-- URL List -->
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="12dp"
android:paddingTop="8dp"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/urlListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
<!-- Bottom hint -->
<TextView
android:id="@+id/navHint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:text="点击书签打开 · 批量粘贴或导入 HTML 添加更多"
android:textColor="#999999"
android:textSize="12sp"
android:gravity="center"
android:background="#FFFFFF" />
</LinearLayout>
<!-- ===== WebView Page (tab strip at bottom) ===== -->
<LinearLayout
android:id="@+id/webPage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone">
<!-- WebView area (fills top) -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout
android:id="@+id/webViewContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="false"
app:trackColor="@android:color/transparent"
app:indicatorColor="@color/hermes_accent"
android:visibility="gone" />
</FrameLayout>
<!-- Tab Strip (fixed at bottom) -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#F5F5F5"
android:elevation="4dp"
android:gravity="center_vertical"
android:paddingStart="0dp"
android:paddingEnd="0dp">
<HorizontalScrollView
android:id="@+id/tabScroll"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:scrollbars="none"
android:fadingEdgeLength="0dp"
android:overScrollMode="never">
<LinearLayout
android:id="@+id/tabStrip"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="6dp"
android:paddingEnd="4dp" />
</HorizontalScrollView>
<ImageButton
android:id="@+id/newTabBtn"
android:layout_width="44dp"
android:layout_height="44dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_input_add"
android:scaleType="center"
android:tint="#555555"
android:contentDescription="新建标签" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="hermes_primary">#1565C0</color>
<color name="hermes_primary_dark">#0D47A1</color>
<color name="hermes_accent">#42A5F5</color>
<color name="hermes_background">#FAFAFA</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Favorites</string>
</resources>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.HermesApp" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/hermes_primary</item>
<item name="colorPrimaryDark">@color/hermes_primary_dark</item>
<item name="colorAccent">@color/hermes_accent</item>
<item name="android:statusBarColor">@color/hermes_primary_dark</item>
<item name="android:windowBackground">@color/hermes_background</item>
<item name="android:windowTranslucentStatus">false</item>
</style>
</resources>
Executable
+45
View File
@@ -0,0 +1,45 @@
#!/bin/bash
set -e
cd "$(dirname "$0")"
PROJECT_DIR="$(pwd)"
APP_DIR="$PROJECT_DIR/app"
OUTPUT_DIR="/vol2/1000/HD2/APK"
# 读取版本号
VERSION=$(cat VERSION 2>/dev/null || echo 0)
NEXT=$((VERSION + 1))
echo "========== Favorites APK Builder =========="
echo " 当前版本: v$VERSION → v$NEXT"
echo " 目标目录: $OUTPUT_DIR"
echo ""
export ANDROID_HOME=/opt/android-sdk
export ANDROID_SDK_ROOT=/opt/android-sdk
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
# 更新 build.gradle.kts 中的 versionCode 和 versionName
sed -i "s/versionCode = [0-9]*/versionCode = $NEXT/" "$APP_DIR/build.gradle.kts"
sed -i "s/versionName = \"[0-9.]*\"/versionName = \"1.0.$NEXT\"/" "$APP_DIR/build.gradle.kts"
# 构建
echo ">>> 开始构建..."
/opt/gradle-8.5/bin/gradle --no-daemon -p "$PROJECT_DIR" :app:assembleRelease 2>&1
echo ""
echo ">>> 构建完成!"
# 复制到目标目录
APK_SRC="$PROJECT_DIR/app/build/outputs/apk/release/app-release.apk"
APK_DST="$OUTPUT_DIR/favorites_v${NEXT}.apk"
mkdir -p "$OUTPUT_DIR"
cp "$APK_SRC" "$APK_DST"
# 写入新版本号
echo "$NEXT" > VERSION
echo ""
echo "========== 成功 =========="
echo " APK: $APK_DST"
ls -lh "$APK_DST"
echo "==========================="
+4
View File
@@ -0,0 +1,4 @@
plugins {
id("com.android.application") version "8.2.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
}
+5
View File
@@ -0,0 +1,5 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
kotlin.code.style=official
android.nonTransitiveRClass=true
android.suppressUnsupportedCompileSdk=35
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+130
View File
@@ -0,0 +1,130 @@
#!/bin/sh
#
# Gradle start up script for POSIX generated by Gradle.
#
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld -- "$app_path" )
link=${ls#*' -> '}
case $link in
/*) app_path=$link ;;
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in
CYGWIN* ) cygwin=true ;;
Darwin* ) darwin=true ;;
MSYS* | MINGW* ) msys=true ;;
NonStop* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1 ; then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
;;
esac
case $MAX_FD in
'' | soft) :;;
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
;;
esac
fi
# Collect all arguments for the java command, stracks://gnu.org/software/bash/manual/html_node/Bash-Startup-Files.html
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and backslashes, so put them in double quotes
# to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-528teleco]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" \
$DEFAULT_JVM_OPTS \
$JAVA_OPTS \
$GRADLE_OPTS \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
+18
View File
@@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "HermesApp"
include(":app")