This commit is contained in:
Phoen1x
2025-11-08 20:29:38 +08:00
commit dea2db8cb8
101 changed files with 8251 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
+1
View File
@@ -0,0 +1 @@
/build
+57
View File
@@ -0,0 +1,57 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.phoenix.gui"
compileSdk = 36
defaultConfig {
applicationId = "com.phoenix.gui"
minSdk = 24
targetSdk = 36
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
viewBinding = true
}
// for debug
packaging {
jniLibs.keepDebugSymbols += "**/*.so"
}
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.activity)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 权限基本用不到,小孩子不懂事调试用的 -->
<application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:screenOrientation="landscape"
android:exported="true">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,424 @@
package com.phoenix.gui;
import android.content.*;
import android.graphics.Color;
import android.os.*;
import android.view.View;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.*;
import androidx.activity.ComponentActivity;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleManager;
import com.phoenix.gui.module.ModuleToggleListener;
import com.phoenix.gui.module.ShortcutToggleListener;
import com.phoenix.gui.ui.ArraylistModule;
import com.phoenix.gui.ui.ArraylistView;
import com.phoenix.gui.ui.DynamicColorExtractor;
import com.phoenix.gui.ui.FloatBallView;
import com.phoenix.gui.ui.ShortcutButton;
import com.phoenix.gui.ui.dynamic.DynamicIslandWindow;
import com.phoenix.gui.ui.ThemeManager;
import com.phoenix.gui.ui.UI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class MainActivity extends ComponentActivity implements ModuleToggleListener, ShortcutToggleListener {
private boolean wifiState = true;
private Button btnStart, btnStop, btnTestProgress, btnTestSwitch, btnHideAll, btnSave, btnToggleDebug, btnToggleArraylist;
private Button btnAddRandom, btnRemoveRandom, btnResetArraylist;
private EditText etUsername;
private SeekBar seekScale;
private ScrollView debugPanel;
private FrameLayout shortcutsContainer;
private final Map<Module, ShortcutButton> shortcutButtons = new HashMap<>();
private final List<String> allTestModules = new ArrayList<>();
private final Random random = new Random();
@Override
protected void onCreate(Bundle b) {
super.onCreate(b);
requestWindowFeature(Window.FEATURE_NO_TITLE);
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
decorView.setSystemUiVisibility(uiOptions);
setContentView(R.layout.activity_main);
UI.init(this); // 封装
initAllTestModules();
bindViews();
loadConfig();
setListeners();
updateBtnState();
toast("MainActivity ready");
}
private void initAllTestModules() {
// 下面那个随机测试的,可以随便填点啥
}
private void bindViews() {
btnStart = findViewById(R.id.btn_start);
btnStop = findViewById(R.id.btn_stop);
btnTestProgress = findViewById(R.id.btn_test_progress);
btnTestSwitch = findViewById(R.id.btn_test_switch);
btnHideAll = findViewById(R.id.btn_hide_all);
btnSave = findViewById(R.id.btn_save);
btnToggleDebug = findViewById(R.id.btn_toggle_debug);
btnToggleArraylist = findViewById(R.id.btn_toggle_arraylist);
btnAddRandom = findViewById(R.id.btn_add_random_module);
btnRemoveRandom = findViewById(R.id.btn_remove_random_module);
btnResetArraylist = findViewById(R.id.btn_reset_arraylist);
etUsername = findViewById(R.id.et_username);
seekScale = findViewById(R.id.seek_scale);
debugPanel = findViewById(R.id.debug_panel);
shortcutsContainer = findViewById(R.id.shortcuts_container);
}
private void loadConfig() {
etUsername.setText(ConfigManager.getDynamicIslandUsername());
seekScale.setProgress((int) (ConfigManager.getDynamicIslandScale() * 100));
}
private void saveConfig() {
String name = etUsername.getText().toString().trim();
float scale = seekScale.getProgress() / 100f;
ConfigManager.setDynamicIslandUsername(name);
ConfigManager.setDynamicIslandScale(scale);
DynamicIslandWindow island = UI.getDynamicIsland();
if (island != null && island.isShowing()) {
island.updateConfig(scale, name);
}
toast("saved");
}
private void setListeners() {
btnStart.setOnClickListener(v -> startGUI());
btnStop.setOnClickListener(v -> stopGUI());
btnSave.setOnClickListener(v -> saveConfig());
btnToggleDebug.setOnClickListener(v -> toggleDebugPanel());
if (btnToggleArraylist != null) {
btnToggleArraylist.setOnClickListener(v -> toggleArraylist());
}
if (btnAddRandom != null) {
btnAddRandom.setOnClickListener(v -> addRandomModule());
}
if (btnRemoveRandom != null) {
btnRemoveRandom.setOnClickListener(v -> removeRandomModule());
}
if (btnResetArraylist != null) {
btnResetArraylist.setOnClickListener(v -> {
addDefaultArraylistModules();
toast("Arraylist reset");
});
}
btnTestProgress.setOnClickListener(v -> {
showProgress();
toast("test progress");
});
btnTestSwitch.setOnClickListener(v -> showSwitch());
btnHideAll.setOnClickListener(v -> {
hideAll();
toast("hide all");
});
}
private void updateBtnState() {
boolean isRunning = UI.isShowing();
btnStart.setEnabled(!isRunning);
btnStop.setEnabled(isRunning);
btnTestProgress.setEnabled(isRunning);
btnTestSwitch.setEnabled(isRunning);
btnHideAll.setEnabled(isRunning);
boolean arraylistControlsEnabled = isRunning && UI.getArraylistView() != null;
if (btnToggleArraylist != null) {
btnToggleArraylist.setEnabled(isRunning);
}
if (btnAddRandom != null) {
btnAddRandom.setEnabled(arraylistControlsEnabled);
}
if (btnRemoveRandom != null) {
btnRemoveRandom.setEnabled(arraylistControlsEnabled);
}
if (btnResetArraylist != null) {
btnResetArraylist.setEnabled(arraylistControlsEnabled);
}
}
private void startGUI() {
if (UI.isShowing()) return;
try {
UI.show(this);
updateBtnState();
toast("GUI started");
} catch (Exception e) {
e.printStackTrace();
toast("Failed to start GUI: " + e.getMessage());
}
}
private void stopGUI() {
if (!UI.isShowing()) return;
try {
if (!shortcutButtons.isEmpty()) {
for (ShortcutButton button : shortcutButtons.values()) {
try {
button.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
shortcutButtons.clear();
shortcutsContainer.removeAllViews();
}
UI.hide();
updateBtnState();
toast("GUI stopped");
} catch (Exception e) {
e.printStackTrace();
toast("Failed to stop GUI: " + e.getMessage());
}
}
private void addDefaultArraylistModules() {
ArraylistView arraylistView = UI.getArraylistView();
if (arraylistView == null) return;
arraylistView.clearModules(); // 是的你没看错这个才是真的能用的
// arraylistView.addModule(new ArraylistModule("like this"));
}
private void addRandomModule() {
ArraylistView arraylistView = UI.getArraylistView();
if (arraylistView == null || allTestModules.isEmpty()) return;
String moduleName = allTestModules.get(random.nextInt(allTestModules.size()));
arraylistView.addModule(new ArraylistModule(moduleName));
toast("Added: " + moduleName);
}
private void removeRandomModule() {
ArraylistView arraylistView = UI.getArraylistView();
if (arraylistView != null && !arraylistView.getModules().isEmpty()) {
int indexToRemove = random.nextInt(arraylistView.getModules().size());
String moduleName = arraylistView.getModules().get(indexToRemove).getName();
arraylistView.removeModule(moduleName);
toast("Removed: " + moduleName);
} else {
toast("Arraylist is empty");
}
}
private void toggleArraylist() {
ArraylistView arraylist = UI.getArraylistView();
if (arraylist == null) {
toast("请先启动 GUI");
} else {
if (arraylist.getVisibility() == View.VISIBLE) {
arraylist.hide();
toast("Arraylist 已隐藏");
} else {
arraylist.show();
toast("Arraylist 已显示");
}
}
updateBtnState();
}
private void toggleDynamicIsland() {
DynamicIslandWindow island = UI.getDynamicIsland();
if (island == null) {
ConfigManager.setDynamicIslandEnabled(true);
toast("请重启 GUI 显示灵动岛");
} else {
try {
island.hide();
ConfigManager.setDynamicIslandEnabled(false);
toast("灵动岛已隐藏");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void showProgress() {
DynamicIslandWindow island = UI.getDynamicIsland();
if (island == null || !island.isShowing()) return;
try {
island.addOrUpdateProgress("test_progress", "progress", "im subtitle", null, 0.65f, null);
} catch (Exception e) {
e.printStackTrace();
}
}
private void showSwitch() {
DynamicIslandWindow island = UI.getDynamicIsland();
if (island == null || !island.isShowing()) return;
try {
wifiState = !wifiState;
island.addSwitch("test_switch", "im a function", wifiState);
toast("test switch -> " + (wifiState ? "ON" : "OFF"));
} catch (Exception e) {
e.printStackTrace();
}
}
private void hideAll() {
DynamicIslandWindow island = UI.getDynamicIsland();
if (island != null && island.isShowing()) {
try {
island.hideAllTasks();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void toggleDebugPanel() {
if (debugPanel.getVisibility() == View.VISIBLE) {
debugPanel.setVisibility(View.GONE);
} else {
debugPanel.setVisibility(View.VISIBLE);
}
}
@Override
public void onModuleToggled(Module module) {
DynamicIslandWindow island = UI.getDynamicIsland();
if (island != null && island.isShowing()) {
try {
island.addSwitch("module_" + module.getName(), module.getName(), module.isEnabled());
} catch (Exception e) {
e.printStackTrace();
}
}
ShortcutButton button = shortcutButtons.get(module);
if (button != null) {
try {
button.updateStyle();
} catch (Exception e) {
e.printStackTrace();
}
}
ArraylistView arraylistView = UI.getArraylistView();
if (arraylistView != null) {
try {
if (module.isEnabled()) {
arraylistView.addModule(module.getName());
} else {
arraylistView.removeModule(module.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onShortcutToggled(Module module, boolean enabled) {
if (enabled) {
createShortcut(module);
} else {
removeShortcut(module);
}
}
private void createShortcut(Module module) {
if (!shortcutButtons.containsKey(module)) {
try {
ShortcutButton button = new ShortcutButton(this, module);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER
);
button.setLayoutParams(params);
shortcutsContainer.addView(button);
button.show();
shortcutButtons.put(module, button);
} catch (Exception e) {
e.printStackTrace();
toast("创建快捷按钮失败: " + e.getMessage());
}
}
}
private void removeShortcut(Module module) {
ShortcutButton button = shortcutButtons.get(module);
if (button != null) {
try {
button.hide();
button.postDelayed(() -> {
shortcutsContainer.removeView(button);
button.destroy();
}, 250);
} catch (Exception e) {
e.printStackTrace();
}
shortcutButtons.remove(module);
}
}
private void toast(String s) {
Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
android.util.Log.d("PHOENIX", s); // me。
}
@Override
protected void onDestroy() {
super.onDestroy();
if (UI.isShowing()) {
stopGUI();
}
UI.cleanup();
}
@Override
public void onBackPressed() {
if (UI.isShowing()) {
stopGUI(); // 回家吃饭
} else {
super.onBackPressed();
}
}
}
@@ -0,0 +1,108 @@
package com.phoenix.gui.config;
import android.content.Context;
import android.content.SharedPreferences;
public class ConfigManager {
private static final String PREF_NAME = "phoenix_gui_config";
private static SharedPreferences sharedPreferences;
private static final String KEY_DYNAMIC_ISLAND_ENABLED = "dynamic_island_enabled";
private static final String KEY_DYNAMIC_ISLAND_SCALE = "dynamic_island_scale";
private static final String KEY_DYNAMIC_ISLAND_USERNAME = "dynamic_island_username";
// 初始化
public static void init(Context context) {
if (sharedPreferences == null) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
}
private static String getKey(String moduleName, String configKey) {
return moduleName + "_" + configKey;
}
private static SharedPreferences getPrefs() {
if (sharedPreferences == null) {
throw new IllegalStateException("ConfigManager not initialized. Call init() first.");
}
return sharedPreferences;
}
public static float getFloat(String moduleName, String key, float defaultValue) {
try {
return getPrefs().getFloat(moduleName + "_" + key, defaultValue);
} catch (ClassCastException e) {
try {
return (float) getPrefs().getInt(moduleName + "_" + key, (int) defaultValue);
} catch (Exception e2) {
return defaultValue;
}
}
}
public static void saveInt(String moduleName, String configKey, int value) {
if (sharedPreferences == null) return;
sharedPreferences.edit().putInt(getKey(moduleName, configKey), value).apply();
}
public static int getInt(String moduleName, String configKey, int defaultValue) {
if (sharedPreferences == null) return defaultValue;
return sharedPreferences.getInt(getKey(moduleName, configKey), defaultValue);
}
public static void saveBoolean(String moduleName, String configKey, boolean value) {
if (sharedPreferences == null) return;
sharedPreferences.edit().putBoolean(getKey(moduleName, configKey), value).apply();
}
public static boolean getBoolean(String moduleName, String configKey, boolean defaultValue) {
if (sharedPreferences == null) return defaultValue;
return sharedPreferences.getBoolean(getKey(moduleName, configKey), defaultValue);
}
public static void saveModuleConfig(String moduleName, String configKey, int value) {
saveInt(moduleName, configKey, value);
}
public static void saveModuleConfig(String moduleName, String configKey, boolean value) {
saveBoolean(moduleName, configKey, value);
}
public static boolean getDynamicIslandEnabled() {
return getPrefs().getBoolean(KEY_DYNAMIC_ISLAND_ENABLED, true);
}
public static void setDynamicIslandEnabled(boolean enabled) {
getPrefs().edit()
.putBoolean(KEY_DYNAMIC_ISLAND_ENABLED, enabled)
.apply();
}
public static float getDynamicIslandScale() {
return getPrefs().getFloat(KEY_DYNAMIC_ISLAND_SCALE, 0.7f);
}
public static void setDynamicIslandScale(float scale) {
getPrefs().edit()
.putFloat(KEY_DYNAMIC_ISLAND_SCALE, scale)
.apply();
}
public static String getDynamicIslandUsername() {
return getPrefs().getString(KEY_DYNAMIC_ISLAND_USERNAME, "User");
}
public static void setDynamicIslandUsername(String username) {
getPrefs().edit()
.putString(KEY_DYNAMIC_ISLAND_USERNAME, username)
.apply();
}
public static void clearAll() {
getPrefs().edit().clear().apply();
}
private ConfigManager() {
}
}
@@ -0,0 +1,91 @@
package com.phoenix.gui.module;
import com.phoenix.gui.ui.widgets.SubMenuPanel;
public abstract class Module {
private final String name;
private final ModuleCategory category;
private final String description;
private boolean isEnabled = false;
private boolean shortcutEnabled = false;
public Module(String name, ModuleCategory category, String description) {
this.name = name;
this.category = category;
this.description = description;
}
public Module(String name, ModuleCategory category) {
this(name, category, "");
}
public String getName() {
return name;
}
public ModuleCategory getCategory() {
return category;
}
public String getDescription() {
return description;
}
public boolean isEnabled() {
return isEnabled;
}
public boolean isShortcutEnabled() {
return shortcutEnabled;
}
public boolean supportsShortcut() {
return true;
}
public void toggle() {
if (isEnabled) {
disable();
} else {
enable();
}
}
public void enable() {
if (!isEnabled) {
isEnabled = true;
onEnable();
ModuleManager.notifyModuleToggled(this);
}
}
public void disable() {
if (isEnabled) {
isEnabled = false;
onDisable();
ModuleManager.notifyModuleToggled(this);
}
}
protected void onEnable() {}
protected void onDisable() {}
public void configureSubMenu(SubMenuPanel subMenu) {
if (supportsShortcut()) {
subMenu.addShortcutSwitch(shortcutEnabled);
}
}
protected void onShortcutToggled(boolean enabled) {}
public void toggleShortcut(boolean enabled) {
if (shortcutEnabled != enabled) {
shortcutEnabled = enabled;
onShortcutToggled(enabled);
ModuleManager.notifyShortcutToggled(this, enabled);
}
}
}
@@ -0,0 +1,19 @@
package com.phoenix.gui.module;
public enum ModuleCategory {
COMBAT("Combat"),
MOVEMENT("Movement"),
PLAYER("Player"),
VISUAL("Visual"),
WORLD("World");
private final String displayName;
ModuleCategory(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
@@ -0,0 +1,124 @@
package com.phoenix.gui.module;
import com.phoenix.gui.module.impl.combat.*;
import com.phoenix.gui.module.impl.movement.*;
import com.phoenix.gui.module.impl.player.*;
import com.phoenix.gui.module.impl.visual.*;
import com.phoenix.gui.module.impl.world.*;
import java.util.ArrayList;
import java.util.List;
public class ModuleManager {
private static final List<Module> modules = new ArrayList<>();
private static final List<ModuleToggleListener> listeners = new ArrayList<>();
private static final List<ShortcutToggleListener> shortcutListeners = new ArrayList<>();
static {
registerModules();
}
private static void registerModules() { //注册
register(new HitBoxModule());
register(new InfiniteAuraModule());
register(new KillAuraModule());
register(new FightBotModule());
register(new RideModule());
register(new LockBackModule());
register(new AirJumpModule());
register(new SpeedModule());
register(new FastStopModule());
register(new ScaffoldModule());
register(new JekBackModule());
register(new FlyModule());
register(new AntiBotModule());
register(new BlinkModule());
register(new NameTagModule());
register(new PhasesModule());
register(new TeleportModule());
register(new ThemeModule());
register(new FovModule());
register(new DisableModule());
register(new GodModeModule());
register(new OnPosModule());
register(new InfiniteYModule());
register(new FuckerModule());
register(new RemoteStopModule());
register(new GameModeModule());
register(new AutoClickModule());
register(new AimAssetsModule());
}
private static void register(Module module) {
modules.add(module);
}
public static List<Module> getAllModules() {
return new ArrayList<>(modules);
}
public static List<Module> getModulesByCategory(ModuleCategory category) {
List<Module> result = new ArrayList<>();
for (Module module : modules) {
if (module.getCategory() == category) {
result.add(module);
}
}
return result;
}
public static Module getModuleByName(String name) {
for (Module module : modules) {
if (module.getName().equalsIgnoreCase(name)) {
return module;
}
}
return null;
}
public static void addToggleListener(ModuleToggleListener listener) {
listeners.add(listener);
}
public static void removeToggleListener(ModuleToggleListener listener) {
listeners.remove(listener);
}
public static void addShortcutListener(ShortcutToggleListener listener) {
shortcutListeners.add(listener);
}
public static void removeShortcutListener(ShortcutToggleListener listener) {
shortcutListeners.remove(listener);
}
public static void notifyModuleToggled(Module module) {
for (ModuleToggleListener listener : listeners) {
listener.onModuleToggled(module);
}
}
public static void notifyShortcutToggled(Module module, boolean enabled) {
for (ShortcutToggleListener listener : shortcutListeners) {
listener.onShortcutToggled(module, enabled);
}
}
public static void disableAll() {
for (Module module : modules) {
if (module.isEnabled()) {
module.disable();
}
}
}
private ModuleManager() {
}
}
@@ -0,0 +1,5 @@
package com.phoenix.gui.module;
public interface ModuleToggleListener {
void onModuleToggled(Module module);
}
@@ -0,0 +1,5 @@
package com.phoenix.gui.module;
public interface ShortcutToggleListener {
void onShortcutToggled(Module module, boolean enabled);
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class FightBotModule extends Module {
public FightBotModule() {
super("FightBot", ModuleCategory.COMBAT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class HitBoxModule extends Module {
public HitBoxModule() {
super("HitBox", ModuleCategory.COMBAT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class InfiniteAuraModule extends Module {
public InfiniteAuraModule() {
super("InfiniteAura", ModuleCategory.COMBAT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,55 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
import com.phoenix.gui.ui.widgets.SubMenuPanel;
import java.util.Arrays;
public class KillAuraModule extends Module {
private float range = 4.0f;
private int cps = 10;
private int targetMode = 0;
private boolean autoBlock = false;
public KillAuraModule() {
super("KillAura", ModuleCategory.COMBAT, "囊囊囊");
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
@Override
public void configureSubMenu(SubMenuPanel subMenu) {
subMenu.addSlider("Range", 300, 800, (int)(range * 100), "%.2f", value -> {
range = value / 100.0f;
});
subMenu.addSlider("CPS", 1, 20, cps, "%d", value -> {
cps = value;
});
subMenu.addMode("Target", Arrays.asList("Single", "Multi"), targetMode, (index, mode) -> {
targetMode = index;
});
subMenu.addSwitch("Auto Block", autoBlock, enabled -> {
autoBlock = enabled;
});
super.configureSubMenu(subMenu);
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class LockBackModule extends Module {
public LockBackModule() {
super("LockBack", ModuleCategory.COMBAT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.combat;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class RideModule extends Module {
public RideModule() {
super("Ride", ModuleCategory.COMBAT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class AirJumpModule extends Module {
public AirJumpModule() {
super("AirJump", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class FastStopModule extends Module {
public FastStopModule() {
super("FastStop", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class FlyModule extends Module {
public FlyModule() {
super("Fly", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class JekBackModule extends Module {
public JekBackModule() {
super("JekBack", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class ScaffoldModule extends Module {
public ScaffoldModule() {
super("Scaffold", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.movement;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class SpeedModule extends Module {
public SpeedModule() {
super("Speed", ModuleCategory.MOVEMENT);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.player;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class AntiBotModule extends Module {
public AntiBotModule() {
super("AntiBot", ModuleCategory.PLAYER);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.player;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class BlinkModule extends Module {
public BlinkModule() {
super("Blink", ModuleCategory.PLAYER);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.player;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class NameTagModule extends Module {
public NameTagModule() {
super("NameTag", ModuleCategory.PLAYER);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.player;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class PhasesModule extends Module {
public PhasesModule() {
super("Phases", ModuleCategory.PLAYER);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.player;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class TeleportModule extends Module {
public TeleportModule() {
super("Teleport", ModuleCategory.PLAYER);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class DisableModule extends Module {
public DisableModule() {
super("Disable", ModuleCategory.VISUAL);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class FovModule extends Module {
public FovModule() {
super("Fov", ModuleCategory.VISUAL);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class GodModeModule extends Module {
public GodModeModule() {
super("GodMode", ModuleCategory.VISUAL);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class InfiniteYModule extends Module {
public InfiniteYModule() {
super("InfiniteY", ModuleCategory.VISUAL);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class OnPosModule extends Module {
public OnPosModule() {
super("OnPos", ModuleCategory.VISUAL);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,39 @@
package com.phoenix.gui.module.impl.visual;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
import com.phoenix.gui.ui.widgets.ColorPickerView;
import com.phoenix.gui.ui.widgets.SubMenuPanel;
public class ThemeModule extends Module {
public ThemeModule() {
super("Theme", ModuleCategory.VISUAL, "Customize the UI colors and themes.");
}
// 这个模块没有简单的开关功能,所以 onEnable/onDisable 为空
@Override
protected void onEnable() {}
@Override
protected void onDisable() {}
// 这个模块不支持快捷方式
@Override
public boolean supportsShortcut() {
return false;
}
@Override
public void configureSubMenu(SubMenuPanel subMenu) {
// 添加一行说明文字
subMenu.addText("Select a preset theme or create your own color below.");
// 添加预设主题按钮
subMenu.addThemeButtons();
// 添加自定义颜色选择器
ColorPickerView colorPicker = new ColorPickerView(subMenu.getContext());
subMenu.addCustomView(colorPicker);
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.world;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class AimAssetsModule extends Module {
public AimAssetsModule() {
super("AimAssets", ModuleCategory.WORLD);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.world;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class AutoClickModule extends Module {
public AutoClickModule() {
super("AutoClick", ModuleCategory.WORLD);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.world;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class FuckerModule extends Module {
public FuckerModule() {
super("Fucker", ModuleCategory.WORLD);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.world;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class GameModeModule extends Module {
public GameModeModule() {
super("GameMode", ModuleCategory.WORLD);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,20 @@
package com.phoenix.gui.module.impl.world;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
public class RemoteStopModule extends Module {
public RemoteStopModule() {
super("RemoteStop", ModuleCategory.WORLD);
}
@Override
protected void onEnable() {
}
@Override
protected void onDisable() {
}
}
@@ -0,0 +1,103 @@
package com.phoenix.gui.ui;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
public class AnimationUtils {
public static void expand(View view, long duration) {
view.measure(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
int targetHeight = view.getMeasuredHeight();
view.getLayoutParams().height = 0;
view.setVisibility(View.VISIBLE);
view.setAlpha(0f);
ValueAnimator animator = ValueAnimator.ofInt(0, targetHeight);
animator.setDuration(duration);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(animation -> {
int value = (int) animation.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
});
animator.start();
view.animate()
.alpha(1f)
.setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.start();
}
public static void expand(View view) {
expand(view, 300);
}
public static void collapse(View view, long duration) {
int initialHeight = view.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(initialHeight, 0);
animator.setDuration(duration);
animator.setInterpolator(new DecelerateInterpolator());
animator.addUpdateListener(animation -> {
int value = (int) animation.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = value;
view.setLayoutParams(layoutParams);
});
animator.start();
view.animate()
.alpha(0f)
.setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.withEndAction(() -> {
view.setVisibility(View.GONE);
view.getLayoutParams().height = 0;
})
.start();
}
public static void collapse(View view) {
collapse(view, 300);
}
public static void fadeIn(View view, long duration) {
view.setAlpha(0f);
view.setVisibility(View.VISIBLE);
view.animate()
.alpha(1f)
.setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.start();
}
public static void fadeIn(View view) {
fadeIn(view, 300);
}
public static void fadeOut(View view, long duration) {
view.animate()
.alpha(0f)
.setDuration(duration)
.setInterpolator(new DecelerateInterpolator())
.withEndAction(() -> view.setVisibility(View.GONE))
.start();
}
public static void fadeOut(View view) {
fadeOut(view, 300);
}
private AnimationUtils() {
}
}
@@ -0,0 +1,130 @@
package com.phoenix.gui.ui;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
public class ArraylistItemView extends FrameLayout implements ThemeManager.OnThemeColorChangeListener {
public enum PositionType {
SINGLE, FIRST, MIDDLE, LAST
}
private TextView textView;
private Paint backgroundPaint;
private Path clipPath;
private float cornerRadius;
private int customTextColor = -1;
private float measuredTextWidth = 0;
private PositionType positionType = PositionType.MIDDLE;
private Paint shadowPaint;
private Path shadowPath;
public ArraylistItemView(Context context) {
super(context);
init();
}
public ArraylistItemView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ArraylistItemView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setWillNotDraw(false);
setLayerType(LAYER_TYPE_SOFTWARE, null);
setClipChildren(false);
setClipToPadding(false);
int shadowSpace = dpToPx(8);
setPadding(shadowSpace, shadowSpace, shadowSpace, shadowSpace);
cornerRadius = dpToPx(3);
textView = new TextView(getContext());
textView.setTextColor(ThemeManager.getThemeColor());
textView.setTextSize(10);
textView.setTypeface(textView.getTypeface(), Typeface.BOLD);
textView.setGravity(Gravity.RIGHT | Gravity.CENTER_VERTICAL);
textView.setPadding(dpToPx(6), dpToPx(2), dpToPx(6), dpToPx(2));
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
params.gravity = Gravity.END;
addView(textView, params);
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(0xB2000000);
backgroundPaint.setStyle(Paint.Style.FILL);
// REMOVED: sheenDrawable initialization
clipPath = new Path();
shadowPath = new Path();
shadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
shadowPaint.setColor(0xFF000000);
shadowPaint.setMaskFilter(new BlurMaskFilter(dpToPx(6), BlurMaskFilter.Blur.NORMAL));
ThemeManager.addListener(this);
}
@Override
protected void onDraw(Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (width <= 0 || height <= 0) {
super.onDraw(canvas);
return;
}
RectF backgroundBounds = new RectF(
getPaddingLeft(), getPaddingTop(),
width - getPaddingRight(), height - getPaddingBottom()
);
drawSelectiveShadow(canvas, backgroundBounds);
drawDynamicBackground(canvas, backgroundBounds);
super.onDraw(canvas);
}
@Override
public void onThemeColorChanged(int newColor) {
if (customTextColor == -1) {
textView.setTextColor(newColor);
}
invalidate();
}
public void cleanup() {
ThemeManager.removeListener(this);
}
// 下面由 gemini 2.5 pro 编写。够屎山。你要是想改你就去找 claude 吧,千万别用 gemini。
public void setPositionType(PositionType type) { if (this.positionType != type) { this.positionType = type; invalidate(); } }
private void drawSelectiveShadow(Canvas canvas, RectF bounds) { shadowPath.reset(); float spread = dpToPx(0.5f); switch (positionType) { case SINGLE: shadowPath.addRoundRect(bounds, cornerRadius, cornerRadius, Path.Direction.CW); break; case FIRST: shadowPath.moveTo(bounds.left + cornerRadius, bounds.top); shadowPath.lineTo(bounds.right - cornerRadius, bounds.top); shadowPath.quadTo(bounds.right, bounds.top, bounds.right, bounds.top + cornerRadius); shadowPath.lineTo(bounds.right, bounds.bottom + spread); shadowPath.lineTo(bounds.left, bounds.bottom + spread); shadowPath.lineTo(bounds.left, bounds.top + cornerRadius); shadowPath.quadTo(bounds.left, bounds.top, bounds.left + cornerRadius, bounds.top); break; case MIDDLE: shadowPath.moveTo(bounds.right, bounds.top - spread); shadowPath.lineTo(bounds.right, bounds.bottom + spread); shadowPath.lineTo(bounds.left, bounds.bottom + spread); shadowPath.lineTo(bounds.left, bounds.top - spread); break; case LAST: shadowPath.moveTo(bounds.left, bounds.top - spread); shadowPath.lineTo(bounds.right, bounds.top - spread); shadowPath.lineTo(bounds.right, bounds.bottom - cornerRadius); shadowPath.quadTo(bounds.right, bounds.bottom, bounds.right - cornerRadius, bounds.bottom); shadowPath.lineTo(bounds.left + cornerRadius, bounds.bottom); shadowPath.quadTo(bounds.left, bounds.bottom, bounds.left, bounds.bottom - cornerRadius); shadowPath.lineTo(bounds.left, bounds.top - spread); break; } shadowPath.close(); canvas.drawPath(shadowPath, shadowPaint); }
private void drawDynamicBackground(Canvas canvas, RectF bounds) { clipPath.reset(); clipPath.moveTo(bounds.left + cornerRadius, bounds.top); if (positionType == PositionType.FIRST || positionType == PositionType.SINGLE) { clipPath.lineTo(bounds.right - cornerRadius, bounds.top); clipPath.quadTo(bounds.right, bounds.top, bounds.right, bounds.top + cornerRadius); } else { clipPath.lineTo(bounds.right, bounds.top); } if (positionType == PositionType.LAST || positionType == PositionType.SINGLE) { clipPath.lineTo(bounds.right, bounds.bottom - cornerRadius); clipPath.quadTo(bounds.right, bounds.bottom, bounds.right - cornerRadius, bounds.bottom); } else { clipPath.lineTo(bounds.right, bounds.bottom); } clipPath.lineTo(bounds.left + cornerRadius, bounds.bottom); clipPath.quadTo(bounds.left, bounds.bottom, bounds.left, bounds.bottom - cornerRadius); clipPath.lineTo(bounds.left, bounds.top + cornerRadius); clipPath.quadTo(bounds.left, bounds.top, bounds.left + cornerRadius, bounds.top); clipPath.close(); canvas.drawPath(clipPath, backgroundPaint); }
public void setText(String text) { textView.setText(text); measureTextWidth(); }
private void measureTextWidth() { TextPaint paint = textView.getPaint(); String text = textView.getText().toString(); measuredTextWidth = paint.measureText(text) + textView.getPaddingLeft() + textView.getPaddingRight(); }
public float getMeasuredTextWidth() { return measuredTextWidth; }
public void setTextColor(int color) { customTextColor = color; textView.setTextColor(color); }
private int dpToPx(float dp) { return (int) (dp * getContext().getResources().getDisplayMetrics().density); }
}
@@ -0,0 +1,47 @@
package com.phoenix.gui.ui;
public class ArraylistModule {
private String name;
private int color;
private boolean useThemeColor;
public ArraylistModule(String name) {
this.name = name;
this.color = ThemeManager.getTextPrimary();
this.useThemeColor = true;
}
public ArraylistModule(String name, int color) {
this.name = name;
this.color = color;
this.useThemeColor = false;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getColor() {
if (useThemeColor) {
return ThemeManager.getTextPrimary();
}
return color;
}
public void setColor(int color) {
this.color = color;
this.useThemeColor = false;
}
public void setUseThemeColor(boolean use) {
this.useThemeColor = use;
}
public boolean isUseThemeColor() {
return useThemeColor;
}
}
@@ -0,0 +1,170 @@
package com.phoenix.gui.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ArraylistView extends LinearLayout {
private List<ArraylistModule> modules;
private Map<String, ArraylistItemView> itemViews;
public ArraylistView(Context context) {
super(context);
init();
}
public ArraylistView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ArraylistView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setOrientation(VERTICAL);
setGravity(Gravity.RIGHT | Gravity.TOP);
modules = new ArrayList<>();
itemViews = new HashMap<>();
setClipChildren(false);
setClipToPadding(false);
int paddingTop = dpToPx(16);
setPadding(0, paddingTop, 0, 0);
setupLayoutAnimations();
}
private void setupLayoutAnimations() {
LayoutTransition transition = new LayoutTransition();
transition.setDuration(300);
transition.setAnimator(LayoutTransition.APPEARING, null);
transition.setAnimator(LayoutTransition.DISAPPEARING, null);
this.setLayoutTransition(transition);
}
private void rebuildViews() {
List<View> viewsToShow = new ArrayList<>();
int moduleCount = modules.size();
for (int i = 0; i < moduleCount; i++) {
ArraylistModule module = modules.get(i);
ArraylistItemView itemView = itemViews.get(module.getName());
if (itemView != null) {
viewsToShow.add(itemView);
if (moduleCount == 1) {
itemView.setPositionType(ArraylistItemView.PositionType.SINGLE);
} else if (i == 0) {
itemView.setPositionType(ArraylistItemView.PositionType.FIRST);
} else if (i == moduleCount - 1) {
itemView.setPositionType(ArraylistItemView.PositionType.LAST);
} else {
itemView.setPositionType(ArraylistItemView.PositionType.MIDDLE);
}
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) itemView.getLayoutParams();
if (params == null) {
params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
}
params.gravity = Gravity.RIGHT;
int shadowSpaceInPx = dpToPx(8);
if (i > 0) {
params.topMargin = -(shadowSpaceInPx * 2);
} else {
params.topMargin = 0;
}
itemView.setLayoutParams(params);
}
}
for (int i = getChildCount() - 1; i >= 0; i--) {
View child = getChildAt(i);
if (!viewsToShow.contains(child)) {
removeViewAt(i);
}
}
for (int i = 0; i < viewsToShow.size(); i++) {
View view = viewsToShow.get(i);
if (indexOfChild(view) != i) {
if (view.getParent() != null) {
removeView(view);
}
addView(view, i);
}
}
}
public void addModule(String moduleName) { addModule(new ArraylistModule(moduleName)); }
public void addModule(String moduleName, int color) { addModule(new ArraylistModule(moduleName, color)); }
public void addModule(ArraylistModule module) {
if (itemViews.containsKey(module.getName())) { return; }
modules.add(module);
ArraylistItemView itemView = new ArraylistItemView(getContext());
itemView.setText(module.getName());
if (!module.isUseThemeColor()) { itemView.setTextColor(module.getColor()); }
itemViews.put(module.getName(), itemView);
itemView.setAlpha(0f);
itemView.setTranslationX(dpToPx(20));
sortModules();
rebuildViews();
animateItemIn(itemView);
}
public void removeModule(String moduleName) {
ArraylistModule toRemove = null;
for (ArraylistModule module : modules) { if (module.getName().equals(moduleName)) { toRemove = module; break; } }
if (toRemove != null) {
modules.remove(toRemove);
ArraylistItemView itemView = itemViews.get(moduleName);
if (itemView != null) {
animateItemOut(itemView, () -> {
itemViews.remove(moduleName);
itemView.cleanup();
sortModules();
rebuildViews();
});
}
} // 下面也是 gemini 写的,由于我懒得动。所以建议不要动。
}
private void sortModules() { Collections.sort(modules, (m1, m2) -> { ArraylistItemView v1 = itemViews.get(m1.getName()); ArraylistItemView v2 = itemViews.get(m2.getName()); if (v1 == null || v2 == null) { return Integer.compare(m2.getName().length(), m1.getName().length()); } float width1 = v1.getMeasuredTextWidth(); float width2 = v2.getMeasuredTextWidth(); int widthCompare = Float.compare(width2, width1); if (widthCompare != 0) { return widthCompare; } return m1.getName().compareTo(m2.getName()); }); }
private void animateItemIn(ArraylistItemView itemView) { ValueAnimator alphaAnimator = ValueAnimator.ofFloat(0f, 1f); alphaAnimator.setDuration(300); alphaAnimator.setInterpolator(new DecelerateInterpolator()); alphaAnimator.addUpdateListener(animation -> { float value = (float) animation.getAnimatedValue(); itemView.setAlpha(value); }); ValueAnimator translationAnimator = ValueAnimator.ofFloat(dpToPx(20), 0f); translationAnimator.setDuration(300); translationAnimator.setInterpolator(new DecelerateInterpolator()); translationAnimator.addUpdateListener(animation -> { float value = (float) animation.getAnimatedValue(); itemView.setTranslationX(value); }); alphaAnimator.start(); translationAnimator.start(); }
private void animateItemOut(ArraylistItemView itemView, Runnable onComplete) { ValueAnimator alphaAnimator = ValueAnimator.ofFloat(itemView.getAlpha(), 0f); alphaAnimator.setDuration(250); alphaAnimator.setInterpolator(new DecelerateInterpolator()); alphaAnimator.addUpdateListener(animation -> { float value = (float) animation.getAnimatedValue(); itemView.setAlpha(value); }); ValueAnimator translationAnimator = ValueAnimator.ofFloat(itemView.getTranslationX(), dpToPx(20)); translationAnimator.setDuration(250); translationAnimator.setInterpolator(new DecelerateInterpolator()); translationAnimator.addUpdateListener(animation -> { float value = (float) animation.getAnimatedValue(); itemView.setTranslationX(value); }); translationAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (onComplete != null) { onComplete.run(); } } }); alphaAnimator.start(); translationAnimator.start(); }
public void clearModules() { for (ArraylistItemView itemView : itemViews.values()) { itemView.cleanup(); } modules.clear(); itemViews.clear(); removeAllViews(); }
public void show() { setVisibility(VISIBLE); }
public void hide() { setVisibility(GONE); }
private int dpToPx(float dp) { return (int) (dp * getContext().getResources().getDisplayMetrics().density); }
public List<ArraylistModule> getModules() { return this.modules; }
}
@@ -0,0 +1,159 @@
package com.phoenix.gui.ui;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
public class DynamicColorExtractor {
private static final String TAG = "DynamicColorExtractor";
private final Context context;
private WallpaperManager wallpaperManager;
private WallpaperManager.OnColorsChangedListener colorsChangedListener;
public DynamicColorExtractor(Context context) {
this.context = context.getApplicationContext();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
wallpaperManager = WallpaperManager.getInstance(context);
}
}
public void init() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
extractAndApplyColors();
startListening();
} else {
Log.w(TAG, "nah i cant use dc");
ThemeManager.applyDefaultTheme();
}
}
@RequiresApi(api = Build.VERSION_CODES.O_MR1)
private void extractAndApplyColors() {
try {
WallpaperColors wallpaperColors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
if (wallpaperColors != null) {
Color primaryColor = wallpaperColors.getPrimaryColor();
if (primaryColor != null) {
int color = primaryColor.toArgb();
int adjustedColor = adjustColorForUI(color);
Log.d(TAG, String.format("color: #%08X -> #%08X", color, adjustedColor));
ThemeManager.setThemeColor(adjustedColor);
Color secondaryColor = wallpaperColors.getSecondaryColor();
if (secondaryColor != null) {
int secColor = secondaryColor.toArgb();
ThemeManager.setGradientStart(adjustedColor);
ThemeManager.setGradientEnd(adjustColorForUI(secColor));
}
Color tertiaryColor = wallpaperColors.getTertiaryColor();
if (tertiaryColor != null) {
Log.d(TAG, "Tertiary color: #" + Integer.toHexString(tertiaryColor.toArgb()));
}
} else {
ThemeManager.applyDefaultTheme();
}
} else {
ThemeManager.applyDefaultTheme();
}
} catch (Exception e) {
ThemeManager.applyDefaultTheme();
}
}
private int adjustColorForUI(int color) {
float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
if (hsv[1] < 0.5f) {
hsv[1] = Math.min(hsv[1] * 1.5f, 0.7f);
}
if (hsv[2] < 0.6f) {
hsv[2] = 0.7f;
} else if (hsv[2] > 0.9f) {
hsv[2] = 0.85f;
}
return Color.HSVToColor(hsv);
}
@RequiresApi(api = Build.VERSION_CODES.O_MR1)
private void startListening() {
if (colorsChangedListener == null) {
colorsChangedListener = new WallpaperManager.OnColorsChangedListener() {
@Override
public void onColorsChanged(WallpaperColors colors, int which) {
if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
extractAndApplyColors();
}
}
};
wallpaperManager.addOnColorsChangedListener(colorsChangedListener, null);
}
}
@RequiresApi(api = Build.VERSION_CODES.O_MR1)
public void stopListening() {
if (colorsChangedListener != null && wallpaperManager != null) {
wallpaperManager.removeOnColorsChangedListener(colorsChangedListener);
colorsChangedListener = null;
}
}
public void refresh() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
extractAndApplyColors();
}
}
@RequiresApi(api = Build.VERSION_CODES.S)
public String getColorSchemeInfo() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
try {
WallpaperColors colors = wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
if (colors != null) {
StringBuilder info = new StringBuilder();
info.append("主题色方案: ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
int colorHints = colors.getColorHints();
if ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) != 0) {
info.append("亮色背景 ");
}
if ((colorHints & WallpaperColors.HINT_SUPPORTS_DARK_THEME) != 0) {
info.append("深色主题 ");
}
}
info.append("\n当前主题色: #").append(Integer.toHexString(ThemeManager.getThemeColor()));
return info.toString();
}
} catch (Exception e) {
Log.e(TAG, "failed", e);
}
}
return "动态取色不可用";
}
}
@@ -0,0 +1,232 @@
package com.phoenix.gui.ui;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.module.ModuleCategory;
import java.util.ArrayList;
import java.util.List;
public class FloatBallView extends LinearLayout {
private final List<MenuView> menuViews = new ArrayList<>();
private boolean menusVisible = false;
private final LinearLayout outerBall;
private final LinearLayout innerBall;
private final FrameLayout menusContainer;
public FloatBallView(Context context, FrameLayout menusContainer) {
super(context);
this.menusContainer = menusContainer;
ConfigManager.init(context);
setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER
));
outerBall = new LinearLayout(context);
int size = (int) (getScreenHeight(context) * 0.05);
outerBall.setLayoutParams(new LayoutParams(size, size));
GradientDrawable gradient = new GradientDrawable();
gradient.setColors(new int[]{ThemeManager.getGradientStart(), ThemeManager.getGradientEnd()});
gradient.setGradientType(GradientDrawable.LINEAR_GRADIENT);
gradient.setShape(GradientDrawable.OVAL);
outerBall.setBackground(gradient);
outerBall.setGravity(Gravity.CENTER);
innerBall = new LinearLayout(context);
int innerSize = (int) (getScreenHeight(context) * 0.03);
innerBall.setLayoutParams(new LayoutParams(innerSize, innerSize));
GradientDrawable innerDrawable = new GradientDrawable();
innerDrawable.setColor(ThemeManager.getTextPrimary());
innerDrawable.setShape(GradientDrawable.OVAL);
innerBall.setBackground(innerDrawable);
outerBall.addView(innerBall);
LinearLayout controlView = new LinearLayout(context);
controlView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
controlView.addView(outerBall);
addView(controlView);
setupTouchListeners();
createMenus();
}
private void createMenus() {
menuViews.add(new MenuView(getContext(), ModuleCategory.PLAYER, MenuView.MenuPosition.POSITION_1));
menuViews.add(new MenuView(getContext(), ModuleCategory.WORLD, MenuView.MenuPosition.POSITION_2));
menuViews.add(new MenuView(getContext(), ModuleCategory.MOVEMENT, MenuView.MenuPosition.POSITION_3));
menuViews.add(new MenuView(getContext(), ModuleCategory.COMBAT, MenuView.MenuPosition.POSITION_4));
menuViews.add(new MenuView(getContext(), ModuleCategory.VISUAL, MenuView.MenuPosition.POSITION_5));
}
private void setupTouchListeners() {
final float[] downPos = new float[2];
final boolean[] hasMoved = {false};
final long[] downTime = {0};
setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downPos[0] = event.getX();
downPos[1] = event.getY();
hasMoved[0] = false;
downTime[0] = System.currentTimeMillis();
animate()
.scaleX(0.9f)
.scaleY(0.9f)
.setDuration(100)
.start();
return true;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
float deltaX = moveX - downPos[0];
float deltaY = moveY - downPos[1];
if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
hasMoved[0] = true;
setTranslationX(getTranslationX() + deltaX);
setTranslationY(getTranslationY() + deltaY);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(100)
.start();
long clickDuration = System.currentTimeMillis() - downTime[0];
if (!hasMoved[0] && clickDuration < 300) {
toggleMenus();
}
return true;
}
return false;
});
}
private void toggleMenus() {
menusVisible = !menusVisible;
if (menusVisible) {
showMenus();
} else {
hideMenus();
}
}
private void showMenus() {
if (menusContainer == null) return;
menusContainer.removeAllViews();
menusContainer.setVisibility(View.VISIBLE);
for (MenuView menu : menuViews) {
if (menu.getParent() != null) {
((FrameLayout) menu.getParent()).removeView(menu);
}
menusContainer.addView(menu);
menu.show();
}
}
private void hideMenus() {
if (menusContainer == null) return;
for (MenuView menu : menuViews) {
menu.hide();
}
postDelayed(() -> {
try {
for (MenuView menu : menuViews) {
if (menu.getParent() != null) {
((FrameLayout) menu.getParent()).removeView(menu);
}
}
menusContainer.setVisibility(View.GONE);
} catch (Exception e) {
e.printStackTrace();
}
}, 300);
}
public void show() {
setVisibility(View.VISIBLE);
setAlpha(0f);
setScaleX(0.8f);
setScaleY(0.8f);
animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(250)
.start();
}
public void hide() {
if (menusVisible) {
hideMenus();
menusVisible = false;
}
animate()
.alpha(0f)
.scaleX(0.8f)
.scaleY(0.8f)
.setDuration(250)
.withEndAction(() -> setVisibility(View.GONE))
.start();
}
public void destroy() {
try {
for (MenuView menu : menuViews) {
if (menu.getParent() != null) {
((FrameLayout) menu.getParent()).removeView(menu);
}
menu.destroy();
}
menuViews.clear();
if (menusContainer != null) {
menusContainer.removeAllViews();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private int getScreenHeight(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
return metrics.heightPixels;
}
}
@@ -0,0 +1,516 @@
package com.phoenix.gui.ui;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewOutlineProvider;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.view.ViewGroup;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.module.ModuleCategory;
import com.phoenix.gui.module.ModuleManager;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MenuView extends LinearLayout implements ThemeManager.OnThemeColorChangeListener {
private static final String PREF_NAME = "menu_view_settings";
private static final String KEY_SCALE_FACTOR = "scale_factor";
private final Context context;
private final ModuleCategory category;
private final MenuPosition position;
private final SharedPreferences preferences;
private boolean isExpanded = false;
private final LinearLayout titleBar;
private final ScrollView scrollView;
private final LinearLayout contentLayout;
private final Map<Module, ModuleItemView> moduleItems = new HashMap<>();
private ValueAnimator expandAnimator;
private final Handler handler = new Handler(Looper.getMainLooper());
private float touchStartX = 0f;
private float touchStartY = 0f;
private boolean isDragging = false;
private final int touchSlop;
private float scaleFactor = 1.0f;
private final float baseScaleFactor;
private static final long ANIMATION_DURATION = 250L;
private static final float CORNER_RADIUS_DP = 8f;
private static int globalMaxWidth = 0;
private final Runnable statusChecker = new Runnable() {
@Override
public void run() {
if (getVisibility() == View.VISIBLE && isExpanded) {
refreshAllModuleStates();
handler.postDelayed(this, 500);
}
}
};
public enum MenuPosition {
POSITION_1, POSITION_2, POSITION_3, POSITION_4, POSITION_5
}
public MenuView(Context context, ModuleCategory category, MenuPosition position) {
super(context);
this.context = context;
this.category = category;
this.position = position;
this.preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
this.touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
this.baseScaleFactor = calculateBaseScaleFactor() * 0.75f;
this.scaleFactor = 1.0f;
setOrientation(VERTICAL);
List<Module> modules = ModuleManager.getModulesByCategory(category);
GradientDrawable bg = new GradientDrawable();
bg.setColor(ThemeManager.getBgPrimary());
bg.setCornerRadius(scaled(CORNER_RADIUS_DP));
setBackground(bg);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setClipToOutline(true);
setOutlineProvider(ViewOutlineProvider.BACKGROUND);
setElevation(scaled(12));
} else {
setClipChildren(true);
setClipToPadding(true);
}
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
setLayoutParams(layoutParams);
int titleHeight = scaled(36);
int spacing = scaled(12);
setTranslationY(position.ordinal() * (titleHeight + spacing));
titleBar = createTitleBar();
addView(titleBar);
scrollView = new ScrollView(context);
scrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 0));
scrollView.setVerticalScrollBarEnabled(false);
scrollView.setVisibility(View.GONE);
scrollView.setClipChildren(false);
scrollView.setClipToPadding(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
scrollView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
contentLayout = new LinearLayout(context);
contentLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
contentLayout.setOrientation(VERTICAL);
contentLayout.setPadding(0, 0, 0, 0);
contentLayout.setClipChildren(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
contentLayout.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
for (Module module : modules) {
ModuleItemView item = new ModuleItemView(context, module, this, baseScaleFactor * scaleFactor);
moduleItems.put(module, item);
contentLayout.addView(item);
}
scrollView.addView(contentLayout);
addView(scrollView);
setupTouchListeners();
ThemeManager.addListener(this);
}
private void refreshAllModuleStates() {
for (Map.Entry<Module, ModuleItemView> entry : moduleItems.entrySet()) {
Module module = entry.getKey();
ModuleItemView item = entry.getValue();
item.syncWithModuleState(module.isEnabled());
}
}
private float calculateBaseScaleFactor() {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
float dpi = metrics.densityDpi;
if (dpi <= 240) {
return 0.7f;
} else if (dpi <= 320) {
return 0.85f;
} else if (dpi <= 480) {
return 1.0f;
} else if (dpi <= 640) {
return 1.15f;
} else {
return 1.3f;
}
}
private int scaled(int baseDp) {
return (int) (dpToPx(baseDp) * baseScaleFactor * scaleFactor);
}
private float scaled(float baseDp) {
return dpToPx((int) baseDp) * baseScaleFactor * scaleFactor;
}
public float getScaleFactor() {
return scaleFactor;
}
public void setScaleFactor(float factor) {
factor = Math.max(0.5f, Math.min(2.0f, factor));
if (Math.abs(this.scaleFactor - factor) < 0.01f) {
return;
}
this.scaleFactor = factor;
preferences.edit()
.putFloat(KEY_SCALE_FACTOR, factor)
.apply();
rebuildUI();
}
public void increaseScale() {
setScaleFactor(scaleFactor * 1.1f);
}
public void decreaseScale() {
setScaleFactor(scaleFactor * 0.9f);
}
public void resetScale() {
setScaleFactor(1.0f);
}
private void rebuildUI() {
boolean wasExpanded = isExpanded;
if (isExpanded) {
collapseContent();
}
GradientDrawable bg = (GradientDrawable) getBackground();
bg.setCornerRadius(scaled(CORNER_RADIUS_DP));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setElevation(scaled(12));
}
updateTitleBar();
for (ModuleItemView item : moduleItems.values()) {
item.updateScale(baseScaleFactor * scaleFactor);
}
globalMaxWidth = 0;
measureInitialSize();
if (wasExpanded) {
handler.postDelayed(this::expandContent, 100);
}
}
private void updateTitleBar() {
int height = scaled(36);
titleBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, height));
titleBar.setPadding(scaled(14), 0, scaled(14), 0);
GradientDrawable bg = new GradientDrawable();
bg.setColor(ThemeManager.getBgSecondary());
bg.setCornerRadius(scaled(CORNER_RADIUS_DP));
titleBar.setBackground(bg);
TextView textView = (TextView) titleBar.getChildAt(0);
textView.setTextSize(11.5f * baseScaleFactor * scaleFactor);
}
private LinearLayout createTitleBar() {
LinearLayout bar = new LinearLayout(context);
int height = scaled(36);
bar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, height));
bar.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
bar.setPadding(scaled(14), 0, scaled(14), 0);
GradientDrawable bg = new GradientDrawable();
bg.setColor(ThemeManager.getBgSecondary());
bg.setCornerRadius(scaled(CORNER_RADIUS_DP));
bar.setBackground(bg);
TextView textView = new TextView(context);
textView.setText(category.getDisplayName());
textView.setTextSize(11.5f * baseScaleFactor * scaleFactor);
textView.setTextColor(ThemeManager.getTextPrimary());
textView.setTypeface(Typeface.DEFAULT_BOLD);
bar.addView(textView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
bar.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
return bar;
}
private void setupTouchListeners() {
final float[] lastRaw = new float[2];
titleBar.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStartX = event.getX();
touchStartY = event.getY();
lastRaw[0] = event.getRawX();
lastRaw[1] = event.getRawY();
isDragging = false;
return true;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(event.getX() - touchStartX);
float deltaY = Math.abs(event.getY() - touchStartY);
if (!isDragging && (deltaX > touchSlop || deltaY > touchSlop)) {
isDragging = true;
}
if (isDragging) {
float nowRawX = event.getRawX();
float nowRawY = event.getRawY();
float moveDeltaX = nowRawX - lastRaw[0];
float moveDeltaY = nowRawY - lastRaw[1];
MenuView.this.setTranslationX(MenuView.this.getTranslationX() + moveDeltaX);
MenuView.this.setTranslationY(MenuView.this.getTranslationY() + moveDeltaY);
lastRaw[0] = nowRawX;
lastRaw[1] = nowRawY;
}
return true;
case MotionEvent.ACTION_UP:
if (!isDragging) {
toggleExpand();
}
isDragging = false;
return true;
case MotionEvent.ACTION_CANCEL:
isDragging = false;
return true;
}
return false;
});
}
private void toggleExpand() {
if (expandAnimator != null) expandAnimator.cancel();
if (isExpanded) {
collapseContent();
} else {
expandContent();
}
}
private void expandContent() {
isExpanded = true;
scrollView.setVisibility(View.VISIBLE);
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics screenSize = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(screenSize);
contentLayout.measure(
MeasureSpec.makeMeasureSpec(globalMaxWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
);
int maxHeight = (int) (screenSize.heightPixels * 0.5);
int targetHeight = Math.min(contentLayout.getMeasuredHeight(), maxHeight);
scrollView.setAlpha(0f);
LayoutParams lp = (LayoutParams) scrollView.getLayoutParams();
lp.height = 0;
scrollView.setLayoutParams(lp);
expandAnimator = ValueAnimator.ofInt(0, targetHeight);
expandAnimator.setDuration(ANIMATION_DURATION);
expandAnimator.setInterpolator(new DecelerateInterpolator());
expandAnimator.addUpdateListener(animator -> {
int value = (int) animator.getAnimatedValue();
LayoutParams params = (LayoutParams) scrollView.getLayoutParams();
params.height = value;
scrollView.setLayoutParams(params);
scrollView.setAlpha((float) value / targetHeight);
});
expandAnimator.start();
handler.post(statusChecker);
}
private void collapseContent() {
isExpanded = false;
int startHeight = scrollView.getHeight();
closeAllSubMenus();
handler.removeCallbacks(statusChecker);
expandAnimator = ValueAnimator.ofInt(startHeight, 0);
expandAnimator.setDuration(ANIMATION_DURATION);
expandAnimator.setInterpolator(new DecelerateInterpolator());
expandAnimator.addUpdateListener(animator -> {
int value = (int) animator.getAnimatedValue();
LayoutParams params = (LayoutParams) scrollView.getLayoutParams();
params.height = value;
scrollView.setLayoutParams(params);
if (startHeight > 0) {
scrollView.setAlpha((float) value / startHeight);
}
});
expandAnimator.addListener(new android.animation.AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(android.animation.Animator animation) {
scrollView.setVisibility(View.GONE);
}
});
expandAnimator.start();
}
public void show() {
setVisibility(View.VISIBLE);
measureInitialSize();
setAlpha(0f);
setScaleX(0.8f);
setScaleY(0.8f);
animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(ANIMATION_DURATION)
.setInterpolator(new DecelerateInterpolator())
.start();
if (isExpanded) {
handler.post(statusChecker);
}
}
private void measureInitialSize() {
int maxWidth = globalMaxWidth;
titleBar.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
);
maxWidth = Math.max(maxWidth, titleBar.getMeasuredWidth());
for (ModuleItemView item : moduleItems.values()) {
item.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
);
maxWidth = Math.max(maxWidth, item.getMeasuredWidth());
}
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics screenSize = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(screenSize);
int smallerDimension = Math.min(screenSize.widthPixels, screenSize.heightPixels);
int minWidth = (int) (smallerDimension * 0.3);
int maxAllowedWidth = (int) (smallerDimension * 0.5);
maxWidth = Math.max(minWidth, Math.min(maxWidth, maxAllowedWidth)); // 你说屎吧是屎 但是好像还有可读性
maxWidth = Math.max(minWidth, Math.min(maxWidth, maxAllowedWidth));
if (maxWidth > globalMaxWidth) {
globalMaxWidth = maxWidth;
}
ViewGroup.LayoutParams params = getLayoutParams();
if (params != null) {
params.width = globalMaxWidth;
setLayoutParams(params);
}
}
public void hide() {
handler.removeCallbacks(statusChecker);
if (expandAnimator != null) expandAnimator.cancel();
animate().cancel();
for (ModuleItemView item : moduleItems.values()) {
item.cancelAllAnimations();
}
animate()
.alpha(0f)
.scaleX(0.8f)
.scaleY(0.8f)
.setDuration(ANIMATION_DURATION)
.setInterpolator(new DecelerateInterpolator())
.withEndAction(() -> setVisibility(View.GONE))
.start();
}
public void closeAllSubMenus() {
for (ModuleItemView item : moduleItems.values()) {
item.closeSubMenuIfOpen();
}
}
public String getScaleInfo() {
return String.format("基础缩放: %.2f, 用户缩放: %.2f, 总缩放: %.2f", //debug
baseScaleFactor, scaleFactor, baseScaleFactor * scaleFactor);
}
@Override
public void onThemeColorChanged(int newColor) {
for (ModuleItemView item : moduleItems.values()) {
item.updateThemeColor();
}
}
public void destroy() {
ThemeManager.removeListener(this);
if (expandAnimator != null) expandAnimator.cancel();
handler.removeCallbacksAndMessages(null);
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,355 @@
package com.phoenix.gui.ui;
import android.animation.ArgbEvaluator;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.ui.widgets.SubMenuPanel;
public class ModuleItemView extends LinearLayout {
private final Module module;
private final MenuView parentMenu;
private final FrameLayout mainContainer;
private final TextView normalText;
private final TextView boldText;
private final SubMenuPanel subMenuPanel;
private ValueAnimator colorAnimator;
private ValueAnimator touchStateAnimator;
private final Handler longPressHandler = new Handler(Looper.getMainLooper());
private Runnable longPressRunnable;
private boolean isLongPressing = false;
private float touchStartX = 0f;
private float touchStartY = 0f;
private boolean hasMoved = false;
private final int touchSlop;
private float scaleFactor = 1.0f;
private boolean lastKnownState = false;
private static final long LONG_PRESS_DELAY = 500L;
private static final float PRESS_SCALE = 0.95f;
private static final int PRESS_ANIMATION_DURATION = 150;
private static final int RELEASE_ANIMATION_DURATION = 250;
private static final int CORNER_RADIUS_DP = 8;
private final float cornerRadiusPx;
private final TimeInterpolator pressInterpolator = new DecelerateInterpolator();
private final TimeInterpolator releaseInterpolator = new OvershootInterpolator(1.5f);
public ModuleItemView(Context context, Module module, MenuView parentMenu, float scaleFactor) {
super(context);
this.module = module;
this.parentMenu = parentMenu;
this.touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
this.lastKnownState = module.isEnabled();
this.scaleFactor = scaleFactor;
this.cornerRadiusPx = dpToPx(CORNER_RADIUS_DP);
setOrientation(VERTICAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mainContainer = createMainContainer();
addView(mainContainer);
normalText = createNormalText();
boldText = createBoldText();
mainContainer.addView(normalText);
mainContainer.addView(boldText);
subMenuPanel = new SubMenuPanel(context, module, scaleFactor);
addView(subMenuPanel);
module.configureSubMenu(subMenuPanel);
subMenuPanel.setOnStateChangedListener(() -> {
});
updateVisualState(module.isEnabled(), false);
setupTouchListeners();
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
setLayerType(LAYER_TYPE_HARDWARE, null);
}
}
public void syncWithModuleState(boolean currentState) {
if (lastKnownState != currentState) {
lastKnownState = currentState;
updateVisualState(currentState, true);
}
}
public void updateScale(float scaleFactor) {
this.scaleFactor = scaleFactor;
LayoutParams params = (LayoutParams) mainContainer.getLayoutParams();
params.setMargins(0, 0, 0, 0);
mainContainer.setLayoutParams(params);
float textSize = 11f * scaleFactor;
int paddingV = scaled(7);
int paddingH = scaled(12);
normalText.setTextSize(textSize);
normalText.setPadding(paddingH, paddingV, paddingH, paddingV);
boldText.setTextSize(textSize);
boldText.setPadding(paddingH, paddingV, paddingH, paddingV);
subMenuPanel.updateScale(scaleFactor);
requestLayout();
}
private int scaled(int baseDp) {
return (int) (dpToPx(baseDp) * scaleFactor);
}
private FrameLayout createMainContainer() {
FrameLayout container = new FrameLayout(getContext());
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, 0);
container.setLayoutParams(params);
GradientDrawable initialBackground = new GradientDrawable();
initialBackground.setColor(Color.TRANSPARENT);
initialBackground.setCornerRadius(0f);
container.setBackground(initialBackground);
return container;
}
private TextView createNormalText() {
TextView textView = new TextView(getContext());
textView.setText(module.getName());
textView.setTextSize(11f * scaleFactor);
textView.setTextColor(ThemeManager.getTextPrimary());
textView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
int paddingV = scaled(7);
int paddingH = scaled(12);
textView.setPadding(paddingH, paddingV, paddingH, paddingV);
textView.setTypeface(Typeface.DEFAULT);
textView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
return textView;
}
private TextView createBoldText() {
TextView textView = new TextView(getContext());
textView.setText(module.getName());
textView.setTextSize(11f * scaleFactor);
textView.setTextColor(ThemeManager.getTextOnTheme());
textView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
int paddingV = scaled(7);
int paddingH = scaled(12);
textView.setPadding(paddingH, paddingV, paddingH, paddingV);
textView.setTypeface(Typeface.DEFAULT_BOLD);
textView.setLayoutParams(new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
textView.setAlpha(0f);
return textView;
}
private void setupTouchListeners() {
mainContainer.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touchStartX = event.getX();
touchStartY = event.getY();
hasMoved = false;
isLongPressing = false;
longPressRunnable = () -> {
if (!hasMoved) {
isLongPressing = true;
onLongPress();
}
};
longPressHandler.postDelayed(longPressRunnable, LONG_PRESS_DELAY);
animateTouchState(true);
return true;
case MotionEvent.ACTION_MOVE:
float deltaX = Math.abs(event.getX() - touchStartX);
float deltaY = Math.abs(event.getY() - touchStartY);
boolean isOutOfBounds = event.getX() < 0 || event.getX() > getWidth() || event.getY() < 0 || event.getY() > getHeight();
if (!hasMoved && (deltaX > touchSlop || deltaY > touchSlop || isOutOfBounds)) {
hasMoved = true;
cancelLongPressDetection();
animateTouchState(false);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
cancelLongPressDetection();
animateTouchState(false);
if (!hasMoved && !isLongPressing) {
module.toggle();
lastKnownState = module.isEnabled();
updateVisualState(lastKnownState, true);
}
isLongPressing = false;
hasMoved = false;
return true;
}
return false;
});
}
private void animateTouchState(boolean pressed) {
if (touchStateAnimator != null) {
touchStateAnimator.cancel();
}
final float startScale = mainContainer.getScaleX();
final float endScale = pressed ? PRESS_SCALE : 1.0f;
final GradientDrawable background = (GradientDrawable) mainContainer.getBackground();
final float startRadius = background.getCornerRadii() != null ? background.getCornerRadii()[0] : 0f;
final float endRadius = pressed ? cornerRadiusPx : 0f;
touchStateAnimator = ValueAnimator.ofFloat(0f, 1f);
touchStateAnimator.setDuration(pressed ? PRESS_ANIMATION_DURATION : RELEASE_ANIMATION_DURATION);
touchStateAnimator.setInterpolator(pressed ? pressInterpolator : releaseInterpolator);
touchStateAnimator.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
float currentScale = startScale + (endScale - startScale) * fraction;
float currentRadius = startRadius + (endRadius - startRadius) * fraction;
mainContainer.setScaleX(currentScale);
mainContainer.setScaleY(currentScale);
background.setCornerRadius(currentRadius);
});
touchStateAnimator.start();
}
private void onLongPress() {
performHapticFeedback(android.view.HapticFeedbackConstants.LONG_PRESS);
subMenuPanel.toggle();
}
private void cancelLongPressDetection() {
if (longPressRunnable != null) {
longPressHandler.removeCallbacks(longPressRunnable);
}
longPressRunnable = null;
}
public void updateVisualState(boolean isEnabled, boolean animated) {
if (animated) {
animateToState(isEnabled);
} else {
setStateImmediately(isEnabled);
}
}
private void setStateImmediately(boolean isEnabled) {
GradientDrawable bg = (GradientDrawable) mainContainer.getBackground();
bg.setColor(isEnabled ? ThemeManager.getThemeColor() : Color.TRANSPARENT);
bg.setCornerRadius(0f);
normalText.setAlpha(isEnabled ? 0f : 1f);
boldText.setAlpha(isEnabled ? 1f : 0f);
}
private void animateToState(boolean isEnabled) {
if (colorAnimator != null) {
colorAnimator.cancel();
}
GradientDrawable bg = (GradientDrawable) mainContainer.getBackground();
int fromColor = (bg.getColor() != null) ? bg.getColor().getDefaultColor() : Color.TRANSPARENT;
int toColor = isEnabled ? ThemeManager.getThemeColor() : Color.TRANSPARENT;
colorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), fromColor, toColor);
colorAnimator.setDuration(150);
colorAnimator.addUpdateListener(animator -> {
bg.setColor((int) animator.getAnimatedValue());
});
colorAnimator.start();
normalText.animate().alpha(isEnabled ? 0f : 1f).setDuration(150).start();
boldText.animate().alpha(isEnabled ? 1f : 0f).setDuration(150).start();
}
public void updateThemeColor() {
if (module.isEnabled()) {
GradientDrawable bg = (GradientDrawable) mainContainer.getBackground();
bg.setColor(ThemeManager.getThemeColor());
}
}
public void closeSubMenuIfOpen() {
if (subMenuPanel.isOpen()) {
subMenuPanel.forceCollapse();
}
}
public void cancelAllAnimations() {
if (colorAnimator != null) colorAnimator.cancel();
if (touchStateAnimator != null) touchStateAnimator.cancel();
mainContainer.animate().cancel(); //保留
normalText.animate().cancel();
boldText.animate().cancel();
cancelLongPressDetection();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
cancelAllAnimations();
longPressHandler.removeCallbacksAndMessages(null);
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,299 @@
package com.phoenix.gui.ui;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.phoenix.gui.module.Module;
public class ShortcutButton extends LinearLayout implements ThemeManager.OnThemeColorChangeListener {
private static final String PREF_NAME = "shortcut_button_positions";
private static final String KEY_PREFIX_X = "button_x_";
private static final String KEY_PREFIX_Y = "button_y_";
private final Module module;
private final SharedPreferences preferences;
private final TextView textView;
private final GradientDrawable background;
private int currentBackgroundColor;
private int fixedTextWidth = -1;
public ShortcutButton(Context context, Module module) {
super(context);
this.module = module;
this.preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER);
background = new GradientDrawable();
currentBackgroundColor = module.isEnabled()
? ThemeManager.getThemeColor()
: ThemeManager.getBgDisabled();
background.setColor(currentBackgroundColor);
background.setCornerRadius(dpToPx(20));
setBackground(background);
int paddingH = dpToPx(12);
int paddingV = dpToPx(8);
setPadding(paddingH, paddingV, paddingH, paddingV);
textView = new TextView(context);
textView.setText(module.getName());
textView.setTextSize(11f);
int textColor = module.isEnabled()
? ThemeManager.getTextOnTheme()
: ThemeManager.getTextPrimary();
textView.setTextColor(textColor);
textView.setTypeface(module.isEnabled()
? Typeface.DEFAULT_BOLD
: Typeface.DEFAULT);
textView.setGravity(Gravity.CENTER);
fixedTextWidth = calculateMaxTextWidth();
LayoutParams textParams = new LayoutParams(fixedTextWidth, LayoutParams.WRAP_CONTENT);
textView.setLayoutParams(textParams);
addView(textView);
loadPosition();
setupTouchListeners();
ThemeManager.addListener(this);
setLayerType(LAYER_TYPE_HARDWARE, null);
}
private int calculateMaxTextWidth() {
Paint paint = new Paint();
paint.setTextSize(textView.getTextSize());
paint.setTypeface(Typeface.DEFAULT);
float normalWidth = paint.measureText(module.getName());
paint.setTypeface(Typeface.DEFAULT_BOLD);
float boldWidth = paint.measureText(module.getName());
return (int) Math.ceil(Math.max(normalWidth, boldWidth));
}
private void loadPosition() {
String key = getPositionKey();
boolean hasSavedPosition = preferences.contains(KEY_PREFIX_X + key)
&& preferences.contains(KEY_PREFIX_Y + key);
if (hasSavedPosition) {
float x = preferences.getInt(KEY_PREFIX_X + key, 0);
float y = preferences.getInt(KEY_PREFIX_Y + key, 0);
setTranslationX(x);
setTranslationY(y);
} else {
generateRandomPosition();
}
}
private void generateRandomPosition() {
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getRealMetrics(metrics);
int estimatedWidth = dpToPx(150);
int estimatedHeight = dpToPx(50);
int margin = dpToPx(30);
int minX = margin - metrics.widthPixels / 2 + estimatedWidth / 2;
int maxX = metrics.widthPixels / 2 - estimatedWidth / 2 - margin;
int minY = margin - metrics.heightPixels / 2 + estimatedHeight / 2;
int maxY = metrics.heightPixels / 2 - estimatedHeight / 2 - margin;
if (maxX > minX) {
setTranslationX(minX + (int) (Math.random() * (maxX - minX)));
}
if (maxY > minY) {
setTranslationY(minY + (int) (Math.random() * (maxY - minY)));
}
}
private void savePosition() {
String key = getPositionKey();
preferences.edit()
.putInt(KEY_PREFIX_X + key, (int) getTranslationX())
.putInt(KEY_PREFIX_Y + key, (int) getTranslationY())
.apply();
}
private String getPositionKey() {
return module.getName().replaceAll("[^a-zA-Z0-9]", "_");
}
private void setupTouchListeners() {
final float[] downPos = new float[2];
final boolean[] hasMoved = {false};
final long[] downTime = {0};
setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downPos[0] = event.getX();
downPos[1] = event.getY();
hasMoved[0] = false;
downTime[0] = System.currentTimeMillis();
animate()
.scaleX(0.9f)
.scaleY(0.9f)
.setDuration(100)
.start();
return true;
case MotionEvent.ACTION_MOVE:
float moveX = event.getX();
float moveY = event.getY();
float deltaX = moveX - downPos[0];
float deltaY = moveY - downPos[1];
if (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10) {
hasMoved[0] = true;
setTranslationX(getTranslationX() + deltaX);
setTranslationY(getTranslationY() + deltaY);
}
return true;
case MotionEvent.ACTION_UP:
animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(100)
.start();
long clickDuration = System.currentTimeMillis() - downTime[0];
if (!hasMoved[0] && clickDuration < 300) {
module.toggle();
updateStyle();
} else if (hasMoved[0]) {
savePosition();
}
hasMoved[0] = false;
return true;
case MotionEvent.ACTION_CANCEL:
animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(100)
.start();
hasMoved[0] = false;
return true;
}
return false;
});
}
public void updateStyle() {
boolean isEnabled = module.isEnabled();
int targetColor = isEnabled ? ThemeManager.getThemeColor() : ThemeManager.getBgDisabled();
int textColor = isEnabled ? ThemeManager.getTextOnTheme() : ThemeManager.getTextPrimary();
Typeface targetTypeface = isEnabled ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT;
ValueAnimator colorAnimator = ValueAnimator.ofArgb(currentBackgroundColor, targetColor);
colorAnimator.setDuration(150);
colorAnimator.addUpdateListener(animator -> {
int color = (int) animator.getAnimatedValue();
background.setColor(color);
currentBackgroundColor = color;
});
colorAnimator.start();
textView.animate()
.alpha(0.5f)
.setDuration(75)
.withEndAction(() -> {
textView.setTextColor(textColor);
textView.setTypeface(targetTypeface);
textView.animate()
.alpha(1f)
.setDuration(75)
.start();
})
.start();
}
public void show() {
setVisibility(VISIBLE);
setAlpha(0f);
setScaleX(0.5f);
setScaleY(0.5f);
animate()
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(200)
.start();
}
public void hide() {
animate()
.alpha(0f)
.scaleX(0.5f)
.scaleY(0.5f)
.setDuration(200)
.withEndAction(() -> setVisibility(GONE))
.start();
}
@Override
public void onThemeColorChanged(int newColor) {
if (module.isEnabled()) {
ValueAnimator colorAnimator = ValueAnimator.ofArgb(currentBackgroundColor, newColor);
colorAnimator.setDuration(150);
colorAnimator.addUpdateListener(animator -> {
int color = (int) animator.getAnimatedValue();
background.setColor(color);
currentBackgroundColor = color;
});
colorAnimator.start();
}
}
public void destroy() {
clearAnimation();
if (animate() != null) {
animate().cancel();
}
if (textView != null && textView.animate() != null) {
textView.animate().cancel();
}
ThemeManager.removeListener(this);
savePosition();
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,139 @@
package com.phoenix.gui.ui;
import android.graphics.Color;
import com.phoenix.gui.config.ConfigManager; // <-- 使用您的 ConfigManager
import java.util.ArrayList;
import java.util.List;
public class ThemeManager {
private static final String CONFIG_MODULE_NAME = "GlobalTheme";
private static final String CONFIG_KEY_THEME_COLOR = "theme_color";
private static final int DEFAULT_THEME_COLOR = 0xFF96CCFF;
private static int themeColor = DEFAULT_THEME_COLOR;
private static int bgPrimary = 0xFF292D2C;
private static int bgSecondary = 0xFF1F2221;
private static int bgDisabled = 0xFF333333;
private static int textPrimary = 0xFFFFFFFF;
private static int textSecondary = 0xFFE0E0E0;
private static int textTertiary = 0xFFAAAAAA;
private static int textOnTheme = 0xFF1A1A1A;
private static int stateDisabled = 0xFF555555;
private static int glowColor = 0x66000000;
private static int glassBackground = 0xE6000000;
private static int gradientStart = 0xFFa7ff8a;
private static int gradientEnd = 0xFF82c1fb;
private static final List<OnThemeColorChangeListener> listeners = new ArrayList<>();
public interface OnThemeColorChangeListener {
void onThemeColorChanged(int newColor);
}
private static void saveThemeColor() {
ConfigManager.saveModuleConfig(CONFIG_MODULE_NAME, CONFIG_KEY_THEME_COLOR, themeColor);
}
public static int getThemeColor() {
return themeColor;
}
public static void setThemeColor(int color) {
if (themeColor != color) {
themeColor = color;
saveThemeColor();
notifyListeners();
}
}
public static float[] getThemeColorHSV() {
float[] hsv = new float[3];
Color.colorToHSV(themeColor, hsv);
return hsv;
}
public static int getBgPrimary() { return bgPrimary; }
public static void setBgPrimary(int color) { bgPrimary = color; notifyListeners(); }
public static int getBgSecondary() { return bgSecondary; }
public static void setBgSecondary(int color) { bgSecondary = color; notifyListeners(); }
public static int getBgDisabled() { return bgDisabled; }
public static void setBgDisabled(int color) { bgDisabled = color; notifyListeners(); }
public static int getTextPrimary() { return textPrimary; }
public static void setTextPrimary(int color) { textPrimary = color; notifyListeners(); }
public static int getTextSecondary() { return textSecondary; }
public static void setTextSecondary(int color) { textSecondary = color; notifyListeners(); }
public static int getTextTertiary() { return textTertiary; }
public static void setTextTertiary(int color) { textTertiary = color; notifyListeners(); }
public static int getTextOnTheme() { return textOnTheme; }
public static void setTextOnTheme(int color) { textOnTheme = color; notifyListeners(); }
public static int getStateDisabled() { return stateDisabled; }
public static void setStateDisabled(int color) { stateDisabled = color; notifyListeners(); }
public static int getGlowColor() { return glowColor; }
public static void setGlowColor(int color) { glowColor = color; notifyListeners(); }
public static int getGlassBackground() { return glassBackground; }
public static void setGlassBackground(int color) { glassBackground = color; notifyListeners(); }
public static int getGradientStart() { return gradientStart; }
public static void setGradientStart(int color) { gradientStart = color; notifyListeners(); }
public static int getGradientEnd() { return gradientEnd; }
public static void setGradientEnd(int color) { gradientEnd = color; notifyListeners(); }
public static void applyDefaultTheme() {
setThemeColor(DEFAULT_THEME_COLOR);
}
public static void applyGreenTheme() {
setThemeColor(0xFF4CAF50);
}
public static void applyPurpleTheme() {
setThemeColor(0xFFBB86FC);
}
public static void applyOrangeTheme() {
setThemeColor(0xFFFF9800);
}
public static void applyPinkTheme() {
setThemeColor(0xFFE91E63);
}
public static void setThemeColorRGB(int r, int g, int b) {
setThemeColor(Color.rgb(r, g, b));
}
public static void setThemeColorHSV(float hue, float saturation, float value) {
setThemeColor(Color.HSVToColor(new float[]{hue, saturation, value}));
}
public static void addListener(OnThemeColorChangeListener listener) {
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
public static void removeListener(OnThemeColorChangeListener listener) {
listeners.remove(listener);
}
private static void notifyListeners() {
for (OnThemeColorChangeListener listener : new ArrayList<>(listeners)) {
try {
listener.onThemeColorChanged(themeColor);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ThemeManager() {
}
}
@@ -0,0 +1,256 @@
package com.phoenix.gui.ui;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import com.phoenix.gui.R;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.module.ModuleManager;
import com.phoenix.gui.module.ModuleToggleListener;
import com.phoenix.gui.module.ShortcutToggleListener;
import com.phoenix.gui.ui.dynamic.DynamicIslandWindow;
// 封装
public class UI {
private static DynamicColorExtractor dynamicColorExtractor;
private static boolean initialized = false;
private static boolean isShowing = false;
// UI
private static FloatBallView floatBall;
private static DynamicIslandWindow dynamicIsland;
private static ArraylistView arraylistView;
// 容器引用
private static FrameLayout guiContainer;
private static FrameLayout floatBallContainer;
private static FrameLayout menusContainer;
private static FrameLayout dynamicIslandContainer;
private static FrameLayout arraylistContainer;
// activity 引用
private static Activity currentActivity;
public static void init(Context ctx) {
if (initialized) {
return;
}
ConfigManager.init(ctx);
dynamicColorExtractor = new DynamicColorExtractor(ctx);
dynamicColorExtractor.init();
initialized = true;
}
public static void show(Activity activity) {
if (isShowing) {
return;
}
try {
currentActivity = activity;
// 绑定容器
bindContainers(activity);
// 显示主容器
if (guiContainer != null) {
guiContainer.setVisibility(View.VISIBLE);
}
// 初始化ppp
initFloatBall();
initDynamicIsland();
initArraylist();
// 注册
if (activity instanceof ModuleToggleListener) {
ModuleManager.addToggleListener((ModuleToggleListener) activity);
}
if (activity instanceof ShortcutToggleListener) {
ModuleManager.addShortcutListener((ShortcutToggleListener) activity);
}
isShowing = true;
} catch (Exception e) {
e.printStackTrace();
}
}
public static void hide() {
if (!isShowing) {
return; // 回家吃饭
}
try {
// 移除
if (currentActivity instanceof ModuleToggleListener) {
ModuleManager.removeToggleListener((ModuleToggleListener) currentActivity);
}
if (currentActivity instanceof ShortcutToggleListener) {
ModuleManager.removeShortcutListener((ShortcutToggleListener) currentActivity);
}
// 清理
if (arraylistView != null) {
arraylistView.clearModules();
arraylistView.hide();
arraylistView = null;
}
// 清理
if (dynamicIsland != null) {
dynamicIsland.hide();
dynamicIsland = null;
}
// 还是清理
if (floatBall != null) {
floatBall.hide();
floatBall.destroy();
floatBall = null;
}
// 清空垃圾桶
if (floatBallContainer != null) floatBallContainer.removeAllViews();
if (menusContainer != null) menusContainer.removeAllViews();
if (arraylistContainer != null) arraylistContainer.removeAllViews();
// 隐藏
if (guiContainer != null) {
guiContainer.setVisibility(View.GONE);
}
isShowing = false;
} catch (Exception e) {
e.printStackTrace();
}
}
private static void bindContainers(Activity activity) {
guiContainer = activity.findViewById(R.id.gui_container);
floatBallContainer = activity.findViewById(R.id.float_ball_container);
menusContainer = activity.findViewById(R.id.menus_container);
dynamicIslandContainer = activity.findViewById(R.id.dynamic_island_container);
arraylistContainer = activity.findViewById(R.id.arraylist_container);
}
private static void initFloatBall() {
try {
if (floatBall != null) {
floatBall.destroy();
floatBall = null;
}
if (floatBallContainer != null) {
floatBallContainer.removeAllViews();
floatBall = new FloatBallView(currentActivity, menusContainer);
floatBallContainer.addView(floatBall);
floatBall.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void initDynamicIsland() {
boolean enabled = ConfigManager.getDynamicIslandEnabled();
if (!enabled) return;
try {
if (dynamicIsland != null) {
dynamicIsland.hide();
dynamicIsland = null;
}
if (dynamicIslandContainer != null) {
dynamicIslandContainer.removeAllViews();
dynamicIsland = new DynamicIslandWindow(currentActivity, dynamicIslandContainer);
dynamicIsland.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void initArraylist() {
try {
if (arraylistView != null) {
arraylistView.clearModules();
arraylistView = null;
}
if (arraylistContainer != null) {
arraylistContainer.removeAllViews();
arraylistView = new ArraylistView(currentActivity);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
);
arraylistContainer.addView(arraylistView, params);
addDefaultArraylistModules();
arraylistView.show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static void addDefaultArraylistModules() {
if (arraylistView == null) return;
arraylistView.clearModules();
// arraylistView.addModule(new ArraylistModule("like this"));
}
public static void cleanup() {
hide();
if (dynamicColorExtractor != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
dynamicColorExtractor.stopListening();
dynamicColorExtractor = null;
}
currentActivity = null;
initialized = false;
}
public static DynamicColorExtractor getDynamicColorExtractor() {
return dynamicColorExtractor;
}
public static DynamicIslandWindow getDynamicIsland() {
return dynamicIsland;
}
public static ArraylistView getArraylistView() {
return arraylistView;
}
public static boolean isShowing() {
return isShowing;
}
private UI() {
// 工具类
}
}
@@ -0,0 +1,60 @@
package com.phoenix.gui.ui.dynamic;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
public abstract class AnimatedTaskItemView extends TaskItemView {
private final Handler updateHandler = new Handler(Looper.getMainLooper());
private final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
updateContent();
updateHandler.postDelayed(this, 16);
}
};
private boolean isUpdating = false;
public AnimatedTaskItemView(@NonNull Context context, DynamicIslandManager.TaskItem task, float scale) {
super(context, task, scale);
}
protected void startUpdating() {
if (!isUpdating) {
isUpdating = true;
updateHandler.post(updateRunnable);
}
}
protected void stopUpdating() {
if (isUpdating) {
isUpdating = false;
updateHandler.removeCallbacks(updateRunnable);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startUpdating();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopUpdating();
}
@Override
protected void onVisibilityChanged(android.view.View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == VISIBLE) {
startUpdating();
} else {
stopUpdating();
}
}
}
@@ -0,0 +1,301 @@
package com.phoenix.gui.ui.dynamic;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.phoenix.gui.ui.ThemeManager;
public class CollapsedContentView extends LinearLayout implements ThemeManager.OnThemeColorChangeListener {
private ImageView brandIcon;
private TextView brandText;
private ImageView userIcon;
private TextView userText;
private ImageView fpsIcon;
private TextView fpsNumberText;
private TextView fpsLabelText;
private FrameLayout fpsNumberContainer;
private DynamicIslandManager manager;
private float scale = 0.7f;
private int currentFps = 0;
public CollapsedContentView(@NonNull Context context) {
super(context);
init(context);
}
private void init(Context context) {
setOrientation(HORIZONTAL);
setGravity(Gravity.CENTER);
setPadding(
(int) dpToPx(12 * scale), 0,
(int) dpToPx(12 * scale), 0
);
createBrandModule(context);
addSeparator(context);
createUserModule(context);
addSeparator(context);
createFpsModule(context);
}
private void createBrandModule(Context context) {
LinearLayout module = new LinearLayout(context);
module.setOrientation(HORIZONTAL);
module.setGravity(Gravity.CENTER_VERTICAL);
brandIcon = new ImageView(context);
try {
int iconId = context.getResources().getIdentifier("icon", "drawable", context.getPackageName());
if (iconId != 0) {
brandIcon.setImageResource(iconId);
} else {
brandIcon.setImageResource(android.R.drawable.ic_menu_info_details);
}
} catch (Exception e) {
brandIcon.setImageResource(android.R.drawable.ic_menu_info_details);
}
// 获取颜色。
brandIcon.setColorFilter(ThemeManager.getThemeColor());
int iconSize = (int) dpToPx(16 * scale);
module.addView(brandIcon, new LayoutParams(iconSize, iconSize));
module.addView(createSpacer(context, 4 * scale), new LayoutParams(
(int) dpToPx(4 * scale), 0
));
brandText = createStyledText(context, "Phoen1xGUI", ThemeManager.getThemeColor(), true); // 神必小小小opai
module.addView(brandText);
addView(module);
}
private void createUserModule(Context context) {
LinearLayout module = new LinearLayout(context);
module.setOrientation(HORIZONTAL);
module.setGravity(Gravity.CENTER_VERTICAL);
userIcon = new ImageView(context);
userIcon.setImageResource(android.R.drawable.ic_menu_myplaces);
userIcon.setColorFilter(com.phoenix.gui.ui.ThemeManager.getTextSecondary());
int iconSize = (int) dpToPx(16 * scale);
module.addView(userIcon, new LayoutParams(iconSize, iconSize));
module.addView(createSpacer(context, 4 * scale), new LayoutParams(
(int) dpToPx(4 * scale), 0
));
userText = createStyledText(context, "Test", com.phoenix.gui.ui.ThemeManager.getTextSecondary(), false); // 用户。
module.addView(userText);
addView(module);
}
private void createFpsModule(Context context) {
LinearLayout module = new LinearLayout(context);
module.setOrientation(HORIZONTAL);
module.setGravity(Gravity.CENTER_VERTICAL);
fpsIcon = new ImageView(context);
fpsIcon.setImageResource(android.R.drawable.ic_menu_manage);
fpsIcon.setColorFilter(com.phoenix.gui.ui.ThemeManager.getTextSecondary());
int iconSize = (int) dpToPx(16 * scale);
module.addView(fpsIcon, new LayoutParams(iconSize, iconSize));
module.addView(createSpacer(context, 4 * scale), new LayoutParams(
(int) dpToPx(4 * scale), 0
));
fpsNumberContainer = new FrameLayout(context);
fpsNumberContainer.setClipChildren(false);
fpsNumberContainer.setClipToPadding(false);
module.addView(fpsNumberContainer, new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
));
fpsNumberText = createStyledText(context, "0", com.phoenix.gui.ui.ThemeManager.getTextSecondary(), false);
fpsNumberContainer.addView(fpsNumberText, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
fpsLabelText = createStyledText(context, " FPS", com.phoenix.gui.ui.ThemeManager.getTextSecondary(), false);
module.addView(fpsLabelText);
addView(module);
}
private TextView createStyledText(Context context, String text, int color, boolean bold) {
TextView textView = new TextView(context);
textView.setText(text);
textView.setTextColor(color);
textView.setTextSize(13 * scale);
textView.setTypeface(null, bold ? Typeface.BOLD : Typeface.NORMAL);
textView.setSingleLine(true);
textView.setIncludeFontPadding(false);
return textView;
}
private TextView createSeparator(Context context) {
TextView separator = new TextView(context);
separator.setText(" • ");
int separatorColor = (com.phoenix.gui.ui.ThemeManager.getTextSecondary() & 0x00FFFFFF) | 0x66000000;
separator.setTextColor(separatorColor);
separator.setTextSize(13 * scale);
separator.setIncludeFontPadding(false);
LayoutParams params = new LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
);
params.setMargins(
(int) dpToPx(4 * scale), 0,
(int) dpToPx(4 * scale), 0
);
return separator;
}
private void addSeparator(Context context) {
addView(createSeparator(context));
}
private android.view.View createSpacer(Context context, float widthDp) {
return new android.view.View(context);
}
public void setManager(DynamicIslandManager manager) {
this.manager = manager;
updateContent();
}
public void updateContent() {
if (manager == null) return;
userText.setText(manager.getPersistentText());
}
public void updateFps(int fps) {
if (currentFps == fps) return;
final String newFpsText = String.valueOf(fps);
currentFps = fps;
fpsNumberText.animate()
.translationY(-dpToPx(20 * scale))
.alpha(0f)
.scaleX(0.8f)
.scaleY(0.8f)
.setDuration(150)
.setInterpolator(new androidx.interpolator.view.animation.FastOutSlowInInterpolator())
.withEndAction(() -> {
final int startWidth = fpsNumberContainer.getWidth();
Paint textPaint = fpsNumberText.getPaint();
final int endWidth = (int) Math.ceil(textPaint.measureText(newFpsText));
fpsNumberText.setText(newFpsText);
fpsNumberText.setTranslationY(dpToPx(20 * scale));
fpsNumberText.setAlpha(0f);
fpsNumberText.setScaleX(1.2f);
fpsNumberText.setScaleY(1.2f);
fpsNumberText.animate()
.translationY(0f)
.alpha(1f)
.scaleX(1f)
.scaleY(1f)
.setDuration(400)
.setInterpolator(new OvershootInterpolator(2.5f))
.start();
if (startWidth != endWidth) {
ValueAnimator widthAnimator = ValueAnimator.ofInt(startWidth, endWidth);
widthAnimator.setDuration(400);
widthAnimator.setInterpolator(new OvershootInterpolator(2.5f));
widthAnimator.addUpdateListener(animation -> {
int animatedWidth = (Integer) animation.getAnimatedValue();
ViewGroup.LayoutParams params = fpsNumberContainer.getLayoutParams();
params.width = animatedWidth;
fpsNumberContainer.setLayoutParams(params);
});
widthAnimator.start();
}
})
.start();
}
public void updateConfig(float scale, String text) {
this.scale = scale;
userText.setText(text);
float textSize = 13 * scale;
brandText.setTextSize(textSize);
userText.setTextSize(textSize);
fpsNumberText.setTextSize(textSize);
fpsLabelText.setTextSize(textSize);
int iconSize = (int) dpToPx(16 * scale);
updateIconSize(brandIcon, iconSize);
updateIconSize(userIcon, iconSize);
updateIconSize(fpsIcon, iconSize);
setPadding(
(int) dpToPx(12 * scale), 0,
(int) dpToPx(12 * scale), 0
);
requestLayout();
}
private void updateIconSize(ImageView icon, int size) {
if (icon != null) {
LayoutParams params = (LayoutParams) icon.getLayoutParams();
params.width = size;
params.height = size;
icon.setLayoutParams(params);
}
}
private float dpToPx(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
@Override
public void onThemeColorChanged(int newColor) {
if (brandIcon != null) {
brandIcon.setColorFilter(newColor);
}
if (brandText != null) {
brandText.setTextColor(newColor);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
onThemeColorChanged(ThemeManager.getThemeColor());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
}
}
@@ -0,0 +1,357 @@
package com.phoenix.gui.ui.dynamic;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class DynamicIslandManager {
public static final long VALUE_PROGRESS_TIMEOUT_MS = 1000L;
public static final long SWITCH_DISPLAY_DURATION_MS = 500L;
public static final long TIME_PROGRESS_GRACE_PERIOD_MS = 1000L;
public static class TaskItem {
public enum Type { SWITCH, PROGRESS }
public final Type type;
public final String identifier;
public String text;
public String subtitle;
public boolean switchState;
public Drawable icon;
public boolean isTimeBased;
public long lastUpdateTime;
public boolean isAwaitingData;
public boolean removing;
public long duration;
public float displayProgress;
public float targetProgress;
public boolean isVisuallyHidden;
private Runnable hideRunnable;
private Runnable progressRunnable;
public TaskItem(Type type, String identifier, String text, @Nullable String subtitle) {
this.type = type;
this.identifier = identifier;
this.text = text;
this.subtitle = subtitle;
this.switchState = false;
this.icon = null;
this.isTimeBased = false;
this.lastUpdateTime = System.currentTimeMillis();
this.isAwaitingData = false;
this.removing = false;
this.duration = 0;
this.displayProgress = 1.0f;
this.targetProgress = 1.0f;
this.isVisuallyHidden = false;
}
public void cancelJobs(Handler handler) {
if (hideRunnable != null) {
handler.removeCallbacks(hideRunnable);
hideRunnable = null;
}
if (progressRunnable != null) {
handler.removeCallbacks(progressRunnable);
progressRunnable = null;
}
}
}
public interface StateChangeListener {
void onTasksChanged();
void onExpandedStateChanged(boolean isExpanded);
void onConfigChanged(float scale, String persistentText);
}
private float scale = 0.7f;
private String persistentText = "User";
private final List<TaskItem> tasks = new CopyOnWriteArrayList<>();
private final List<StateChangeListener> listeners = new CopyOnWriteArrayList<>();
private final Handler handler = new Handler(Looper.getMainLooper());
private int currentFps = 0;
private final Runnable updateRunnable = new Runnable() {
@Override
public void run() {
updateTasks();
handler.postDelayed(this, 250);
}
};
public DynamicIslandManager(float initialScale, String initialText) {
this.scale = Math.max(0.5f, Math.min(2.0f, initialScale));
this.persistentText = initialText;
handler.post(updateRunnable);
}
public float getScale() { return scale; }
public String getPersistentText() { return persistentText; }
public List<TaskItem> getTasks() { return new ArrayList<>(tasks); }
public int getCurrentFps() { return currentFps; }
public void setCurrentFps(int fps) { this.currentFps = fps; }
public boolean isExpanded() {
for (TaskItem task : tasks) {
if (!task.removing && !task.isVisuallyHidden) return true;
}
return false;
}
public List<TaskItem> getVisibleTasks() {
List<TaskItem> activeSwitchTasks = new ArrayList<>();
List<TaskItem> otherTasks = new ArrayList<>();
for (TaskItem task : tasks) {
if (task.removing || task.isVisuallyHidden) continue;
if (task.type == TaskItem.Type.SWITCH) {
activeSwitchTasks.add(task);
} else {
otherTasks.add(task);
}
}
if (!activeSwitchTasks.isEmpty()) {
return activeSwitchTasks;
} else {
return otherTasks;
}
}
public void updateConfig(float newScale, String newText) {
this.scale = Math.max(0.5f, Math.min(2.0f, newScale));
this.persistentText = newText;
notifyConfigChanged();
}
public void addSwitch(String identifier, String text, boolean state) {
android.util.Log.d("DynamicIsland", String.format("addSwitch: id=%s, text=%s, state=%s", identifier, text, state));
String mainTitle = "功能开关";
String subTitle = text + "|已被" + (state ? "开启" : "关闭");
int taskIndex = findTaskIndex(identifier);
TaskItem task;
if (taskIndex != -1) {
task = tasks.get(taskIndex);
task.cancelJobs(handler);
task.text = mainTitle;
task.subtitle = subTitle;
task.switchState = state;
task.lastUpdateTime = System.currentTimeMillis();
task.duration = SWITCH_DISPLAY_DURATION_MS;
task.removing = false;
task.isTimeBased = true;
startTimeBasedAnimation(task);
android.util.Log.d("DynamicIsland", "Updated existing switch task");
} else {
task = new TaskItem(TaskItem.Type.SWITCH, identifier, mainTitle, subTitle);
task.switchState = state;
task.duration = SWITCH_DISPLAY_DURATION_MS;
task.isTimeBased = true;
startTimeBasedAnimation(task);
tasks.add(0, task);
android.util.Log.d("DynamicIsland", "Created new switch task");
}
notifyTasksChanged();
}
public void addOrUpdateProgress(String identifier, String text,
@Nullable String subtitle, @Nullable Drawable icon,
@Nullable Float progress, @Nullable Long duration) {
int taskIndex = findTaskIndex(identifier);
if (taskIndex != -1) {
updateProgressInternal(tasks.get(taskIndex), text, subtitle, progress, duration);
} else {
addProgressInternal(identifier, text, subtitle, icon, progress, duration);
}
notifyTasksChanged();
}
public void removeTask(String identifier) {
int taskIndex = findTaskIndex(identifier);
if (taskIndex != -1) {
TaskItem task = tasks.get(taskIndex);
if (task.removing) return;
task.removing = true;
task.cancelJobs(handler);
notifyTasksChanged();
handler.postDelayed(() -> {
tasks.remove(task);
notifyTasksChanged();
}, 500);
}
}
public void hide() {
for (TaskItem task : tasks) {
task.cancelJobs(handler);
}
tasks.clear();
notifyTasksChanged();
}
public void destroy() {
handler.removeCallbacksAndMessages(null);
hide();
}
public void addListener(StateChangeListener listener) { listeners.add(listener); }
public void removeListener(StateChangeListener listener) { listeners.remove(listener); }
private void notifyTasksChanged() {
boolean expanded = isExpanded();
for (StateChangeListener listener : listeners) {
listener.onTasksChanged();
listener.onExpandedStateChanged(expanded);
}
}
private void notifyConfigChanged() {
for (StateChangeListener listener : listeners) {
handler.post(() -> listener.onConfigChanged(scale, persistentText));
}
}
private int findTaskIndex(String identifier) {
for (int i = 0; i < tasks.size(); i++) {
if (tasks.get(i).identifier.equals(identifier)) return i;
}
return -1;
}
private void updateTasks() {
if (tasks.isEmpty()) return;
long currentTime = System.currentTimeMillis();
boolean hasChanged = false;
Iterator<TaskItem> iterator = tasks.iterator();
while (iterator.hasNext()) {
TaskItem task = iterator.next();
if (task.removing) continue;
boolean shouldBeMarkedForRemoval = false;
if (task.isTimeBased) {
if (task.displayProgress <= 0.01f && !task.isAwaitingData) {
task.isAwaitingData = true;
task.lastUpdateTime = currentTime;
}
if (task.isAwaitingData && (currentTime - task.lastUpdateTime > TIME_PROGRESS_GRACE_PERIOD_MS)) {
shouldBeMarkedForRemoval = true;
}
}
else if (task.type == TaskItem.Type.PROGRESS) {
if (currentTime - task.lastUpdateTime > VALUE_PROGRESS_TIMEOUT_MS) {
shouldBeMarkedForRemoval = true;
}
}
if (shouldBeMarkedForRemoval) {
task.removing = true;
hasChanged = true;
handler.postDelayed(() -> {
tasks.remove(task);
notifyTasksChanged();
}, 500);
}
}
if (hasChanged) {
notifyTasksChanged();
}
}
private void addProgressInternal(String identifier, String text, String subtitle,
Drawable icon, Float progressValue, Long duration) {
TaskItem newTask = new TaskItem(TaskItem.Type.PROGRESS, identifier, text, subtitle);
newTask.icon = icon != null ? icon.mutate() : null;
if (progressValue != null) {
newTask.isTimeBased = false;
newTask.displayProgress = 0f;
animateProgressTo(newTask, progressValue, 400);
} else {
newTask.isTimeBased = true;
newTask.duration = duration != null ? duration : 5000L;
startTimeBasedAnimation(newTask);
}
tasks.add(0, newTask);
}
private void updateProgressInternal(TaskItem task, String text, String subtitle,
Float progressValue, Long duration) {
task.text = text;
task.subtitle = subtitle;
task.lastUpdateTime = System.currentTimeMillis();
task.isAwaitingData = false;
task.removing = false;
task.cancelJobs(handler);
if (progressValue != null) {
task.isTimeBased = false;
animateProgressTo(task, progressValue, 400);
} else {
task.isTimeBased = true;
task.duration = duration != null ? duration : 5000L;
startTimeBasedAnimation(task);
}
}
private void animateProgressTo(TaskItem task, float targetProgress, long durationMs) {
task.cancelJobs(handler);
task.targetProgress = Math.max(0f, Math.min(1f, targetProgress));
final float startProgress = task.displayProgress;
final float delta = task.targetProgress - startProgress;
if (Math.abs(delta) < 0.001f) return;
final long startTime = System.currentTimeMillis();
final boolean isSwitch = task.type == TaskItem.Type.SWITCH;
task.progressRunnable = new Runnable() {
@Override
public void run() {
long elapsed = System.currentTimeMillis() - startTime;
float fraction = Math.min(1f, elapsed / (float) durationMs);
float easedFraction = (float) (1 - Math.pow(1 - fraction, 3));
task.displayProgress = startProgress + delta * easedFraction;
if (!isSwitch) {
notifyTasksChanged();
}
if (fraction < 1f) {
handler.postDelayed(this, 16);
} else {
task.displayProgress = task.targetProgress;
task.progressRunnable = null;
notifyTasksChanged();
}
}
};
handler.post(task.progressRunnable);
}
private void startTimeBasedAnimation(TaskItem task) {
task.cancelJobs(handler);
if (task.displayProgress < 1.0f) {
animateProgressTo(task, 1.0f, 500);
handler.postDelayed(() -> animateProgressTo(task, 0f, task.duration), 500);
} else {
animateProgressTo(task, 0f, task.duration);
}
}
}
@@ -0,0 +1,569 @@
package com.phoenix.gui.ui.dynamic;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.view.Choreographer;
import android.view.Gravity;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import com.phoenix.gui.ui.ThemeManager;
import java.util.ArrayList;
import java.util.List;
public class DynamicIslandView extends FrameLayout implements DynamicIslandManager.StateChangeListener, ThemeManager.OnThemeColorChangeListener {
private static final float COLLAPSED_HEIGHT_DP = 36f;
private static final float EXPANDED_CORNER_RADIUS_DP = 28f;
private static final float ITEM_HEIGHT_DP = 52f;
private static final float VIEW_PADDING_DP = 12f;
private static final float HORIZONTAL_ITEM_PADDING_DP = 24f;
private static final float ICON_SIZE_DP = 40f;
private static final float ICON_SPACING_DP = 12f;
private static final float GLOW_BLUR_RADIUS_DP = 12f;
private static final float GLOW_SPREAD_RADIUS_DP = 4f;
private static final float SHADOW_SAFETY_MARGIN_DP = 8f;
private static final int SIZE_ANIMATION_DURATION_MS = 350;
private static final int CONTENT_FADE_OUT_MS = 100;
private static final int CONTENT_FADE_IN_MS = 200;
private DynamicIslandManager manager;
private Paint backgroundPaint;
private Paint glowPaint;
private RectF backgroundRect = new RectF();
private RectF glowRect = new RectF();
private float sheenPosition = -1.0f;
private ValueAnimator sheenAnimator;
private CollapsedContentView collapsedContent;
private FrameLayout expandedContainer;
private float currentHeight;
private float currentWidth;
private float currentCornerRadius;
private ValueAnimator sizeAnimator;
private boolean wasExpanded = false;
private TextPaint textPaint;
private int frameCount = 0;
private long lastFpsTime = System.nanoTime();
public DynamicIslandView(@NonNull Context context) {
super(context);
init(context);
}
public DynamicIslandView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
setWillNotDraw(false);
setLayerType(LAYER_TYPE_SOFTWARE, null);
setClipChildren(false);
setClipToPadding(false);
textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
initPaints();
initSheenAnimation();
initContentViews(context);
startFpsMonitor();
}
private void initPaints() {
glowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
glowPaint.setColor(ThemeManager.getGlowColor());
glowPaint.setStyle(Paint.Style.FILL);
glowPaint.setMaskFilter(new BlurMaskFilter(dpToPx(GLOW_BLUR_RADIUS_DP), BlurMaskFilter.Blur.NORMAL));
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(ThemeManager.getGlassBackground());
backgroundPaint.setStyle(Paint.Style.FILL);
}
private void initSheenAnimation() {
sheenAnimator = ValueAnimator.ofFloat(-1.0f, 2.0f);
sheenAnimator.setDuration(2500);
sheenAnimator.setStartDelay(500);
sheenAnimator.setInterpolator(new android.view.animation.LinearInterpolator());
sheenAnimator.setRepeatCount(ValueAnimator.INFINITE);
sheenAnimator.setRepeatMode(ValueAnimator.RESTART);
sheenAnimator.addUpdateListener(animation -> {
sheenPosition = (float) animation.getAnimatedValue();
invalidate();
});
sheenAnimator.start();
}
private void initContentViews(Context context) {
collapsedContent = new CollapsedContentView(context);
collapsedContent.setVisibility(VISIBLE);
LayoutParams collapsedParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
collapsedParams.gravity = Gravity.CENTER;
addView(collapsedContent, collapsedParams);
expandedContainer = new FrameLayout(context);
expandedContainer.setVisibility(GONE);
expandedContainer.setAlpha(0f);
expandedContainer.setBackgroundColor(Color.TRANSPARENT);
LayoutParams expandedParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
expandedParams.gravity = Gravity.CENTER;
addView(expandedContainer, expandedParams);
}
private void startFpsMonitor() {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
frameCount++;
long elapsedNanos = frameTimeNanos - lastFpsTime;
if (elapsedNanos >= 1_000_000_000L) {
if (manager != null) {
manager.setCurrentFps(frameCount);
if (collapsedContent != null) {
collapsedContent.updateFps(frameCount);
}
}
frameCount = 0;
lastFpsTime = frameTimeNanos;
}
Choreographer.getInstance().postFrameCallback(this);
}
});
}
public void setManager(DynamicIslandManager manager) {
if (this.manager != null) {
this.manager.removeListener(this);
}
this.manager = manager;
this.manager.addListener(this);
if (collapsedContent != null) {
collapsedContent.setManager(manager);
}
updateContent();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
float scale = manager != null ? manager.getScale() : 0.7f;
int glowPadding = (int) (dpToPx(GLOW_BLUR_RADIUS_DP + GLOW_SPREAD_RADIUS_DP) * scale);
int safetyMargin = (int) (dpToPx(SHADOW_SAFETY_MARGIN_DP) * scale);
int totalPadding = glowPadding + safetyMargin;
int contentWidth = (int) currentWidth;
int contentHeight = (int) currentHeight;
if (currentWidth == 0 || currentHeight == 0) {
boolean isExpanded = manager != null && manager.isExpanded();
if (isExpanded) {
contentHeight = calculateExpandedHeight(scale);
contentWidth = calculateExpandedWidth(scale);
} else {
contentHeight = (int) (dpToPx(COLLAPSED_HEIGHT_DP) * scale);
contentWidth = calculateCollapsedWidth(scale);
}
currentHeight = contentHeight;
currentWidth = contentWidth;
currentCornerRadius = isExpanded ? dpToPx(EXPANDED_CORNER_RADIUS_DP) * scale : contentHeight / 2f;
}
int contentWidthSpec = MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.AT_MOST);
int contentHeightSpec = MeasureSpec.makeMeasureSpec(contentHeight, MeasureSpec.EXACTLY);
measureChild(collapsedContent, contentWidthSpec, contentHeightSpec);
measureChild(expandedContainer, MeasureSpec.makeMeasureSpec(contentWidth, MeasureSpec.EXACTLY), contentHeightSpec);
setMeasuredDimension(
contentWidth + totalPadding * 2,
contentHeight + totalPadding * 2
);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
float scale = manager != null ? manager.getScale() : 0.7f;
int glowPadding = (int) (dpToPx(GLOW_BLUR_RADIUS_DP + GLOW_SPREAD_RADIUS_DP) * scale);
int safetyMargin = (int) (dpToPx(SHADOW_SAFETY_MARGIN_DP) * scale);
int totalPadding = glowPadding + safetyMargin;
int contentLeft = totalPadding;
int contentTop = totalPadding;
int contentRight = getWidth() - totalPadding;
int contentBottom = getHeight() - totalPadding;
int contentWidth = contentRight - contentLeft;
int collapsedWidth = collapsedContent.getMeasuredWidth();
int collapsedLeft = contentLeft + (contentWidth - collapsedWidth) / 2;
collapsedContent.layout(collapsedLeft, contentTop, collapsedLeft + collapsedWidth, contentBottom);
expandedContainer.layout(contentLeft, contentTop, contentRight, contentBottom);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float scale = manager != null ? manager.getScale() : 0.7f;
int glowPadding = (int) (dpToPx(GLOW_BLUR_RADIUS_DP + GLOW_SPREAD_RADIUS_DP) * scale);
int safetyMargin = (int) (dpToPx(SHADOW_SAFETY_MARGIN_DP) * scale);
int totalPadding = glowPadding + safetyMargin;
float contentLeft = totalPadding;
float contentTop = totalPadding;
float contentRight = getWidth() - totalPadding;
float contentBottom = getHeight() - totalPadding;
backgroundRect.set(contentLeft, contentTop, contentRight, contentBottom);
float spreadPx = dpToPx(GLOW_SPREAD_RADIUS_DP) * scale;
glowRect.set(
contentLeft - spreadPx,
contentTop - spreadPx,
contentRight + spreadPx,
contentBottom + spreadPx
);
canvas.drawRoundRect(glowRect, currentCornerRadius, currentCornerRadius, glowPaint);
canvas.drawRoundRect(backgroundRect, currentCornerRadius, currentCornerRadius, backgroundPaint);
drawSheen(canvas, backgroundRect, currentCornerRadius);
}
private void drawSheen(Canvas canvas, RectF rect, float cornerRadius) {
float gradientWidth = rect.width() * 0.5f;
float gradientStart = rect.width() * sheenPosition;
LinearGradient gradient = new LinearGradient(
gradientStart - gradientWidth, rect.top,
gradientStart, rect.bottom,
new int[]{0x00FFFFFF, 0x14FFFFFF, 0x00FFFFFF},
new float[]{0f, 0.5f, 1f},
Shader.TileMode.CLAMP
);
Paint sheenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
sheenPaint.setShader(gradient);
canvas.save();
canvas.clipRect(rect);
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, sheenPaint);
canvas.restore();
}
@Override
public void onTasksChanged() {
updateContent();
post(() -> animateSizeToFitContent());
}
@Override
public void onExpandedStateChanged(boolean isExpanded) {
if (wasExpanded != isExpanded) {
wasExpanded = isExpanded;
updateContent();
animateToState(isExpanded);
}
}
@Override
public void onConfigChanged(float scale, String persistentText) {
if (collapsedContent != null) {
collapsedContent.updateConfig(scale, persistentText);
}
requestLayout();
}
private void updateContent() {
if (manager == null) return;
boolean isExpanded = manager.isExpanded();
if (isExpanded) {
LinearLayout taskContainer = null;
if (expandedContainer.getChildCount() > 0 && expandedContainer.getChildAt(0) instanceof LinearLayout) {
taskContainer = (LinearLayout) expandedContainer.getChildAt(0);
}
if (taskContainer == null) {
rebuildExpandedContent();
} else {
if (!areTasksMatching(taskContainer)) {
rebuildExpandedContent();
} else {
updateExistingContent(taskContainer);
}
}
}
if (collapsedContent != null) {
collapsedContent.updateContent();
}
}
private boolean areTasksMatching(LinearLayout taskContainer) {
List<DynamicIslandManager.TaskItem> tasks = manager.getVisibleTasks();
if (taskContainer.getChildCount() != tasks.size()) {
android.util.Log.d("DynamicIsland", String.format(
"Tasks not matching: count mismatch (view=%d, task=%d)",
taskContainer.getChildCount(), tasks.size()
));
return false;
}
for (int i = 0; i < tasks.size(); i++) {
View child = taskContainer.getChildAt(i);
if (!(child instanceof TaskItemView)) {
android.util.Log.d("DynamicIsland", "Tasks not matching: child is not TaskItemView");
return false;
}
TaskItemView view = (TaskItemView) child;
DynamicIslandManager.TaskItem task = tasks.get(i);
if (!view.task.identifier.equals(task.identifier)) {
android.util.Log.d("DynamicIsland", String.format(
"Tasks not matching: identifier mismatch at %d (view=%s, task=%s)",
i, view.task.identifier, task.identifier
));
return false;
}
if (view.task.type != task.type) {
android.util.Log.d("DynamicIsland", String.format(
"Tasks not matching: type mismatch at %d (view=%s, task=%s)",
i, view.task.type, task.type
));
return false;
}
}
android.util.Log.d("DynamicIsland", "Tasks matching, using updateExistingContent");
return true;
}
private void updateExistingContent(LinearLayout taskContainer) {
List<DynamicIslandManager.TaskItem> tasks = manager.getVisibleTasks();
android.util.Log.d("DynamicIsland", String.format("updateExistingContent: %d tasks", tasks.size()));
for (int i = 0; i < tasks.size(); i++) {
if (i >= taskContainer.getChildCount()) break;
View child = taskContainer.getChildAt(i);
if (!(child instanceof TaskItemView)) continue;
TaskItemView view = (TaskItemView) child;
DynamicIslandManager.TaskItem newTask = tasks.get(i);
view.task = newTask;
view.updateContent();
}
}
private void rebuildExpandedContent() {
android.util.Log.d("DynamicIsland", "rebuildExpandedContent called");
expandedContainer.removeAllViews();
LinearLayout taskContainer = new LinearLayout(getContext());
taskContainer.setOrientation(LinearLayout.VERTICAL);
taskContainer.setPadding(
0, (int) (dpToPx(VIEW_PADDING_DP) * manager.getScale()),
0, (int) (dpToPx(VIEW_PADDING_DP) * manager.getScale())
);
for (DynamicIslandManager.TaskItem task : manager.getVisibleTasks()) {
TaskItemView itemView = createTaskItemView(task);
taskContainer.addView(itemView);
}
expandedContainer.addView(taskContainer, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
private TaskItemView createTaskItemView(DynamicIslandManager.TaskItem task) {
switch (task.type) {
case SWITCH:
return new SwitchItemView(getContext(), task, manager.getScale());
case PROGRESS:
return new ProgressItemView(getContext(), task, manager.getScale());
default:
throw new IllegalArgumentException("Unknown task type: " + task.type);
}
}
private void animateSizeToFitContent() {
if (manager == null) return;
boolean isExpanded = manager.isExpanded();
float scale = manager.getScale();
float targetHeight = isExpanded ? calculateExpandedHeight(scale) : dpToPx(COLLAPSED_HEIGHT_DP) * scale;
float targetWidth = isExpanded ? calculateExpandedWidth(scale) : calculateCollapsedWidth(scale);
float targetCorner = isExpanded ? dpToPx(EXPANDED_CORNER_RADIUS_DP) * scale : targetHeight / 2f;
if (Math.abs(targetHeight - currentHeight) > 1f || Math.abs(targetWidth - currentWidth) > 1f) {
animateSizeWithSpring(targetHeight, targetWidth, targetCorner);
}
}
private void animateToState(boolean expanded) {
if (sizeAnimator != null) sizeAnimator.cancel();
float scale = manager != null ? manager.getScale() : 0.7f;
float targetHeight = expanded ? calculateExpandedHeight(scale) : dpToPx(COLLAPSED_HEIGHT_DP) * scale;
float targetWidth = expanded ? calculateExpandedWidth(scale) : calculateCollapsedWidth(scale);
float targetCorner = expanded ? dpToPx(EXPANDED_CORNER_RADIUS_DP) * scale : targetHeight / 2f;
if (expanded) {
collapsedContent.animate().alpha(0f).scaleX(0.9f).scaleY(0.9f).setDuration(CONTENT_FADE_OUT_MS)
.setInterpolator(new FastOutSlowInInterpolator()).withEndAction(() -> collapsedContent.setVisibility(GONE)).start();
expandedContainer.setVisibility(VISIBLE);
expandedContainer.setAlpha(0f);
expandedContainer.setScaleX(0.9f);
expandedContainer.setScaleY(0.9f);
expandedContainer.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(CONTENT_FADE_IN_MS)
.setInterpolator(new FastOutSlowInInterpolator()).start();
} else {
expandedContainer.animate().alpha(0f).scaleX(0.9f).scaleY(0.9f).setDuration(CONTENT_FADE_OUT_MS)
.setInterpolator(new FastOutSlowInInterpolator()).withEndAction(() -> expandedContainer.setVisibility(GONE)).start();
collapsedContent.setVisibility(VISIBLE);
collapsedContent.setAlpha(0f);
collapsedContent.setScaleX(0.9f);
collapsedContent.setScaleY(0.9f);
collapsedContent.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(CONTENT_FADE_IN_MS)
.setInterpolator(new FastOutSlowInInterpolator()).start();
}
animateSizeWithSpring(targetHeight, targetWidth, targetCorner);
}
private void animateSizeWithSpring(float targetHeight, float targetWidth, float targetCorner) {
final float startHeight = currentHeight;
final float startWidth = currentWidth;
final float startCorner = currentCornerRadius;
if (sizeAnimator != null) sizeAnimator.cancel();
sizeAnimator = ValueAnimator.ofFloat(0f, 1f);
sizeAnimator.setDuration(SIZE_ANIMATION_DURATION_MS);
sizeAnimator.setInterpolator(new OvershootInterpolator(0.8f));
sizeAnimator.addUpdateListener(animation -> {
float fraction = animation.getAnimatedFraction();
currentHeight = startHeight + (targetHeight - startHeight) * fraction;
currentWidth = startWidth + (targetWidth - startWidth) * fraction;
currentCornerRadius = startCorner + (targetCorner - startCorner) * fraction;
requestLayout();
});
sizeAnimator.start();
}
private int calculateExpandedHeight(float scale) {
if (manager == null) return (int) (dpToPx(COLLAPSED_HEIGHT_DP) * scale);
int totalHeight = 0;
for (DynamicIslandManager.TaskItem task : manager.getVisibleTasks()) {
switch (task.type) {
case SWITCH:
totalHeight += dpToPx(ITEM_HEIGHT_DP) * scale;
break;
case PROGRESS:
totalHeight += dpToPx(ITEM_HEIGHT_DP + 8 + 12) * scale;
break;
}
}
return totalHeight + (int) (dpToPx(VIEW_PADDING_DP * 2) * scale);
}
private int calculateExpandedWidth(float scale) {
if (manager == null) return (int) (dpToPx(280) * scale);
int minWidth = (int) (dpToPx(280) * scale);
int maxWidth = minWidth;
for (DynamicIslandManager.TaskItem task : manager.getVisibleTasks()) {
int requiredWidth = calculateTaskWidth(task, scale);
maxWidth = Math.max(maxWidth, requiredWidth);
}
int absoluteMaxWidth = (int) (dpToPx(450) * scale);
return Math.min(maxWidth, absoluteMaxWidth);
}
private int calculateCollapsedWidth(float scale) {
if (collapsedContent == null) return (int) (dpToPx(280) * scale);
int widthSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
int heightSpec = MeasureSpec.makeMeasureSpec((int)(dpToPx(COLLAPSED_HEIGHT_DP) * scale), MeasureSpec.EXACTLY);
collapsedContent.measure(widthSpec, heightSpec);
int contentWidth = collapsedContent.getMeasuredWidth();
int padding = (int) (dpToPx(40) * scale);
return contentWidth + padding;
}
private int calculateTaskWidth(DynamicIslandManager.TaskItem task, float scale) {
float sidePadding = dpToPx(HORIZONTAL_ITEM_PADDING_DP * 2) * scale;
float iconWidth = 0;
float spacing = 0;
if (task.icon != null || task.type == DynamicIslandManager.TaskItem.Type.SWITCH) {
if (task.type == DynamicIslandManager.TaskItem.Type.SWITCH) iconWidth = dpToPx(52) * scale;
else iconWidth = dpToPx(ICON_SIZE_DP) * scale;
spacing = dpToPx(ICON_SPACING_DP) * scale;
}
textPaint.setTextSize(spToPx(15 * scale));
textPaint.setTypeface(Typeface.DEFAULT_BOLD);
float titleWidth = textPaint.measureText(task.text);
float subtitleWidth = 0;
if (task.subtitle != null) {
textPaint.setTextSize(spToPx(12 * scale));
textPaint.setTypeface(Typeface.DEFAULT);
subtitleWidth = textPaint.measureText(task.subtitle.replace("|", " "));
}
float textWidth = Math.max(titleWidth, subtitleWidth);
float extraPadding = dpToPx(32) * scale;
return (int) (sidePadding + iconWidth + spacing + textWidth + extraPadding);
}
private float dpToPx(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
private int spToPx(float sp) {
return (int) (sp * getResources().getDisplayMetrics().scaledDensity);
}
@Override
public void onThemeColorChanged(int newColor) {
invalidate();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (sheenAnimator != null) sheenAnimator.cancel();
if (sizeAnimator != null) sizeAnimator.cancel();
// 停止监听应该是
ThemeManager.removeListener(this);
}
}
@@ -0,0 +1,107 @@
package com.phoenix.gui.ui.dynamic;
import android.content.Context;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
public class DynamicIslandWindow {
private final Context context;
private final FrameLayout container;
private DynamicIslandView dynamicIslandView;
private DynamicIslandManager manager;
private boolean isShowing = false;
public DynamicIslandWindow(@NonNull Context context, FrameLayout container) {
this.context = context.getApplicationContext();
this.container = container;
}
public void show() {
if (isShowing) return;
android.content.SharedPreferences prefs = context.getSharedPreferences("SettingsPrefs", Context.MODE_PRIVATE);
float scale = prefs.getFloat("dynamicIslandScale", 0.7f);
String username = prefs.getString("dynamicIslandUsername", "User");
manager = new DynamicIslandManager(scale, username);
dynamicIslandView = new DynamicIslandView(context);
dynamicIslandView.setManager(manager);
dynamicIslandView.setAlpha(0.80f);
if (container != null) {
container.removeAllViews();
container.addView(dynamicIslandView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
));
container.setVisibility(View.VISIBLE);
isShowing = true;
}
}
public void hide() {
if (!isShowing) return;
try {
if (container != null) {
container.removeAllViews();
container.setVisibility(View.GONE);
}
if (dynamicIslandView != null) {
dynamicIslandView = null;
}
if (manager != null) {
manager.destroy();
manager = null;
}
isShowing = false;
} catch (Exception e) {
e.printStackTrace();
}
}
public void updateConfig(float scale, String username) {
if (manager != null) {
manager.updateConfig(scale, username);
}
}
public void addSwitch(String identifier, String text, boolean state) {
if (manager != null) {
manager.addSwitch(identifier, text, state);
}
}
public void addOrUpdateProgress(String identifier, String text, String subtitle,
android.graphics.drawable.Drawable icon,
Float progress, Long duration) {
if (manager != null) {
manager.addOrUpdateProgress(identifier, text, subtitle, icon, progress, duration);
}
}
public void removeTask(String identifier) {
if (manager != null) {
manager.removeTask(identifier);
}
}
public void hideAllTasks() {
if (manager != null) {
manager.hide();
}
}
public boolean isShowing() {
return isShowing;
}
public DynamicIslandManager getManager() {
return manager;
}
}
@@ -0,0 +1,66 @@
package com.phoenix.gui.ui.dynamic;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
public class GlowDrawable extends Drawable {
private final Paint paint;
private float cornerRadius = 0;
private float blurRadius = 0;
private float spreadRadius = 0;
public GlowDrawable() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(0x66000000);
paint.setStyle(Paint.Style.FILL);
}
public void setCornerRadius(float radius) {
this.cornerRadius = radius;
invalidateSelf();
}
public void setBlurRadius(float radius) {
this.blurRadius = radius;
if (radius > 0) {
paint.setMaskFilter(new BlurMaskFilter(radius, BlurMaskFilter.Blur.NORMAL));
} else {
paint.setMaskFilter(null);
}
invalidateSelf();
}
public void setSpreadRadius(float radius) {
this.spreadRadius = radius;
invalidateSelf();
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
RectF rect = new RectF(
bounds.left - spreadRadius,
bounds.top - spreadRadius,
bounds.right + spreadRadius,
bounds.bottom + spreadRadius
);
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
@@ -0,0 +1,161 @@
package com.phoenix.gui.ui.dynamic;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class MaterialProgressBar extends View {
private static final float DEFAULT_HEIGHT_DP = 8f;
private float progress = 0f;
private float sheenPosition = 0f;
private ValueAnimator sheenAnimator;
private Paint backgroundPaint;
private Paint progressPaint;
private float barHeight;
private float cornerRadius;
public MaterialProgressBar(@NonNull Context context) {
super(context);
init(context);
}
public MaterialProgressBar(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
float density = context.getResources().getDisplayMetrics().density;
barHeight = DEFAULT_HEIGHT_DP * density;
cornerRadius = barHeight / 2f;
backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setStyle(Paint.Style.FILL);
int backgroundColor = (com.phoenix.gui.ui.ThemeManager.getThemeColor() & 0x00FFFFFF) | 0x26000000;
backgroundPaint.setColor(backgroundColor);
progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
progressPaint.setStyle(Paint.Style.FILL);
progressPaint.setColor(com.phoenix.gui.ui.ThemeManager.getThemeColor());
startSheenAnimation();
}
private void startSheenAnimation() {
sheenAnimator = ValueAnimator.ofFloat(0f, 2f);
sheenAnimator.setDuration(1000);
sheenAnimator.setInterpolator(new LinearInterpolator());
sheenAnimator.setRepeatCount(ValueAnimator.INFINITE);
sheenAnimator.setRepeatMode(ValueAnimator.RESTART);
sheenAnimator.addUpdateListener(animation -> {
sheenPosition = (float) animation.getAnimatedValue();
invalidate();
});
sheenAnimator.start();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = (int) (barHeight + getPaddingTop() + getPaddingBottom());
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
float width = getWidth() - getPaddingLeft() - getPaddingRight();
float left = getPaddingLeft();
float top = getPaddingTop();
float right = left + width;
float bottom = top + barHeight;
RectF backgroundRect = new RectF(left, top, right, bottom);
canvas.drawRoundRect(backgroundRect, cornerRadius, cornerRadius, backgroundPaint);
float progressWidth = Math.max(width * progress, barHeight);
float progressRight = left + progressWidth;
if (progress > 0.001f) {
canvas.save();
RectF progressRect = new RectF(left, top, progressRight, bottom);
canvas.clipRect(progressRect);
canvas.drawRoundRect(progressRect, cornerRadius, cornerRadius, progressPaint);
drawSheen(canvas, progressRect);
canvas.restore();
}
}
private void drawSheen(Canvas canvas, RectF rect) {
float gradientWidth = rect.width() * 0.3f;
float gradientStart = rect.width() * (sheenPosition - 1f);
int colorSheen = (com.phoenix.gui.ui.ThemeManager.getTextPrimary() & 0x00FFFFFF) | 0x4D000000;
LinearGradient gradient = new LinearGradient(
rect.left + gradientStart,
rect.top,
rect.left + gradientStart + gradientWidth,
rect.top,
new int[]{0x00FFFFFF, colorSheen, 0x00FFFFFF},
new float[]{0f, 0.5f, 1f},
Shader.TileMode.CLAMP
);
Paint sheenPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
sheenPaint.setShader(gradient);
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, sheenPaint);
}
public void setProgress(float progress) {
this.progress = Math.max(0f, Math.min(1f, progress));
invalidate();
}
public float getProgress() {
return progress;
}
public void setBarHeight(float heightDp) {
float density = getResources().getDisplayMetrics().density;
this.barHeight = heightDp * density;
this.cornerRadius = barHeight / 2f;
requestLayout();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (sheenAnimator != null) {
sheenAnimator.cancel();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (sheenAnimator != null && !sheenAnimator.isRunning()) {
sheenAnimator.start();
}
}
}
@@ -0,0 +1,206 @@
package com.phoenix.gui.ui.dynamic;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.OvershootInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.phoenix.gui.ui.ThemeManager;
public class MaterialSwitchView extends View implements ThemeManager.OnThemeColorChangeListener {
private static final float TRACK_WIDTH_DP = 52f;
private static final float TRACK_HEIGHT_DP = 32f;
private static final float THUMB_SIZE_DP = 24f;
private static final float THUMB_PADDING_DP = 4f;
private boolean isChecked = false;
private float thumbPosition = 0f;
private float trackColorFraction = 0f;
private ValueAnimator thumbAnimator;
private ValueAnimator colorAnimator;
private Paint trackPaint;
private Paint thumbPaint;
private float trackWidth;
private float trackHeight;
private float thumbSize;
private float thumbPadding;
private float trackRadius;
private float thumbRadius;
public MaterialSwitchView(@NonNull Context context) {
super(context);
init(context);
}
public MaterialSwitchView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context) {
float density = context.getResources().getDisplayMetrics().density;
trackWidth = TRACK_WIDTH_DP * density;
trackHeight = TRACK_HEIGHT_DP * density;
thumbSize = THUMB_SIZE_DP * density;
thumbPadding = THUMB_PADDING_DP * density;
trackRadius = trackHeight / 2f;
thumbRadius = thumbSize / 2f;
trackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
trackPaint.setStyle(Paint.Style.FILL);
thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
thumbPaint.setStyle(Paint.Style.FILL);
thumbPaint.setColor(com.phoenix.gui.ui.ThemeManager.getTextPrimary());
thumbPaint.setShadowLayer(
4f * density,
0f,
2f * density,
0x40000000
);
setLayerType(LAYER_TYPE_SOFTWARE, null);
setClickable(false);
setFocusable(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = (int) (trackWidth + getPaddingLeft() + getPaddingRight());
int height = (int) (trackHeight + getPaddingTop() + getPaddingBottom());
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
float centerX = getWidth() / 2f;
float centerY = getHeight() / 2f;
float trackLeft = centerX - trackWidth / 2f;
float trackTop = centerY - trackHeight / 2f;
float trackRight = centerX + trackWidth / 2f;
float trackBottom = centerY + trackHeight / 2f;
int colorTrackChecked = ThemeManager.getThemeColor();
int colorTrackUnchecked = (com.phoenix.gui.ui.ThemeManager.getTextPrimary() & 0x00FFFFFF) | 0x4D000000;
int trackColor = interpolateColor(colorTrackUnchecked, colorTrackChecked, trackColorFraction);
trackPaint.setColor(trackColor);
RectF trackRect = new RectF(trackLeft, trackTop, trackRight, trackBottom);
canvas.drawRoundRect(trackRect, trackRadius, trackRadius, trackPaint);
float thumbTravelDistance = trackWidth - thumbSize - 2 * thumbPadding;
float thumbCenterX = trackLeft + thumbPadding + thumbRadius + thumbTravelDistance * thumbPosition;
float thumbCenterY = centerY;
canvas.drawCircle(thumbCenterX, thumbCenterY, thumbRadius, thumbPaint);
}
public void setChecked(boolean checked, boolean animate) {
if (this.isChecked == checked) return;
this.isChecked = checked;
if (animate) {
animateToPosition(checked ? 1f : 0f);
animateTrackColor(checked ? 1f : 0f);
} else {
thumbPosition = checked ? 1f : 0f;
trackColorFraction = checked ? 1f : 0f;
invalidate();
}
}
public void setChecked(boolean checked) {
setChecked(checked, true);
}
public boolean isChecked() {
return isChecked;
}
private void animateToPosition(float targetPosition) {
if (thumbAnimator != null) {
thumbAnimator.cancel();
}
thumbAnimator = ValueAnimator.ofFloat(thumbPosition, targetPosition);
thumbAnimator.setDuration(200);
thumbAnimator.setInterpolator(new OvershootInterpolator(1.2f));
thumbAnimator.addUpdateListener(animation -> {
thumbPosition = (float) animation.getAnimatedValue();
invalidate();
});
thumbAnimator.start();
}
private void animateTrackColor(float targetFraction) {
if (colorAnimator != null) {
colorAnimator.cancel();
}
colorAnimator = ValueAnimator.ofFloat(trackColorFraction, targetFraction);
colorAnimator.setDuration(150);
colorAnimator.addUpdateListener(animation -> {
trackColorFraction = (float) animation.getAnimatedValue();
invalidate();
});
colorAnimator.start();
}
private int interpolateColor(int colorA, int colorB, float fraction) {
int alphaA = Color.alpha(colorA);
int redA = Color.red(colorA);
int greenA = Color.green(colorA);
int blueA = Color.blue(colorA);
int alphaB = Color.alpha(colorB);
int redB = Color.red(colorB);
int greenB = Color.green(colorB);
int blueB = Color.blue(colorB);
int alpha = (int) (alphaA + (alphaB - alphaA) * fraction);
int red = (int) (redA + (redB - redA) * fraction);
int green = (int) (greenA + (greenB - greenA) * fraction);
int blue = (int) (blueA + (blueB - blueA) * fraction);
return Color.argb(alpha, red, green, blue);
}
@Override
public void onThemeColorChanged(int newColor) {
if (isChecked) {
invalidate();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (thumbAnimator != null) {
thumbAnimator.cancel();
}
if (colorAnimator != null) {
colorAnimator.cancel();
}
ThemeManager.removeListener(this);
}
}
@@ -0,0 +1,186 @@
package com.phoenix.gui.ui.dynamic;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.phoenix.gui.ui.ThemeManager;
public class ProgressItemView extends AnimatedTaskItemView implements ThemeManager.OnThemeColorChangeListener {
private int colorPrimary;
private int colorOnPrimary;
private GradientDrawable iconBackground;
private static final int ICON_SIZE_DP = 40;
private static final int ICON_CORNER_RADIUS_DP = 14;
private static final int ICON_INNER_SIZE_DP = 24;
private ImageView iconView;
private TextView titleText;
private TextView subtitleText;
private MaterialProgressBar progressBar;
public ProgressItemView(@NonNull Context context, DynamicIslandManager.TaskItem task, float scale) {
super(context, task, scale);
}
@Override
protected void init(Context context) {
colorPrimary = ThemeManager.getThemeColor();
colorOnPrimary = ThemeManager.getTextOnTheme();
setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,
(int) (dpToPx(52 + 8 + 12) * scale)
));
LinearLayout mainContainer = new LinearLayout(context);
mainContainer.setOrientation(LinearLayout.VERTICAL);
mainContainer.setGravity(Gravity.CENTER_VERTICAL);
addView(mainContainer, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
LinearLayout topContainer = new LinearLayout(context);
topContainer.setOrientation(LinearLayout.HORIZONTAL);
topContainer.setGravity(Gravity.CENTER_VERTICAL);
topContainer.setPadding(
(int) (dpToPx(24) * scale), 0,
(int) (dpToPx(24) * scale), 0
);
mainContainer.addView(topContainer, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(int) (dpToPx(52) * scale)
));
iconView = new ImageView(context);
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
int iconSize = (int) (dpToPx(ICON_SIZE_DP) * scale);
iconBackground = new GradientDrawable();
iconBackground.setShape(GradientDrawable.RECTANGLE);
iconBackground.setCornerRadius(dpToPx(ICON_CORNER_RADIUS_DP) * scale);
iconBackground.setColor(adjustAlpha(colorPrimary, 0.5f));
iconView.setBackground(iconBackground);
int iconPadding = (int) ((dpToPx(ICON_SIZE_DP) - dpToPx(ICON_INNER_SIZE_DP)) / 2 * scale);
iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding);
iconView.setColorFilter(colorOnPrimary);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(iconSize, iconSize);
topContainer.addView(iconView, iconParams);
topContainer.addView(new android.view.View(context), new LinearLayout.LayoutParams(
(int) (dpToPx(12) * scale), 0
));
LinearLayout textContainer = new LinearLayout(context);
textContainer.setOrientation(LinearLayout.VERTICAL);
textContainer.setGravity(Gravity.CENTER_VERTICAL);
topContainer.addView(textContainer, new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f
));
titleText = new TextView(context);
titleText.setTextColor(com.phoenix.gui.ui.ThemeManager.getTextPrimary());
titleText.setTextSize(15 * scale);
titleText.setTypeface(null, Typeface.BOLD);
titleText.setSingleLine(true);
titleText.setIncludeFontPadding(false);
textContainer.addView(titleText);
subtitleText = new TextView(context);
subtitleText.setTextColor(com.phoenix.gui.ui.ThemeManager.getTextSecondary());
subtitleText.setTextSize(12 * scale);
subtitleText.setSingleLine(true);
subtitleText.setIncludeFontPadding(false);
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
subtitleParams.topMargin = (int) (dpToPx(2) * scale);
textContainer.addView(subtitleText, subtitleParams);
mainContainer.addView(new android.view.View(context), new LinearLayout.LayoutParams(
0, (int) (dpToPx(6) * scale)
));
progressBar = new MaterialProgressBar(context);
progressBar.setBarHeight(8 * scale);
LinearLayout.LayoutParams progressParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
(int) (dpToPx(8) * scale)
);
progressParams.setMargins(
(int) (dpToPx(24) * scale),
0,
(int) (dpToPx(24) * scale),
0
);
mainContainer.addView(progressBar, progressParams);
mainContainer.addView(new android.view.View(context), new LinearLayout.LayoutParams(
0, (int) (dpToPx(6) * scale)
));
updateContent();
}
@Override
protected void updateContent() {
titleText.setText(task.text);
if (task.subtitle != null && !task.subtitle.isEmpty()) {
subtitleText.setText(task.subtitle);
subtitleText.setVisibility(VISIBLE);
} else {
subtitleText.setVisibility(GONE);
}
if (task.icon != null) {
iconView.setImageDrawable(task.icon);
iconView.setVisibility(VISIBLE);
} else {
iconView.setVisibility(GONE);
}
progressBar.setProgress(task.displayProgress);
}
private int adjustAlpha(int color, float alpha) {
int alphaInt = Math.round(Color.alpha(color) * alpha);
return Color.argb(alphaInt, Color.red(color), Color.green(color), Color.blue(color));
}
@Override
public void onThemeColorChanged(int newColor) {
this.colorPrimary = newColor;
this.colorOnPrimary = ThemeManager.getTextOnTheme();
if (iconBackground != null) {
iconBackground.setColor(adjustAlpha(colorPrimary, 0.5f));
}
if (iconView != null) {
iconView.setColorFilter(colorOnPrimary);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
}
}
@@ -0,0 +1,77 @@
package com.phoenix.gui.ui.dynamic;
import android.animation.ValueAnimator;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
public class SheenDrawable extends Drawable {
private final Paint paint;
private float cornerRadius = 0;
private float sheenPosition = -1.0f;
private ValueAnimator animator;
public SheenDrawable() {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
startAnimation();
}
public void setCornerRadius(float radius) {
this.cornerRadius = radius;
invalidateSelf();
}
private void startAnimation() {
animator = ValueAnimator.ofFloat(-1.0f, 2.0f);
animator.setDuration(2500);
animator.setStartDelay(500);
animator.setInterpolator(new LinearInterpolator());
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.addUpdateListener(animation -> {
sheenPosition = (float) animation.getAnimatedValue();
invalidateSelf();
});
animator.start();
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
float gradientWidth = bounds.width() * 0.5f;
float gradientStart = bounds.width() * sheenPosition;
LinearGradient gradient = new LinearGradient(
gradientStart - gradientWidth, 0,
gradientStart, bounds.height(),
new int[]{0x00FFFFFF, 0x14FFFFFF, 0x00FFFFFF},
new float[]{0f, 0.5f, 1f},
Shader.TileMode.CLAMP
);
paint.setShader(gradient);
RectF rect = new RectF(bounds);
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
@@ -0,0 +1,187 @@
package com.phoenix.gui.ui.dynamic;
import android.content.Context;
import android.graphics.Typeface;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.phoenix.gui.ui.ThemeManager;
import java.util.Objects;
public class SwitchItemView extends TaskItemView implements ThemeManager.OnThemeColorChangeListener {
private int colorPrimary;
private MaterialSwitchView switchView;
private TextView titleText;
private LinearLayout subtitleContainer;
private String lastRenderedText = null;
private String lastRenderedSubtitle = null;
private boolean lastRenderedState = false;
private boolean isInitialized = false;
public SwitchItemView(@NonNull Context context, DynamicIslandManager.TaskItem task, float scale) {
super(context, task, scale);
}
@Override
protected void init(Context context) {
colorPrimary = ThemeManager.getThemeColor();
setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT,
(int) (dpToPx(52) * scale)
));
setPadding(
(int) (dpToPx(24) * scale), 0,
(int) (dpToPx(24) * scale), 0
);
LinearLayout container = new LinearLayout(context);
container.setOrientation(LinearLayout.HORIZONTAL);
container.setGravity(Gravity.CENTER_VERTICAL);
container.setClickable(false);
addView(container, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
switchView = new MaterialSwitchView(context);
switchView.setScaleX(scale * 1.1f);
switchView.setScaleY(scale * 1.1f);
switchView.setClickable(false);
LinearLayout.LayoutParams switchParams = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
container.addView(switchView, switchParams);
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
(int) (dpToPx(12) * scale), 0
);
container.addView(new android.view.View(context), spacerParams);
LinearLayout textContainer = new LinearLayout(context);
textContainer.setOrientation(LinearLayout.VERTICAL);
textContainer.setGravity(Gravity.CENTER_VERTICAL);
container.addView(textContainer, new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f
));
titleText = new TextView(context);
titleText.setTextColor(com.phoenix.gui.ui.ThemeManager.getTextPrimary());
titleText.setTextSize(15 * scale);
titleText.setTypeface(null, Typeface.BOLD);
titleText.setSingleLine(true);
titleText.setIncludeFontPadding(false);
textContainer.addView(titleText, new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
));
subtitleContainer = new LinearLayout(context);
subtitleContainer.setOrientation(LinearLayout.HORIZONTAL);
subtitleContainer.setGravity(Gravity.CENTER_VERTICAL);
LinearLayout.LayoutParams subtitleContainerParams = new LinearLayout.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
);
subtitleContainerParams.topMargin = (int) (dpToPx(2) * scale);
textContainer.addView(subtitleContainer, subtitleContainerParams);
updateContent();
}
@Override
protected void updateContent() {
if (!isInitialized) {
switchView.setChecked(task.switchState, false);
titleText.setText(task.text);
updateSubtitle();
lastRenderedText = task.text;
lastRenderedSubtitle = task.subtitle;
lastRenderedState = task.switchState;
isInitialized = true;
android.util.Log.d("SwitchItemView", String.format(
"First render: id=%s, state=%s", task.identifier, task.switchState
));
return;
}
if (lastRenderedState != task.switchState) {
switchView.setChecked(task.switchState, true);
lastRenderedState = task.switchState;
android.util.Log.d("SwitchItemView", String.format(
"State changed: id=%s, new=%s", task.identifier, task.switchState
));
}
if (!Objects.equals(lastRenderedText, task.text)) {
titleText.setText(task.text);
lastRenderedText = task.text;
}
if (!Objects.equals(lastRenderedSubtitle, task.subtitle)) {
updateSubtitle();
lastRenderedSubtitle = task.subtitle;
}
}
private void updateSubtitle() {
subtitleContainer.removeAllViews();
if (task.subtitle != null && task.subtitle.contains("|")) {
String[] parts = task.subtitle.split("\\|", 2);
TextView part1 = new TextView(getContext());
part1.setText(parts[0]);
part1.setTextColor(colorPrimary);
part1.setTextSize(12 * scale);
part1.setTypeface(null, Typeface.BOLD);
part1.setSingleLine(true);
part1.setIncludeFontPadding(false);
subtitleContainer.addView(part1);
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams((int) (dpToPx(4) * scale), 0);
subtitleContainer.addView(new android.view.View(getContext()), spacerParams);
TextView part2 = new TextView(getContext());
part2.setText(parts[1]);
part2.setTextColor(com.phoenix.gui.ui.ThemeManager.getTextSecondary());
part2.setTextSize(12 * scale);
part2.setSingleLine(true);
part2.setIncludeFontPadding(false);
subtitleContainer.addView(part2);
} else if (task.subtitle != null) {
TextView subtitle = new TextView(getContext());
subtitle.setText(task.subtitle);
subtitle.setTextColor(com.phoenix.gui.ui.ThemeManager.getTextSecondary());
subtitle.setTextSize(12 * scale);
subtitle.setSingleLine(true);
subtitle.setIncludeFontPadding(false);
subtitleContainer.addView(subtitle);
}
}
@Override
public void onThemeColorChanged(int newColor) {
this.colorPrimary = newColor;
updateSubtitle();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
}
}
@@ -0,0 +1,26 @@
package com.phoenix.gui.ui.dynamic;
import android.content.Context;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
public abstract class TaskItemView extends FrameLayout {
public DynamicIslandManager.TaskItem task;
protected float scale;
public TaskItemView(@NonNull Context context, DynamicIslandManager.TaskItem task, float scale) {
super(context);
this.task = task;
this.scale = scale;
init(context);
}
protected abstract void init(Context context);
protected abstract void updateContent();
protected float dpToPx(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
}
@@ -0,0 +1,139 @@
package com.phoenix.gui.ui.widgets;
// powered by qp
import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.GradientDrawable;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import com.phoenix.gui.ui.ThemeManager;
public class ColorPickerView extends LinearLayout {
private final ColorPaletteView paletteView;
private final SeekBar hueSlider;
private float currentHue;
private float currentSat;
private float currentVal;
public ColorPickerView(Context context) {
super(context);
setOrientation(VERTICAL);
setPadding(0, dpToPx(5), 0, 0);
float[] initialHsv = ThemeManager.getThemeColorHSV();
currentHue = initialHsv[0];
currentSat = initialHsv[1];
currentVal = initialHsv[2];
paletteView = new ColorPaletteView(context);
addView(paletteView, new LayoutParams(LayoutParams.MATCH_PARENT, dpToPx(100)));
hueSlider = new SeekBar(context);
hueSlider.setMax(360);
hueSlider.setProgress((int) currentHue);
int[] hueColors = new int[361];
for (int i = 0; i < hueColors.length; i++) {
hueColors[i] = Color.HSVToColor(new float[]{i, 1f, 1f});
}
GradientDrawable hueGradient = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, hueColors);
hueSlider.setProgressDrawable(hueGradient);
LayoutParams sliderParams = new LayoutParams(LayoutParams.MATCH_PARENT, dpToPx(10));
sliderParams.setMargins(0, dpToPx(12), 0, 0);
hueSlider.setLayoutParams(sliderParams);
hueSlider.getThumb().setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN));
hueSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
currentHue = progress;
paletteView.setHue(currentHue);
updateThemeColor();
}
}
@Override public void onStartTrackingTouch(SeekBar seekBar) {}
@Override public void onStopTrackingTouch(SeekBar seekBar) {}
});
addView(hueSlider);
}
private void updateThemeColor() {
float[] hsv = {currentHue, currentSat, currentVal};
int color = Color.HSVToColor(hsv);
ThemeManager.setThemeColor(color);
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
private class ColorPaletteView extends View {
private final Paint saturationPaint, valuePaint;
private final Paint indicatorPaint;
private Shader valueShader, saturationShader;
private float hue;
private float indicatorX, indicatorY;
public ColorPaletteView(Context context) {
super(context);
saturationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
valuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
indicatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
indicatorPaint.setStyle(Paint.Style.STROKE);
indicatorPaint.setStrokeWidth(dpToPx(2));
indicatorPaint.setColor(Color.WHITE);
indicatorPaint.setShadowLayer(dpToPx(2), 0, 0, Color.BLACK);
setHue(currentHue);
}
public void setHue(float hue) {
this.hue = hue;
if (getWidth() > 0) {
int hueColor = Color.HSVToColor(new float[]{hue, 1f, 1f});
saturationShader = new LinearGradient(0, 0, getWidth(), 0, Color.WHITE, hueColor, Shader.TileMode.CLAMP);
saturationPaint.setShader(saturationShader);
}
invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
valueShader = new LinearGradient(0, 0, 0, h, Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
valuePaint.setShader(valueShader);
setHue(hue);
indicatorX = w * currentSat;
indicatorY = h * (1 - currentVal);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRect(0, 0, getWidth(), getHeight(), saturationPaint);
canvas.drawRect(0, 0, getWidth(), getHeight(), valuePaint);
canvas.drawCircle(indicatorX, indicatorY, dpToPx(6), indicatorPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
indicatorX = Math.max(0, Math.min(getWidth(), event.getX()));
indicatorY = Math.max(0, Math.min(getHeight(), event.getY()));
currentSat = indicatorX / getWidth();
currentVal = 1 - (indicatorY / getHeight());
updateThemeColor();
invalidate();
return true;
}
return super.onTouchEvent(event);
}
}
}
@@ -0,0 +1,130 @@
package com.phoenix.gui.ui.widgets;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.ui.ThemeManager;
public class CustomSlider extends View implements ThemeManager.OnThemeColorChangeListener {
private final String moduleName;
private final String configKey;
private final int min;
private final int max;
private final OnValueChangedListener onValueChanged;
private float progress = 0f;
private float scaleFactor = 1.0f;
private final Paint trackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint thumbFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint thumbStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
public interface OnValueChangedListener {
void onValueChanged(int value);
}
public CustomSlider(Context context, String moduleName, String configKey,
int min, int max, int initialValue, OnValueChangedListener listener) {
super(context);
this.moduleName = moduleName;
this.configKey = configKey;
this.min = min;
this.max = max;
this.onValueChanged = listener;
updatePaints();
thumbFillPaint.setColor(ThemeManager.getTextPrimary());
setValue(initialValue, false);
ThemeManager.addListener(this);
}
public void updateScale(float scaleFactor) {
this.scaleFactor = scaleFactor;
updatePaints();
invalidate();
}
private void updatePaints() {
trackPaint.setStrokeCap(Paint.Cap.ROUND);
trackPaint.setStrokeWidth(scaledDp(3f));
thumbStrokePaint.setStyle(Paint.Style.STROKE);
thumbStrokePaint.setStrokeWidth(scaledDp(2f));
}
private float scaledDp(float baseDp) {
return dpToPx(baseDp) * scaleFactor;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int themeColor = ThemeManager.getThemeColor();
thumbStrokePaint.setColor(themeColor);
float trackY = getHeight() / 2f;
float thumbRadius = getHeight() / 2f - scaledDp(3f);
float thumbX = thumbRadius + (getWidth() - 2 * thumbRadius) * progress;
trackPaint.setColor(ThemeManager.getStateDisabled());
canvas.drawLine(thumbX, trackY, getWidth() - thumbRadius, trackY, trackPaint);
trackPaint.setColor(themeColor);
canvas.drawLine(thumbRadius, trackY, thumbX, trackY, trackPaint);
canvas.drawCircle(thumbX, trackY, thumbRadius, thumbFillPaint);
canvas.drawCircle(thumbX, trackY, thumbRadius, thumbStrokePaint);
}
public void setValue(int value, boolean notify) {
progress = (float)(value - min) / (max - min);
if (notify) {
onValueChanged.onValueChanged(value);
ConfigManager.saveModuleConfig(moduleName, configKey, value);
}
invalidate();
}
public int getValue() {
return min + (int)(progress * (max - min));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
float newProgress = event.getX() / getWidth();
progress = Math.max(0f, Math.min(1f, newProgress));
int currentValue = getValue();
onValueChanged.onValueChanged(currentValue);
ConfigManager.saveModuleConfig(moduleName, configKey, currentValue);
invalidate();
return true;
}
return super.onTouchEvent(event);
}
@Override
public void onThemeColorChanged(int newColor) {
invalidate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
}
private float dpToPx(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
}
@@ -0,0 +1,123 @@
package com.phoenix.gui.ui.widgets;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.ui.ThemeManager;
public class CustomSwitch extends View implements ThemeManager.OnThemeColorChangeListener {
private final String moduleName;
private final String configKey;
private final OnStateChangedListener onStateChanged;
private boolean isChecked = false;
private float thumbX = 0f;
private float scaleFactor = 1.0f;
private final Paint trackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Paint thumbPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final RectF trackRect = new RectF();
private ValueAnimator thumbAnimator;
public interface OnStateChangedListener {
void onStateChanged(boolean enabled);
}
public CustomSwitch(Context context, String moduleName, String configKey,
boolean initialState, OnStateChangedListener listener) {
super(context);
this.moduleName = moduleName;
this.configKey = configKey;
this.onStateChanged = listener;
thumbPaint.setColor(ThemeManager.getTextPrimary());
setChecked(initialState, false);
setOnClickListener(v -> setChecked(!isChecked, true));
ThemeManager.addListener(this);
}
public void updateScale(float scaleFactor) {
this.scaleFactor = scaleFactor;
setChecked(isChecked, false);
invalidate();
}
private float scaledDp(float baseDp) {
return dpToPx(baseDp) * scaleFactor;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int themeColor = ThemeManager.getThemeColor();
trackRect.set(0f, 0f, getWidth(), getHeight());
trackPaint.setColor(isChecked ? themeColor : ThemeManager.getStateDisabled());
canvas.drawRoundRect(trackRect, getHeight() / 2f, getHeight() / 2f, trackPaint);
float thumbRadius = getHeight() / 2f - scaledDp(2f);
canvas.drawCircle(thumbX, getHeight() / 2f, thumbRadius, thumbPaint);
}
public void setChecked(boolean checked, boolean animate) {
if (isChecked == checked && thumbAnimator != null && thumbAnimator.isRunning()) return;
isChecked = checked;
float thumbRadius = getHeight() / 2f - scaledDp(2f);
float targetX = isChecked ? getWidth() - thumbRadius - scaledDp(2f) : thumbRadius + scaledDp(2f);
if (animate) {
if (thumbAnimator != null) thumbAnimator.cancel();
thumbAnimator = ValueAnimator.ofFloat(thumbX, targetX);
thumbAnimator.setDuration(200);
thumbAnimator.setInterpolator(new DecelerateInterpolator());
thumbAnimator.addUpdateListener(animation -> {
thumbX = (float) animation.getAnimatedValue();
invalidate();
});
thumbAnimator.start();
} else {
thumbX = targetX;
invalidate();
}
onStateChanged.onStateChanged(isChecked);
ConfigManager.saveModuleConfig(moduleName, configKey, isChecked);
}
public boolean isChecked() {
return isChecked;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setChecked(isChecked, false);
}
@Override
public void onThemeColorChanged(int newColor) {
invalidate();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
if (thumbAnimator != null) thumbAnimator.cancel();
}
private float dpToPx(float dp) {
return dp * getResources().getDisplayMetrics().density;
}
}
@@ -0,0 +1,226 @@
package com.phoenix.gui.ui.widgets;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Typeface;
import android.graphics.drawable.GradientDrawable;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.phoenix.gui.config.ConfigManager;
import com.phoenix.gui.ui.ThemeManager;
import java.util.ArrayList;
import java.util.List;
public class ModeChips extends LinearLayout implements ThemeManager.OnThemeColorChangeListener {
private final String moduleName;
private final String configKey;
private final List<String> modes;
private final OnModeChangedListener onModeChanged;
private int selectedIndex;
private final List<TextView> chips = new ArrayList<>();
private final List<GradientDrawable> chipBackgrounds = new ArrayList<>();
private float scaleFactor = 1.0f;
public interface OnModeChangedListener {
void onModeChanged(int index, String mode);
}
public ModeChips(Context context, String moduleName, String configKey,
List<String> modes, int initialMode, OnModeChangedListener listener) {
super(context);
this.moduleName = moduleName;
this.configKey = configKey;
this.modes = modes;
this.onModeChanged = listener;
this.selectedIndex = initialMode;
setOrientation(HORIZONTAL);
for (int i = 0; i < modes.size(); i++) {
TextView chip = createChip(modes.get(i), i);
chips.add(chip);
addView(chip);
if (i < modes.size() - 1) {
addView(createSpacer());
}
}
updateSelection(initialMode, false);
ThemeManager.addListener(this);
}
public void updateScale(float scaleFactor) {
this.scaleFactor = scaleFactor;
for (TextView chip : chips) {
chip.setTextSize(11f * scaleFactor);
int paddingH = scaled(12);
int paddingV = scaled(6);
chip.setPadding(paddingH, paddingV, paddingH, paddingV);
}
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i) instanceof LinearLayout) {
LinearLayout spacer = (LinearLayout) getChildAt(i);
if (spacer.getChildCount() == 0) {
spacer.setLayoutParams(new LayoutParams(scaled(4), LayoutParams.MATCH_PARENT));
}
}
}
updateSelection(selectedIndex, false);
requestLayout();
}
private int scaled(int baseDp) {
return (int) (dpToPx(baseDp) * scaleFactor);
}
private TextView createChip(String text, final int index) {
TextView chip = new TextView(getContext());
chip.setText(text);
chip.setTextSize(11f * scaleFactor);
chip.setGravity(Gravity.CENTER);
chip.setPadding(scaled(12), scaled(6), scaled(12), scaled(6));
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, 0);
chip.setLayoutParams(params);
GradientDrawable bg = new GradientDrawable();
bg.setCornerRadius(scaled(8));
chipBackgrounds.add(bg);
chip.setBackground(bg);
chip.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
v.animate()
.scaleX(0.9f)
.scaleY(0.9f)
.setDuration(100)
.start();
return true;
case MotionEvent.ACTION_UP:
v.animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(100)
.start();
if (event.getX() >= 0 && event.getX() <= v.getWidth() &&
event.getY() >= 0 && event.getY() <= v.getHeight()) {
if (selectedIndex != index) {
updateSelection(index, true);
}
}
return true;
case MotionEvent.ACTION_CANCEL:
v.animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(100)
.start();
return true;
}
return false;
}
});
return chip;
}
private LinearLayout createSpacer() {
LinearLayout spacer = new LinearLayout(getContext());
spacer.setLayoutParams(new LayoutParams(scaled(4), LayoutParams.MATCH_PARENT));
return spacer;
}
private void updateSelection(int newIndex, boolean animate) {
int oldIndex = selectedIndex;
selectedIndex = newIndex;
for (int i = 0; i < chips.size(); i++) {
applyChipStyle(i, i == newIndex);
}
if (animate && newIndex >= 0 && newIndex < chips.size()) {
TextView newChip = chips.get(newIndex);
newChip.setScaleX(0.9f);
newChip.setScaleY(0.9f);
newChip.animate()
.scaleX(1f)
.scaleY(1f)
.setDuration(200)
.setInterpolator(new OvershootInterpolator(2f))
.start();
onModeChanged.onModeChanged(newIndex, modes.get(newIndex));
ConfigManager.saveModuleConfig(moduleName, configKey, newIndex);
}
}
private void applyChipStyle(int index, boolean selected) {
if (index < 0 || index >= chips.size()) return;
TextView chip = chips.get(index);
GradientDrawable bg = chipBackgrounds.get(index);
if (selected) {
bg.setColor(ThemeManager.getThemeColor());
bg.setCornerRadius(scaled(8));
chip.setTextColor(ThemeManager.getTextOnTheme());
chip.setTypeface(Typeface.DEFAULT_BOLD);
} else {
bg.setColor(ThemeManager.getBgDisabled());
bg.setCornerRadius(scaled(8));
chip.setTextColor(ThemeManager.getTextSecondary());
chip.setTypeface(Typeface.DEFAULT);
}
}
public String getSelectedMode() {
return modes.get(selectedIndex);
}
public int getSelectedIndex() {
return selectedIndex;
}
@Override
public void onThemeColorChanged(int newColor) {
updateSelection(selectedIndex, false);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
for (TextView chip : chips) {
chip.animate().cancel();
}
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
}
@@ -0,0 +1,498 @@
package com.phoenix.gui.ui.widgets;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.phoenix.gui.module.Module;
import com.phoenix.gui.ui.ThemeManager;
import java.util.ArrayList;
import java.util.List;
public class SubMenuPanel extends LinearLayout implements ThemeManager.OnThemeColorChangeListener {
private final Module module;
private boolean isOpen = false;
private ValueAnimator heightAnimator;
private Runnable onStateChangedListener;
private float scaleFactor = 1.0f;
private final List<ScalableWidget> scalableWidgets = new ArrayList<>();
private final List<TextView> themeButtons = new ArrayList<>();
private final int[] themeColors = {
0xFF96CCFF, // Default
0xFF4CAF50, // Green
0xFFBB86FC, // Purple
0xFFFF9800, // Orange
0xFFE91E63 // Pink
};
public SubMenuPanel(Context context, Module module, float scaleFactor) {
super(context);
this.module = module;
this.scaleFactor = scaleFactor;
setOrientation(VERTICAL);
setBackgroundColor(Color.TRANSPARENT);
updatePadding();
setVisibility(View.GONE);
setAlpha(0f);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
params.setMargins(scaled(6), scaled(2), scaled(6), 0);
setLayoutParams(params);
}
public SubMenuPanel(Context context, Module module) {
this(context, module, 1.0f);
}
public void updateScale(float scaleFactor) {
this.scaleFactor = scaleFactor;
updatePadding();
LayoutParams params = (LayoutParams) getLayoutParams();
params.setMargins(scaled(6), scaled(2), scaled(6), 0);
setLayoutParams(params);
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
updateChildScale(child);
}
for (ScalableWidget widget : scalableWidgets) {
widget.updateScale(scaleFactor);
}
requestLayout();
}
private void updatePadding() {
int padding = scaled(4);
setPadding(padding, 0, padding, scaled(4));
}
private void updateChildScale(View view) {
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
ViewGroup.LayoutParams lp = group.getLayoutParams();
if (lp instanceof MarginLayoutParams) {
MarginLayoutParams mlp = (MarginLayoutParams) lp;
mlp.topMargin = scaled(2);
mlp.bottomMargin = scaled(2);
}
for (int i = 0; i < group.getChildCount(); i++) {
updateChildScale(group.getChildAt(i));
}
} else if (view instanceof TextView) {
TextView tv = (TextView) view;
float baseSize = 11f;
if (tv.getCurrentTextColor() == ThemeManager.getTextTertiary()) {
baseSize = 10f;
}
tv.setTextSize(baseSize * scaleFactor);
}
}
private int scaled(int baseDp) {
return (int) (dpToPx(baseDp) * scaleFactor);
}
public void addSlider(String label, int min, int max, int defaultValue, String format,
CustomSlider.OnValueChangedListener onValueChanged) {
LinearLayout container = new LinearLayout(getContext());
container.setOrientation(VERTICAL);
LayoutParams containerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
containerParams.setMargins(0, scaled(2), 0, scaled(2));
container.setLayoutParams(containerParams);
LinearLayout labelRow = new LinearLayout(getContext());
labelRow.setOrientation(HORIZONTAL);
TextView labelText = new TextView(getContext());
labelText.setText(label);
labelText.setTextSize(11f * scaleFactor);
labelText.setTextColor(ThemeManager.getTextSecondary());
LayoutParams labelParams = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f);
labelText.setLayoutParams(labelParams);
labelRow.addView(labelText);
TextView valueText = new TextView(getContext());
valueText.setTextSize(11f * scaleFactor);
valueText.setTextColor(ThemeManager.getTextSecondary());
labelRow.addView(valueText);
container.addView(labelRow);
CustomSlider slider = new CustomSlider(getContext(), module.getName(), label.toLowerCase(),
min, max, defaultValue, value -> {
if ("%.2f".equals(format)) {
valueText.setText(String.format("%.2f", value / 100.0));
} else {
valueText.setText(String.valueOf(value));
}
onValueChanged.onValueChanged(value);
});
slider.updateScale(scaleFactor);
LayoutParams sliderParams = new LayoutParams(LayoutParams.MATCH_PARENT, scaled(20));
sliderParams.setMargins(0, scaled(2), 0, 0);
container.addView(slider, sliderParams);
scalableWidgets.add(new ScalableWidget(slider, labelText, valueText, sliderParams, 20));
if ("%.2f".equals(format)) {
valueText.setText(String.format("%.2f", defaultValue / 100.0));
} else {
valueText.setText(String.valueOf(defaultValue));
}
addView(container);
}
public void addShortcutSwitch(boolean defaultValue) {
LinearLayout container = new LinearLayout(getContext());
container.setOrientation(HORIZONTAL);
container.setGravity(Gravity.CENTER_VERTICAL);
LayoutParams containerParams = new LayoutParams(LayoutParams.MATCH_PARENT, scaled(28));
containerParams.setMargins(0, scaled(2), 0, scaled(2));
container.setLayoutParams(containerParams);
TextView labelText = new TextView(getContext());
labelText.setText("Shortcut");
labelText.setTextSize(11f * scaleFactor);
labelText.setTextColor(ThemeManager.getTextSecondary());
LayoutParams labelParams = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f);
labelText.setLayoutParams(labelParams);
container.addView(labelText);
CustomSwitch switchView = new CustomSwitch(getContext(), module.getName(), "shortcut",
defaultValue, enabled -> {
module.toggleShortcut(enabled);
});
switchView.updateScale(scaleFactor);
LayoutParams switchParams = new LayoutParams(scaled(32), scaled(18));
container.addView(switchView, switchParams);
scalableWidgets.add(new ScalableWidget(switchView, labelText, containerParams, switchParams, 28, 32, 18));
addView(container);
}
public void addSwitch(String label, boolean defaultValue, CustomSwitch.OnStateChangedListener onStateChanged) {
LinearLayout container = new LinearLayout(getContext());
container.setOrientation(HORIZONTAL);
container.setGravity(Gravity.CENTER_VERTICAL);
LayoutParams containerParams = new LayoutParams(LayoutParams.MATCH_PARENT, scaled(28));
containerParams.setMargins(0, scaled(2), 0, scaled(2));
container.setLayoutParams(containerParams);
TextView labelText = new TextView(getContext());
labelText.setText(label);
labelText.setTextSize(11f * scaleFactor);
labelText.setTextColor(ThemeManager.getTextSecondary());
LayoutParams labelParams = new LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f);
labelText.setLayoutParams(labelParams);
container.addView(labelText);
CustomSwitch switchView = new CustomSwitch(getContext(), module.getName(), label.toLowerCase(),
defaultValue, onStateChanged);
switchView.updateScale(scaleFactor);
LayoutParams switchParams = new LayoutParams(scaled(32), scaled(18));
container.addView(switchView, switchParams);
scalableWidgets.add(new ScalableWidget(switchView, labelText, containerParams, switchParams, 28, 32, 18));
addView(container);
}
public void addMode(String label, List<String> modes, int defaultMode, ModeChips.OnModeChangedListener onModeChanged) {
LinearLayout container = new LinearLayout(getContext());
container.setOrientation(VERTICAL);
LayoutParams containerParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
containerParams.setMargins(0, scaled(2), 0, scaled(2));
container.setLayoutParams(containerParams);
TextView labelText = new TextView(getContext());
labelText.setText(label);
labelText.setTextSize(11f * scaleFactor);
labelText.setTextColor(ThemeManager.getTextSecondary());
LayoutParams labelParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
labelParams.setMargins(0, 0, 0, scaled(2));
labelText.setLayoutParams(labelParams);
container.addView(labelText);
HorizontalScrollView scrollView = new HorizontalScrollView(getContext());
scrollView.setHorizontalScrollBarEnabled(false);
scrollView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
scrollView.setOverScrollMode(View.OVER_SCROLL_NEVER);
ModeChips chips = new ModeChips(getContext(), module.getName(), label.toLowerCase(),
modes, defaultMode, onModeChanged);
chips.updateScale(scaleFactor);
scrollView.addView(chips);
container.addView(scrollView);
scalableWidgets.add(new ScalableWidget(chips, labelText, null, null, 0));
addView(container);
}
public void addText(String text) {
TextView textView = new TextView(getContext());
textView.setText(text);
textView.setTextSize(10f * scaleFactor);
textView.setTextColor(ThemeManager.getTextTertiary());
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.setMargins(0, scaled(2), 0, scaled(2));
textView.setLayoutParams(params);
addView(textView);
}
public void addCustomView(View view) {
if (view.getLayoutParams() == null) {
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.setMargins(0, scaled(4), 0, scaled(4));
view.setLayoutParams(params);
}
addView(view);
}
public void addThemeButtons() {
themeButtons.clear();
LinearLayout container = new LinearLayout(getContext());
container.setOrientation(HORIZONTAL);
String[] themeNames = {"Default", "Green", "Purple", "Orange", "Pink"};
Runnable[] themeActions = {
ThemeManager::applyDefaultTheme,
ThemeManager::applyGreenTheme,
ThemeManager::applyPurpleTheme,
ThemeManager::applyOrangeTheme,
ThemeManager::applyPinkTheme
};
for(int i = 0; i < themeNames.length; i++) {
TextView button = new TextView(getContext());
button.setText(themeNames[i]);
button.setTextSize(10f * scaleFactor);
button.setGravity(Gravity.CENTER);
int paddingH = scaled(8);
int paddingV = scaled(5);
button.setPadding(paddingH, paddingV, paddingH, paddingV);
button.setBackground(new GradientDrawable());
final Runnable action = themeActions[i];
button.setOnClickListener(v -> action.run());
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
if (i > 0) {
params.leftMargin = scaled(4);
}
container.addView(button, params);
themeButtons.add(button);
}
HorizontalScrollView scrollView = new HorizontalScrollView(getContext());
scrollView.setHorizontalScrollBarEnabled(false);
scrollView.addView(container);
addCustomView(scrollView);
updateThemeButtonStyles();
}
private void updateThemeButtonStyles() {
int currentThemeColor = ThemeManager.getThemeColor();
for (int i = 0; i < themeButtons.size(); i++) {
TextView button = themeButtons.get(i);
GradientDrawable bg = (GradientDrawable) button.getBackground();
boolean isSelected = currentThemeColor == themeColors[i];
if (isSelected) {
bg.setColor(ThemeManager.getThemeColor());
button.setTextColor(ThemeManager.getTextOnTheme());
} else {
bg.setColor(ThemeManager.getBgDisabled());
button.setTextColor(ThemeManager.getTextPrimary());
}
bg.setCornerRadius(scaled(6));
}
}
public void toggle() {
toggle(null);
}
public void toggle(Runnable onComplete) {
if (isOpen) {
collapse(onComplete);
} else {
expand(onComplete);
}
}
private void expand(Runnable onComplete) {
isOpen = true;
setVisibility(View.VISIBLE);
measure(
MeasureSpec.makeMeasureSpec(((ViewGroup)getParent()).getWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
);
int targetHeight = getMeasuredHeight();
if (heightAnimator != null) heightAnimator.cancel();
getLayoutParams().height = 0;
heightAnimator = ValueAnimator.ofInt(0, targetHeight);
heightAnimator.setDuration(250);
heightAnimator.addUpdateListener(animation -> {
ViewGroup.LayoutParams params = getLayoutParams();
params.height = (int) animation.getAnimatedValue();
setLayoutParams(params);
});
heightAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
ViewGroup.LayoutParams params = getLayoutParams();
params.height = LayoutParams.WRAP_CONTENT;
setLayoutParams(params);
if (onComplete != null) onComplete.run();
if (onStateChangedListener != null) onStateChangedListener.run();
}
});
heightAnimator.start();
setAlpha(0f);
animate().alpha(1f).setDuration(250).start();
}
public void collapse(Runnable onComplete) {
if (!isOpen) {
if (onComplete != null) onComplete.run();
return;
}
isOpen = false;
int currentHeight = getHeight();
getLayoutParams().height = currentHeight;
if (heightAnimator != null) heightAnimator.cancel();
animate().alpha(0f).setDuration(250).start();
heightAnimator = ValueAnimator.ofInt(currentHeight, 0);
heightAnimator.setDuration(250);
heightAnimator.addUpdateListener(animation -> {
ViewGroup.LayoutParams params = getLayoutParams();
params.height = (int) animation.getAnimatedValue();
setLayoutParams(params);
});
heightAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setVisibility(View.GONE);
if (onComplete != null) onComplete.run();
if (onStateChangedListener != null) onStateChangedListener.run();
}
});
heightAnimator.start();
}
public void forceCollapse() {
if (heightAnimator != null) heightAnimator.cancel();
isOpen = false;
setVisibility(View.GONE);
setAlpha(0f);
getLayoutParams().height = 0;
}
public boolean isOpen() {
return isOpen;
}
public void setOnStateChangedListener(Runnable listener) {
onStateChangedListener = listener;
}
private int dpToPx(int dp) {
return (int) (dp * getResources().getDisplayMetrics().density);
}
// 3. 实现 onThemeColorChanged 方法
@Override
public void onThemeColorChanged(int newColor) {
updateThemeButtonStyles();
}
// 4. 注册和注销监听器
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ThemeManager.addListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
ThemeManager.removeListener(this);
}
private class ScalableWidget {
View widget;
TextView label;
TextView valueText;
LayoutParams sliderParams;
LayoutParams containerParams;
LayoutParams switchParams;
int baseSliderHeight;
int baseContainerHeight;
int baseSwitchWidth;
int baseSwitchHeight;
ScalableWidget(View widget, TextView label, TextView valueText, LayoutParams sliderParams, int baseSliderHeight) {
this.widget = widget;
this.label = label;
this.valueText = valueText;
this.sliderParams = sliderParams;
this.baseSliderHeight = baseSliderHeight;
}
ScalableWidget(View widget, TextView label, LayoutParams containerParams, LayoutParams switchParams,
int baseContainerHeight, int baseSwitchWidth, int baseSwitchHeight) {
this.widget = widget;
this.label = label;
this.containerParams = containerParams;
this.switchParams = switchParams;
this.baseContainerHeight = baseContainerHeight;
this.baseSwitchWidth = baseSwitchWidth;
this.baseSwitchHeight = baseSwitchHeight;
}
void updateScale(float scale) {
if (label != null) {
label.setTextSize(11f * scale);
}
if (valueText != null) {
valueText.setTextSize(11f * scale);
}
if (sliderParams != null) {
sliderParams.height = scaled(baseSliderHeight);
sliderParams.setMargins(0, scaled(2), 0, 0);
}
if (containerParams != null) {
containerParams.height = scaled(baseContainerHeight);
containerParams.setMargins(0, scaled(2), 0, scaled(2));
}
if (switchParams != null) {
switchParams.width = scaled(baseSwitchWidth);
switchParams.height = scaled(baseSwitchHeight);
}
if (widget instanceof CustomSlider) {
((CustomSlider) widget).updateScale(scale);
} else if (widget instanceof CustomSwitch) {
((CustomSwitch) widget).updateScale(scale);
} else if (widget instanceof ModeChips) {
((ModeChips) widget).updateScale(scale);
}
}
}
}
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,170 @@
<?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">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>
+206
View File
@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 容器 -->
<FrameLayout
android:id="@+id/gui_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F5F5"
android:visibility="gone">
<FrameLayout
android:id="@+id/dynamic_island_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:layout_marginTop="24dp">
</FrameLayout>
<FrameLayout
android:id="@+id/float_ball_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<FrameLayout
android:id="@+id/menus_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
</FrameLayout>
<FrameLayout
android:id="@+id/shortcuts_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
<FrameLayout
android:id="@+id/arraylist_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</FrameLayout>
<ScrollView
android:id="@+id/debug_panel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="8dp"
android:background="#E0FFFFFF"
android:elevation="8dp"
android:padding="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="GUI 控制"
android:textSize="12sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_start"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="显示 GUI"
android:textSize="11sp" />
<Button
android:id="@+id/btn_stop"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="隐藏 GUI"
android:textSize="11sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="灵动岛测试"
android:textSize="12sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_test_progress"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="测试进度"
android:textSize="11sp" />
<Button
android:id="@+id/btn_test_switch"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="测试开关"
android:textSize="11sp" />
<Button
android:id="@+id/btn_hide_all"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="隐藏全部"
android:textSize="11sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Arraylist 测试"
android:textSize="12sp"
android:textStyle="bold" />
<Button
android:id="@+id/btn_toggle_arraylist"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="切换 Arraylist"
android:textSize="11sp" />
<Button
android:id="@+id/btn_add_random_module"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="添加随机"
android:textSize="11sp" />
<Button
android:id="@+id/btn_remove_random_module"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="移除随机"
android:textSize="11sp" />
<Button
android:id="@+id/btn_reset_arraylist"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="重置"
android:textSize="11sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="设置"
android:textSize="12sp"
android:textStyle="bold" />
<EditText
android:id="@+id/et_username"
android:layout_width="150dp"
android:layout_height="40dp"
android:hint="用户名"
android:text="User"
android:textSize="11sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="缩放"
android:textSize="10sp" />
<!-- 缩放我推荐是不要用,难搞 -->
<SeekBar
android:id="@+id/seek_scale"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:max="150"
android:progress="70" />
<Button
android:id="@+id/btn_save"
android:layout_width="150dp"
android:layout_height="40dp"
android:text="保存设置"
android:textSize="11sp" />
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/btn_toggle_debug"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="top"
android:layout_margin="8dp"
android:elevation="8dp"
android:text="M"
android:textSize="6sp" />
<!-- 爱慕超人 -->
</FrameLayout>
@@ -0,0 +1,6 @@
<?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" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,6 @@
<?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" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<resources></resources>
+9
View File
@@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="AppTheme" parent="Base.AppTheme" />
</resources>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Phoen1xGUI</string>
</resources>
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme using AppCompat -->
<style name="Theme.Phoen1xGUI" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Primary brand color -->
<item name="colorPrimary">#96CCFF</item>
<item name="colorPrimaryDark">#1a1a2e</item>
<item name="colorAccent">#4CAF50</item>
<!-- Status bar color -->
<item name="android:statusBarColor">@android:color/transparent</item>
<!-- Window flags for edge-to-edge -->
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowTranslucentStatus">false</item>
<item name="android:windowTranslucentNavigation">false</item>
</style>
</resources>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
+5
View File
@@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
}
+24
View File
@@ -0,0 +1,24 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.aapt2FromMavenOverride=/home/ph/app/aapt2
+26
View File
@@ -0,0 +1,26 @@
[versions]
agp = "8.13.0"
kotlin = "2.0.21"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
appcompat = "1.6.1"
activity = "1.8.0"
material = "1.12.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Fri Oct 03 19:51:02 CST 2025
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# 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
;;
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
# IBM's JDK on AIX uses strange locations for the executables
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"
which java >/dev/null 2>&1 || 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
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
Vendored
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+7
View File
@@ -0,0 +1,7 @@
使用 gradle 8.13 构建
aide 是构建不了的
androidide 需要自己迁移 src
售出不退
含少量屎山
灵动岛名称为 Phoen1xGUI 的即为 v2.0 版本,该版本优化了性能。

Some files were not shown because too many files have changed in this diff Show More