Merge remote-tracking branch 'upstream/main'
1
.github/workflows/build.yml
vendored
@ -60,6 +60,7 @@ jobs:
|
|||||||
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
|
||||||
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||||
buildToolsVersion: 33.0.0
|
buildToolsVersion: 33.0.0
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Rename Signed APK
|
- name: Rename Signed APK
|
||||||
run: |
|
run: |
|
||||||
|
4
.gitignore
vendored
@ -6,3 +6,7 @@
|
|||||||
/app/debug
|
/app/debug
|
||||||
/app/release
|
/app/release
|
||||||
/local.properties
|
/local.properties
|
||||||
|
/.vs
|
||||||
|
/.kotlin
|
||||||
|
/app/debug
|
||||||
|
/app/release
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application'
|
alias(libs.plugins.androidApplication)
|
||||||
id 'org.jetbrains.kotlin.android'
|
alias(libs.plugins.kotlinAndroid)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
|
alias(libs.plugins.serialization)
|
||||||
}
|
}
|
||||||
android.buildFeatures.buildConfig true
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace 'io.github.chinosk.gakumas.localify'
|
namespace 'io.github.chinosk.gakumas.localify'
|
||||||
@ -14,7 +15,7 @@ android {
|
|||||||
minSdk 29
|
minSdk 29
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 2
|
versionCode 2
|
||||||
versionName "Dev"
|
versionName "v1.2"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@ -35,38 +36,38 @@ android {
|
|||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
buildConfigField "boolean", "ENABLE_LOG", "true"
|
buildConfigField "boolean", "ENABLE_LOG", "true"
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '1.8'
|
jvmTarget = '11'
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
buildConfig true
|
||||||
compose true
|
compose true
|
||||||
prefab true
|
prefab true
|
||||||
}
|
}
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion '1.5.1'
|
|
||||||
}
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path file('src/main/cpp/CMakeLists.txt')
|
path file('src/main/cpp/CMakeLists.txt')
|
||||||
version '3.22.1'
|
version '3.22.1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
packagingOptions {
|
|
||||||
resources {
|
packaging {
|
||||||
excludes.add('/META-INF/{AL2.0,LGPL2.1}')
|
|
||||||
}
|
|
||||||
jniLibs {
|
jniLibs {
|
||||||
pickFirsts += ['**/libxdl.so', '**/libshadowhook.so']
|
pickFirsts += "**/libxdl.so"
|
||||||
|
pickFirsts += "**/libshadowhook.so"
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
excludes += "**/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
excludes += "kotlin/**"
|
||||||
|
excludes += "**.bin"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
dataBinding {
|
|
||||||
enable true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.configureEach { variant ->
|
applicationVariants.configureEach { variant ->
|
||||||
@ -80,17 +81,40 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
|
implementation(libs.androidx.material3)
|
||||||
|
implementation(libs.material)
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.13.1'
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2'
|
implementation(libs.androidx.appcompat)
|
||||||
implementation platform('androidx.compose:compose-bom:2024.06.00')
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation 'androidx.compose.material3:material3'
|
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
|
||||||
|
|
||||||
|
def composeBom = platform(libs.androidx.compose.bom)
|
||||||
|
implementation(composeBom)
|
||||||
|
androidTestImplementation(composeBom)
|
||||||
|
implementation(libs.androidx.runtime)
|
||||||
|
implementation(libs.androidx.material)
|
||||||
|
implementation(libs.androidx.foundation)
|
||||||
|
implementation(libs.androidx.foundation.layout)
|
||||||
|
implementation(libs.androidx.animation)
|
||||||
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
implementation(libs.accompanist.pager)
|
||||||
|
implementation(libs.accompanist.pager.indicators)
|
||||||
|
implementation(libs.androidx.lifecycle.viewmodel.compose)
|
||||||
|
|
||||||
implementation 'io.github.hexhacking:xdl:2.1.1'
|
implementation(libs.coil.compose)
|
||||||
implementation 'com.bytedance.android:shadowhook:1.0.9'
|
implementation(libs.coil.svg)
|
||||||
compileOnly 'de.robv.android.xposed:api:82'
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.20"
|
implementation(platform(libs.okhttp.bom))
|
||||||
implementation 'com.google.code.gson:gson:2.11.0'
|
implementation(libs.okhttp)
|
||||||
|
implementation(libs.logging.interceptor)
|
||||||
|
|
||||||
|
implementation(libs.xdl)
|
||||||
|
implementation(libs.shadowhook)
|
||||||
|
compileOnly(libs.xposed.api)
|
||||||
|
implementation(libs.kotlinx.serialization.json)
|
||||||
}
|
}
|
7
app/proguard-rules.pro
vendored
@ -18,4 +18,9 @@
|
|||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
-keep class io.github.chinosk.gakumas.localify.GakumasHookMain {
|
||||||
|
<init>();
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@ -10,6 +12,7 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.GakumasLocalify"
|
android:theme="@style/Theme.GakumasLocalify"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
@ -32,7 +35,6 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.GakumasLocalify">
|
android:theme="@style/Theme.GakumasLocalify">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
@ -40,6 +42,23 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".TranslucentActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.GakumasLocalify.NoDisplay">
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
29
app/src/main/assets/about_contributors_en.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
|
||||||
|
"main_contributors": [
|
||||||
|
{
|
||||||
|
"name": "chinosk (Plugin code)",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/chinosk6"},
|
||||||
|
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DarwinTree (Translation Workflow)",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/darwintree"},
|
||||||
|
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "And all other translators",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contrib_img": {
|
||||||
|
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||||
|
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||||
|
}
|
||||||
|
}
|
29
app/src/main/assets/about_contributors_zh_cn.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
|
||||||
|
"main_contributors": [
|
||||||
|
{
|
||||||
|
"name": "chinosk(插件本体)",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/chinosk6"},
|
||||||
|
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DarwinTree(译文工作流)",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/darwintree"},
|
||||||
|
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "以及其他所有翻译贡献者",
|
||||||
|
"links": [
|
||||||
|
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"contrib_img": {
|
||||||
|
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||||
|
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||||
|
}
|
||||||
|
}
|
@ -24,3 +24,16 @@
|
|||||||
|
|
||||||
#define WM_KEYDOWN 0
|
#define WM_KEYDOWN 0
|
||||||
#define WM_KEYUP 1
|
#define WM_KEYUP 1
|
||||||
|
|
||||||
|
#define BTN_A 96
|
||||||
|
#define BTN_B 97
|
||||||
|
#define BTN_X 99
|
||||||
|
#define BTN_Y 100
|
||||||
|
#define BTN_LB 102
|
||||||
|
#define BTN_RB 103
|
||||||
|
#define BTN_THUMBL 106
|
||||||
|
#define BTN_THUMBR 107
|
||||||
|
#define BTN_SELECT 109
|
||||||
|
#define BTN_START 108
|
||||||
|
#define BTN_SHARE 130
|
||||||
|
#define BTN_XBOX 110
|
@ -87,7 +87,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsNativeObjectAlive(void* obj) {
|
bool IsNativeObjectAlive(void* obj) {
|
||||||
static UnityResolve::Method* IsNativeObjectAliveMtd = NULL;
|
static UnityResolve::Method* IsNativeObjectAliveMtd = nullptr;
|
||||||
if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
|
if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||||
"Object", "IsNativeObjectAlive");
|
"Object", "IsNativeObjectAlive");
|
||||||
return IsNativeObjectAliveMtd->Invoke<bool>(obj);
|
return IsNativeObjectAliveMtd->Invoke<bool>(obj);
|
||||||
@ -109,18 +109,18 @@ namespace GakumasLocal::HookMain {
|
|||||||
return GetResolution->Invoke<Il2cppUtils::Resolution_t>();
|
return GetResolution->Invoke<Il2cppUtils::Resolution_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* _this, float value)) {
|
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
if (_this == mainCameraCache) {
|
if (self == mainCameraCache) {
|
||||||
value = GKCamera::baseCamera.fov;
|
value = GKCamera::baseCamera.fov;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unity_set_fieldOfView_Orig(_this, value);
|
Unity_set_fieldOfView_Orig(self, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* _this)) {
|
DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* self)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
if (_this == mainCameraCache) {
|
if (self == mainCameraCache) {
|
||||||
static auto get_orthographic = reinterpret_cast<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
|
static auto get_orthographic = reinterpret_cast<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
|
||||||
"UnityEngine.Camera::get_orthographic()"
|
"UnityEngine.Camera::get_orthographic()"
|
||||||
));
|
));
|
||||||
@ -133,30 +133,30 @@ namespace GakumasLocal::HookMain {
|
|||||||
// set_orthographic(i, false);
|
// set_orthographic(i, false);
|
||||||
Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov);
|
Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov);
|
||||||
}
|
}
|
||||||
Unity_set_fieldOfView_Orig(_this, GKCamera::baseCamera.fov);
|
Unity_set_fieldOfView_Orig(self, GKCamera::baseCamera.fov);
|
||||||
|
|
||||||
// Log::DebugFmt("main - get_orthographic: %d", get_orthographic(_this));
|
// Log::DebugFmt("main - get_orthographic: %d", get_orthographic(self));
|
||||||
return GKCamera::baseCamera.fov;
|
return GKCamera::baseCamera.fov;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Unity_get_fieldOfView_Orig(_this);
|
return Unity_get_fieldOfView_Orig(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
UnityResolve::UnityType::Transform* cacheTrans = NULL;
|
UnityResolve::UnityType::Transform* cacheTrans = nullptr;
|
||||||
UnityResolve::UnityType::Quaternion cacheRotation{};
|
UnityResolve::UnityType::Quaternion cacheRotation{};
|
||||||
UnityResolve::UnityType::Vector3 cachePosition{};
|
UnityResolve::UnityType::Vector3 cachePosition{};
|
||||||
UnityResolve::UnityType::Vector3 cacheForward{};
|
UnityResolve::UnityType::Vector3 cacheForward{};
|
||||||
UnityResolve::UnityType::Vector3 cacheLookAt{};
|
UnityResolve::UnityType::Vector3 cacheLookAt{};
|
||||||
|
|
||||||
DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Quaternion* value)) {
|
DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Quaternion* value)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
static auto lookat_injected = reinterpret_cast<void (*)(void*_this,
|
static auto lookat_injected = reinterpret_cast<void (*)(void*self,
|
||||||
UnityResolve::UnityType::Vector3* worldPosition, UnityResolve::UnityType::Vector3* worldUp)>(
|
UnityResolve::UnityType::Vector3* worldPosition, UnityResolve::UnityType::Vector3* worldUp)>(
|
||||||
Il2cppUtils::il2cpp_resolve_icall(
|
Il2cppUtils::il2cpp_resolve_icall(
|
||||||
"UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)"));
|
"UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)"));
|
||||||
static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0);
|
static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0);
|
||||||
|
|
||||||
if (cameraTransformCache == _this) {
|
if (cameraTransformCache == self) {
|
||||||
const auto cameraMode = GKCamera::GetCameraMode();
|
const auto cameraMode = GKCamera::GetCameraMode();
|
||||||
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
||||||
@ -167,7 +167,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
static GakumasLocal::Misc::FixedSizeQueue<float> recordsY(60);
|
static GakumasLocal::Misc::FixedSizeQueue<float> recordsY(60);
|
||||||
const auto newY = GKCamera::CheckNewY(cacheLookAt, true, recordsY);
|
const auto newY = GKCamera::CheckNewY(cacheLookAt, true, recordsY);
|
||||||
UnityResolve::UnityType::Vector3 newCacheLookAt{cacheLookAt.x, newY, cacheLookAt.z};
|
UnityResolve::UnityType::Vector3 newCacheLookAt{cacheLookAt.x, newY, cacheLookAt.z};
|
||||||
lookat_injected(_this, &newCacheLookAt, &worldUp);
|
lookat_injected(self, &newCacheLookAt, &worldUp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -175,25 +175,25 @@ namespace GakumasLocal::HookMain {
|
|||||||
else if (cameraMode == GKCamera::CameraMode::FOLLOW) {
|
else if (cameraMode == GKCamera::CameraMode::FOLLOW) {
|
||||||
auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition,
|
auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition,
|
||||||
GKCamera::followPosOffset, true);
|
GKCamera::followPosOffset, true);
|
||||||
lookat_injected(_this, &newLookAtPos, &worldUp);
|
lookat_injected(self, &newLookAtPos, &worldUp);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
auto& origCameraLookat = GKCamera::baseCamera.lookAt;
|
auto& origCameraLookat = GKCamera::baseCamera.lookAt;
|
||||||
lookat_injected(_this, &origCameraLookat, &worldUp);
|
lookat_injected(self, &origCameraLookat, &worldUp);
|
||||||
// Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov);
|
// Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Unity_set_rotation_Injected_Orig(_this, value);
|
return Unity_set_rotation_Injected_Orig(self, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Vector3* data)) {
|
DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Vector3* data)) {
|
||||||
if (Config::enableFreeCamera) {
|
if (Config::enableFreeCamera) {
|
||||||
CheckAndUpdateMainCamera();
|
CheckAndUpdateMainCamera();
|
||||||
|
|
||||||
if (cameraTransformCache == _this) {
|
if (cameraTransformCache == self) {
|
||||||
const auto cameraMode = GKCamera::GetCameraMode();
|
const auto cameraMode = GKCamera::GetCameraMode();
|
||||||
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) {
|
||||||
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
if (cacheTrans && IsNativeObjectAlive(cacheTrans)) {
|
||||||
@ -218,16 +218,16 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Unity_set_position_Injected_Orig(_this, data);
|
return Unity_set_position_Injected_Orig(self, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* _this, int type, void* c, void* tc, void* mtd)) {
|
DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* self, int type, void* c, void* tc, void* mtd)) {
|
||||||
switch (Config::gameOrientation) {
|
switch (Config::gameOrientation) {
|
||||||
case 1: type = 0x2; break; // FixedPortrait
|
case 1: type = 0x2; break; // FixedPortrait
|
||||||
case 2: type = 0x3; break; // FixedLandscape
|
case 2: type = 0x3; break; // FixedLandscape
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return InternalSetOrientationAsync_Orig(_this, type, c, tc, mtd);
|
return InternalSetOrientationAsync_Orig(self, type, c, tc, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
||||||
@ -248,16 +248,16 @@ namespace GakumasLocal::HookMain {
|
|||||||
|
|
||||||
std::unordered_map<void*, std::string> loadHistory{};
|
std::unordered_map<void*, std::string> loadHistory{};
|
||||||
|
|
||||||
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
|
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* self, Il2cppString* name, void* type)) {
|
||||||
// Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str());
|
// Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str());
|
||||||
auto ret = AssetBundle_LoadAssetAsync_Orig(_this, name, type);
|
auto ret = AssetBundle_LoadAssetAsync_Orig(self, name, type);
|
||||||
loadHistory.emplace(ret, name->ToString());
|
loadHistory.emplace(ret, name->ToString());
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* _this)) {
|
DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* self)) {
|
||||||
auto result = AssetBundleRequest_GetResult_Orig(_this);
|
auto result = AssetBundleRequest_GetResult_Orig(self);
|
||||||
if (const auto iter = loadHistory.find(_this); iter != loadHistory.end()) {
|
if (const auto iter = loadHistory.find(self); iter != loadHistory.end()) {
|
||||||
const auto name = iter->second;
|
const auto name = iter->second;
|
||||||
loadHistory.erase(iter);
|
loadHistory.erase(iter);
|
||||||
|
|
||||||
@ -275,25 +275,25 @@ namespace GakumasLocal::HookMain {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, I18nHelper_SetUpI18n, (void* _this, Il2cppString* lang, Il2cppString* localizationText, int keyComparison)) {
|
DEFINE_HOOK(void, I18nHelper_SetUpI18n, (void* self, Il2cppString* lang, Il2cppString* localizationText, int keyComparison)) {
|
||||||
// Log::InfoFmt("SetUpI18n lang: %s, key: %d text: %s", lang->ToString().c_str(), keyComparison, localizationText->ToString().c_str());
|
// Log::InfoFmt("SetUpI18n lang: %s, key: %d text: %s", lang->ToString().c_str(), keyComparison, localizationText->ToString().c_str());
|
||||||
// TODO 此处为 dump 原文 csv
|
// TODO 此处为 dump 原文 csv
|
||||||
I18nHelper_SetUpI18n_Orig(_this, lang, localizationText, keyComparison);
|
I18nHelper_SetUpI18n_Orig(self, lang, localizationText, keyComparison);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, I18nHelper_SetValue, (void* _this, Il2cppString* key, Il2cppString* value)) {
|
DEFINE_HOOK(void, I18nHelper_SetValue, (void* self, Il2cppString* key, Il2cppString* value)) {
|
||||||
// Log::InfoFmt("I18nHelper_SetValue: %s - %s", key->ToString().c_str(), value->ToString().c_str());
|
// Log::InfoFmt("I18nHelper_SetValue: %s - %s", key->ToString().c_str(), value->ToString().c_str());
|
||||||
std::string local;
|
std::string local;
|
||||||
if (Local::GetI18n(key->ToString(), &local)) {
|
if (Local::GetI18n(key->ToString(), &local)) {
|
||||||
I18nHelper_SetValue_Orig(_this, key, UnityResolve::UnityType::String::New(local));
|
I18nHelper_SetValue_Orig(self, key, UnityResolve::UnityType::String::New(local));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Local::DumpI18nItem(key->ToString(), value->ToString());
|
Local::DumpI18nItem(key->ToString(), value->ToString());
|
||||||
if (Config::textTest) {
|
if (Config::textTest) {
|
||||||
I18nHelper_SetValue_Orig(_this, key, Il2cppString::New("[I18]" + value->ToString()));
|
I18nHelper_SetValue_Orig(self, key, Il2cppString::New("[I18]" + value->ToString()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
I18nHelper_SetValue_Orig(_this, key, value);
|
I18nHelper_SetValue_Orig(self, key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,12 +315,12 @@ namespace GakumasLocal::HookMain {
|
|||||||
updatedFontPtrs.emplace(fontAsset);
|
updatedFontPtrs.emplace(fontAsset);
|
||||||
UpdateFontAssetData->Invoke<void>(fontAsset);
|
UpdateFontAssetData->Invoke<void>(fontAsset);
|
||||||
}
|
}
|
||||||
set_font->Invoke<void>(TMP_Text_this, fontAsset);
|
set_font->Invoke<void>(TMP_Textself, fontAsset);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* _this, UnityResolve::UnityType::String* text, int start, int length)) {
|
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* self, UnityResolve::UnityType::String* text, int start, int length)) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length);
|
return TMP_Text_PopulateTextBackingArray_Orig(self, text, start, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto Substring = Il2cppUtils::GetMethod("mscorlib.dll", "System", "String", "Substring",
|
static auto Substring = Il2cppUtils::GetMethod("mscorlib.dll", "System", "String", "Substring",
|
||||||
@ -330,48 +330,48 @@ namespace GakumasLocal::HookMain {
|
|||||||
std::string transText;
|
std::string transText;
|
||||||
if (Local::GetGenericText(origText, &transText)) {
|
if (Local::GetGenericText(origText, &transText)) {
|
||||||
const auto newText = UnityResolve::UnityType::String::New(transText);
|
const auto newText = UnityResolve::UnityType::String::New(transText);
|
||||||
return TMP_Text_PopulateTextBackingArray_Orig(_this, newText, 0, newText->length);
|
return TMP_Text_PopulateTextBackingArray_Orig(self, newText, 0, newText->length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config::textTest) {
|
if (Config::textTest) {
|
||||||
TMP_Text_PopulateTextBackingArray_Orig(_this, UnityResolve::UnityType::String::New("[TP]" + text->ToString()), start, length + 4);
|
TMP_Text_PopulateTextBackingArray_Orig(self, UnityResolve::UnityType::String::New("[TP]" + text->ToString()), start, length + 4);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length);
|
TMP_Text_PopulateTextBackingArray_Orig(self, text, start, length);
|
||||||
}
|
}
|
||||||
UpdateFont(_this);
|
UpdateFont(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* _this, void* method)) {
|
DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* self, void* method)) {
|
||||||
// Log::InfoFmt("TextMeshProUGUI_Awake at %p, _this at %p", TextMeshProUGUI_Awake_Orig, _this);
|
// Log::InfoFmt("TextMeshProUGUI_Awake at %p, self at %p", TextMeshProUGUI_Awake_Orig, self);
|
||||||
|
|
||||||
const auto TMP_Text_klass = Il2cppUtils::GetClass("Unity.TextMeshPro.dll",
|
const auto TMP_Text_klass = Il2cppUtils::GetClass("Unity.TextMeshPro.dll",
|
||||||
"TMPro", "TMP_Text");
|
"TMPro", "TMP_Text");
|
||||||
const auto get_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("get_text");
|
const auto get_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("get_text");
|
||||||
const auto set_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("set_text");
|
const auto set_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("set_text");
|
||||||
const auto currText = get_Text_method->Invoke<UnityResolve::UnityType::String*>(_this);
|
const auto currText = get_Text_method->Invoke<UnityResolve::UnityType::String*>(self);
|
||||||
if (currText) {
|
if (currText) {
|
||||||
//Log::InfoFmt("TextMeshProUGUI_Awake: %s", currText->ToString().c_str());
|
//Log::InfoFmt("TextMeshProUGUI_Awake: %s", currText->ToString().c_str());
|
||||||
std::string transText;
|
std::string transText;
|
||||||
if (Local::GetGenericText(currText->ToString(), &transText)) {
|
if (Local::GetGenericText(currText->ToString(), &transText)) {
|
||||||
if (Config::textTest) {
|
if (Config::textTest) {
|
||||||
set_Text_method->Invoke<void>(_this, UnityResolve::UnityType::String::New("[TA]" + transText));
|
set_Text_method->Invoke<void>(self, UnityResolve::UnityType::String::New("[TA]" + transText));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
set_Text_method->Invoke<void>(_this, UnityResolve::UnityType::String::New(transText));
|
set_Text_method->Invoke<void>(self, UnityResolve::UnityType::String::New(transText));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set_font->Invoke<void>(_this, font);
|
// set_font->Invoke<void>(self, font);
|
||||||
UpdateFont(_this);
|
UpdateFont(self);
|
||||||
TextMeshProUGUI_Awake_Orig(_this, method);
|
TextMeshProUGUI_Awake_Orig(self, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 文本未hook完整
|
// TODO 文本未hook完整
|
||||||
DEFINE_HOOK(void, TextField_set_value, (void* _this, Il2cppString* value)) {
|
DEFINE_HOOK(void, TextField_set_value, (void* self, Il2cppString* value)) {
|
||||||
Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str());
|
Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str());
|
||||||
TextField_set_value_Orig(_this, value);
|
TextField_set_value_Orig(self, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
|
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
|
||||||
@ -381,7 +381,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, OctoResourceLoader_LoadFromCacheOrDownload,
|
DEFINE_HOOK(void, OctoResourceLoader_LoadFromCacheOrDownload,
|
||||||
(void* _this, Il2cppString* resourceName, void* onComplete, void* onProgress, void* method)) {
|
(void* self, Il2cppString* resourceName, void* onComplete, void* onProgress, void* method)) {
|
||||||
|
|
||||||
Log::DebugFmt("OctoResourceLoader_LoadFromCacheOrDownload: %s\n", resourceName->ToString().c_str());
|
Log::DebugFmt("OctoResourceLoader_LoadFromCacheOrDownload: %s\n", resourceName->ToString().c_str());
|
||||||
|
|
||||||
@ -394,24 +394,24 @@ namespace GakumasLocal::HookMain {
|
|||||||
const auto onComplete_invoke = reinterpret_cast<void (*)(void*, Il2cppString*, void*)>(
|
const auto onComplete_invoke = reinterpret_cast<void (*)(void*, Il2cppString*, void*)>(
|
||||||
onComplete_invoke_mtd->methodPointer
|
onComplete_invoke_mtd->methodPointer
|
||||||
);
|
);
|
||||||
onComplete_invoke(onComplete, UnityResolve::UnityType::String::New(replaceStr), NULL);
|
onComplete_invoke(onComplete, UnityResolve::UnityType::String::New(replaceStr), nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return OctoResourceLoader_LoadFromCacheOrDownload_Orig(_this, resourceName, onComplete, onProgress, method);
|
return OctoResourceLoader_LoadFromCacheOrDownload_Orig(self, resourceName, onComplete, onProgress, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, OnDownloadProgress_Invoke, (void* _this, Il2cppString* name, uint64_t receivedLength, uint64_t contentLength)) {
|
DEFINE_HOOK(void, OnDownloadProgress_Invoke, (void* self, Il2cppString* name, uint64_t receivedLength, uint64_t contentLength)) {
|
||||||
Log::DebugFmt("OnDownloadProgress_Invoke: %s, %lu/%lu", name->ToString().c_str(), receivedLength, contentLength);
|
Log::DebugFmt("OnDownloadProgress_Invoke: %s, %lu/%lu", name->ToString().c_str(), receivedLength, contentLength);
|
||||||
OnDownloadProgress_Invoke_Orig(_this, name, receivedLength, contentLength);
|
OnDownloadProgress_Invoke_Orig(self, name, receivedLength, contentLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnHooked
|
// UnHooked
|
||||||
DEFINE_HOOK(UnityResolve::UnityType::String*, UI_I18n_GetOrDefault, (void* _this,
|
DEFINE_HOOK(UnityResolve::UnityType::String*, UI_I18n_GetOrDefault, (void* self,
|
||||||
UnityResolve::UnityType::String* key, UnityResolve::UnityType::String* defaultKey, void* method)) {
|
UnityResolve::UnityType::String* key, UnityResolve::UnityType::String* defaultKey, void* method)) {
|
||||||
|
|
||||||
auto ret = UI_I18n_GetOrDefault_Orig(_this, key, defaultKey, method);
|
auto ret = UI_I18n_GetOrDefault_Orig(self, key, defaultKey, method);
|
||||||
|
|
||||||
// Log::DebugFmt("UI_I18n_GetOrDefault: key: %s, default: %s, result: %s", key->ToString().c_str(), defaultKey->ToString().c_str(), ret->ToString().c_str());
|
// Log::DebugFmt("UI_I18n_GetOrDefault: key: %s, default: %s, result: %s", key->ToString().c_str(), defaultKey->ToString().c_str(), ret->ToString().c_str());
|
||||||
|
|
||||||
@ -419,16 +419,16 @@ namespace GakumasLocal::HookMain {
|
|||||||
// return UnityResolve::UnityType::String::New("[I18]" + ret->ToString());
|
// return UnityResolve::UnityType::String::New("[I18]" + ret->ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* _this, void* liveData, bool isUnlocked, bool isNew)) {
|
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* self, void* liveData, bool isUnlocked, bool isNew, void* ct, void* mtd)) {
|
||||||
// Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew);
|
// Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew);
|
||||||
if (Config::unlockAllLive) {
|
if (Config::dbgMode && Config::unlockAllLive) {
|
||||||
isUnlocked = true;
|
isUnlocked = true;
|
||||||
}
|
}
|
||||||
PictureBookLiveThumbnailView_SetData_Orig(_this, liveData, isUnlocked, isNew);
|
PictureBookLiveThumbnailView_SetData_Orig(self, liveData, isUnlocked, isNew, ct, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool needRestoreHides = false;
|
bool needRestoreHides = false;
|
||||||
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* _this, void* produceLive,
|
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive,
|
||||||
Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) {
|
Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) {
|
||||||
needRestoreHides = false;
|
needRestoreHides = false;
|
||||||
Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,",
|
Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,",
|
||||||
@ -439,18 +439,18 @@ namespace GakumasLocal::HookMain {
|
|||||||
characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006,
|
characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006,
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (Config::enableLiveCustomeDress) {
|
if (Config::dbgMode && Config::enableLiveCustomeDress) {
|
||||||
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
|
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
|
||||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId,
|
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId,
|
||||||
Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId),
|
Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId),
|
||||||
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId));
|
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, costumeId, costumeHeadId);
|
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, costumeId, costumeHeadId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// std::string lastMusicId;
|
// std::string lastMusicId;
|
||||||
DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* _this, void* itemModel, bool isFirst, void* mtd)) {
|
DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* self, void* itemModel, void* ct, void* mtd)) {
|
||||||
/* // 修改角色后,Live 结束返回时, itemModel 为 null
|
/* // 修改角色后,Live 结束返回时, itemModel 为 null
|
||||||
Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel);
|
Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel);
|
||||||
|
|
||||||
@ -472,7 +472,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New<void*>();
|
auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New<void*>();
|
||||||
PictureBookLiveSelectMusicListItemModel_ctor->Invoke<void>(newItemModel, music, false);
|
PictureBookLiveSelectMusicListItemModel_ctor->Invoke<void>(newItemModel, music, false);
|
||||||
|
|
||||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd);
|
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, newItemModel, isFirst, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (itemModel) {
|
if (itemModel) {
|
||||||
@ -481,23 +481,23 @@ namespace GakumasLocal::HookMain {
|
|||||||
lastMusicId = musicId->ToString();
|
lastMusicId = musicId->ToString();
|
||||||
}*/
|
}*/
|
||||||
if (!itemModel) return;
|
if (!itemModel) return;
|
||||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, itemModel, isFirst, mtd);
|
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, itemModel, ct, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(bool, VLDOF_IsActive, (void* _this)) {
|
DEFINE_HOOK(bool, VLDOF_IsActive, (void* self)) {
|
||||||
if (Config::enableFreeCamera) return false;
|
if (Config::enableFreeCamera) return false;
|
||||||
return VLDOF_IsActive_Orig(_this);
|
return VLDOF_IsActive_Orig(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, CampusQualityManager_set_TargetFrameRate, (void* _this, float value)) {
|
DEFINE_HOOK(void, CampusQualityManager_set_TargetFrameRate, (void* self, float value)) {
|
||||||
// Log::InfoFmt("CampusQualityManager_set_TargetFrameRate: %f", value);
|
// Log::InfoFmt("CampusQualityManager_set_TargetFrameRate: %f", value);
|
||||||
const auto configFps = Config::targetFrameRate;
|
const auto configFps = Config::targetFrameRate;
|
||||||
CampusQualityManager_set_TargetFrameRate_Orig(_this, configFps == 0 ? value : (float)configFps);
|
CampusQualityManager_set_TargetFrameRate_Orig(self, configFps == 0 ? value : (float)configFps);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, CampusQualityManager_ApplySetting, (void* _this, int qualitySettingsLevel, int maxBufferPixel, float renderScale, int volumeIndex)) {
|
DEFINE_HOOK(void, CampusQualityManager_ApplySetting, (void* self, int qualitySettingsLevel, int maxBufferPixel, float renderScale, int volumeIndex)) {
|
||||||
if (Config::targetFrameRate != 0) {
|
if (Config::targetFrameRate != 0) {
|
||||||
CampusQualityManager_set_TargetFrameRate_Orig(_this, Config::targetFrameRate);
|
CampusQualityManager_set_TargetFrameRate_Orig(self, Config::targetFrameRate);
|
||||||
}
|
}
|
||||||
if (Config::useCustomeGraphicSettings) {
|
if (Config::useCustomeGraphicSettings) {
|
||||||
static auto SetReflectionQuality = Il2cppUtils::GetMethod("campus-submodule.Runtime.dll", "Campus.Common",
|
static auto SetReflectionQuality = Il2cppUtils::GetMethod("campus-submodule.Runtime.dll", "Campus.Common",
|
||||||
@ -516,8 +516,8 @@ namespace GakumasLocal::HookMain {
|
|||||||
if (Config::lodQualityLevel >= values.size()) Config::lodQualityLevel = values.size() - 1;
|
if (Config::lodQualityLevel >= values.size()) Config::lodQualityLevel = values.size() - 1;
|
||||||
if (Config::reflectionQualityLevel >= values.size()) Config::reflectionQualityLevel = values.size() - 1;
|
if (Config::reflectionQualityLevel >= values.size()) Config::reflectionQualityLevel = values.size() - 1;
|
||||||
|
|
||||||
SetLODQuality->Invoke<void>(_this, values[Config::lodQualityLevel]);
|
SetLODQuality->Invoke<void>(self, values[Config::lodQualityLevel]);
|
||||||
SetReflectionQuality->Invoke<void>(_this, values[Config::reflectionQualityLevel]);
|
SetReflectionQuality->Invoke<void>(self, values[Config::reflectionQualityLevel]);
|
||||||
|
|
||||||
qualitySettingsLevel = Config::qualitySettingsLevel;
|
qualitySettingsLevel = Config::qualitySettingsLevel;
|
||||||
maxBufferPixel = Config::maxBufferPixel;
|
maxBufferPixel = Config::maxBufferPixel;
|
||||||
@ -528,7 +528,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
qualitySettingsLevel , renderScale);
|
qualitySettingsLevel , renderScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
CampusQualityManager_ApplySetting_Orig(_this, qualitySettingsLevel, maxBufferPixel, renderScale, volumeIndex);
|
CampusQualityManager_ApplySetting_Orig(self, qualitySettingsLevel, maxBufferPixel, renderScale, volumeIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, UIManager_UpdateRenderTarget, (UnityResolve::UnityType::Vector2 ratio, void* mtd)) {
|
DEFINE_HOOK(void, UIManager_UpdateRenderTarget, (UnityResolve::UnityType::Vector2 ratio, void* mtd)) {
|
||||||
@ -537,10 +537,10 @@ namespace GakumasLocal::HookMain {
|
|||||||
return UIManager_UpdateRenderTarget_Orig(ratio, mtd);
|
return UIManager_UpdateRenderTarget_Orig(ratio, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, VLSRPCameraController_UpdateRenderTarget, (void* _this, int width, int height, bool forceAlpha, void* method)) {
|
DEFINE_HOOK(void, VLSRPCameraController_UpdateRenderTarget, (void* self, int width, int height, bool forceAlpha, void* method)) {
|
||||||
// const auto resolution = GetResolution();
|
// const auto resolution = GetResolution();
|
||||||
// Log::DebugFmt("VLSRPCameraController_UpdateRenderTarget: %d, %d", width, height);
|
// Log::DebugFmt("VLSRPCameraController_UpdateRenderTarget: %d, %d", width, height);
|
||||||
return VLSRPCameraController_UpdateRenderTarget_Orig(_this, width, height, forceAlpha, method);
|
return VLSRPCameraController_UpdateRenderTarget_Orig(self, width, height, forceAlpha, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void*, VLUtility_GetLimitedResolution, (int32_t screenWidth, int32_t screenHeight,
|
DEFINE_HOOK(void*, VLUtility_GetLimitedResolution, (int32_t screenWidth, int32_t screenHeight,
|
||||||
@ -555,8 +555,8 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DEFINE_HOOK(void, CampusActorModelParts_OnRegisterBone, (void* _this, Il2cppString** name, UnityResolve::UnityType::Transform* bone)) {
|
DEFINE_HOOK(void, CampusActorModelParts_OnRegisterBone, (void* self, Il2cppString** name, UnityResolve::UnityType::Transform* bone)) {
|
||||||
CampusActorModelParts_OnRegisterBone_Orig(_this, name, bone);
|
CampusActorModelParts_OnRegisterBone_Orig(self, name, bone);
|
||||||
// Log::DebugFmt("CampusActorModelParts_OnRegisterBone: %s, %p", (*name)->ToString().c_str(), bone);
|
// Log::DebugFmt("CampusActorModelParts_OnRegisterBone: %s, %p", (*name)->ToString().c_str(), bone);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +578,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> namesVec{};
|
std::vector<std::string> namesVec{};
|
||||||
|
namesVec.reserve(names.size());
|
||||||
for (auto i :names) {
|
for (auto i :names) {
|
||||||
namesVec.push_back(i->ToString());
|
namesVec.push_back(i->ToString());
|
||||||
}
|
}
|
||||||
@ -616,7 +617,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, CampusActorController_LateUpdate, (void* _this, void* mtd)) {
|
DEFINE_HOOK(void, CampusActorController_LateUpdate, (void* self, void* mtd)) {
|
||||||
static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll",
|
static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll",
|
||||||
"Campus.Common", "CampusActorController");
|
"Campus.Common", "CampusActorController");
|
||||||
static auto rootBody_field = CampusActorController_klass->Get<UnityResolve::Field>("_rootBody");
|
static auto rootBody_field = CampusActorController_klass->Get<UnityResolve::Field>("_rootBody");
|
||||||
@ -625,10 +626,10 @@ namespace GakumasLocal::HookMain {
|
|||||||
if (!Config::enableFreeCamera || (GKCamera::GetCameraMode() == GKCamera::CameraMode::FREE)) {
|
if (!Config::enableFreeCamera || (GKCamera::GetCameraMode() == GKCamera::CameraMode::FREE)) {
|
||||||
if (needRestoreHides) {
|
if (needRestoreHides) {
|
||||||
needRestoreHides = false;
|
needRestoreHides = false;
|
||||||
HideHead(NULL, false);
|
HideHead(nullptr, false);
|
||||||
HideHead(NULL, true);
|
HideHead(nullptr, true);
|
||||||
}
|
}
|
||||||
return CampusActorController_LateUpdate_Orig(_this, mtd);
|
return CampusActorController_LateUpdate_Orig(self, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static auto GetHumanBodyBoneTransform_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(parentKlass, "GetHumanBodyBoneTransform", 1);
|
static auto GetHumanBodyBoneTransform_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(parentKlass, "GetHumanBodyBoneTransform", 1);
|
||||||
@ -639,13 +640,13 @@ namespace GakumasLocal::HookMain {
|
|||||||
static auto get_Index = get_index_mtd ? reinterpret_cast<int (*)(void*)>(
|
static auto get_Index = get_index_mtd ? reinterpret_cast<int (*)(void*)>(
|
||||||
get_index_mtd->methodPointer) : [](void*){return 0;};
|
get_index_mtd->methodPointer) : [](void*){return 0;};
|
||||||
|
|
||||||
const auto currIndex = get_Index(_this);
|
const auto currIndex = get_Index(self);
|
||||||
if (currIndex == GKCamera::followCharaIndex) {
|
if (currIndex == GKCamera::followCharaIndex) {
|
||||||
static auto initPartsSuccess = InitBodyParts();
|
static auto initPartsSuccess = InitBodyParts();
|
||||||
static auto headBodyId = initPartsSuccess ? GKCamera::bodyPartsEnum.GetValueByName("Head") : 0xA;
|
static auto headBodyId = initPartsSuccess ? GKCamera::bodyPartsEnum.GetValueByName("Head") : 0xA;
|
||||||
const auto isFirstPerson = GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON;
|
const auto isFirstPerson = GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON;
|
||||||
|
|
||||||
auto targetTrans = GetHumanBodyBoneTransform(_this,
|
auto targetTrans = GetHumanBodyBoneTransform(self,
|
||||||
isFirstPerson ? headBodyId : GKCamera::bodyPartsEnum.GetCurrent().second);
|
isFirstPerson ? headBodyId : GKCamera::bodyPartsEnum.GetCurrent().second);
|
||||||
|
|
||||||
if (targetTrans) {
|
if (targetTrans) {
|
||||||
@ -655,7 +656,7 @@ namespace GakumasLocal::HookMain {
|
|||||||
cacheForward = cacheTrans->GetForward();
|
cacheForward = cacheTrans->GetForward();
|
||||||
cacheLookAt = cacheTrans->GetPosition() + cacheTrans->GetForward() * 3;
|
cacheLookAt = cacheTrans->GetPosition() + cacheTrans->GetForward() * 3;
|
||||||
|
|
||||||
auto rootBody = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::Transform*>(_this, rootBody_field);
|
auto rootBody = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::Transform*>(self, rootBody_field);
|
||||||
auto rootModel = rootBody->GetParent();
|
auto rootModel = rootBody->GetParent();
|
||||||
auto rootModelChildCount = rootModel->GetChildCount();
|
auto rootModelChildCount = rootModel->GetChildCount();
|
||||||
for (int i = 0; i < rootModelChildCount; i++) {
|
for (int i = 0; i < rootModelChildCount; i++) {
|
||||||
@ -677,12 +678,12 @@ namespace GakumasLocal::HookMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
cacheTrans = NULL;
|
cacheTrans = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CampusActorController_LateUpdate_Orig(_this, mtd);
|
CampusActorController_LateUpdate_Orig(self, mtd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateSwingBreastBonesData(void* initializeData) {
|
void UpdateSwingBreastBonesData(void* initializeData) {
|
||||||
@ -785,9 +786,9 @@ namespace GakumasLocal::HookMain {
|
|||||||
// Log::DebugFmt("\n");
|
// Log::DebugFmt("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_HOOK(void, CampusActorAnimation_Setup, (void* _this, void* rootTrans, void* initializeData)) {
|
DEFINE_HOOK(void, CampusActorAnimation_Setup, (void* self, void* rootTrans, void* initializeData)) {
|
||||||
UpdateSwingBreastBonesData(initializeData);
|
UpdateSwingBreastBonesData(initializeData);
|
||||||
return CampusActorAnimation_Setup_Orig(_this, rootTrans, initializeData);
|
return CampusActorAnimation_Setup_Orig(self, rootTrans, initializeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartInjectFunctions() {
|
void StartInjectFunctions() {
|
||||||
@ -833,14 +834,14 @@ namespace GakumasLocal::HookMain {
|
|||||||
|
|
||||||
ADD_HOOK(PictureBookLiveThumbnailView_SetData,
|
ADD_HOOK(PictureBookLiveThumbnailView_SetData,
|
||||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||||
"PictureBookLiveThumbnailView", "SetData"));
|
"PictureBookLiveThumbnailView", "SetDataAsync", {"*", "*", "*", "*"}));
|
||||||
|
|
||||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
|
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
|
||||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||||
"PictureBookLiveSelectScreenPresenter", "MoveLiveScene"));
|
"PictureBookLiveSelectScreenPresenter", "MoveLiveScene"));
|
||||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic,
|
ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic,
|
||||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||||
"PictureBookLiveSelectScreenPresenter", "OnSelectMusic"));
|
"PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync"));
|
||||||
|
|
||||||
ADD_HOOK(VLDOF_IsActive,
|
ADD_HOOK(VLDOF_IsActive,
|
||||||
Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering",
|
Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering",
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
extern JavaVM* g_javaVM;
|
extern JavaVM* g_javaVM;
|
||||||
extern jclass g_gakumasHookMainClass;
|
extern jclass g_gakumasHookMainClass;
|
||||||
@ -24,9 +25,13 @@ extern jmethodID showToastMethodId;
|
|||||||
|
|
||||||
|
|
||||||
namespace GakumasLocal::Log {
|
namespace GakumasLocal::Log {
|
||||||
|
namespace {
|
||||||
|
std::queue<std::string> showingToasts{};
|
||||||
|
}
|
||||||
|
|
||||||
std::string StringFormat(const char* fmt, ...) {
|
std::string StringFormat(const char* fmt, ...) {
|
||||||
GetParamStringResult(result);
|
GetParamStringResult(result);
|
||||||
return result.c_str();
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Log(int prio, const char* msg) {
|
void Log(int prio, const char* msg) {
|
||||||
@ -70,8 +75,8 @@ namespace GakumasLocal::Log {
|
|||||||
__android_log_write(prio, "GakumasLog", result.c_str());
|
__android_log_write(prio, "GakumasLog", result.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowToast(const std::string& text) {
|
void ShowToastJNI(const char* text) {
|
||||||
DebugFmt("Toast: %s", text.c_str());
|
DebugFmt("Toast: %s", text);
|
||||||
|
|
||||||
std::thread([text](){
|
std::thread([text](){
|
||||||
auto env = Misc::GetJNIEnv();
|
auto env = Misc::GetJNIEnv();
|
||||||
@ -89,15 +94,50 @@ namespace GakumasLocal::Log {
|
|||||||
g_javaVM->DetachCurrentThread();
|
g_javaVM->DetachCurrentThread();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jstring param = env->NewStringUTF(text.c_str());
|
jstring param = env->NewStringUTF(text);
|
||||||
env->CallStaticVoidMethod(kotlinClass, methodId, param);
|
env->CallStaticVoidMethod(kotlinClass, methodId, param);
|
||||||
|
|
||||||
g_javaVM->DetachCurrentThread();
|
g_javaVM->DetachCurrentThread();
|
||||||
}).detach();
|
}).detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ShowToast(const std::string& text) {
|
||||||
|
showingToasts.push(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowToast(const char* text) {
|
||||||
|
DebugFmt("Toast: %s", text);
|
||||||
|
return ShowToast(std::string(text));
|
||||||
|
}
|
||||||
|
|
||||||
void ShowToastFmt(const char* fmt, ...) {
|
void ShowToastFmt(const char* fmt, ...) {
|
||||||
GetParamStringResult(result);
|
GetParamStringResult(result);
|
||||||
ShowToast(result);
|
ShowToast(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GetQueuedToast() {
|
||||||
|
if (showingToasts.empty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const auto ret = showingToasts.front();
|
||||||
|
showingToasts.pop();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ToastLoop(JNIEnv *env, jclass clazz) {
|
||||||
|
const auto toastString = GetQueuedToast();
|
||||||
|
if (toastString.empty()) return;
|
||||||
|
|
||||||
|
static auto _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
|
||||||
|
|
||||||
|
if (env && clazz && _showToastMethodId) {
|
||||||
|
jstring param = env->NewStringUTF(toastString.c_str());
|
||||||
|
env->CallStaticVoidMethod(clazz, _showToastMethodId, param);
|
||||||
|
env->DeleteLocalRef(param);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define GAKUMAS_LOCALIFY_LOG_H
|
#define GAKUMAS_LOCALIFY_LOG_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
namespace GakumasLocal::Log {
|
namespace GakumasLocal::Log {
|
||||||
std::string StringFormat(const char* fmt, ...);
|
std::string StringFormat(const char* fmt, ...);
|
||||||
@ -16,6 +17,8 @@ namespace GakumasLocal::Log {
|
|||||||
|
|
||||||
void ShowToast(const char* text);
|
void ShowToast(const char* text);
|
||||||
void ShowToastFmt(const char* fmt, ...);
|
void ShowToastFmt(const char* fmt, ...);
|
||||||
|
|
||||||
|
void ToastLoop(JNIEnv *env, jclass clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif //GAKUMAS_LOCALIFY_LOG_H
|
#endif //GAKUMAS_LOCALIFY_LOG_H
|
||||||
|
@ -62,14 +62,14 @@ namespace BaseCamera {
|
|||||||
return lookAt;
|
return lookAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::set_lon_move(float vertanglePlus, LonMoveHState moveState) { // 前后移动
|
void Camera::set_lon_move(float vertanglePlus, LonMoveHState moveState, float multiplier) { // 前后移动
|
||||||
auto radian = (verticalAngle + vertanglePlus) * M_PI / 180;
|
auto radian = (verticalAngle + vertanglePlus) * M_PI / 180;
|
||||||
auto radianH = (double)horizontalAngle * M_PI / 180;
|
auto radianH = (double)horizontalAngle * M_PI / 180;
|
||||||
|
|
||||||
auto f_step = cos(radian) * moveStep * cos(radianH) / smoothLevel; // ↑↓
|
auto f_step = cos(radian) * moveStep * cos(radianH) / smoothLevel * multiplier; // ↑↓
|
||||||
auto l_step = sin(radian) * moveStep * cos(radianH) / smoothLevel; // ←→
|
auto l_step = sin(radian) * moveStep * cos(radianH) / smoothLevel * multiplier; // ←→
|
||||||
// auto h_step = tan(radianH) * sqrt(pow(f_step, 2) + pow(l_step, 2));
|
// auto h_step = tan(radianH) * sqrt(pow(f_step, 2) + pow(l_step, 2));
|
||||||
auto h_step = sin(radianH) * moveStep / smoothLevel;
|
auto h_step = sin(radianH) * moveStep / smoothLevel * multiplier;
|
||||||
|
|
||||||
switch (moveState)
|
switch (moveState)
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace BaseCamera {
|
|||||||
void setPos(float x, float y, float z);
|
void setPos(float x, float y, float z);
|
||||||
void setLookAt(float x, float y, float z);
|
void setLookAt(float x, float y, float z);
|
||||||
|
|
||||||
void set_lon_move(float vertanglePlus, LonMoveHState moveState = LonMoveHState::LonMoveLeftAndRight);
|
void set_lon_move(float vertanglePlus, LonMoveHState moveState = LonMoveHState::LonMoveLeftAndRight, float multiplier = 1.0f);
|
||||||
void updateVertLook();
|
void updateVertLook();
|
||||||
void setHoriLook(float vertangle);
|
void setHoriLook(float vertangle);
|
||||||
|
|
||||||
|
@ -16,6 +16,9 @@ namespace GKCamera {
|
|||||||
UnityResolve::UnityType::Vector2 followLookAtOffset{0, 0};
|
UnityResolve::UnityType::Vector2 followLookAtOffset{0, 0};
|
||||||
float offsetMoveStep = 0.008;
|
float offsetMoveStep = 0.008;
|
||||||
int followCharaIndex = 0;
|
int followCharaIndex = 0;
|
||||||
|
float l_sensitivity = 0.5f;
|
||||||
|
float r_sensitivity = 0.5f;
|
||||||
|
bool showToast = true;
|
||||||
GakumasLocal::Misc::CSEnum bodyPartsEnum("Head", 0xa);
|
GakumasLocal::Misc::CSEnum bodyPartsEnum("Head", 0xa);
|
||||||
|
|
||||||
// bool rMousePressFlg = false;
|
// bool rMousePressFlg = false;
|
||||||
@ -59,16 +62,16 @@ namespace GKCamera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void camera_back() { // 后退
|
void camera_back(float multiplier = 1.0f) { // 后退
|
||||||
switch (cameraMode) {
|
switch (cameraMode) {
|
||||||
case CameraMode::FREE: {
|
case CameraMode::FREE: {
|
||||||
baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack);
|
baseCamera.set_lon_move(180, LonMoveHState::LonMoveBack, multiplier);
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FIRST_PERSON: {
|
case CameraMode::FIRST_PERSON: {
|
||||||
firstPersonPosOffset.z -= offsetMoveStep;
|
firstPersonPosOffset.z -= offsetMoveStep * multiplier;
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FOLLOW: {
|
case CameraMode::FOLLOW: {
|
||||||
followPosOffset.z += offsetMoveStep;
|
followPosOffset.z += offsetMoveStep * multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,24 +89,24 @@ namespace GKCamera {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
void camera_right() { // 向右
|
void camera_right(float multiplier = 1.0f) { // 向右
|
||||||
switch (cameraMode) {
|
switch (cameraMode) {
|
||||||
case CameraMode::FREE: {
|
case CameraMode::FREE: {
|
||||||
baseCamera.set_lon_move(-90);
|
baseCamera.set_lon_move(-90, LonMoveLeftAndRight, multiplier);
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FOLLOW: {
|
case CameraMode::FOLLOW: {
|
||||||
// followPosOffset.x -= 0.8;
|
// followPosOffset.x -= 0.8;
|
||||||
followLookAtOffset.x -= offsetMoveStep;
|
followLookAtOffset.x -= offsetMoveStep * multiplier;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void camera_down() { // 向下
|
void camera_down(float multiplier = 1.0f) { // 向下
|
||||||
switch (cameraMode) {
|
switch (cameraMode) {
|
||||||
case CameraMode::FREE: {
|
case CameraMode::FREE: {
|
||||||
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel;
|
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel * multiplier;
|
||||||
|
|
||||||
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
|
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
|
||||||
baseCamera.pos.y -= preStep;
|
baseCamera.pos.y -= preStep;
|
||||||
@ -112,19 +115,19 @@ namespace GKCamera {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FIRST_PERSON: {
|
case CameraMode::FIRST_PERSON: {
|
||||||
firstPersonPosOffset.y -= offsetMoveStep;
|
firstPersonPosOffset.y -= offsetMoveStep * multiplier;
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FOLLOW: {
|
case CameraMode::FOLLOW: {
|
||||||
// followPosOffset.y -= offsetMoveStep;
|
// followPosOffset.y -= offsetMoveStep;
|
||||||
followLookAtOffset.y -= offsetMoveStep;
|
followLookAtOffset.y -= offsetMoveStep * multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void camera_up() { // 向上
|
void camera_up(float multiplier = 1.0f) { // 向上
|
||||||
switch (cameraMode) {
|
switch (cameraMode) {
|
||||||
case CameraMode::FREE: {
|
case CameraMode::FREE: {
|
||||||
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel;
|
float preStep = BaseCamera::moveStep / BaseCamera::smoothLevel * multiplier;
|
||||||
|
|
||||||
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
|
for (int i = 0; i < BaseCamera::smoothLevel; i++) {
|
||||||
baseCamera.pos.y += preStep;
|
baseCamera.pos.y += preStep;
|
||||||
@ -133,11 +136,11 @@ namespace GKCamera {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FIRST_PERSON: {
|
case CameraMode::FIRST_PERSON: {
|
||||||
firstPersonPosOffset.y += offsetMoveStep;
|
firstPersonPosOffset.y += offsetMoveStep * multiplier;
|
||||||
} break;
|
} break;
|
||||||
case CameraMode::FOLLOW: {
|
case CameraMode::FOLLOW: {
|
||||||
// followPosOffset.y += offsetMoveStep;
|
// followPosOffset.y += offsetMoveStep;
|
||||||
followLookAtOffset.y += offsetMoveStep;
|
followLookAtOffset.y += offsetMoveStep * multiplier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,6 +252,142 @@ namespace GKCamera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ShowToast(const char *text) {
|
||||||
|
if (showToast) {
|
||||||
|
GakumasLocal::Log::ShowToast(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JLThumbRight(float value) {
|
||||||
|
camera_right(value * l_sensitivity * baseCamera.fov / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JLThumbDown(float value) {
|
||||||
|
camera_back(value * l_sensitivity * baseCamera.fov / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JRThumbRight(float value) {
|
||||||
|
cameraLookat_right(value * r_sensitivity * baseCamera.fov / 60);
|
||||||
|
ChangeLiveFollowCameraOffsetX(-1 * value * r_sensitivity * baseCamera.fov / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JRThumbDown(float value) {
|
||||||
|
cameraLookat_down(value * r_sensitivity * baseCamera.fov / 60);
|
||||||
|
ChangeLiveFollowCameraOffsetY(-0.1 * value * r_sensitivity * baseCamera.fov / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JDadUp(){
|
||||||
|
reset_camera();
|
||||||
|
ShowToast("Reset Camera");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JDadDown(){
|
||||||
|
ShowToast("Notification off, click again to turn it on.");
|
||||||
|
showToast = !showToast;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JDadLeft(){
|
||||||
|
l_sensitivity = 1.0f;
|
||||||
|
ShowToast("Reset Movement Sensitivity");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JDadRight(){
|
||||||
|
r_sensitivity = 1.0f;
|
||||||
|
ShowToast("Reset Camera Sensitivity");
|
||||||
|
}
|
||||||
|
|
||||||
|
void JAKeyDown() {
|
||||||
|
if (cameraMode == CameraMode::FOLLOW) {
|
||||||
|
const auto currPart = bodyPartsEnum.Next();
|
||||||
|
if (showToast) {
|
||||||
|
GakumasLocal::Log::ShowToastFmt("Look at: %s (0x%x)", currPart.first.c_str(),
|
||||||
|
currPart.second);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r_sensitivity *= 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JBKeyDown() {
|
||||||
|
if (cameraMode == CameraMode::FOLLOW) {
|
||||||
|
const auto currPart = bodyPartsEnum.Last();
|
||||||
|
if (showToast) {
|
||||||
|
GakumasLocal::Log::ShowToastFmt("Look at: %s (0x%x)", currPart.first.c_str(),
|
||||||
|
currPart.second);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r_sensitivity *= 1.2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JXKeyDown() {
|
||||||
|
if (cameraMode == CameraMode::FOLLOW) {
|
||||||
|
OnLeftDown();
|
||||||
|
if (showToast) {
|
||||||
|
GakumasLocal::Log::ShowToastFmt("Look at position: %d", followCharaIndex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l_sensitivity *= 0.8f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JYKeyDown() {
|
||||||
|
if (cameraMode == CameraMode::FOLLOW) {
|
||||||
|
OnRightDown();
|
||||||
|
if (showToast) {
|
||||||
|
GakumasLocal::Log::ShowToastFmt("Look at position: %d", followCharaIndex);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l_sensitivity *= 1.2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JSelectKeyDown() {
|
||||||
|
switch (cameraMode) {
|
||||||
|
case CameraMode::FREE: {
|
||||||
|
cameraMode = CameraMode::FOLLOW;
|
||||||
|
ShowToast("Follow Mode");
|
||||||
|
} break;
|
||||||
|
case CameraMode::FOLLOW: {
|
||||||
|
cameraMode = CameraMode::FIRST_PERSON;
|
||||||
|
ShowToast("First-person Mode");
|
||||||
|
} break;
|
||||||
|
case CameraMode::FIRST_PERSON: {
|
||||||
|
cameraMode = CameraMode::FREE;
|
||||||
|
ShowToast("Free Mode");
|
||||||
|
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void JStartKeyDown() {
|
||||||
|
switch (cameraMode) {
|
||||||
|
case CameraMode::FIRST_PERSON: {
|
||||||
|
if (firstPersonRoll == FirstPersonRoll::ENABLE_ROLL) {
|
||||||
|
firstPersonRoll = FirstPersonRoll::DISABLE_ROLL;
|
||||||
|
ShowToast("Camera Horizontal Fixed");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
firstPersonRoll = FirstPersonRoll::ENABLE_ROLL;
|
||||||
|
ShowToast("Camera Horizontal Rollable");
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case CameraMode::FOLLOW: {
|
||||||
|
if (followModeY == FollowModeY::APPLY_Y) {
|
||||||
|
followModeY = FollowModeY::SMOOTH_Y;
|
||||||
|
ShowToast("Smooth Lift");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
followModeY = FollowModeY::APPLY_Y;
|
||||||
|
ShowToast("Instant Lift");
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target,
|
UnityResolve::UnityType::Vector3 CalcPositionFromLookAt(const UnityResolve::UnityType::Vector3& target,
|
||||||
const UnityResolve::UnityType::Vector3& offset) {
|
const UnityResolve::UnityType::Vector3& offset) {
|
||||||
// offset: z 远近, y 高低, x角度
|
// offset: z 远近, y 高低, x角度
|
||||||
@ -350,14 +489,50 @@ namespace GKCamera {
|
|||||||
bool k = false;
|
bool k = false;
|
||||||
bool j = false;
|
bool j = false;
|
||||||
bool l = false;
|
bool l = false;
|
||||||
|
float thumb_l_right = 0.0f;
|
||||||
|
float thumb_l_down = 0.0f;
|
||||||
|
bool thumb_l_button = false;
|
||||||
|
float thumb_r_right = 0.0f;
|
||||||
|
float thumb_r_down = 0.0f;
|
||||||
|
bool thumb_r_button = false;
|
||||||
|
bool dpad_up = false;
|
||||||
|
bool dpad_down = false;
|
||||||
|
bool dpad_left = false;
|
||||||
|
bool dpad_right = false;
|
||||||
|
bool a_button = false;
|
||||||
|
bool b_button = false;
|
||||||
|
bool x_button = false;
|
||||||
|
bool y_button = false;
|
||||||
|
bool lb_button = false;
|
||||||
|
float lt_button = 0.0f;
|
||||||
|
bool rb_button = false;
|
||||||
|
float rt_button = 0.0f;
|
||||||
|
bool select_button = false;
|
||||||
|
bool start_button = false;
|
||||||
|
bool share_button = false;
|
||||||
|
bool xbox_button = false;
|
||||||
bool threadRunning = false;
|
bool threadRunning = false;
|
||||||
|
|
||||||
void resetAll() {
|
void resetAll() {
|
||||||
auto p = reinterpret_cast<bool*>(this);
|
// 获取当前对象的指针并转换为 unsigned char* 类型
|
||||||
const auto numMembers = sizeof(*this) / sizeof(bool);
|
unsigned char* p = reinterpret_cast<unsigned char*>(this);
|
||||||
for (size_t idx = 0; idx < numMembers; ++idx) {
|
|
||||||
p[idx] = false;
|
// 遍历对象的每个字节
|
||||||
}
|
for (size_t offset = 0; offset < sizeof(*this); ) {
|
||||||
|
if (offset + sizeof(bool) <= sizeof(*this) && reinterpret_cast<bool*>(p + offset) == reinterpret_cast<bool*>(this) + offset / sizeof(bool)) {
|
||||||
|
// 如果当前偏移量适用于 bool 类型,则将其设置为 false
|
||||||
|
*reinterpret_cast<bool*>(p + offset) = false;
|
||||||
|
offset += sizeof(bool);
|
||||||
|
} else if (offset + sizeof(float) <= sizeof(*this) && reinterpret_cast<float*>(p + offset) == reinterpret_cast<float*>(this) + offset / sizeof(float)) {
|
||||||
|
// 如果当前偏移量适用于 float 类型,则将其设置为 0.0
|
||||||
|
*reinterpret_cast<float*>(p + offset) = 0.0f;
|
||||||
|
offset += sizeof(float);
|
||||||
|
} else {
|
||||||
|
// 处理未定义的情况(例如混合类型数组或其他类型成员)
|
||||||
|
// 可以根据实际情况调整逻辑或添加更多类型检查
|
||||||
|
offset += 1; // 跳过一个字节
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} cameraMoveState;
|
} cameraMoveState;
|
||||||
|
|
||||||
@ -385,6 +560,32 @@ namespace GKCamera {
|
|||||||
if (cameraMoveState.k) ChangeLiveFollowCameraOffsetY(-offsetMoveStep);
|
if (cameraMoveState.k) ChangeLiveFollowCameraOffsetY(-offsetMoveStep);
|
||||||
if (cameraMoveState.j) ChangeLiveFollowCameraOffsetX(0.8);
|
if (cameraMoveState.j) ChangeLiveFollowCameraOffsetX(0.8);
|
||||||
if (cameraMoveState.l) ChangeLiveFollowCameraOffsetX(-0.8);
|
if (cameraMoveState.l) ChangeLiveFollowCameraOffsetX(-0.8);
|
||||||
|
// 手柄操作响应
|
||||||
|
// 左摇杆
|
||||||
|
if (std::abs(cameraMoveState.thumb_l_right) > 0.1f)
|
||||||
|
JLThumbRight(cameraMoveState.thumb_l_right);
|
||||||
|
if (std::abs(cameraMoveState.thumb_l_down) > 0.1f)
|
||||||
|
JLThumbDown(cameraMoveState.thumb_l_down);
|
||||||
|
// 右摇杆
|
||||||
|
if (std::abs(cameraMoveState.thumb_r_right) > 0.1f)
|
||||||
|
JRThumbRight(cameraMoveState.thumb_r_right);
|
||||||
|
if (std::abs(cameraMoveState.thumb_r_down) > 0.1f)
|
||||||
|
JRThumbDown(cameraMoveState.thumb_r_down);
|
||||||
|
// 左扳机
|
||||||
|
if (std::abs(cameraMoveState.lt_button) > 0.1f)
|
||||||
|
camera_down(cameraMoveState.lt_button * l_sensitivity * baseCamera.fov / 60);
|
||||||
|
// 右扳机
|
||||||
|
if (std::abs(cameraMoveState.rt_button) > 0.1f)
|
||||||
|
camera_up(cameraMoveState.rt_button * l_sensitivity * baseCamera.fov / 60);
|
||||||
|
// 左肩键
|
||||||
|
if (cameraMoveState.lb_button) changeCameraFOV(0.5f * r_sensitivity);
|
||||||
|
// 右肩键
|
||||||
|
if (cameraMoveState.rb_button) changeCameraFOV(-0.5f * r_sensitivity);
|
||||||
|
// 十字键
|
||||||
|
if (cameraMoveState.dpad_up) JDadUp();
|
||||||
|
// if (cameraMoveState.dpad_down) JDadDown();
|
||||||
|
if (cameraMoveState.dpad_left) JDadLeft();
|
||||||
|
if (cameraMoveState.dpad_right) JDadRight();
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
}
|
}
|
||||||
}).detach();
|
}).detach();
|
||||||
@ -446,11 +647,88 @@ namespace GKCamera {
|
|||||||
} break;
|
} break;
|
||||||
case KEY_F: if (message == WM_KEYDOWN) SwitchCameraMode(); break;
|
case KEY_F: if (message == WM_KEYDOWN) SwitchCameraMode(); break;
|
||||||
case KEY_V: if (message == WM_KEYDOWN) SwitchCameraSubMode(); break;
|
case KEY_V: if (message == WM_KEYDOWN) SwitchCameraSubMode(); break;
|
||||||
|
// 手柄操作响应
|
||||||
|
case BTN_A:
|
||||||
|
cameraMoveState.a_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JAKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_B:
|
||||||
|
cameraMoveState.b_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JBKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_X:
|
||||||
|
cameraMoveState.x_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JXKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_Y:
|
||||||
|
cameraMoveState.y_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JYKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_LB:
|
||||||
|
cameraMoveState.lb_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
case BTN_RB:
|
||||||
|
cameraMoveState.rb_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
case BTN_THUMBL:
|
||||||
|
cameraMoveState.thumb_l_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
case BTN_THUMBR:
|
||||||
|
cameraMoveState.thumb_r_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
case BTN_SELECT:
|
||||||
|
cameraMoveState.select_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JSelectKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_START:
|
||||||
|
cameraMoveState.start_button = message == WM_KEYDOWN;
|
||||||
|
if (message == WM_KEYDOWN) JStartKeyDown();
|
||||||
|
break;
|
||||||
|
case BTN_SHARE:
|
||||||
|
cameraMoveState.share_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
case BTN_XBOX:
|
||||||
|
cameraMoveState.xbox_button = message == WM_KEYDOWN;
|
||||||
|
break;
|
||||||
|
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
on_cam_rawinput_joystick(JoystickEvent event) {
|
||||||
|
int message = event.getMessage();
|
||||||
|
float leftStickX = event.getLeftStickX();
|
||||||
|
float leftStickY = event.getLeftStickY();
|
||||||
|
float rightStickX = event.getRightStickX();
|
||||||
|
float rightStickY = event.getRightStickY();
|
||||||
|
float leftTrigger = event.getLeftTrigger();
|
||||||
|
float rightTrigger = event.getRightTrigger();
|
||||||
|
float hatX = event.getHatX();
|
||||||
|
float hatY = event.getHatY();
|
||||||
|
|
||||||
|
cameraMoveState.thumb_l_right = (std::abs(leftStickX) > 0.1f) ? leftStickX : 0;
|
||||||
|
cameraMoveState.thumb_l_down = (std::abs(leftStickY) > 0.1f) ? leftStickY : 0;
|
||||||
|
cameraMoveState.thumb_r_right = (std::abs(rightStickX) > 0.1f) ? rightStickX : 0;
|
||||||
|
cameraMoveState.thumb_r_down = (std::abs(rightStickY) > 0.1f) ? rightStickY : 0;
|
||||||
|
cameraMoveState.lt_button = (std::abs(leftTrigger) > 0.1f) ? leftTrigger : 0;
|
||||||
|
cameraMoveState.rt_button = (std::abs(rightTrigger) > 0.1f) ? rightTrigger : 0;
|
||||||
|
cameraMoveState.dpad_up = hatY == -1.0f;
|
||||||
|
cameraMoveState.dpad_down = hatY == 1.0f;
|
||||||
|
cameraMoveState.dpad_left = hatX == -1.0f;
|
||||||
|
cameraMoveState.dpad_right = hatX == 1.0f;
|
||||||
|
|
||||||
|
if (cameraMoveState.dpad_down) {
|
||||||
|
JDadDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// GakumasLocal::Log::InfoFmt(
|
||||||
|
// "Motion event: action=%d, leftStickX=%.2f, leftStickY=%.2f, rightStickX=%.2f, rightStickY=%.2f, leftTrigger=%.2f, rightTrigger=%.2f, hatX=%.2f, hatY=%.2f",
|
||||||
|
// message, leftStickX, leftStickY, rightStickX, rightStickY, leftTrigger,
|
||||||
|
// rightTrigger, hatX, hatY);
|
||||||
|
}
|
||||||
|
|
||||||
void initCameraSettings() {
|
void initCameraSettings() {
|
||||||
reset_camera();
|
reset_camera();
|
||||||
cameraRawInputThread();
|
cameraRawInputThread();
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "baseCamera.hpp"
|
#include "baseCamera.hpp"
|
||||||
|
#include "Joystick/JoystickEvent.h"
|
||||||
|
|
||||||
namespace GKCamera {
|
namespace GKCamera {
|
||||||
enum class CameraMode {
|
enum class CameraMode {
|
||||||
@ -44,5 +45,6 @@ namespace GKCamera {
|
|||||||
const bool recordY = false);
|
const bool recordY = false);
|
||||||
|
|
||||||
void on_cam_rawinput_keyboard(int message, int key);
|
void on_cam_rawinput_keyboard(int message, int key);
|
||||||
|
void on_cam_rawinput_joystick(JoystickEvent event);
|
||||||
void initCameraSettings();
|
void initCameraSettings();
|
||||||
}
|
}
|
||||||
|
67
app/src/main/cpp/deps/Joystick/JoystickEvent.h
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
//
|
||||||
|
// Created by RanKaeder on 2024/6/18.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GAKUMAS_LOCALIFY_JOYSTICKEVENT_H
|
||||||
|
#define GAKUMAS_LOCALIFY_JOYSTICKEVENT_H
|
||||||
|
|
||||||
|
class JoystickEvent {
|
||||||
|
public:
|
||||||
|
JoystickEvent(int message, float leftStickX, float leftStickY, float rightStickX,
|
||||||
|
float rightStickY, float leftTrigger, float rightTrigger,
|
||||||
|
float hatX, float hatY)
|
||||||
|
: message(message), leftStickX(leftStickX), leftStickY(leftStickY),
|
||||||
|
rightStickX(rightStickX), rightStickY(rightStickY), leftTrigger(leftTrigger),
|
||||||
|
rightTrigger(rightTrigger), hatX(hatX), hatY(hatY) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter 方法
|
||||||
|
int getMessage() const {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getLeftStickX() const {
|
||||||
|
return leftStickX;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getLeftStickY() const {
|
||||||
|
return leftStickY;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRightStickX() const {
|
||||||
|
return rightStickX;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRightStickY() const {
|
||||||
|
return rightStickY;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getLeftTrigger() const {
|
||||||
|
return leftTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRightTrigger() const {
|
||||||
|
return rightTrigger;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getHatX() const {
|
||||||
|
return hatX;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getHatY() const {
|
||||||
|
return hatY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int message;
|
||||||
|
float leftStickX;
|
||||||
|
float leftStickY;
|
||||||
|
float rightStickX;
|
||||||
|
float rightStickY;
|
||||||
|
float leftTrigger;
|
||||||
|
float rightTrigger;
|
||||||
|
float hatX;
|
||||||
|
float hatY;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //GAKUMAS_LOCALIFY_JOYSTICKEVENT_H
|
@ -9,6 +9,7 @@
|
|||||||
#include "xdl.h"
|
#include "xdl.h"
|
||||||
#include "GakumasLocalify/camera/camera.hpp"
|
#include "GakumasLocalify/camera/camera.hpp"
|
||||||
#include "GakumasLocalify/config/Config.hpp"
|
#include "GakumasLocalify/config/Config.hpp"
|
||||||
|
#include "Joystick/JoystickEvent.h"
|
||||||
|
|
||||||
JavaVM* g_javaVM = nullptr;
|
JavaVM* g_javaVM = nullptr;
|
||||||
jclass g_gakumasHookMainClass = nullptr;
|
jclass g_gakumasHookMainClass = nullptr;
|
||||||
@ -87,6 +88,22 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_keyboardEvent(JNIEnv *en
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_joystickEvent(JNIEnv *env, jclass clazz,
|
||||||
|
jint action,
|
||||||
|
jfloat leftStickX,
|
||||||
|
jfloat leftStickY,
|
||||||
|
jfloat rightStickX,
|
||||||
|
jfloat rightStickY,
|
||||||
|
jfloat leftTrigger,
|
||||||
|
jfloat rightTrigger,
|
||||||
|
jfloat hatX,
|
||||||
|
jfloat hatY) {
|
||||||
|
JoystickEvent event(action, leftStickX, leftStickY, rightStickX, rightStickY, leftTrigger, rightTrigger, hatX, hatY);
|
||||||
|
GKCamera::on_cam_rawinput_joystick(event);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env, jclass clazz,
|
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env, jclass clazz,
|
||||||
@ -94,4 +111,11 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
|
|||||||
const auto configJsonStrChars = env->GetStringUTFChars(config_json_str, nullptr);
|
const auto configJsonStrChars = env->GetStringUTFChars(config_json_str, nullptr);
|
||||||
const std::string configJson = configJsonStrChars;
|
const std::string configJson = configJsonStrChars;
|
||||||
GakumasLocal::Config::LoadConfig(configJson);
|
GakumasLocal::Config::LoadConfig(configJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
JNIEXPORT void JNICALL
|
||||||
|
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
|
||||||
|
jclass clazz) {
|
||||||
|
GakumasLocal::Log::ToastLoop(env, clazz);
|
||||||
}
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfigSerializer
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
interface IHasConfigItems {
|
||||||
|
var config: GakumasConfig
|
||||||
|
var programConfig: ProgramConfig
|
||||||
|
|
||||||
|
fun saveConfig() {} // do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IConfigurableActivity<T : Activity> : IHasConfigItems
|
||||||
|
|
||||||
|
|
||||||
|
fun <T> T.getConfigContent(): String where T : Activity {
|
||||||
|
val configFile = File(filesDir, "gkms-config.json")
|
||||||
|
return if (configFile.exists()) {
|
||||||
|
configFile.readText()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "检测到第一次启动,初始化配置文件...", Toast.LENGTH_SHORT).show()
|
||||||
|
configFile.writeText("{}")
|
||||||
|
"{}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> T.getProgramConfigContent(
|
||||||
|
excludes: List<String> = emptyList(),
|
||||||
|
origProgramConfig: ProgramConfig? = null
|
||||||
|
): String where T : Activity {
|
||||||
|
val configFile = File(filesDir, "localify-config.json")
|
||||||
|
if (excludes.isEmpty()) {
|
||||||
|
return if (configFile.exists()) {
|
||||||
|
configFile.readText()
|
||||||
|
} else {
|
||||||
|
"{}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return if (origProgramConfig == null) {
|
||||||
|
if (configFile.exists()) {
|
||||||
|
val parsedConfig = json.decodeFromString<ProgramConfig>(configFile.readText())
|
||||||
|
json.encodeToString(ProgramConfigSerializer(excludes), parsedConfig)
|
||||||
|
} else {
|
||||||
|
"{}"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
json.encodeToString(ProgramConfigSerializer(excludes), origProgramConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> T.loadConfig() where T : Activity, T : IHasConfigItems {
|
||||||
|
val configStr = getConfigContent()
|
||||||
|
config = try {
|
||||||
|
json.decodeFromString<GakumasConfig>(configStr)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
Toast.makeText(this, "配置文件异常: $e", Toast.LENGTH_SHORT).show()
|
||||||
|
GakumasConfig()
|
||||||
|
}
|
||||||
|
saveConfig()
|
||||||
|
|
||||||
|
val programConfigStr = getProgramConfigContent()
|
||||||
|
programConfig = try {
|
||||||
|
json.decodeFromString<ProgramConfig>(programConfigStr)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
ProgramConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
|
||||||
|
val intent = Intent().apply {
|
||||||
|
setClassName(
|
||||||
|
"com.bandainamcoent.idolmaster_gakuen",
|
||||||
|
"com.google.firebase.MessagingUnityPlayerActivity"
|
||||||
|
)
|
||||||
|
putExtra("gkmsData", getConfigContent())
|
||||||
|
putExtra(
|
||||||
|
"localData",
|
||||||
|
getProgramConfigContent(listOf("transRemoteZipUrl", "p"), programConfig)
|
||||||
|
)
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
}
|
||||||
|
|
||||||
|
val updateFile = File(filesDir, "update_trans.zip")
|
||||||
|
if (updateFile.exists()) {
|
||||||
|
val dirUri = FileProvider.getUriForFile(
|
||||||
|
this,
|
||||||
|
"io.github.chinosk.gakumas.localify.fileprovider",
|
||||||
|
File(updateFile.absolutePath)
|
||||||
|
)
|
||||||
|
intent.setDataAndType(dirUri, "resource/file")
|
||||||
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||||
|
}
|
||||||
|
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
@ -1,11 +1,18 @@
|
|||||||
package io.github.chinosk.gakumas.localify
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
|
||||||
interface ConfigListener {
|
interface ConfigListener {
|
||||||
fun onClickStartGame()
|
|
||||||
fun onEnabledChanged(value: Boolean)
|
fun onEnabledChanged(value: Boolean)
|
||||||
fun onForceExportResourceChanged(value: Boolean)
|
fun onForceExportResourceChanged(value: Boolean)
|
||||||
fun onTextTestChanged(value: Boolean)
|
fun onTextTestChanged(value: Boolean)
|
||||||
@ -45,47 +52,75 @@ interface ConfigListener {
|
|||||||
fun onBUseArmCorrectionChanged(value: Boolean)
|
fun onBUseArmCorrectionChanged(value: Boolean)
|
||||||
fun onBUseScaleChanged(value: Boolean)
|
fun onBUseScaleChanged(value: Boolean)
|
||||||
fun onBClickPresetChanged(index: Int)
|
fun onBClickPresetChanged(index: Int)
|
||||||
|
fun onPCheckBuiltInAssetsChanged(value: Boolean)
|
||||||
|
fun onPUseRemoteAssetsChanged(value: Boolean)
|
||||||
|
fun onPCleanLocalAssetsChanged(value: Boolean)
|
||||||
|
fun onPDelRemoteAfterUpdateChanged(value: Boolean)
|
||||||
|
fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||||
|
fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean? = null,
|
||||||
|
downloadProgressState: Float? = null,
|
||||||
|
localResourceVersionState: String? = null,
|
||||||
|
errorString: String? = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(UserConfigViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return UserConfigViewModel(initialValue) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() {
|
||||||
|
val configState = MutableStateFlow(initValue)
|
||||||
|
val config: StateFlow<GakumasConfig> = configState.asStateFlow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface ConfigUpdateListener: ConfigListener {
|
interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
|
||||||
var binding: ActivityMainBinding
|
var factory: UserConfigViewModelFactory
|
||||||
|
var viewModel: UserConfigViewModel
|
||||||
|
|
||||||
|
var programConfigFactory: ProgramConfigViewModelFactory
|
||||||
|
var programConfigViewModel: ProgramConfigViewModel
|
||||||
|
|
||||||
fun pushKeyEvent(event: KeyEvent): Boolean
|
fun pushKeyEvent(event: KeyEvent): Boolean
|
||||||
fun getConfigContent(): String
|
fun checkConfigAndUpdateView() {} // do nothing
|
||||||
fun checkConfigAndUpdateView()
|
// fun saveConfig()
|
||||||
fun saveConfig()
|
fun saveProgramConfig()
|
||||||
|
|
||||||
|
|
||||||
override fun onEnabledChanged(value: Boolean) {
|
override fun onEnabledChanged(value: Boolean) {
|
||||||
binding.config!!.enabled = value
|
config.enabled = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
pushKeyEvent(KeyEvent(1145, 29))
|
pushKeyEvent(KeyEvent(1145, 29))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onForceExportResourceChanged(value: Boolean) {
|
override fun onForceExportResourceChanged(value: Boolean) {
|
||||||
binding.config!!.forceExportResource = value
|
config.forceExportResource = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
pushKeyEvent(KeyEvent(1145, 30))
|
pushKeyEvent(KeyEvent(1145, 30))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onTextTestChanged(value: Boolean) {
|
override fun onTextTestChanged(value: Boolean) {
|
||||||
binding.config!!.textTest = value
|
config.textTest = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDumpTextChanged(value: Boolean) {
|
override fun onDumpTextChanged(value: Boolean) {
|
||||||
binding.config!!.dumpText = value
|
config.dumpText = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnableFreeCameraChanged(value: Boolean) {
|
override fun onEnableFreeCameraChanged(value: Boolean) {
|
||||||
binding.config!!.enableFreeCamera = value
|
config.enableFreeCamera = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnlockAllLiveChanged(value: Boolean) {
|
override fun onUnlockAllLiveChanged(value: Boolean) {
|
||||||
binding.config!!.unlockAllLive = value
|
config.unlockAllLive = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +133,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
} else {
|
} else {
|
||||||
valueStr.toInt()
|
valueStr.toInt()
|
||||||
}
|
}
|
||||||
binding.config!!.targetFrameRate = value
|
config.targetFrameRate = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -107,22 +142,22 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLiveCustomeDressChanged(value: Boolean) {
|
override fun onLiveCustomeDressChanged(value: Boolean) {
|
||||||
binding.config!!.enableLiveCustomeDress = value
|
config.enableLiveCustomeDress = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.liveCustomeCostumeId = s.toString()
|
config.liveCustomeCostumeId = s.toString()
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUseCustomeGraphicSettingsChanged(value: Boolean) {
|
override fun onUseCustomeGraphicSettingsChanged(value: Boolean) {
|
||||||
binding.config!!.useCustomeGraphicSettings = value
|
config.useCustomeGraphicSettings = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.renderScale = try {
|
config.renderScale = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -132,7 +167,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.qualitySettingsLevel = try {
|
config.qualitySettingsLevel = try {
|
||||||
s.toString().toInt()
|
s.toString().toInt()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -142,7 +177,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.volumeIndex = try {
|
config.volumeIndex = try {
|
||||||
s.toString().toInt()
|
s.toString().toInt()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -152,7 +187,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.maxBufferPixel = try {
|
config.maxBufferPixel = try {
|
||||||
s.toString().toInt()
|
s.toString().toInt()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -162,12 +197,12 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.liveCustomeHeadId = s.toString()
|
config.liveCustomeHeadId = s.toString()
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.reflectionQualityLevel = try {
|
config.reflectionQualityLevel = try {
|
||||||
val value = s.toString().toInt()
|
val value = s.toString().toInt()
|
||||||
if (value > 5) 5 else value
|
if (value > 5) 5 else value
|
||||||
}
|
}
|
||||||
@ -178,7 +213,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.lodQualityLevel = try {
|
config.lodQualityLevel = try {
|
||||||
val value = s.toString().toInt()
|
val value = s.toString().toInt()
|
||||||
if (value > 5) 5 else value
|
if (value > 5) 5 else value
|
||||||
}
|
}
|
||||||
@ -191,44 +226,44 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
override fun onChangePresetQuality(level: Int) {
|
override fun onChangePresetQuality(level: Int) {
|
||||||
when (level) {
|
when (level) {
|
||||||
0 -> {
|
0 -> {
|
||||||
binding.config!!.renderScale = 0.5f
|
config.renderScale = 0.5f
|
||||||
binding.config!!.qualitySettingsLevel = 1
|
config.qualitySettingsLevel = 1
|
||||||
binding.config!!.volumeIndex = 0
|
config.volumeIndex = 0
|
||||||
binding.config!!.maxBufferPixel = 1024
|
config.maxBufferPixel = 1024
|
||||||
binding.config!!.lodQualityLevel = 1
|
config.lodQualityLevel = 1
|
||||||
binding.config!!.reflectionQualityLevel = 1
|
config.reflectionQualityLevel = 1
|
||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
binding.config!!.renderScale = 0.59f
|
config.renderScale = 0.59f
|
||||||
binding.config!!.qualitySettingsLevel = 1
|
config.qualitySettingsLevel = 1
|
||||||
binding.config!!.volumeIndex = 1
|
config.volumeIndex = 1
|
||||||
binding.config!!.maxBufferPixel = 1440
|
config.maxBufferPixel = 1440
|
||||||
binding.config!!.lodQualityLevel = 2
|
config.lodQualityLevel = 2
|
||||||
binding.config!!.reflectionQualityLevel = 2
|
config.reflectionQualityLevel = 2
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
binding.config!!.renderScale = 0.67f
|
config.renderScale = 0.67f
|
||||||
binding.config!!.qualitySettingsLevel = 2
|
config.qualitySettingsLevel = 2
|
||||||
binding.config!!.volumeIndex = 2
|
config.volumeIndex = 2
|
||||||
binding.config!!.maxBufferPixel = 2538
|
config.maxBufferPixel = 2538
|
||||||
binding.config!!.lodQualityLevel = 3
|
config.lodQualityLevel = 3
|
||||||
binding.config!!.reflectionQualityLevel = 3
|
config.reflectionQualityLevel = 3
|
||||||
}
|
}
|
||||||
3 -> {
|
3 -> {
|
||||||
binding.config!!.renderScale = 0.77f
|
config.renderScale = 0.77f
|
||||||
binding.config!!.qualitySettingsLevel = 3
|
config.qualitySettingsLevel = 3
|
||||||
binding.config!!.volumeIndex = 3
|
config.volumeIndex = 3
|
||||||
binding.config!!.maxBufferPixel = 3384
|
config.maxBufferPixel = 3384
|
||||||
binding.config!!.lodQualityLevel = 4
|
config.lodQualityLevel = 4
|
||||||
binding.config!!.reflectionQualityLevel = 4
|
config.reflectionQualityLevel = 4
|
||||||
}
|
}
|
||||||
4 -> {
|
4 -> {
|
||||||
binding.config!!.renderScale = 1.0f
|
config.renderScale = 1.0f
|
||||||
binding.config!!.qualitySettingsLevel = 5
|
config.qualitySettingsLevel = 5
|
||||||
binding.config!!.volumeIndex = 4
|
config.volumeIndex = 4
|
||||||
binding.config!!.maxBufferPixel = 8190
|
config.maxBufferPixel = 8190
|
||||||
binding.config!!.lodQualityLevel = 5
|
config.lodQualityLevel = 5
|
||||||
binding.config!!.reflectionQualityLevel = 5
|
config.reflectionQualityLevel = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checkConfigAndUpdateView()
|
checkConfigAndUpdateView()
|
||||||
@ -236,33 +271,31 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onGameOrientationChanged(checkedId: Int) {
|
override fun onGameOrientationChanged(checkedId: Int) {
|
||||||
when (checkedId) {
|
if (checkedId in listOf(0, 1, 2)) {
|
||||||
R.id.radioButtonGameDefault -> binding.config!!.gameOrientation = 0
|
config.gameOrientation = checkedId
|
||||||
R.id.radioButtonGamePortrait -> binding.config!!.gameOrientation = 1
|
|
||||||
R.id.radioButtonGameLandscape -> binding.config!!.gameOrientation = 2
|
|
||||||
}
|
}
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnableBreastParamChanged(value: Boolean) {
|
override fun onEnableBreastParamChanged(value: Boolean) {
|
||||||
binding.config!!.enableBreastParam = value
|
config.enableBreastParam = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
checkConfigAndUpdateView()
|
checkConfigAndUpdateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBUseArmCorrectionChanged(value: Boolean) {
|
override fun onBUseArmCorrectionChanged(value: Boolean) {
|
||||||
binding.config!!.bUseArmCorrection = value
|
config.bUseArmCorrection = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBUseScaleChanged(value: Boolean) {
|
override fun onBUseScaleChanged(value: Boolean) {
|
||||||
binding.config!!.bUseScale = value
|
config.bUseScale = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
checkConfigAndUpdateView()
|
checkConfigAndUpdateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bDamping = try {
|
config.bDamping = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -272,7 +305,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bStiffness = try {
|
config.bStiffness = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -282,7 +315,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bSpring = try {
|
config.bSpring = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -292,7 +325,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bPendulum = try {
|
config.bPendulum = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -302,7 +335,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bPendulumRange = try {
|
config.bPendulumRange = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -312,7 +345,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bAverage = try {
|
config.bAverage = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -322,7 +355,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){
|
||||||
binding.config!!.bRootWeight = try {
|
config.bRootWeight = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -332,13 +365,13 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBUseLimitChanged(value: Boolean){
|
override fun onBUseLimitChanged(value: Boolean){
|
||||||
binding.config!!.bUseLimit = value
|
config.bUseLimit = value
|
||||||
saveConfig()
|
saveConfig()
|
||||||
checkConfigAndUpdateView()
|
checkConfigAndUpdateView()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitXx = try {
|
config.bLimitXx = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -348,7 +381,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitXy = try {
|
config.bLimitXy = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -358,7 +391,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitYx = try {
|
config.bLimitYx = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -368,7 +401,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitYy = try {
|
config.bLimitYy = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -378,7 +411,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitZx = try {
|
config.bLimitZx = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -388,7 +421,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bLimitZy = try {
|
config.bLimitZy = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -399,7 +432,7 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
|
|
||||||
|
|
||||||
override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
binding.config!!.bScale = try {
|
config.bScale = try {
|
||||||
s.toString().toFloat()
|
s.toString().toFloat()
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
@ -429,30 +462,62 @@ interface ConfigUpdateListener: ConfigListener {
|
|||||||
1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
|
1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.config!!.bDamping = setData[0]
|
config.bDamping = setData[0]
|
||||||
binding.config!!.bStiffness = setData[1]
|
config.bStiffness = setData[1]
|
||||||
binding.config!!.bSpring = setData[2]
|
config.bSpring = setData[2]
|
||||||
binding.config!!.bPendulum = setData[3]
|
config.bPendulum = setData[3]
|
||||||
binding.config!!.bPendulumRange = setData[4]
|
config.bPendulumRange = setData[4]
|
||||||
binding.config!!.bAverage = setData[5]
|
config.bAverage = setData[5]
|
||||||
binding.config!!.bRootWeight = setData[6]
|
config.bRootWeight = setData[6]
|
||||||
binding.config!!.bUseLimit = if (setData[7] == 0f) {
|
config.bUseLimit = if (setData[7] == 0f) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
binding.config!!.bLimitXx = setData[8]
|
config.bLimitXx = setData[8]
|
||||||
binding.config!!.bLimitXy = setData[9]
|
config.bLimitXy = setData[9]
|
||||||
binding.config!!.bLimitYx = setData[10]
|
config.bLimitYx = setData[10]
|
||||||
binding.config!!.bLimitYy = setData[11]
|
config.bLimitYy = setData[11]
|
||||||
binding.config!!.bLimitZx = setData[12]
|
config.bLimitZx = setData[12]
|
||||||
binding.config!!.bLimitZy = setData[13]
|
config.bLimitZy = setData[13]
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.config!!.bUseArmCorrection = true
|
config.bUseArmCorrection = true
|
||||||
|
|
||||||
checkConfigAndUpdateView()
|
checkConfigAndUpdateView()
|
||||||
saveConfig()
|
saveConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
override fun onPCheckBuiltInAssetsChanged(value: Boolean) {
|
||||||
|
programConfig.checkBuiltInAssets = value
|
||||||
|
saveProgramConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPUseRemoteAssetsChanged(value: Boolean) {
|
||||||
|
programConfig.useRemoteAssets = value
|
||||||
|
saveProgramConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPCleanLocalAssetsChanged(value: Boolean) {
|
||||||
|
programConfig.cleanLocalAssets = value
|
||||||
|
saveProgramConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPDelRemoteAfterUpdateChanged(value: Boolean) {
|
||||||
|
programConfig.delRemoteAfterUpdate = value
|
||||||
|
saveProgramConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPTransRemoteZipUrlChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||||
|
programConfig.transRemoteZipUrl = s.toString()
|
||||||
|
saveProgramConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mainPageAssetsViewDataUpdate(downloadAbleState: Boolean?, downloadProgressState: Float?,
|
||||||
|
localResourceVersionState: String?, errorString: String?) {
|
||||||
|
downloadAbleState?.let { programConfigViewModel.downloadAbleState.value = downloadAbleState }
|
||||||
|
downloadProgressState?.let{ programConfigViewModel.downloadProgressState.value = downloadProgressState }
|
||||||
|
localResourceVersionState?.let{ programConfigViewModel.localResourceVersionState.value = localResourceVersionState }
|
||||||
|
errorString?.let{ programConfigViewModel.errorStringState.value = errorString }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,21 +11,30 @@ import android.net.Uri
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.widget.Toast
|
||||||
import com.bytedance.shadowhook.ShadowHook
|
import com.bytedance.shadowhook.ShadowHook
|
||||||
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
|
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
|
||||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||||
import de.robv.android.xposed.XC_MethodHook
|
import de.robv.android.xposed.XC_MethodHook
|
||||||
|
import de.robv.android.xposed.XposedBridge
|
||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.widget.Toast
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import de.robv.android.xposed.XposedBridge
|
|
||||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
|
||||||
val TAG = "GakumasLocalify"
|
val TAG = "GakumasLocalify"
|
||||||
|
|
||||||
@ -39,8 +48,23 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
private var gkmsDataInited = false
|
private var gkmsDataInited = false
|
||||||
|
|
||||||
private var getConfigError: Exception? = null
|
private var getConfigError: Exception? = null
|
||||||
|
private var externalFilesChecked: Boolean = false
|
||||||
|
|
||||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
||||||
|
// if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
|
||||||
|
// XposedHelpers.findAndHookMethod(
|
||||||
|
// "io.github.chinosk.gakumas.localify.MainActivity",
|
||||||
|
// lpparam.classLoader,
|
||||||
|
// "showToast",
|
||||||
|
// String::class.java,
|
||||||
|
// object : XC_MethodHook() {
|
||||||
|
// override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
// Log.d(TAG, "beforeHookedMethod hooked: ${param.args}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
if (lpparam.packageName != targetPackageName) {
|
if (lpparam.packageName != targetPackageName) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -61,6 +85,50 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
XposedHelpers.findAndHookMethod(
|
||||||
|
"android.app.Activity",
|
||||||
|
lpparam.classLoader,
|
||||||
|
"dispatchGenericMotionEvent",
|
||||||
|
MotionEvent::class.java,
|
||||||
|
object : XC_MethodHook() {
|
||||||
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
|
val motionEvent = param.args[0] as MotionEvent
|
||||||
|
val action = motionEvent.action
|
||||||
|
|
||||||
|
// 左摇杆的X和Y轴
|
||||||
|
val leftStickX = motionEvent.getAxisValue(MotionEvent.AXIS_X)
|
||||||
|
val leftStickY = motionEvent.getAxisValue(MotionEvent.AXIS_Y)
|
||||||
|
|
||||||
|
// 右摇杆的X和Y轴
|
||||||
|
val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z)
|
||||||
|
val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ)
|
||||||
|
|
||||||
|
// 左扳机
|
||||||
|
val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER)
|
||||||
|
|
||||||
|
// 右扳机
|
||||||
|
val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER)
|
||||||
|
|
||||||
|
// 十字键
|
||||||
|
val hatX = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X)
|
||||||
|
val hatY = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y)
|
||||||
|
|
||||||
|
// 处理摇杆和扳机事件
|
||||||
|
joystickEvent(
|
||||||
|
action,
|
||||||
|
leftStickX,
|
||||||
|
leftStickY,
|
||||||
|
rightStickX,
|
||||||
|
rightStickY,
|
||||||
|
leftTrigger,
|
||||||
|
rightTrigger,
|
||||||
|
hatX,
|
||||||
|
hatY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
val appActivityClass = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader)
|
val appActivityClass = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader)
|
||||||
XposedBridge.hookAllMethods(appActivityClass, "onStart", object : XC_MethodHook() {
|
XposedBridge.hookAllMethods(appActivityClass, "onStart", object : XC_MethodHook() {
|
||||||
override fun beforeHookedMethod(param: MethodHookParam) {
|
override fun beforeHookedMethod(param: MethodHookParam) {
|
||||||
@ -118,7 +186,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
requestConfig(app.applicationContext)
|
requestConfig(app.applicationContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
FilesChecker.initAndCheck(app.filesDir, modulePath)
|
FilesChecker.initDir(app.filesDir, modulePath)
|
||||||
initHook(
|
initHook(
|
||||||
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
|
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
|
||||||
File(
|
File(
|
||||||
@ -130,23 +198,74 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
alreadyInitialized = true
|
alreadyInitialized = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
startLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
|
private fun startLoop() {
|
||||||
|
GlobalScope.launch {
|
||||||
|
val interval = 1000L / 30
|
||||||
|
while (isActive) {
|
||||||
|
val timeTaken = measureTimeMillis {
|
||||||
|
pluginCallbackLooper()
|
||||||
|
}
|
||||||
|
delay(interval - timeTaken)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initGkmsConfig(activity: Activity) {
|
fun initGkmsConfig(activity: Activity) {
|
||||||
val intent = activity.intent
|
val intent = activity.intent
|
||||||
val gkmsData = intent.getStringExtra("gkmsData")
|
val gkmsData = intent.getStringExtra("gkmsData")
|
||||||
|
val programData = intent.getStringExtra("localData")
|
||||||
if (gkmsData != null) {
|
if (gkmsData != null) {
|
||||||
gkmsDataInited = true
|
gkmsDataInited = true
|
||||||
val initConfig = try {
|
val initConfig = try {
|
||||||
Gson().fromJson(gkmsData, GakumasConfig::class.java)
|
json.decodeFromString<GakumasConfig>(gkmsData)
|
||||||
}
|
}
|
||||||
catch (e: Exception) {
|
catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
val programConfig = try {
|
||||||
|
if (programData == null) {
|
||||||
|
ProgramConfig()
|
||||||
|
} else {
|
||||||
|
json.decodeFromString<ProgramConfig>(programData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理本地文件
|
||||||
|
if (programConfig?.cleanLocalAssets == true) {
|
||||||
|
FilesChecker.cleanAssets()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 files 版本和 assets 版本并更新
|
||||||
|
if (programConfig?.checkBuiltInAssets == true) {
|
||||||
|
FilesChecker.initAndCheck(activity.filesDir, modulePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 强制导出 assets 文件
|
||||||
if (initConfig?.forceExportResource == true) {
|
if (initConfig?.forceExportResource == true) {
|
||||||
FilesChecker.updateFiles()
|
FilesChecker.updateFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用热更新文件
|
||||||
|
if (programConfig?.useRemoteAssets == true) {
|
||||||
|
val dataUri = intent.data
|
||||||
|
if (dataUri != null) {
|
||||||
|
if (!externalFilesChecked) {
|
||||||
|
externalFilesChecked = true
|
||||||
|
// Log.d(TAG, "dataUri: $dataUri")
|
||||||
|
FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir,
|
||||||
|
programConfig.delRemoteAfterUpdate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadConfig(gkmsData)
|
loadConfig(gkmsData)
|
||||||
Log.d(TAG, "gkmsData: $gkmsData")
|
Log.d(TAG, "gkmsData: $gkmsData")
|
||||||
}
|
}
|
||||||
@ -228,7 +347,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
fun requestConfig(activity: Context) {
|
fun requestConfig(activity: Context) {
|
||||||
try {
|
try {
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.MainActivity")
|
setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.TranslucentActivity")
|
||||||
putExtra("gkmsData", "requestConfig")
|
putExtra("gkmsData", "requestConfig")
|
||||||
flags = FLAG_ACTIVITY_NEW_TASK
|
flags = FLAG_ACTIVITY_NEW_TASK
|
||||||
}
|
}
|
||||||
@ -256,8 +375,23 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
external fun keyboardEvent(keyCode: Int, action: Int)
|
external fun keyboardEvent(keyCode: Int, action: Int)
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
external fun joystickEvent(
|
||||||
|
action: Int,
|
||||||
|
leftStickX: Float,
|
||||||
|
leftStickY: Float,
|
||||||
|
rightStickX: Float,
|
||||||
|
rightStickY: Float,
|
||||||
|
leftTrigger: Float,
|
||||||
|
rightTrigger: Float,
|
||||||
|
hatX: Float,
|
||||||
|
hatY: Float
|
||||||
|
)
|
||||||
|
@JvmStatic
|
||||||
external fun loadConfig(configJsonStr: String)
|
external fun loadConfig(configJsonStr: String)
|
||||||
|
|
||||||
|
// Toast快速切换内容
|
||||||
|
private var toast: Toast? = null
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun showToast(message: String) {
|
fun showToast(message: String) {
|
||||||
val app = AndroidAppHelper.currentApplication()
|
val app = AndroidAppHelper.currentApplication()
|
||||||
@ -265,13 +399,21 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
|
|||||||
if (context != null) {
|
if (context != null) {
|
||||||
val handler = Handler(Looper.getMainLooper())
|
val handler = Handler(Looper.getMainLooper())
|
||||||
handler.post {
|
handler.post {
|
||||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
|
// 取消之前的 Toast
|
||||||
|
toast?.cancel()
|
||||||
|
// 创建新的 Toast
|
||||||
|
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
|
||||||
|
// 展示新的 Toast
|
||||||
|
toast?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Log.e(TAG, "showToast: $message failed: applicationContext is null")
|
Log.e(TAG, "showToast: $message failed: applicationContext is null")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun pluginCallbackLooper()
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -1,165 +1,198 @@
|
|||||||
package io.github.chinosk.gakumas.localify
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewTreeObserver
|
|
||||||
import android.widget.ScrollView
|
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.activity.compose.setContent
|
||||||
import com.google.android.material.button.MaterialButton
|
import androidx.compose.runtime.Composable
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import androidx.compose.runtime.State
|
||||||
import com.google.gson.Gson
|
import androidx.compose.runtime.collectAsState
|
||||||
import com.google.gson.JsonSyntaxException
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
|
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
|
||||||
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
|
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.pages.MainUI
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ConfigUpdateListener {
|
class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableActivity<MainActivity> {
|
||||||
override lateinit var binding: ActivityMainBinding
|
override lateinit var config: GakumasConfig
|
||||||
private val TAG = "GakumasLocalify"
|
override lateinit var programConfig: ProgramConfig
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override lateinit var factory: UserConfigViewModelFactory
|
||||||
super.onCreate(savedInstanceState)
|
override lateinit var viewModel: UserConfigViewModel
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
|
override lateinit var programConfigFactory: ProgramConfigViewModelFactory
|
||||||
loadConfig()
|
override lateinit var programConfigViewModel: ProgramConfigViewModel
|
||||||
binding.listener = this
|
|
||||||
|
|
||||||
val requestData = intent.getStringExtra("gkmsData")
|
|
||||||
if (requestData != null) {
|
|
||||||
if (requestData == "requestConfig") {
|
|
||||||
onClickStartGame()
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
showVersion()
|
|
||||||
|
|
||||||
val scrollView: ScrollView = findViewById(R.id.scrollView)
|
|
||||||
scrollView.viewTreeObserver.addOnScrollChangedListener { onScrollChanged() }
|
|
||||||
onScrollChanged()
|
|
||||||
|
|
||||||
val coordinatorLayout = findViewById<View>(R.id.coordinatorLayout)
|
|
||||||
coordinatorLayout.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
|
||||||
override fun onGlobalLayout() {
|
|
||||||
onScrollChanged()
|
|
||||||
coordinatorLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClickStartGame() {
|
|
||||||
val intent = Intent().apply {
|
|
||||||
setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
|
|
||||||
putExtra("gkmsData", getConfigContent())
|
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onScrollChanged() {
|
|
||||||
val fab: FloatingActionButton = findViewById(R.id.fabStartGame)
|
|
||||||
val startGameButton: MaterialButton = findViewById(R.id.StartGameButton)
|
|
||||||
val scrollView: ScrollView = findViewById(R.id.scrollView)
|
|
||||||
|
|
||||||
val location = IntArray(2)
|
|
||||||
startGameButton.getLocationOnScreen(location)
|
|
||||||
val buttonTop = location[1]
|
|
||||||
val buttonBottom = buttonTop + startGameButton.height
|
|
||||||
|
|
||||||
val scrollViewLocation = IntArray(2)
|
|
||||||
scrollView.getLocationOnScreen(scrollViewLocation)
|
|
||||||
val scrollViewTop = scrollViewLocation[1]
|
|
||||||
val scrollViewBottom = scrollViewTop + scrollView.height
|
|
||||||
|
|
||||||
val isButtonVisible = buttonTop >= scrollViewTop && buttonBottom <= scrollViewBottom
|
|
||||||
|
|
||||||
if (isButtonVisible) {
|
|
||||||
fab.hide()
|
|
||||||
} else {
|
|
||||||
fab.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun showToast(message: String) {
|
private fun showToast(message: String) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getConfigContent(): String {
|
|
||||||
val configFile = File(filesDir, "gkms-config.json")
|
|
||||||
return if (configFile.exists()) {
|
|
||||||
configFile.readText()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
showToast("Initializing configuration file...")
|
|
||||||
"{}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun saveConfig() {
|
override fun saveConfig() {
|
||||||
|
try {
|
||||||
|
config.pf = false
|
||||||
|
viewModel.configState.value = config.copy( pf = true ) // 更新 UI
|
||||||
|
}
|
||||||
|
catch (e: RuntimeException) {
|
||||||
|
Log.d(TAG, e.toString())
|
||||||
|
}
|
||||||
val configFile = File(filesDir, "gkms-config.json")
|
val configFile = File(filesDir, "gkms-config.json")
|
||||||
configFile.writeText(Gson().toJson(binding.config!!))
|
configFile.writeText(json.encodeToString(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
override fun saveProgramConfig() {
|
||||||
private fun showVersion() {
|
try {
|
||||||
val titleLabel = findViewById<TextView>(R.id.textViewTitle)
|
programConfig.p = false
|
||||||
val versionLabel = findViewById<TextView>(R.id.textViewResVersion)
|
programConfigViewModel.configState.value = programConfig.copy( p = true ) // 更新 UI
|
||||||
var versionText = "unknown"
|
}
|
||||||
|
catch (e: RuntimeException) {
|
||||||
|
Log.d(TAG, e.toString())
|
||||||
|
}
|
||||||
|
val configFile = File(filesDir, "localify-config.json")
|
||||||
|
configFile.writeText(json.encodeToString(programConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getVersion(): List<String> {
|
||||||
|
var versionText = ""
|
||||||
|
var resVersionText = "unknown"
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
|
val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
|
||||||
versionText = FilesChecker.convertToString(stream)
|
resVersionText = FilesChecker.convertToString(stream)
|
||||||
|
|
||||||
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
val packInfo = packageManager.getPackageInfo(packageName, 0)
|
||||||
val version = packInfo.versionName
|
val version = packInfo.versionName
|
||||||
titleLabel.text = "${titleLabel.text} $version"
|
val versionCode = packInfo.longVersionCode
|
||||||
|
versionText = "$version ($versionCode)"
|
||||||
}
|
}
|
||||||
catch (_: Exception) {}
|
catch (_: Exception) {}
|
||||||
versionLabel.text = "Assets Version: $versionText"
|
|
||||||
|
return listOf(versionText, resVersionText)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadConfig() {
|
fun openUrl(url: String) {
|
||||||
val configStr = getConfigContent()
|
val webpage = Uri.parse(url)
|
||||||
binding.config = try {
|
val intent = Intent(Intent.ACTION_VIEW, webpage)
|
||||||
Gson().fromJson(configStr, GakumasConfig::class.java)
|
startActivity(intent)
|
||||||
}
|
|
||||||
catch (e: JsonSyntaxException) {
|
|
||||||
showToast("Configuration file has been reset: $e")
|
|
||||||
Gson().fromJson("{}", GakumasConfig::class.java)
|
|
||||||
}
|
|
||||||
saveConfig()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkConfigAndUpdateView() {
|
|
||||||
binding.config = binding.config
|
|
||||||
binding.notifyChange()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pushKeyEvent(event: KeyEvent): Boolean {
|
override fun pushKeyEvent(event: KeyEvent): Boolean {
|
||||||
return dispatchKeyEvent(event)
|
return dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("RestrictedApi")
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
// Log.d(TAG, "${event.keyCode}, ${event.action}")
|
// Log.d(TAG, "${event.keyCode}, ${event.action}")
|
||||||
if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
|
if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
|
||||||
val origDbg = binding.config?.dbgMode
|
val origDbg = config.dbgMode
|
||||||
if (origDbg != null) {
|
config.dbgMode = !origDbg
|
||||||
binding.config!!.dbgMode = !origDbg
|
checkConfigAndUpdateView()
|
||||||
checkConfigAndUpdateView()
|
saveConfig()
|
||||||
saveConfig()
|
showToast("Test Mode: ${!origDbg}")
|
||||||
showToast("Test Mode: ${!origDbg}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return if (event.action == 1145) true else super.dispatchKeyEvent(event)
|
return if (event.action == 1145) true else super.dispatchKeyEvent(event)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
loadConfig()
|
||||||
|
|
||||||
|
factory = UserConfigViewModelFactory(config)
|
||||||
|
viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java]
|
||||||
|
|
||||||
|
programConfigFactory = ProgramConfigViewModelFactory(programConfig,
|
||||||
|
FileHotUpdater.getZipResourceVersion(File(filesDir, "update_trans.zip").absolutePath).toString()
|
||||||
|
)
|
||||||
|
programConfigViewModel = ViewModelProvider(this, programConfigFactory)[ProgramConfigViewModel::class.java]
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
GakumasLocalifyTheme(dynamicColor = false, darkTheme = false) {
|
||||||
|
MainUI(context = this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getConfigState(context: MainActivity?, previewData: GakumasConfig?): State<GakumasConfig> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.viewModel.config.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow(previewData!!)
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getProgramConfigState(context: MainActivity?, previewData: ProgramConfig? = null): State<ProgramConfig> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.programConfigViewModel.config.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow(previewData ?: ProgramConfig())
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getProgramDownloadState(context: MainActivity?): State<Float> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.programConfigViewModel.downloadProgress.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow(0f)
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getProgramDownloadAbleState(context: MainActivity?): State<Boolean> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.programConfigViewModel.downloadAble.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow(true)
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getProgramLocalResourceVersionState(context: MainActivity?): State<String> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.programConfigViewModel.localResourceVersion.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow("null")
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
|
||||||
|
return if (context != null) {
|
||||||
|
context.programConfigViewModel.errorString.collectAsState()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val configMSF = MutableStateFlow("")
|
||||||
|
configMSF.asStateFlow().collectAsState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ProgramConfig
|
||||||
|
|
||||||
|
|
||||||
|
class TranslucentActivity : ComponentActivity(), IConfigurableActivity<TranslucentActivity> {
|
||||||
|
override lateinit var config: GakumasConfig
|
||||||
|
override lateinit var programConfig: ProgramConfig
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
loadConfig()
|
||||||
|
val requestData = intent.getStringExtra("gkmsData")
|
||||||
|
if (requestData != null) {
|
||||||
|
if (requestData == "requestConfig") {
|
||||||
|
onClickStartGame()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.hookUtils
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import io.github.chinosk.gakumas.localify.GakumasHookMain
|
||||||
|
import io.github.chinosk.gakumas.localify.TAG
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.util.zip.ZipInputStream
|
||||||
|
|
||||||
|
object FileHotUpdater {
|
||||||
|
private fun unzip(zipFile: InputStream, destDir: String, matchNamePrefix: String = "",
|
||||||
|
replaceMatchNamePrefix: String? = null) {
|
||||||
|
val buffer = ByteArray(1024)
|
||||||
|
try {
|
||||||
|
val folder = File(destDir)
|
||||||
|
if (!folder.exists()) {
|
||||||
|
folder.mkdir()
|
||||||
|
}
|
||||||
|
|
||||||
|
val zipIn = ZipInputStream(zipFile)
|
||||||
|
|
||||||
|
var entry = zipIn.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
var writeEntryName = entry.name
|
||||||
|
if (matchNamePrefix.isNotEmpty()) {
|
||||||
|
if (!entry.name.startsWith(matchNamePrefix)) {
|
||||||
|
zipIn.closeEntry()
|
||||||
|
entry = zipIn.nextEntry
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
replaceMatchNamePrefix?.let {
|
||||||
|
writeEntryName = replaceMatchNamePrefix + writeEntryName.substring(
|
||||||
|
matchNamePrefix.length, writeEntryName.length
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val filePath = destDir + File.separator + writeEntryName
|
||||||
|
if (!entry.isDirectory) {
|
||||||
|
extractFile(zipIn, filePath, buffer)
|
||||||
|
} else {
|
||||||
|
val dir = File(filePath)
|
||||||
|
dir.mkdirs()
|
||||||
|
}
|
||||||
|
zipIn.closeEntry()
|
||||||
|
entry = zipIn.nextEntry
|
||||||
|
}
|
||||||
|
zipIn.close()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "unzip error: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unzip(zipFile: String, destDir: String, matchNamePrefix: String = "") {
|
||||||
|
return unzip(FileInputStream(zipFile), destDir, matchNamePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun extractFile(zipIn: ZipInputStream, filePath: String, buffer: ByteArray) {
|
||||||
|
val fout = FileOutputStream(filePath)
|
||||||
|
var length: Int
|
||||||
|
while (zipIn.read(buffer).also { length = it } > 0) {
|
||||||
|
fout.write(buffer, 0, length)
|
||||||
|
}
|
||||||
|
fout.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getZipResourcePath(zipFile: InputStream): String? {
|
||||||
|
try {
|
||||||
|
val zipIn = ZipInputStream(zipFile)
|
||||||
|
|
||||||
|
var entry = zipIn.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
if (entry.isDirectory) {
|
||||||
|
if (entry.name.endsWith("local-files/")) {
|
||||||
|
zipIn.close()
|
||||||
|
var retPath = File(entry.name, "..").canonicalPath
|
||||||
|
if (retPath.startsWith("/")) retPath = retPath.substring(1)
|
||||||
|
return retPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipIn.closeEntry()
|
||||||
|
entry = zipIn.nextEntry
|
||||||
|
}
|
||||||
|
zipIn.close()
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.e(TAG, "getZipResourcePath error: $e")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getZipResourceVersion(zipFile: InputStream, basePath: String): String? {
|
||||||
|
try {
|
||||||
|
val targetVersionFilePath = File(basePath, "version.txt").canonicalPath
|
||||||
|
|
||||||
|
val zipIn = ZipInputStream(zipFile)
|
||||||
|
var entry = zipIn.nextEntry
|
||||||
|
while (entry != null) {
|
||||||
|
if (!entry.isDirectory) {
|
||||||
|
if ("/${entry.name}" == targetVersionFilePath) {
|
||||||
|
Log.d(TAG, "targetVersionFilePath: $targetVersionFilePath")
|
||||||
|
val reader = BufferedReader(InputStreamReader(zipIn))
|
||||||
|
val versionContent = reader.use { it.readText() }
|
||||||
|
Log.d(TAG, "versionContent: $versionContent")
|
||||||
|
zipIn.close()
|
||||||
|
return versionContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zipIn.closeEntry()
|
||||||
|
entry = zipIn.nextEntry
|
||||||
|
}
|
||||||
|
zipIn.close()
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.e(TAG, "getZipResourceVersion error: $e")
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getZipResourceVersion(zipFile: String, basePath: String): String? {
|
||||||
|
return getZipResourceVersion(FileInputStream(zipFile), basePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getZipResourceVersion(zipFile: String): String? {
|
||||||
|
return try {
|
||||||
|
val basePath = getZipResourcePath(FileInputStream(zipFile))
|
||||||
|
basePath?.let { getZipResourceVersion(zipFile, it) }
|
||||||
|
}
|
||||||
|
catch (_: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateFilesFromZip(activity: Activity, zipFileUri: Uri, filesDir: File, deleteAfterUpdate: Boolean) {
|
||||||
|
try {
|
||||||
|
GakumasHookMain.showToast("Updating files from zip...")
|
||||||
|
|
||||||
|
var basePath: String?
|
||||||
|
activity.contentResolver.openInputStream(zipFileUri).use {
|
||||||
|
basePath = it?.let { getZipResourcePath(it) }
|
||||||
|
if (basePath == null) {
|
||||||
|
Log.e(TAG, "getZipResourcePath failed.")
|
||||||
|
return@updateFilesFromZip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
var resourceVersion: String?
|
||||||
|
activity.contentResolver.openInputStream(zipFileUri).use {
|
||||||
|
resourceVersion = it?.let { getZipResourceVersion(it, basePath!!) }
|
||||||
|
Log.d(TAG, "resourceVersion: $resourceVersion ($basePath)")
|
||||||
|
}*/
|
||||||
|
|
||||||
|
activity.contentResolver.openInputStream(zipFileUri).use {
|
||||||
|
it?.let {
|
||||||
|
unzip(it, File(filesDir, FilesChecker.localizationFilesDir).absolutePath,
|
||||||
|
basePath!!, "../gakumas-local/")
|
||||||
|
if (deleteAfterUpdate) {
|
||||||
|
activity.contentResolver.delete(zipFileUri, null, null)
|
||||||
|
}
|
||||||
|
GakumasHookMain.showToast("Update success.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (e: java.io.FileNotFoundException) {
|
||||||
|
Log.i(TAG, "updateFilesFromZip - file not found: $e")
|
||||||
|
GakumasHookMain.showToast("Update file not found.")
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
Log.e(TAG, "updateFilesFromZip failed: $e")
|
||||||
|
GakumasHookMain.showToast("Updating files failed: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,12 +16,16 @@ object FilesChecker {
|
|||||||
var filesUpdated = false
|
var filesUpdated = false
|
||||||
|
|
||||||
fun initAndCheck(fileDir: File, modulePath: String) {
|
fun initAndCheck(fileDir: File, modulePath: String) {
|
||||||
this.filesDir = fileDir
|
initDir(fileDir, modulePath)
|
||||||
this.modulePath = modulePath
|
|
||||||
|
|
||||||
checkFiles()
|
checkFiles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initDir(fileDir: File, modulePath: String) {
|
||||||
|
this.filesDir = fileDir
|
||||||
|
this.modulePath = modulePath
|
||||||
|
}
|
||||||
|
|
||||||
fun checkFiles() {
|
fun checkFiles() {
|
||||||
val installedVersion = getInstalledVersion()
|
val installedVersion = getInstalledVersion()
|
||||||
val pluginVersion = getPluginVersion()
|
val pluginVersion = getPluginVersion()
|
||||||
@ -118,4 +122,45 @@ object FilesChecker {
|
|||||||
return stringBuilder.toString()
|
return stringBuilder.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun deleteRecursively(file: File): Boolean {
|
||||||
|
if (file.isDirectory) {
|
||||||
|
val children = file.listFiles()
|
||||||
|
if (children != null) {
|
||||||
|
for (child in children) {
|
||||||
|
val success = deleteRecursively(child)
|
||||||
|
if (!success) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file.delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cleanAssets() {
|
||||||
|
val pluginBasePath = File(filesDir, localizationFilesDir)
|
||||||
|
val localFilesDir = File(pluginBasePath, "local-files")
|
||||||
|
|
||||||
|
val fontFile = File(localFilesDir, "gkamsZHFontMIX.otf")
|
||||||
|
val resourceDir = File(localFilesDir, "resource")
|
||||||
|
val genericTransDir = File(localFilesDir, "genericTrans")
|
||||||
|
val genericTransFile = File(localFilesDir, "generic.json")
|
||||||
|
val i18nFile = File(localFilesDir, "localization.json")
|
||||||
|
|
||||||
|
if (fontFile.exists()) {
|
||||||
|
fontFile.delete()
|
||||||
|
}
|
||||||
|
if (deleteRecursively(resourceDir)) {
|
||||||
|
resourceDir.mkdirs()
|
||||||
|
}
|
||||||
|
if (deleteRecursively(genericTransDir)) {
|
||||||
|
genericTransDir.mkdirs()
|
||||||
|
}
|
||||||
|
if (genericTransFile.exists()) {
|
||||||
|
genericTransFile.writeText("{}")
|
||||||
|
}
|
||||||
|
if (i18nFile.exists()) {
|
||||||
|
i18nFile.writeText("{}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,11 +1,10 @@
|
|||||||
package io.github.chinosk.gakumas.localify.hookUtils
|
package io.github.chinosk.gakumas.localify.hookUtils
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
|
||||||
object MainKeyEventDispatcher {
|
object MainKeyEventDispatcher {
|
||||||
private val targetDbgKeyList: IntArray = intArrayOf(19, 19, 20, 20, 21, 22, 21, 22, 30, 29)
|
private val targetDbgKeyList: IntArray = intArrayOf(19, 19, 20, 20, 21, 22, 21, 22, 30, 29)
|
||||||
private var currentIndex = 0;
|
private var currentIndex = 0
|
||||||
|
|
||||||
fun checkDbgKey(code: Int, action: Int): Boolean {
|
fun checkDbgKey(code: Int, action: Int): Boolean {
|
||||||
if (action == KeyEvent.ACTION_UP) return false
|
if (action == KeyEvent.ACTION_UP) return false
|
||||||
|
@ -0,0 +1,137 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.mainUtils
|
||||||
|
|
||||||
|
import okhttp3.*
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object FileDownloader {
|
||||||
|
private val client = OkHttpClient.Builder()
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(0, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private var call: Call? = null
|
||||||
|
|
||||||
|
fun downloadFile(
|
||||||
|
url: String,
|
||||||
|
onDownload: (Float, downloaded: Long, size: Long) -> Unit,
|
||||||
|
onSuccess: (ByteArray) -> Unit,
|
||||||
|
onFailed: (Int, String) -> Unit,
|
||||||
|
checkContentTypes: List<String>? = null
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (call != null) {
|
||||||
|
onFailed(-1, "Another file is downloading.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
call = client.newCall(request)
|
||||||
|
call?.enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
if (call.isCanceled()) {
|
||||||
|
onFailed(-1, "Download canceled")
|
||||||
|
} else {
|
||||||
|
onFailed(-1, e.message ?: "Unknown error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
onFailed(response.code, response.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkContentTypes != null) {
|
||||||
|
val contentType = response.header("Content-Type")
|
||||||
|
if (!checkContentTypes.contains(contentType)) {
|
||||||
|
onFailed(-1, "Unexpected content type: $contentType")
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.body?.let { responseBody ->
|
||||||
|
val contentLength = responseBody.contentLength()
|
||||||
|
val inputStream = responseBody.byteStream()
|
||||||
|
val buffer = ByteArray(8 * 1024)
|
||||||
|
var downloadedBytes = 0L
|
||||||
|
var read: Int
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (inputStream.read(buffer).also { read = it } != -1) {
|
||||||
|
outputStream.write(buffer, 0, read)
|
||||||
|
downloadedBytes += read
|
||||||
|
val progress = if (contentLength < 0) {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
downloadedBytes.toFloat() / contentLength
|
||||||
|
}
|
||||||
|
onDownload(progress, downloadedBytes, contentLength)
|
||||||
|
}
|
||||||
|
onSuccess(outputStream.toByteArray())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
if (call.isCanceled()) {
|
||||||
|
onFailed(-1, "Download canceled")
|
||||||
|
} else {
|
||||||
|
onFailed(-1, e.message ?: "Error reading stream")
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
inputStream.close()
|
||||||
|
outputStream.close()
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
onFailed(-1, "Response body is null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
onFailed(-1, e.toString())
|
||||||
|
call = null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
call?.cancel()
|
||||||
|
this@FileDownloader.call = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return: Status, newString
|
||||||
|
* Status: 0 - not change, 1 - need check, 2 - modified, 3 - checked
|
||||||
|
**/
|
||||||
|
fun checkAndChangeDownloadURL(url: String, forceEdit: Boolean = false): Pair<Int, String> {
|
||||||
|
|
||||||
|
if (!url.startsWith("https://github.com/")) { // check github only
|
||||||
|
return Pair(0, url)
|
||||||
|
}
|
||||||
|
if (url.endsWith(".zip")) {
|
||||||
|
return Pair(0, url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/chinosk6/GakumasTranslationData
|
||||||
|
// https://github.com/chinosk6/GakumasTranslationData.git
|
||||||
|
// https://github.com/chinosk6/GakumasTranslationData/archive/refs/heads/main.zip
|
||||||
|
if (url.endsWith(".git")) {
|
||||||
|
return Pair(2, "${url.substring(0, url.length - 4)}/archive/refs/heads/main.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forceEdit) {
|
||||||
|
return Pair(3, "$url/archive/refs/heads/main.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(1, url)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.mainUtils
|
||||||
|
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
val json = Json {
|
||||||
|
encodeDefaults = true
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AboutPageConfig(
|
||||||
|
val plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify",
|
||||||
|
val main_contributors: List<MainContributors> = listOf(),
|
||||||
|
val contrib_img: ContribImg = ContribImg(
|
||||||
|
"https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||||
|
"https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MainContributors(
|
||||||
|
val name: String,
|
||||||
|
val links: List<Links>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ContribImg(
|
||||||
|
val plugin: String,
|
||||||
|
val translation: String
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Links(
|
||||||
|
val name: String,
|
||||||
|
val link: String
|
||||||
|
)
|
@ -1,6 +1,8 @@
|
|||||||
package io.github.chinosk.gakumas.localify.models
|
package io.github.chinosk.gakumas.localify.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
data class GakumasConfig (
|
data class GakumasConfig (
|
||||||
var dbgMode: Boolean = false,
|
var dbgMode: Boolean = false,
|
||||||
var enabled: Boolean = true,
|
var enabled: Boolean = true,
|
||||||
@ -41,4 +43,6 @@ data class GakumasConfig (
|
|||||||
var bLimitYy: Float = 1.0f,
|
var bLimitYy: Float = 1.0f,
|
||||||
var bLimitZx: Float = 1.0f,
|
var bLimitZx: Float = 1.0f,
|
||||||
var bLimitZy: Float = 1.0f,
|
var bLimitZy: Float = 1.0f,
|
||||||
|
|
||||||
|
var pf: Boolean = false
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.models
|
||||||
|
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
|
import kotlinx.serialization.KSerializer
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||||
|
import kotlinx.serialization.encoding.Decoder
|
||||||
|
import kotlinx.serialization.encoding.Encoder
|
||||||
|
import kotlinx.serialization.encoding.encodeStructure
|
||||||
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.encodeToJsonElement
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ProgramConfig(
|
||||||
|
var checkBuiltInAssets: Boolean = true,
|
||||||
|
var transRemoteZipUrl: String = "",
|
||||||
|
var useRemoteAssets: Boolean = false,
|
||||||
|
var delRemoteAfterUpdate: Boolean = true,
|
||||||
|
var cleanLocalAssets: Boolean = false,
|
||||||
|
var p: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
class ProgramConfigSerializer(
|
||||||
|
private val excludes: List<String> = emptyList(),
|
||||||
|
) : KSerializer<ProgramConfig> {
|
||||||
|
override val descriptor: SerialDescriptor = ProgramConfig.serializer().descriptor
|
||||||
|
override fun serialize(encoder: Encoder, value: ProgramConfig) {
|
||||||
|
val jsonObject = json.encodeToJsonElement(value).jsonObject
|
||||||
|
encoder.encodeStructure(descriptor) {
|
||||||
|
jsonObject.keys.forEachIndexed { index, k ->
|
||||||
|
if (k in excludes) return@forEachIndexed
|
||||||
|
encodeSerializableElement(descriptor, index, JsonElement.serializer(), jsonObject[k]!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(decoder: Decoder): ProgramConfig {
|
||||||
|
return ProgramConfig.serializer().deserialize(decoder)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.models
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
open class CollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : ViewModel() {
|
||||||
|
open var expanded by mutableStateOf(initiallyBreastExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BreastCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) {
|
||||||
|
override var expanded by mutableStateOf(initiallyBreastExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceCollapsibleBoxViewModel(initiallyBreastExpanded: Boolean = false) : CollapsibleBoxViewModel(initiallyBreastExpanded) {
|
||||||
|
override var expanded by mutableStateOf(initiallyBreastExpanded)
|
||||||
|
}
|
||||||
|
|
||||||
|
class BreastCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(BreastCollapsibleBoxViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return BreastCollapsibleBoxViewModel(initiallyExpanded) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ResourceCollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(ResourceCollapsibleBoxViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return ResourceCollapsibleBoxViewModel(initiallyExpanded) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProgramConfigViewModelFactory(private val initialValue: ProgramConfig,
|
||||||
|
private val localResourceVersion: String) : ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(ProgramConfigViewModel::class.java)) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return ProgramConfigViewModel(initialValue, localResourceVersion) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgramConfigViewModel(initValue: ProgramConfig, initLocalResourceVersion: String) : ViewModel() {
|
||||||
|
val configState = MutableStateFlow(initValue)
|
||||||
|
val config: StateFlow<ProgramConfig> = configState.asStateFlow()
|
||||||
|
|
||||||
|
val downloadProgressState = MutableStateFlow(-1f)
|
||||||
|
val downloadProgress: StateFlow<Float> = downloadProgressState.asStateFlow()
|
||||||
|
|
||||||
|
val downloadAbleState = MutableStateFlow(true)
|
||||||
|
val downloadAble: StateFlow<Boolean> = downloadAbleState.asStateFlow()
|
||||||
|
|
||||||
|
val localResourceVersionState = MutableStateFlow(initLocalResourceVersion)
|
||||||
|
val localResourceVersion: StateFlow<String> = localResourceVersionState.asStateFlow()
|
||||||
|
|
||||||
|
val errorStringState = MutableStateFlow("")
|
||||||
|
val errorString: StateFlow<String> = errorStringState.asStateFlow()
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuButton(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角
|
||||||
|
shadowElevation: Dp = 8.dp, // 阴影的高度
|
||||||
|
borderWidth: Dp = 1.dp, // 描边的宽度
|
||||||
|
borderColor: Color = Color.Transparent, // 描边的颜色
|
||||||
|
enabled: Boolean = true
|
||||||
|
) {
|
||||||
|
var buttonSize by remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
|
||||||
|
val gradient = remember(buttonSize) {
|
||||||
|
Brush.linearGradient(
|
||||||
|
colors = if (enabled) listOf(Color(0xFFFF5F19), Color(0xFFFFA028)) else
|
||||||
|
listOf(Color(0xFFF9F9F9), Color(0xFFF0F0F0)),
|
||||||
|
start = Offset(0f, 0f),
|
||||||
|
end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = enabled,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
modifier = modifier
|
||||||
|
.onGloballyPositioned { layoutCoordinates ->
|
||||||
|
buttonSize = layoutCoordinates.size
|
||||||
|
}
|
||||||
|
.shadow(elevation = shadowElevation, shape = shape)
|
||||||
|
.clip(shape)
|
||||||
|
.background(gradient)
|
||||||
|
.border(borderWidth, borderColor, shape),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Text(text = text, color = if (enabled) Color.White else Color(0xFF111111))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun GakuButtonPreview() {
|
||||||
|
GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {},
|
||||||
|
enabled = true)
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuGroupBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
title: String = "Title",
|
||||||
|
maxWidth: Dp = 500.dp,
|
||||||
|
contentPadding: Dp = 8.dp,
|
||||||
|
rightHead: @Composable (() -> Unit)? = null,
|
||||||
|
onHeadClick: () -> Unit = {},
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.shadow(4.dp, RoundedCornerShape(
|
||||||
|
bottomStart = 16.dp,
|
||||||
|
bottomEnd = 8.dp,
|
||||||
|
topEnd = 16.dp,
|
||||||
|
topStart = 0.dp
|
||||||
|
))
|
||||||
|
// .background(Color.White, RoundedCornerShape(8.dp))
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier.widthIn(max = maxWidth)) {
|
||||||
|
// Header
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(Color.Transparent)
|
||||||
|
.height(23.dp)
|
||||||
|
.clickable {
|
||||||
|
onHeadClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.bg_h1),
|
||||||
|
contentDescription = null,
|
||||||
|
// modifier = Modifier.fillMaxSize(),
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.FillBounds
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
color = Color.White,
|
||||||
|
modifier = modifier
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
.padding(start = (maxWidth.value * 0.043f).dp)
|
||||||
|
)
|
||||||
|
if (rightHead != null) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.padding(end = (maxWidth.value * 0.1f).dp)) {
|
||||||
|
rightHead()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.background(
|
||||||
|
color = Color.White,
|
||||||
|
shape = RoundedCornerShape(
|
||||||
|
bottomStart = 16.dp,
|
||||||
|
bottomEnd = 8.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.padding(contentPadding)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun PreviewGakuGroupBox() {
|
||||||
|
GakuGroupBox(
|
||||||
|
title = "GroupBox Title"
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
Text("This is the content of the GroupBox.")
|
||||||
|
Text("This is the content of the GroupBox.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuProgressBar(modifier: Modifier = Modifier, progress: Float, isError: Boolean = false) {
|
||||||
|
val animatedProgress by animateFloatAsState(targetValue = progress, label = "progressAnime")
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = androidx.compose.ui.Alignment.CenterVertically,
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
if (progress <= 0f) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.height(8.dp),
|
||||||
|
color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
progress = { animatedProgress },
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.height(8.dp),
|
||||||
|
color = if (isError) Color(0xFFE2041B) else Color(0xFFF9C114),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
|
Text(if (progress > 0f) "${(progress * 100).toInt()}%" else if (isError) "Failed" else "Downloading")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun GakuProgressBarPreview() {
|
||||||
|
GakuProgressBar(progress = 0.25f)
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.RadioButtonDefaults
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuRadio(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
selected: Boolean,
|
||||||
|
fontSize: TextUnit = 14.sp,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
val backgroundColor = if (selected) Color(0xFFFFEEC3) else Color(0xFFF8F7F5)
|
||||||
|
val radioButtonColor = if (selected) Color(0xFFFF7601) else MaterialTheme.colorScheme.onSurface
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
shape = RoundedCornerShape(
|
||||||
|
topStart = 4.dp,
|
||||||
|
topEnd = 16.dp,
|
||||||
|
bottomEnd = 4.dp,
|
||||||
|
bottomStart = 16.dp
|
||||||
|
),
|
||||||
|
color = backgroundColor,
|
||||||
|
modifier = modifier
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectTapGestures(onTap = {
|
||||||
|
onClick()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = modifier.padding(start = 0.dp, end = 4.dp)
|
||||||
|
) {
|
||||||
|
RadioButton(
|
||||||
|
modifier = Modifier.padding(start = 0.dp),
|
||||||
|
selected = selected,
|
||||||
|
onClick = onClick,
|
||||||
|
colors = RadioButtonDefaults.colors(
|
||||||
|
selectedColor = radioButtonColor,
|
||||||
|
unselectedColor = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// Spacer(modifier = modifier.width(16.dp))
|
||||||
|
AutoSizeText(text = text,
|
||||||
|
textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize = fontSize))
|
||||||
|
// Text(text = text, color = MaterialTheme.colorScheme.onSurface, fontSize = fontSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 100, heightDp = 40)
|
||||||
|
@Composable
|
||||||
|
fun GakuRadioPreview() {
|
||||||
|
GakuRadio(text = "GakuRadioooo", selected = true, onClick = {})
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.SwitchDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuSwitch(modifier: Modifier = Modifier,
|
||||||
|
text: String = "",
|
||||||
|
checked: Boolean = false,
|
||||||
|
leftPart: @Composable (() -> Unit)? = null,
|
||||||
|
onCheckedChange: (Boolean) -> Unit = {}) {
|
||||||
|
Row(modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
AutoSizeText(text = text, fontSize = 16.sp)
|
||||||
|
}
|
||||||
|
leftPart?.invoke()
|
||||||
|
Switch(checked = checked,
|
||||||
|
onCheckedChange = { value -> onCheckedChange(value) },
|
||||||
|
modifier = Modifier,
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = Color(0xFFFFFFFF),
|
||||||
|
checkedTrackColor = Color(0xFFF9C114),
|
||||||
|
|
||||||
|
uncheckedThumbColor = Color(0xFFFFFFFF),
|
||||||
|
uncheckedTrackColor = Color(0xFFCFD8DC),
|
||||||
|
uncheckedBorderColor = Color(0xFFCFD8DC),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun GakuSwitchPreview() {
|
||||||
|
GakuSwitch(text = "Switch", checked = true)
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun GakuTabRow(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
pagerState: PagerState,
|
||||||
|
tabs: List<String>,
|
||||||
|
onTabSelected: (index: Int) -> Unit
|
||||||
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
|
onTabSelected(pagerState.currentPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.shadow(4.dp, RoundedCornerShape(16.dp))
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
.shadow(4.dp),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
TabRow(
|
||||||
|
modifier = modifier.background(Color.Transparent),
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
selectedTabIndex = pagerState.currentPage,
|
||||||
|
indicator = @Composable { tabPositions ->
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||||
|
.height(4.dp)
|
||||||
|
.background(Color(0xFFFFA500))
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
tabs.forEachIndexed { index, title ->
|
||||||
|
Tab(
|
||||||
|
selected = pagerState.currentPage == index,
|
||||||
|
onClick = {
|
||||||
|
coroutineScope.launch {
|
||||||
|
pagerState.scrollToPage(index)
|
||||||
|
// pagerState.animateScrollToPage(
|
||||||
|
// page = index,
|
||||||
|
// animationSpec = tween(durationMillis = 250)
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
color = if (pagerState.currentPage == index) Color(0xFFFFA500) else Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun GakuTabRowPreview(modifier: Modifier = Modifier) {
|
||||||
|
val pagerState = rememberPagerState(initialPage = 1, pageCount = { 3 })
|
||||||
|
GakuTabRow(modifier, pagerState, listOf("TAB 1", "TAB 2", "TAB 3")) { _ -> }
|
||||||
|
}
|
@ -0,0 +1,188 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.LocalTextStyle
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextFieldColors
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakuTextInput(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
label: @Composable (() -> Unit)? = null,
|
||||||
|
fontSize: Float = 16f,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default
|
||||||
|
) {
|
||||||
|
val shape: Shape = remember {
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 4.dp,
|
||||||
|
topEnd = 16.dp,
|
||||||
|
bottomEnd = 4.dp,
|
||||||
|
bottomStart = 16.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var localValue by remember { mutableStateOf(value) }
|
||||||
|
var isUserInput by remember { mutableStateOf(false) }
|
||||||
|
val textStyle = remember {
|
||||||
|
TextStyle(fontSize = fontSize.sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(value) {
|
||||||
|
if (!isUserInput) {
|
||||||
|
localValue = value
|
||||||
|
}
|
||||||
|
isUserInput = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
OutlinedTextFieldNoPadding(
|
||||||
|
singleLine = true,
|
||||||
|
value = localValue,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
isUserInput = true
|
||||||
|
localValue = newValue
|
||||||
|
onValueChange(newValue)
|
||||||
|
},
|
||||||
|
label = label,
|
||||||
|
modifier = modifier,
|
||||||
|
textStyle = textStyle,
|
||||||
|
shape = shape,
|
||||||
|
keyboardOptions = keyboardOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun OutlinedTextFieldNoPadding(
|
||||||
|
value: String,
|
||||||
|
onValueChange: (String) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
enabled: Boolean = true,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
textStyle: TextStyle = LocalTextStyle.current,
|
||||||
|
label: @Composable (() -> Unit)? = null,
|
||||||
|
placeholder: @Composable (() -> Unit)? = null,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
prefix: @Composable (() -> Unit)? = null,
|
||||||
|
suffix: @Composable (() -> Unit)? = null,
|
||||||
|
supportingText: @Composable (() -> Unit)? = null,
|
||||||
|
isError: Boolean = false,
|
||||||
|
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||||
|
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||||
|
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||||
|
singleLine: Boolean = false,
|
||||||
|
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||||
|
minLines: Int = 1,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
shape: Shape = OutlinedTextFieldDefaults.shape,
|
||||||
|
colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
|
||||||
|
) {
|
||||||
|
// If color is not provided via the text style, use content color as a default
|
||||||
|
val textColor = textStyle.color
|
||||||
|
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
|
||||||
|
|
||||||
|
CompositionLocalProvider {
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
modifier = if (label != null) {
|
||||||
|
modifier
|
||||||
|
// Merge semantics at the beginning of the modifier chain to ensure padding is
|
||||||
|
// considered part of the text field.
|
||||||
|
.semantics(mergeDescendants = true) {}
|
||||||
|
.padding(top = 8.dp)
|
||||||
|
} else {
|
||||||
|
modifier
|
||||||
|
}
|
||||||
|
.defaultMinSize(
|
||||||
|
minWidth = OutlinedTextFieldDefaults.MinWidth,
|
||||||
|
minHeight = OutlinedTextFieldDefaults.MinHeight
|
||||||
|
),
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
enabled = enabled,
|
||||||
|
readOnly = readOnly,
|
||||||
|
textStyle = mergedTextStyle,
|
||||||
|
cursorBrush = SolidColor(if (!isError) MaterialTheme.colorScheme.primary else Color.Red),
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
keyboardOptions = keyboardOptions,
|
||||||
|
keyboardActions = keyboardActions,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
singleLine = singleLine,
|
||||||
|
maxLines = maxLines,
|
||||||
|
minLines = minLines,
|
||||||
|
decorationBox = @Composable { innerTextField ->
|
||||||
|
OutlinedTextFieldDefaults.DecorationBox(
|
||||||
|
contentPadding = PaddingValues.Absolute(left = 16.dp, right = 16.dp),
|
||||||
|
value = value,
|
||||||
|
visualTransformation = visualTransformation,
|
||||||
|
innerTextField = innerTextField,
|
||||||
|
placeholder = placeholder,
|
||||||
|
label = label,
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
|
trailingIcon = trailingIcon,
|
||||||
|
prefix = prefix,
|
||||||
|
suffix = suffix,
|
||||||
|
supportingText = supportingText,
|
||||||
|
singleLine = singleLine,
|
||||||
|
enabled = enabled,
|
||||||
|
isError = isError,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
colors = colors,
|
||||||
|
container = {
|
||||||
|
OutlinedTextFieldDefaults.ContainerBox(
|
||||||
|
enabled,
|
||||||
|
isError,
|
||||||
|
interactionSource,
|
||||||
|
colors,
|
||||||
|
shape
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun GakuTextInputPreview() {
|
||||||
|
GakuTextInput(modifier = Modifier.height(50.dp),
|
||||||
|
fontSize = 16f,
|
||||||
|
value = "123456", onValueChange = { }, label = { Text("Label") })
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components.base
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AutoSizeText(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
text: String,
|
||||||
|
color: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
fontSize: TextUnit = TextUnit.Unspecified,
|
||||||
|
textStyle: TextStyle? = null,
|
||||||
|
minSize: TextUnit = 8.sp
|
||||||
|
) {
|
||||||
|
var scaledTextStyle by remember { mutableStateOf(textStyle ?: TextStyle(color = color, fontSize = fontSize)) }
|
||||||
|
var readyToDraw by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (LocalInspectionMode.current) {
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
modifier,
|
||||||
|
style = scaledTextStyle
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text,
|
||||||
|
modifier.drawWithContent {
|
||||||
|
if (readyToDraw) {
|
||||||
|
drawContent()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = scaledTextStyle,
|
||||||
|
softWrap = false,
|
||||||
|
onTextLayout = { textLayoutResult ->
|
||||||
|
if (textLayoutResult.didOverflowWidth) {
|
||||||
|
val newSize = (scaledTextStyle.fontSize.value - 1.sp.value).sp
|
||||||
|
if (minSize <= newSize) {
|
||||||
|
scaledTextStyle = scaledTextStyle.copy(fontSize = newSize)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
readyToDraw = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
readyToDraw = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.components.base
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CollapsibleBox(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
collapsedHeight: Dp = 28.dp,
|
||||||
|
viewModel: CollapsibleBoxViewModel = viewModel(),
|
||||||
|
showExpand: Boolean = true,
|
||||||
|
expandState: Boolean? = null,
|
||||||
|
innerPaddingTopBottom: Dp = 0.dp,
|
||||||
|
innerPaddingLeftRight: Dp = 0.dp,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val expanded by viewModel::expanded
|
||||||
|
|
||||||
|
// var offsetY by remember { mutableFloatStateOf(0f) }
|
||||||
|
|
||||||
|
val animatedHeight by animateDpAsState(
|
||||||
|
targetValue = if (expandState ?: expanded) Dp.Unspecified else collapsedHeight,
|
||||||
|
label = "CollapsibleBox$collapsedHeight"
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.animateContentSize()/*
|
||||||
|
.pointerInput(Unit) {
|
||||||
|
detectVerticalDragGestures(
|
||||||
|
onVerticalDrag = { change, dragAmount ->
|
||||||
|
change.consume()
|
||||||
|
offsetY += dragAmount
|
||||||
|
if (expanded && offsetY > 0) {
|
||||||
|
viewModel.expanded = false
|
||||||
|
} else if (!expanded && offsetY < 0) {
|
||||||
|
viewModel.expanded = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragEnd = {
|
||||||
|
offsetY = 0f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}*/
|
||||||
|
.background(MaterialTheme.colorScheme.background)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(animatedHeight)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = innerPaddingLeftRight, end = innerPaddingLeftRight,
|
||||||
|
top = innerPaddingTopBottom, bottom = innerPaddingTopBottom)
|
||||||
|
// .fillMaxSize()
|
||||||
|
.clickable {
|
||||||
|
if (!expanded && showExpand) {
|
||||||
|
viewModel.expanded = expandState ?: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
//item {
|
||||||
|
if (expandState ?: expanded) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
else if (showExpand) {
|
||||||
|
Text(text = "Details ↓", color = Color.Gray)
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun CollapsibleBoxPreview() {
|
||||||
|
CollapsibleBox(showExpand = true) {}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.pages
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import io.github.chinosk.gakumas.localify.MainActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
||||||
|
previewData: GakumasConfig? = null) {
|
||||||
|
val imagePainter = painterResource(R.drawable.bg_pattern)
|
||||||
|
val versionInfo = remember {
|
||||||
|
context?.getVersion() ?: listOf("", "Unknown")
|
||||||
|
}
|
||||||
|
// val config = getConfigState(context, previewData)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color(0xFFFDFDFD))
|
||||||
|
) {
|
||||||
|
val screenH = imageRepeater(
|
||||||
|
painter = imagePainter,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(10.dp, 10.dp, 10.dp, 0.dp),
|
||||||
|
verticalArrangement = Arrangement.Top
|
||||||
|
) {
|
||||||
|
Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp)
|
||||||
|
Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp)
|
||||||
|
|
||||||
|
SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home),
|
||||||
|
stringResource(R.string.advanced_settings)),
|
||||||
|
context = context, previewData = previewData, screenH = screenH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun imageRepeater(
|
||||||
|
painter: Painter,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
): Dp {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val imageHeightPx = painter.intrinsicSize.height
|
||||||
|
val imageHeightDp = with(density) { imageHeightPx.toDp() }
|
||||||
|
var retMaxH = 1080.dp
|
||||||
|
BoxWithConstraints(modifier = modifier) {
|
||||||
|
retMaxH = maxHeight
|
||||||
|
val screenHeight = maxHeight
|
||||||
|
val repeatCount = (screenHeight / imageHeightDp).toInt() + 1
|
||||||
|
|
||||||
|
Column {
|
||||||
|
repeat(repeatCount) {
|
||||||
|
Image(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(imageHeightDp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retMaxH
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380)
|
||||||
|
@Composable
|
||||||
|
fun MainUIPreview(modifier: Modifier = Modifier) {
|
||||||
|
val previewConfig = GakumasConfig()
|
||||||
|
previewConfig.enabled = true
|
||||||
|
|
||||||
|
GakumasLocalifyTheme {
|
||||||
|
MainUI(previewData = previewConfig)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.pages
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import io.github.chinosk.gakumas.localify.MainActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.onClickStartGame
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.pages.subPages.HomePage
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun SettingsTabs(modifier: Modifier = Modifier,
|
||||||
|
titles: List<String>,
|
||||||
|
context: MainActivity? = null,
|
||||||
|
previewData: GakumasConfig? = null,
|
||||||
|
screenH: Dp = 1080.dp
|
||||||
|
) {
|
||||||
|
|
||||||
|
val pagerState = rememberPagerState(initialPage = 1, pageCount = { titles.size })
|
||||||
|
|
||||||
|
Box {
|
||||||
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
pageSpacing = 10.dp
|
||||||
|
) { page ->
|
||||||
|
Column(modifier = modifier
|
||||||
|
.padding(5.dp)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
when (page) {
|
||||||
|
0 -> AboutPage(modifier, context = context, previewData = previewData, screenH = screenH)
|
||||||
|
1 -> HomePage(modifier, context = context, previewData = previewData, screenH = screenH)
|
||||||
|
2 -> AdvanceSettingsPage(modifier, context = context, previewData = previewData, screenH = screenH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(bottom = 6.dp)) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = { context?.onClickStartGame() },
|
||||||
|
modifier = Modifier.align(Alignment.End),
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
shape = CircleShape
|
||||||
|
) {
|
||||||
|
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||||
|
contentDescription = "StartGame")
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTabRow(modifier, pagerState, titles) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, heightDp = 760)
|
||||||
|
@Composable
|
||||||
|
fun SettingTabsPreview(modifier: Modifier = Modifier) {
|
||||||
|
SettingsTabs(titles = listOf("TAB 1", "TAB 2", "TAB 3"), previewData = GakumasConfig())
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SplashScreen(navController: NavController) {
|
||||||
|
/*Image(
|
||||||
|
painter = painterResource(id = R.drawable.splash_image),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentScale = ContentScale.FillHeight
|
||||||
|
)*/
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
kotlinx.coroutines.delay(100)
|
||||||
|
|
||||||
|
navController.navigate("main") {
|
||||||
|
popUpTo("splash") { inclusive = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,214 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.pages.subPages
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.decode.SvgDecoder
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.size.Size
|
||||||
|
import io.github.chinosk.gakumas.localify.MainActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.convertToString
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.json
|
||||||
|
import io.github.chinosk.gakumas.localify.models.AboutPageConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuButton
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AboutPage(modifier: Modifier = Modifier,
|
||||||
|
context: MainActivity? = null,
|
||||||
|
previewData: GakumasConfig? = null,
|
||||||
|
bottomSpacerHeight: Dp = 120.dp,
|
||||||
|
screenH: Dp = 1080.dp) {
|
||||||
|
// val config = getConfigState(context, previewData)
|
||||||
|
val contributorInfo = remember {
|
||||||
|
val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let {
|
||||||
|
convertToString(context.assets?.open(it))
|
||||||
|
}
|
||||||
|
dataJsonString?.let { json.decodeFromString<AboutPageConfig>(it) }
|
||||||
|
?: AboutPageConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.sizeIn(maxHeight = screenH)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(stringResource(R.string.about_warn_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.error)
|
||||||
|
Text(stringResource(R.string.about_warn_p1))
|
||||||
|
Text(stringResource(R.string.about_warn_p2))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(stringResource(R.string.about_about_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||||
|
Text(stringResource(R.string.about_about_p1))
|
||||||
|
Text(stringResource(R.string.about_about_p2))
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
start = 8.dp, end = 8.dp, top = 8.dp, bottom = 0.dp
|
||||||
|
)) {
|
||||||
|
GakuButton(text = "Github", modifier = modifier
|
||||||
|
.weight(1f)
|
||||||
|
.sizeIn(maxWidth = 600.dp), onClick = {
|
||||||
|
context?.openUrl(contributorInfo.plugin_repo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.sizeIn(maxHeight = screenH)
|
||||||
|
.fillMaxWidth()) {
|
||||||
|
item {
|
||||||
|
Text(stringResource(R.string.project_contribution), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||||
|
}
|
||||||
|
for (contributor in contributorInfo.main_contributors) {
|
||||||
|
item {
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(0.dp, 8.dp, 8.dp, 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(contributor.name, fontSize = 16.sp)
|
||||||
|
for (link in contributor.links) {
|
||||||
|
GakuButton(text = link.name, modifier = modifier.height(40.dp),
|
||||||
|
onClick = {
|
||||||
|
context?.openUrl(link.link)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(stringResource(R.string.contributors), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer)
|
||||||
|
|
||||||
|
Text(stringResource(R.string.plugin_code), fontSize = 16.sp)
|
||||||
|
NetworkSvgImage(
|
||||||
|
url = contributorInfo.contrib_img.plugin,
|
||||||
|
contentDescription = "plugin-contrib"
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(stringResource(R.string.translation_repository), fontSize = 16.sp)
|
||||||
|
NetworkSvgImage(
|
||||||
|
url = contributorInfo.contrib_img.translation,
|
||||||
|
contentDescription = "translation-contrib"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = modifier.height(bottomSpacerHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NetworkImage(
|
||||||
|
url: String,
|
||||||
|
contentDescription: String?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val painter = rememberAsyncImagePainter(model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(url)
|
||||||
|
.crossfade(true)
|
||||||
|
.size(Size.ORIGINAL)
|
||||||
|
.build())
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NetworkSvgImage(
|
||||||
|
url: String,
|
||||||
|
contentDescription: String?,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val imageLoader = ImageLoader.Builder(LocalContext.current)
|
||||||
|
.components {
|
||||||
|
add(SvgDecoder.Factory())
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val painter = rememberAsyncImagePainter(
|
||||||
|
model = ImageRequest.Builder(LocalContext.current)
|
||||||
|
.data(url)
|
||||||
|
.size(Size.ORIGINAL)
|
||||||
|
.build(),
|
||||||
|
imageLoader = imageLoader
|
||||||
|
)
|
||||||
|
|
||||||
|
Image(
|
||||||
|
painter = painter,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun AboutPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
|
||||||
|
AboutPage(modifier, previewData = data)
|
||||||
|
}
|
@ -0,0 +1,397 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.pages.subPages
|
||||||
|
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.MainActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
import io.github.chinosk.gakumas.localify.getConfigState
|
||||||
|
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.models.BreastCollapsibleBoxViewModelFactory
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuButton
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AdvanceSettingsPage(modifier: Modifier = Modifier,
|
||||||
|
context: MainActivity? = null,
|
||||||
|
previewData: GakumasConfig? = null,
|
||||||
|
bottomSpacerHeight: Dp = 120.dp,
|
||||||
|
screenH: Dp = 1080.dp) {
|
||||||
|
val config = getConfigState(context, previewData)
|
||||||
|
// val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
val breastParamViewModel: BreastCollapsibleBoxViewModel =
|
||||||
|
viewModel(factory = BreastCollapsibleBoxViewModelFactory(initiallyExpanded = false))
|
||||||
|
val keyBoardOptionsDecimal = remember {
|
||||||
|
KeyboardOptions(keyboardType = KeyboardType.Decimal)
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.sizeIn(maxHeight = screenH)
|
||||||
|
// .fillMaxHeight()
|
||||||
|
// .verticalScroll(scrollState)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier, stringResource(R.string.camera_settings)) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.enable_free_camera), checked = config.value.enableFreeCamera) {
|
||||||
|
v -> context?.onEnableFreeCameraChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier, stringResource(R.string.debug_settings)) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) {
|
||||||
|
v -> context?.onTextTestChanged(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.export_text), checked = config.value.dumpText) {
|
||||||
|
v -> context?.onDumpTextChanged(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
|
||||||
|
v -> context?.onForceExportResourceChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier, stringResource(R.string.breast_param),
|
||||||
|
contentPadding = 0.dp,
|
||||||
|
onHeadClick = {
|
||||||
|
breastParamViewModel.expanded = !breastParamViewModel.expanded
|
||||||
|
}) {
|
||||||
|
CollapsibleBox(modifier = modifier,
|
||||||
|
viewModel = breastParamViewModel
|
||||||
|
) {
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.sizeIn(maxHeight = screenH),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier,
|
||||||
|
checked = config.value.enableBreastParam,
|
||||||
|
text = stringResource(R.string.enable_breast_param)
|
||||||
|
) { v -> context?.onEnableBreastParamChanged(v) }
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
val buttonModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "??", onClick = { context?.onBClickPresetChanged(5) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "+5", onClick = { context?.onBClickPresetChanged(4) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "+4", onClick = { context?.onBClickPresetChanged(3) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "+3", onClick = { context?.onBClickPresetChanged(2) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "+2", onClick = { context?.onBClickPresetChanged(1) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = "+1", onClick = { context?.onBClickPresetChanged(0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bDamping.toString(),
|
||||||
|
onValueChange = { c -> context?.onBDampingChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.damping)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bStiffness.toString(),
|
||||||
|
onValueChange = { c -> context?.onBStiffnessChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.stiffness)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bSpring.toString(),
|
||||||
|
onValueChange = { c -> context?.onBSpringChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.spring)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bPendulum.toString(),
|
||||||
|
onValueChange = { c -> context?.onBPendulumChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.pendulum)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bPendulumRange.toString(),
|
||||||
|
onValueChange = { c -> context?.onBPendulumRangeChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.pendulumrange)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bAverage.toString(),
|
||||||
|
onValueChange = { c -> context?.onBAverageChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.average)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bRootWeight.toString(),
|
||||||
|
onValueChange = { c -> context?.onBRootWeightChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.rootweight)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier,
|
||||||
|
checked = config.value.bUseScale,
|
||||||
|
leftPart = {
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bScale.toString(),
|
||||||
|
onValueChange = { c -> context?.onBScaleChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.breast_scale)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { v -> context?.onBUseScaleChanged(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier,
|
||||||
|
checked = config.value.bUseArmCorrection,
|
||||||
|
text = stringResource(R.string.usearmcorrection)
|
||||||
|
) { v -> context?.onBUseArmCorrectionChanged(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier,
|
||||||
|
checked = config.value.bUseLimit,
|
||||||
|
text = stringResource(R.string.uselimit_0_1)
|
||||||
|
) { v ->
|
||||||
|
context?.onBUseLimitChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
CollapsibleBox(modifier = modifier,
|
||||||
|
expandState = config.value.bUseLimit,
|
||||||
|
collapsedHeight = 0.dp,
|
||||||
|
showExpand = false
|
||||||
|
){
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val textInputModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitXx.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitXxChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisx_x)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitYx.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitYxChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisy_x)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitZx.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitZxChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisz_x)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val textInputModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitXy.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitXyChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisx_y)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitYy.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitYyChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisy_y)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.bLimitZy.toString(),
|
||||||
|
onValueChange = { c -> context?.onBLimitZyChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.axisz_y)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
if (config.value.dbgMode) {
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
|
||||||
|
GakuGroupBox(modifier, stringResource(R.string.test_mode_live)) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.unlockAllLive),
|
||||||
|
checked = config.value.unlockAllLive) {
|
||||||
|
v -> context?.onUnlockAllLiveChanged(v)
|
||||||
|
}
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.liveUseCustomeDress),
|
||||||
|
checked = config.value.enableLiveCustomeDress) {
|
||||||
|
v -> context?.onLiveCustomeDressChanged(v)
|
||||||
|
}
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.liveCustomeHeadId,
|
||||||
|
onValueChange = { c -> context?.onLiveCustomeHeadIdChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.live_costume_head_id),
|
||||||
|
fontSize = 12.sp) }
|
||||||
|
)
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.liveCustomeCostumeId,
|
||||||
|
onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.live_custome_dress_id)) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = modifier.height(bottomSpacerHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun AdvanceSettingsPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
|
||||||
|
AdvanceSettingsPage(modifier, previewData = data)
|
||||||
|
}
|
@ -0,0 +1,476 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.pages.subPages
|
||||||
|
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.MainActivity
|
||||||
|
import io.github.chinosk.gakumas.localify.R
|
||||||
|
import io.github.chinosk.gakumas.localify.getConfigState
|
||||||
|
import io.github.chinosk.gakumas.localify.getProgramConfigState
|
||||||
|
import io.github.chinosk.gakumas.localify.getProgramDownloadAbleState
|
||||||
|
import io.github.chinosk.gakumas.localify.getProgramDownloadErrorStringState
|
||||||
|
import io.github.chinosk.gakumas.localify.getProgramDownloadState
|
||||||
|
import io.github.chinosk.gakumas.localify.getProgramLocalResourceVersionState
|
||||||
|
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
|
||||||
|
import io.github.chinosk.gakumas.localify.mainUtils.FileDownloader
|
||||||
|
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModel
|
||||||
|
import io.github.chinosk.gakumas.localify.models.ResourceCollapsibleBoxViewModelFactory
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuButton
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuProgressBar
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuRadio
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch
|
||||||
|
import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomePage(modifier: Modifier = Modifier,
|
||||||
|
context: MainActivity? = null,
|
||||||
|
previewData: GakumasConfig? = null,
|
||||||
|
bottomSpacerHeight: Dp = 120.dp,
|
||||||
|
screenH: Dp = 1080.dp) {
|
||||||
|
val config = getConfigState(context, previewData)
|
||||||
|
val programConfig = getProgramConfigState(context)
|
||||||
|
|
||||||
|
val downloadProgress by getProgramDownloadState(context)
|
||||||
|
val downloadAble by getProgramDownloadAbleState(context)
|
||||||
|
val localResourceVersion by getProgramLocalResourceVersionState(context)
|
||||||
|
val downloadErrorString by getProgramDownloadErrorStringState(context)
|
||||||
|
|
||||||
|
// val scrollState = rememberScrollState()
|
||||||
|
val keyboardOptionsNumber = remember {
|
||||||
|
KeyboardOptions(keyboardType = KeyboardType.Number)
|
||||||
|
}
|
||||||
|
val keyBoardOptionsDecimal = remember {
|
||||||
|
KeyboardOptions(keyboardType = KeyboardType.Decimal)
|
||||||
|
}
|
||||||
|
|
||||||
|
val resourceSettingsViewModel: ResourceCollapsibleBoxViewModel =
|
||||||
|
viewModel(factory = ResourceCollapsibleBoxViewModelFactory(initiallyExpanded = false))
|
||||||
|
|
||||||
|
fun onClickDownload() {
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
downloadAbleState = false,
|
||||||
|
errorString = "",
|
||||||
|
downloadProgressState = -1f
|
||||||
|
)
|
||||||
|
val (_, newUrl) = FileDownloader.checkAndChangeDownloadURL(programConfig.value.transRemoteZipUrl)
|
||||||
|
context?.onPTransRemoteZipUrlChanged(newUrl, 0, 0, 0)
|
||||||
|
FileDownloader.downloadFile(
|
||||||
|
newUrl,
|
||||||
|
checkContentTypes = listOf("application/zip", "application/octet-stream"),
|
||||||
|
onDownload = { progress, _, _ ->
|
||||||
|
context?.mainPageAssetsViewDataUpdate(downloadProgressState = progress)
|
||||||
|
},
|
||||||
|
|
||||||
|
onSuccess = { byteArray ->
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
downloadAbleState = true,
|
||||||
|
errorString = "",
|
||||||
|
downloadProgressState = -1f
|
||||||
|
)
|
||||||
|
val file = File(context?.filesDir, "update_trans.zip")
|
||||||
|
file.writeBytes(byteArray)
|
||||||
|
val newFileVersion = FileHotUpdater.getZipResourceVersion(file.absolutePath)
|
||||||
|
if (newFileVersion != null) {
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
localResourceVersionState = newFileVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
localResourceVersionState = context.getString(
|
||||||
|
R.string.invalid_zip_file
|
||||||
|
),
|
||||||
|
errorString = context.getString(R.string.invalid_zip_file_warn)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFailed = { code, reason ->
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
downloadAbleState = true,
|
||||||
|
errorString = reason,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.sizeIn(maxHeight = screenH)
|
||||||
|
// .fillMaxHeight()
|
||||||
|
// .verticalScroll(scrollState)
|
||||||
|
// .width(IntrinsicSize.Max)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier = modifier, stringResource(R.string.basic_settings)) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.enable_plugin), checked = config.value.enabled) {
|
||||||
|
v -> context?.onEnabledChanged(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuSwitch(modifier, stringResource(R.string.replace_font), checked = config.value.replaceFont) {
|
||||||
|
v -> context?.onReplaceFontChanged(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier, stringResource(R.string.resource_settings),
|
||||||
|
contentPadding = 0.dp,
|
||||||
|
onHeadClick = {
|
||||||
|
resourceSettingsViewModel.expanded = !resourceSettingsViewModel.expanded
|
||||||
|
}) {
|
||||||
|
CollapsibleBox(modifier = modifier,
|
||||||
|
viewModel = resourceSettingsViewModel
|
||||||
|
) {
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
// .padding(8.dp)
|
||||||
|
.sizeIn(maxHeight = screenH),
|
||||||
|
// verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp, top = 8.dp),
|
||||||
|
checked = programConfig.value.checkBuiltInAssets,
|
||||||
|
text = stringResource(id = R.string.check_built_in_resource)
|
||||||
|
) { v -> context?.onPCheckBuiltInAssetsChanged(v) }
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
|
||||||
|
checked = programConfig.value.cleanLocalAssets,
|
||||||
|
text = stringResource(id = R.string.delete_plugin_resource)
|
||||||
|
) { v -> context?.onPCleanLocalAssetsChanged(v) }
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier = modifier.padding(start = 8.dp, end = 8.dp),
|
||||||
|
checked = programConfig.value.useRemoteAssets,
|
||||||
|
text = stringResource(id = R.string.use_remote_zip_resource)
|
||||||
|
) { v -> context?.onPUseRemoteAssetsChanged(v) }
|
||||||
|
|
||||||
|
CollapsibleBox(modifier = modifier.graphicsLayer(clip = false),
|
||||||
|
expandState = programConfig.value.useRemoteAssets,
|
||||||
|
collapsedHeight = 0.dp,
|
||||||
|
innerPaddingLeftRight = 8.dp,
|
||||||
|
showExpand = false
|
||||||
|
) {
|
||||||
|
GakuSwitch(modifier = modifier,
|
||||||
|
checked = programConfig.value.delRemoteAfterUpdate,
|
||||||
|
text = stringResource(id = R.string.del_remote_after_update)
|
||||||
|
) { v -> context?.onPDelRemoteAfterUpdateChanged(v) }
|
||||||
|
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
// .padding(8.dp)
|
||||||
|
.sizeIn(maxHeight = screenH),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.padding(end = 8.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = programConfig.value.transRemoteZipUrl,
|
||||||
|
onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(id = R.string.resource_url)) },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
|
||||||
|
if (downloadAble) {
|
||||||
|
GakuButton(modifier = modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.sizeIn(minWidth = 80.dp),
|
||||||
|
text = stringResource(id = R.string.download),
|
||||||
|
onClick = { onClickDownload() })
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GakuButton(modifier = modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.sizeIn(minWidth = 80.dp),
|
||||||
|
text = stringResource(id = R.string.cancel), onClick = {
|
||||||
|
FileDownloader.cancel()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadProgress >= 0) {
|
||||||
|
item {
|
||||||
|
GakuProgressBar(progress = downloadProgress, isError = downloadErrorString.isNotEmpty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadErrorString.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(text = downloadErrorString, color = Color(0xFFE2041B))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Text(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
val file =
|
||||||
|
File(context?.filesDir, "update_trans.zip")
|
||||||
|
context?.mainPageAssetsViewDataUpdate(
|
||||||
|
localResourceVersionState = FileHotUpdater
|
||||||
|
.getZipResourceVersion(file.absolutePath)
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
}, text = "${stringResource(R.string.downloaded_resource_version)}: $localResourceVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.height(0.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(6.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuGroupBox(modifier = modifier, contentPadding = 0.dp, title = stringResource(R.string.graphic_settings)) {
|
||||||
|
LazyColumn(modifier = Modifier
|
||||||
|
.sizeIn(maxHeight = screenH),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
GakuTextInput(modifier = modifier
|
||||||
|
.padding(start = 4.dp, end = 4.dp)
|
||||||
|
.height(45.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.targetFrameRate.toString(),
|
||||||
|
onValueChange = { c -> context?.onTargetFpsChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.setFpsTitle)) },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Column(modifier = Modifier.padding(start = 8.dp, end = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
Text(stringResource(R.string.orientation_lock))
|
||||||
|
Row(modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||||
|
val radioModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuRadio(modifier = radioModifier,
|
||||||
|
text = stringResource(R.string.orientation_orig), selected = config.value.gameOrientation == 0,
|
||||||
|
onClick = { context?.onGameOrientationChanged(0) })
|
||||||
|
|
||||||
|
GakuRadio(modifier = radioModifier,
|
||||||
|
text = stringResource(R.string.orientation_portrait), selected = config.value.gameOrientation == 1,
|
||||||
|
onClick = { context?.onGameOrientationChanged(1) })
|
||||||
|
|
||||||
|
GakuRadio(modifier = radioModifier,
|
||||||
|
text = stringResource(R.string.orientation_landscape), selected = config.value.gameOrientation == 2,
|
||||||
|
onClick = { context?.onGameOrientationChanged(2) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 1.dp,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
GakuSwitch(modifier.padding(start = 8.dp, end = 8.dp),
|
||||||
|
stringResource(R.string.useCustomeGraphicSettings),
|
||||||
|
checked = config.value.useCustomeGraphicSettings) {
|
||||||
|
v -> context?.onUseCustomeGraphicSettingsChanged(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
CollapsibleBox(modifier = modifier,
|
||||||
|
expandState = config.value.useCustomeGraphicSettings,
|
||||||
|
collapsedHeight = 0.dp,
|
||||||
|
showExpand = false
|
||||||
|
) {
|
||||||
|
LazyColumn(modifier = modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.sizeIn(maxHeight = screenH)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val buttonModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(40.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = stringResource(R.string.max_high), onClick = { context?.onChangePresetQuality(4) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = stringResource(R.string.very_high), onClick = { context?.onChangePresetQuality(3) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = stringResource(R.string.hign), onClick = { context?.onChangePresetQuality(2) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = stringResource(R.string.middle), onClick = { context?.onChangePresetQuality(1) })
|
||||||
|
|
||||||
|
GakuButton(modifier = buttonModifier,
|
||||||
|
text = stringResource(R.string.low), onClick = { context?.onChangePresetQuality(0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val textInputModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.renderScale.toString(),
|
||||||
|
onValueChange = { c -> context?.onRenderScaleChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text(stringResource(R.string.renderscale)) },
|
||||||
|
keyboardOptions = keyBoardOptionsDecimal)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.qualitySettingsLevel.toString(),
|
||||||
|
onValueChange = { c -> context?.onQualitySettingsLevelChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text("QualityLevel (1/1/2/3/5)") },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val textInputModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.volumeIndex.toString(),
|
||||||
|
onValueChange = { c -> context?.onVolumeIndexChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text("VolumeIndex (0/1/2/3/4)") },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.maxBufferPixel.toString(),
|
||||||
|
onValueChange = { c -> context?.onMaxBufferPixelChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text("MaxBufferPixel (1024/1440/2538/3384/8190)", fontSize = 10.sp) },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Row(modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
val textInputModifier = remember {
|
||||||
|
modifier
|
||||||
|
.height(45.dp)
|
||||||
|
.weight(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.reflectionQualityLevel.toString(),
|
||||||
|
onValueChange = { c -> context?.onReflectionQualityLevelChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text( text = "ReflectionLevel (0~5)") },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
|
||||||
|
GakuTextInput(modifier = textInputModifier,
|
||||||
|
fontSize = 14f,
|
||||||
|
value = config.value.lodQualityLevel.toString(),
|
||||||
|
onValueChange = { c -> context?.onLodQualityLevelChanged(c, 0, 0, 0)},
|
||||||
|
label = { Text("LOD Level (0~5)") },
|
||||||
|
keyboardOptions = keyboardOptionsNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(modifier = modifier.height(bottomSpacerHeight))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Composable
|
||||||
|
fun HomePagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
|
||||||
|
HomePage(modifier, previewData = data)
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
package io.github.chinosk.gakumas.localify.ui.theme
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
|
private val DarkColorScheme = darkColorScheme(
|
||||||
|
primary = Purple80,
|
||||||
|
secondary = PurpleGrey80,
|
||||||
|
tertiary = Pink80
|
||||||
|
)
|
||||||
|
|
||||||
|
private val LightColorScheme = lightColorScheme(
|
||||||
|
primary = Color(0xFFF89400),
|
||||||
|
secondary = PurpleGrey40,
|
||||||
|
tertiary = Pink40
|
||||||
|
|
||||||
|
/* Other default colors to override
|
||||||
|
background = Color(0xFFFFFBFE),
|
||||||
|
surface = Color(0xFFFFFBFE),
|
||||||
|
onPrimary = Color.White,
|
||||||
|
onSecondary = Color.White,
|
||||||
|
onTertiary = Color.White,
|
||||||
|
onBackground = Color(0xFF1C1B1F),
|
||||||
|
onSurface = Color(0xFF1C1B1F),
|
||||||
|
*/
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun GakumasLocalifyTheme(
|
||||||
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
|
// Dynamic color is available on Android 12+
|
||||||
|
dynamicColor: Boolean = true,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val colorScheme = when {
|
||||||
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
|
val context = LocalContext.current
|
||||||
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
darkTheme -> DarkColorScheme
|
||||||
|
else -> LightColorScheme
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = LocalView.current
|
||||||
|
if (!view.isInEditMode) {
|
||||||
|
SideEffect {
|
||||||
|
val window = (view.context as Activity).window
|
||||||
|
window.statusBarColor = colorScheme.primary.toArgb()
|
||||||
|
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = colorScheme,
|
||||||
|
typography = Typography,
|
||||||
|
content = content
|
||||||
|
)
|
||||||
|
}
|
BIN
app/src/main/res/drawable-hdpi/splash.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
app/src/main/res/drawable-mdpi/splash.png
Normal file
After Width: | Height: | Size: 650 KiB |
BIN
app/src/main/res/drawable-xhdpi/splash.png
Normal file
After Width: | Height: | Size: 2.8 MiB |
BIN
app/src/main/res/drawable-xxhdpi/splash.png
Normal file
After Width: | Height: | Size: 5.1 MiB |
BIN
app/src/main/res/drawable/bg_h1.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable/bg_pattern.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
app/src/main/res/drawable/splash.png
Normal file
After Width: | Height: | Size: 2.6 MiB |
13
app/src/main/res/drawable/splash_style.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="@color/white" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<bitmap android:src="@drawable/splash"
|
||||||
|
android:gravity="center"
|
||||||
|
android:tileMode="disabled"/>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
@ -43,5 +43,36 @@
|
|||||||
<string name="axisx_y">axisX.y</string>
|
<string name="axisx_y">axisX.y</string>
|
||||||
<string name="axisy_y">axisY.y</string>
|
<string name="axisy_y">axisY.y</string>
|
||||||
<string name="axisz_y">axisZ.y</string>
|
<string name="axisz_y">axisZ.y</string>
|
||||||
|
<string name="basic_settings">Basic Ssettings</string>
|
||||||
|
<string name="graphic_settings">Graphic Settings</string>
|
||||||
|
<string name="camera_settings">Camera Settings</string>
|
||||||
|
<string name="test_mode_live">Test Mode - LIVE</string>
|
||||||
|
<string name="debug_settings">Debug Settings</string>
|
||||||
|
<string name="breast_param">Breast Parameters</string>
|
||||||
|
<string name="about">About</string>
|
||||||
|
<string name="home">Home</string>
|
||||||
|
<string name="advanced_settings">Advanced</string>
|
||||||
|
<string name="about_warn_title">WARNING</string>
|
||||||
|
<string name="about_warn_p1">This plugin is for learning and communication only.</string>
|
||||||
|
<string name="about_warn_p2">Using external plugin against the relevant TOS so proceed at your own risk.</string>
|
||||||
|
<string name="about_about_title">About This Plugin</string>
|
||||||
|
<string name="about_about_p1">This plugin is completely free. If you paid for this plugin, please report the seller.</string>
|
||||||
|
<string name="about_about_p2">Plugin QQ group: 975854705</string>
|
||||||
|
<string name="project_contribution">Project Contribution</string>
|
||||||
|
<string name="plugin_code">Plugin Code</string>
|
||||||
|
<string name="contributors">Contributors</string>
|
||||||
|
<string name="translation_repository">Translation Repository</string>
|
||||||
|
<string name="resource_settings">Resource Settings</string>
|
||||||
|
<string name="check_built_in_resource">Check Built-in Assets Update</string>
|
||||||
|
<string name="delete_plugin_resource">Delete Plugin Resource</string>
|
||||||
|
<string name="use_remote_zip_resource">Use Remote ZIP Resource</string>
|
||||||
|
<string name="resource_url">Resource URL</string>
|
||||||
|
<string name="download">Download</string>
|
||||||
|
<string name="invalid_zip_file">Invalid file</string>
|
||||||
|
<string name="invalid_zip_file_warn">This file is not a valid ZIP translation resource pack.</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="downloaded_resource_version">Downloaded Version</string>
|
||||||
|
<string name="del_remote_after_update">Delete Cache File After Update</string>
|
||||||
|
|
||||||
|
<string name="about_contributors_asset_file">about_contributors_en.json</string>
|
||||||
</resources>
|
</resources>
|
@ -1,5 +1,30 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<style name="Theme.GakumasLocalify" parent="@style/Theme.Material3.DynamicColors.DayNight.NoActionBar" />
|
<style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" >
|
||||||
|
<item name="android:windowBackground">@drawable/splash_style</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
<style name="Theme.GakumasLocalify.NoDisplay" parent="@style/Theme.MaterialComponents.Light.NoActionBar" >
|
||||||
|
<item name="android:windowFrame">@null</item>
|
||||||
|
<!-- 设置是否可滑动 -->
|
||||||
|
<item name="android:windowIsFloating">true</item>
|
||||||
|
<!-- 设置是否透明 -->
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<!-- 无标题 -->
|
||||||
|
<item name="android:windowNoTitle">true</item>
|
||||||
|
<!-- 背景 -->
|
||||||
|
<item name="android:background">@null</item>
|
||||||
|
<!-- 窗口背景 -->
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<!-- 是否变暗 -->
|
||||||
|
<item name="android:backgroundDimEnabled">false</item>
|
||||||
|
<!-- 点击空白部分activity不消失 -->
|
||||||
|
<item name="android:windowCloseOnTouchOutside">true</item>
|
||||||
|
<!-- 无标题 有的手机设置这行代码-->
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
|
<item name="android:windowAnimationStyle">@null</item>
|
||||||
|
<item name="android:windowDisablePreview">true</item>
|
||||||
|
<item name="android:windowNoDisplay">true</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
|
4
app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths>
|
||||||
|
<files-path name="files" path="." />
|
||||||
|
</paths>
|
@ -1,5 +1,4 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.4.2' apply false
|
alias(libs.plugins.androidApplication) apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
|
alias(libs.plugins.kotlinAndroid) apply false
|
||||||
}
|
}
|
||||||
|
54
gradle/libs.versions.toml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
[versions]
|
||||||
|
accompanistPager = "0.30.0"
|
||||||
|
activityCompose = "1.9.0"
|
||||||
|
okhttpBom = "4.12.0"
|
||||||
|
xposedApi = "82"
|
||||||
|
appcompat = "1.7.0"
|
||||||
|
coil = "2.6.0"
|
||||||
|
composeBom = "2024.06.00"
|
||||||
|
agp = "8.5.0"
|
||||||
|
coreKtx = "1.13.1"
|
||||||
|
kotlin = "2.0.0"
|
||||||
|
lifecycle = "2.8.2"
|
||||||
|
material = "1.12.0"
|
||||||
|
navigationCompose = "2.7.7"
|
||||||
|
xdl = "2.1.1"
|
||||||
|
shadowhook = "1.0.9"
|
||||||
|
serialization="1.7.1"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
accompanist-pager = { module = "com.google.accompanist:accompanist-pager", version.ref = "accompanistPager" }
|
||||||
|
accompanist-pager-indicators = { module = "com.google.accompanist:accompanist-pager-indicators", version.ref = "accompanistPager" }
|
||||||
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
|
||||||
|
androidx-animation = { module = "androidx.compose.animation:animation" }
|
||||||
|
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
|
||||||
|
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
|
||||||
|
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-foundation = { module = "androidx.compose.foundation:foundation" }
|
||||||
|
androidx-foundation-layout = { module = "androidx.compose.foundation:foundation-layout" }
|
||||||
|
androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
|
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" }
|
||||||
|
androidx-material = { module = "androidx.compose.material:material" }
|
||||||
|
androidx-material3 = { module = "androidx.compose.material3:material3" }
|
||||||
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
|
androidx-runtime = { module = "androidx.compose.runtime:runtime" }
|
||||||
|
androidx-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" }
|
||||||
|
androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
|
||||||
|
androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
|
||||||
|
androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
|
||||||
|
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
|
||||||
|
okhttp = { module = "com.squareup.okhttp3:okhttp" }
|
||||||
|
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
|
||||||
|
xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposedApi" }
|
||||||
|
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
|
||||||
|
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
|
||||||
|
material = { module = "com.google.android.material:material", version.ref = "material" }
|
||||||
|
shadowhook = { module = "com.bytedance.android:shadowhook", version.ref = "shadowhook" }
|
||||||
|
xdl = { module = "io.github.hexhacking:xdl", version.ref = "xdl" }
|
||||||
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
|
||||||
|
|
||||||
|
[plugins]
|
||||||
|
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
5
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,7 @@
|
|||||||
#Fri May 17 13:19:52 CST 2024
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
294
gradlew
vendored
Normal file → Executable file
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/sh
|
||||||
|
|
||||||
#
|
#
|
||||||
# Copyright 2015 the original author or authors.
|
# Copyright © 2015-2021 the original authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
@ -17,67 +17,99 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
##
|
#
|
||||||
## Gradle start up script for UN*X
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
##
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
# Attempt to set APP_HOME
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
# Resolve links: $0 may be a link
|
# Resolve links: $0 may be a link
|
||||||
PRG="$0"
|
app_path=$0
|
||||||
# Need this for relative symlinks.
|
|
||||||
while [ -h "$PRG" ] ; do
|
# Need this for daisy-chained symlinks.
|
||||||
ls=`ls -ld "$PRG"`
|
while
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
[ -h "$app_path" ]
|
||||||
PRG="$link"
|
do
|
||||||
else
|
ls=$( ls -ld "$app_path" )
|
||||||
PRG=`dirname "$PRG"`"/$link"
|
link=${ls#*' -> '}
|
||||||
fi
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
done
|
done
|
||||||
SAVED="`pwd`"
|
|
||||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
|
||||||
APP_HOME="`pwd -P`"
|
|
||||||
cd "$SAVED" >/dev/null
|
|
||||||
|
|
||||||
APP_NAME="Gradle"
|
# This is normally unused
|
||||||
APP_BASE_NAME=`basename "$0"`
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
|||||||
if [ -n "$JAVA_HOME" ] ; then
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
|
|||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD="java"
|
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.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC2039,SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# 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
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
35
gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
|||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google {
|
||||||
|
content {
|
||||||
|
includeGroupByRegex("com\\.android.*")
|
||||||
|
includeGroupByRegex("com\\.google.*")
|
||||||
|
includeGroupByRegex("androidx.*")
|
||||||
|
}
|
||||||
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
|