Compare commits
	
		
			No commits in common. "c28e05e271a14c8c739dd4a38f071f3c502ccfee" and "9fede70bec3e8b5e52f3bbdde0efbdfb4bf04780" have entirely different histories.
		
	
	
		
			c28e05e271
			...
			9fede70bec
		
	
		| 
						 | 
				
			
			@ -72,10 +72,8 @@ jobs:
 | 
			
		|||
        keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
 | 
			
		||||
        keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
 | 
			
		||||
        buildToolsVersion: 33.0.0
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: actions/upload-artifact@v4
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Signed-apk
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedFile}}
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,4 @@
 | 
			
		|||
local.properties
 | 
			
		||||
/.idea
 | 
			
		||||
/.vs
 | 
			
		||||
/.kotlin
 | 
			
		||||
/app/debug
 | 
			
		||||
/app/release
 | 
			
		||||
/app
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										101
									
								
								app/build.gradle
								
								
								
								
							
							
						
						
									
										101
									
								
								app/build.gradle
								
								
								
								
							| 
						 | 
				
			
			@ -1,9 +1,8 @@
 | 
			
		|||
plugins {
 | 
			
		||||
    alias(libs.plugins.androidApplication)
 | 
			
		||||
    alias(libs.plugins.kotlinAndroid)
 | 
			
		||||
    alias(libs.plugins.compose.compiler)
 | 
			
		||||
    alias(libs.plugins.serialization)
 | 
			
		||||
    id 'com.android.application'
 | 
			
		||||
    id 'org.jetbrains.kotlin.android'
 | 
			
		||||
}
 | 
			
		||||
android.buildFeatures.buildConfig true
 | 
			
		||||
 | 
			
		||||
android {
 | 
			
		||||
    namespace 'io.github.chinosk.gakumas.localify'
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +14,7 @@ android {
 | 
			
		|||
        minSdk 29
 | 
			
		||||
        targetSdk 34
 | 
			
		||||
        versionCode 2
 | 
			
		||||
        versionName "v1.2"
 | 
			
		||||
        versionName "v1.1"
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
        vectorDrawables {
 | 
			
		||||
| 
						 | 
				
			
			@ -36,38 +35,40 @@ android {
 | 
			
		|||
            minifyEnabled false
 | 
			
		||||
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 | 
			
		||||
            buildConfigField "boolean", "ENABLE_LOG", "true"
 | 
			
		||||
            signingConfig signingConfigs.debug
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    compileOptions {
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_11
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_11
 | 
			
		||||
        sourceCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
        targetCompatibility JavaVersion.VERSION_1_8
 | 
			
		||||
    }
 | 
			
		||||
    kotlinOptions {
 | 
			
		||||
        jvmTarget = '11'
 | 
			
		||||
        jvmTarget = '1.8'
 | 
			
		||||
    }
 | 
			
		||||
    buildFeatures {
 | 
			
		||||
        buildConfig true
 | 
			
		||||
        compose true
 | 
			
		||||
        prefab true
 | 
			
		||||
    }
 | 
			
		||||
    composeOptions {
 | 
			
		||||
        kotlinCompilerExtensionVersion '1.5.1'
 | 
			
		||||
    }
 | 
			
		||||
    packaging {
 | 
			
		||||
        resources {
 | 
			
		||||
            excludes += '/META-INF/{AL2.0,LGPL2.1}'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    externalNativeBuild {
 | 
			
		||||
        cmake {
 | 
			
		||||
            path file('src/main/cpp/CMakeLists.txt')
 | 
			
		||||
            version '3.22.1'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    packaging {
 | 
			
		||||
        jniLibs {
 | 
			
		||||
            pickFirsts += "**/libxdl.so"
 | 
			
		||||
            pickFirsts += "**/libshadowhook.so"
 | 
			
		||||
        }
 | 
			
		||||
        resources {
 | 
			
		||||
            excludes += "**/META-INF/{AL2.0,LGPL2.1}"
 | 
			
		||||
            excludes += "kotlin/**"
 | 
			
		||||
            excludes += "**.bin"
 | 
			
		||||
        }
 | 
			
		||||
    packagingOptions {
 | 
			
		||||
        pickFirst '**/libxdl.so'
 | 
			
		||||
        pickFirst '**/libshadowhook.so'
 | 
			
		||||
        exclude 'gakumas-local/gakuen-adapted-translation-data/**'
 | 
			
		||||
    }
 | 
			
		||||
    dataBinding {
 | 
			
		||||
        enable true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    applicationVariants.configureEach { variant ->
 | 
			
		||||
| 
						 | 
				
			
			@ -82,40 +83,38 @@ android {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation(libs.androidx.core.ktx)
 | 
			
		||||
    implementation(libs.androidx.lifecycle.runtime.ktx)
 | 
			
		||||
    implementation(libs.androidx.material3)
 | 
			
		||||
    implementation(libs.material)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.androidx.activity.compose)
 | 
			
		||||
    implementation(libs.androidx.appcompat)
 | 
			
		||||
    implementation(libs.androidx.navigation.compose)
 | 
			
		||||
    implementation 'androidx.core:core-ktx:1.13.1'
 | 
			
		||||
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2'
 | 
			
		||||
    implementation 'androidx.compose.material3:material3'
 | 
			
		||||
    implementation 'com.google.android.material:material:1.12.0'
 | 
			
		||||
 | 
			
		||||
    def composeBom = platform(libs.androidx.compose.bom)
 | 
			
		||||
    implementation "androidx.activity:activity-compose:1.9.0"
 | 
			
		||||
    implementation "androidx.appcompat:appcompat:1.7.0"
 | 
			
		||||
    implementation 'androidx.navigation:navigation-compose:2.7.7'
 | 
			
		||||
 | 
			
		||||
    def composeBom = platform('androidx.compose:compose-bom:2024.06.00')
 | 
			
		||||
    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 "androidx.compose.runtime:runtime"
 | 
			
		||||
    implementation "androidx.compose.material:material"
 | 
			
		||||
    implementation "androidx.compose.foundation:foundation"
 | 
			
		||||
    implementation "androidx.compose.foundation:foundation-layout"
 | 
			
		||||
    implementation "androidx.compose.animation:animation"
 | 
			
		||||
    implementation "androidx.compose.ui:ui-tooling-preview"
 | 
			
		||||
    androidTestImplementation "androidx.compose.ui:ui-test-junit4"
 | 
			
		||||
    debugImplementation "androidx.compose.ui:ui-tooling"
 | 
			
		||||
    debugImplementation "androidx.compose.ui:ui-test-manifest"
 | 
			
		||||
    implementation "com.google.accompanist:accompanist-pager:0.30.0"
 | 
			
		||||
    implementation "com.google.accompanist:accompanist-pager-indicators:0.30.0"
 | 
			
		||||
    implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.8.2"
 | 
			
		||||
 | 
			
		||||
    implementation(libs.coil.compose)
 | 
			
		||||
    implementation(libs.coil.svg)
 | 
			
		||||
    implementation "io.coil-kt:coil-compose:2.6.0"
 | 
			
		||||
    implementation "io.coil-kt:coil-svg:2.6.0"
 | 
			
		||||
 | 
			
		||||
    implementation(platform(libs.okhttp.bom))
 | 
			
		||||
    implementation(libs.okhttp)
 | 
			
		||||
    implementation(libs.logging.interceptor)
 | 
			
		||||
 | 
			
		||||
    implementation(libs.xdl)
 | 
			
		||||
    implementation(libs.shadowhook)
 | 
			
		||||
    compileOnly(libs.xposed.api)
 | 
			
		||||
    implementation(libs.kotlinx.serialization.json)
 | 
			
		||||
    implementation 'io.github.hexhacking:xdl:2.1.1'
 | 
			
		||||
    implementation 'com.bytedance.android:shadowhook:1.0.9'
 | 
			
		||||
    compileOnly 'de.robv.android.xposed:api:82'
 | 
			
		||||
    implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22"
 | 
			
		||||
    implementation 'com.google.code.gson:gson:2.11.0'
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -19,8 +19,3 @@
 | 
			
		|||
# If you keep the line number information, uncomment this to
 | 
			
		||||
# hide the original source file name.
 | 
			
		||||
#-renamesourcefileattribute SourceFile
 | 
			
		||||
 | 
			
		||||
-keep class io.github.chinosk.gakumas.localify.GakumasHookMain {
 | 
			
		||||
    <init>();
 | 
			
		||||
    native <methods>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,7 @@
 | 
			
		|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    xmlns:tools="http://schemas.android.com/tools">
 | 
			
		||||
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET" />
 | 
			
		||||
    <uses-permission android:name="android.permission.INTERNET"/>
 | 
			
		||||
 | 
			
		||||
    <application
 | 
			
		||||
        android:allowBackup="true"
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,6 @@
 | 
			
		|||
        android:label="@string/app_name"
 | 
			
		||||
        android:supportsRtl="true"
 | 
			
		||||
        android:theme="@style/Theme.GakumasLocalify"
 | 
			
		||||
        android:usesCleartextTraffic="true"
 | 
			
		||||
        tools:targetApi="31">
 | 
			
		||||
 | 
			
		||||
        <meta-data
 | 
			
		||||
| 
						 | 
				
			
			@ -35,6 +34,7 @@
 | 
			
		|||
        <activity
 | 
			
		||||
            android:name=".MainActivity"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:label="@string/app_name"
 | 
			
		||||
            android:theme="@style/Theme.GakumasLocalify">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.MAIN" />
 | 
			
		||||
| 
						 | 
				
			
			@ -42,23 +42,6 @@
 | 
			
		|||
                <category android:name="android.intent.category.LAUNCHER" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
        </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>
 | 
			
		||||
 | 
			
		||||
</manifest>
 | 
			
		||||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit a60a171b40b22b04d567ab39a8fd7f571c7921f5
 | 
			
		||||
Subproject commit cdd0ad064cf6d3f13107e19b5d08c582d8d0664e
 | 
			
		||||
| 
						 | 
				
			
			@ -299,11 +299,6 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
 | 
			
		||||
    void* fontCache = nullptr;
 | 
			
		||||
    void* GetReplaceFont() {
 | 
			
		||||
        static std::string fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf";
 | 
			
		||||
        if (!std::filesystem::exists(fontName)) {
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        static auto CreateFontFromPath = reinterpret_cast<void (*)(void* self, Il2cppString* path)>(
 | 
			
		||||
                Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)")
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -320,6 +315,7 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
        const auto newFont = Font_klass->New<void*>();
 | 
			
		||||
        Font_ctor->Invoke<void>(newFont);
 | 
			
		||||
 | 
			
		||||
        static std::string fontName = Local::GetBasePath() / "local-files" / "gkamsZHFontMIX.otf";
 | 
			
		||||
        CreateFontFromPath(newFont, Il2cppString::New(fontName));
 | 
			
		||||
        fontCache = newFont;
 | 
			
		||||
        return newFont;
 | 
			
		||||
| 
						 | 
				
			
			@ -338,10 +334,9 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
        static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
 | 
			
		||||
                                                                 "TMP_FontAsset", "UpdateFontAssetData");
 | 
			
		||||
 | 
			
		||||
        auto newFont = GetReplaceFont();
 | 
			
		||||
        if (!newFont) return;
 | 
			
		||||
        auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
 | 
			
		||||
        if (fontAsset) {
 | 
			
		||||
        auto newFont = GetReplaceFont();
 | 
			
		||||
        if (fontAsset && newFont) {
 | 
			
		||||
            set_sourceFontFile->Invoke<void>(fontAsset, newFont);
 | 
			
		||||
            if (!updatedFontPtrs.contains(fontAsset)) {
 | 
			
		||||
                updatedFontPtrs.emplace(fontAsset);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@
 | 
			
		|||
#include <sstream>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <queue>
 | 
			
		||||
 | 
			
		||||
extern JavaVM* g_javaVM;
 | 
			
		||||
extern jclass g_gakumasHookMainClass;
 | 
			
		||||
| 
						 | 
				
			
			@ -25,13 +24,9 @@ extern jmethodID showToastMethodId;
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Log {
 | 
			
		||||
    namespace {
 | 
			
		||||
        std::queue<std::string> showingToasts{};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string StringFormat(const char* fmt, ...) {
 | 
			
		||||
        GetParamStringResult(result);
 | 
			
		||||
        return result;
 | 
			
		||||
        return result.c_str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Log(int prio, const char* msg) {
 | 
			
		||||
| 
						 | 
				
			
			@ -75,7 +70,38 @@ namespace GakumasLocal::Log {
 | 
			
		|||
        __android_log_write(prio, "GakumasLog", result.c_str());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToastJNI(const char* text) {
 | 
			
		||||
    void ShowToast(const std::string& text) {
 | 
			
		||||
        DebugFmt("Toast: %s", text.c_str());
 | 
			
		||||
 | 
			
		||||
        std::thread([text](){
 | 
			
		||||
            auto env = Misc::GetJNIEnv();
 | 
			
		||||
            if (!env) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            jclass& kotlinClass = g_gakumasHookMainClass;
 | 
			
		||||
            if (!kotlinClass) {
 | 
			
		||||
                g_javaVM->DetachCurrentThread();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            jmethodID& methodId = showToastMethodId;
 | 
			
		||||
            if (!methodId) {
 | 
			
		||||
                g_javaVM->DetachCurrentThread();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            jstring param = env->NewStringUTF(text.c_str());
 | 
			
		||||
            env->CallStaticVoidMethod(kotlinClass, methodId, param);
 | 
			
		||||
 | 
			
		||||
            g_javaVM->DetachCurrentThread();
 | 
			
		||||
        }).detach();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToastFmt(const char* fmt, ...) {
 | 
			
		||||
        GetParamStringResult(result);
 | 
			
		||||
        ShowToast(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ShowToast(const char* text) {
 | 
			
		||||
        DebugFmt("Toast: %s", text);
 | 
			
		||||
 | 
			
		||||
        std::thread([text](){
 | 
			
		||||
| 
						 | 
				
			
			@ -100,44 +126,4 @@ namespace GakumasLocal::Log {
 | 
			
		|||
            g_javaVM->DetachCurrentThread();
 | 
			
		||||
        }).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, ...) {
 | 
			
		||||
        GetParamStringResult(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,7 +2,6 @@
 | 
			
		|||
#define GAKUMAS_LOCALIFY_LOG_H
 | 
			
		||||
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <jni.h>
 | 
			
		||||
 | 
			
		||||
namespace GakumasLocal::Log {
 | 
			
		||||
    std::string StringFormat(const char* fmt, ...);
 | 
			
		||||
| 
						 | 
				
			
			@ -17,8 +16,6 @@ namespace GakumasLocal::Log {
 | 
			
		|||
 | 
			
		||||
    void ShowToast(const char* text);
 | 
			
		||||
    void ShowToastFmt(const char* fmt, ...);
 | 
			
		||||
 | 
			
		||||
    void ToastLoop(JNIEnv *env, jclass clazz);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //GAKUMAS_LOCALIFY_LOG_H
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,10 +112,3 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
 | 
			
		|||
    const std::string configJson = configJsonStrChars;
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,105 +0,0 @@
 | 
			
		|||
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,18 +1,20 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.mutableStateOf
 | 
			
		||||
import androidx.compose.runtime.setValue
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
 | 
			
		||||
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 {
 | 
			
		||||
    fun onClickStartGame()
 | 
			
		||||
    fun onEnabledChanged(value: Boolean)
 | 
			
		||||
    fun onForceExportResourceChanged(value: Boolean)
 | 
			
		||||
    fun onTextTestChanged(value: Boolean)
 | 
			
		||||
| 
						 | 
				
			
			@ -53,15 +55,6 @@ interface ConfigListener {
 | 
			
		|||
    fun onBUseArmCorrectionChanged(value: Boolean)
 | 
			
		||||
    fun onBUseScaleChanged(value: Boolean)
 | 
			
		||||
    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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -80,54 +73,52 @@ class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		||||
interface ConfigUpdateListener: ConfigListener {
 | 
			
		||||
    var binding: ActivityMainBinding
 | 
			
		||||
    var factory: UserConfigViewModelFactory
 | 
			
		||||
    var viewModel: UserConfigViewModel
 | 
			
		||||
 | 
			
		||||
    var programConfigFactory: ProgramConfigViewModelFactory
 | 
			
		||||
    var programConfigViewModel: ProgramConfigViewModel
 | 
			
		||||
 | 
			
		||||
    fun pushKeyEvent(event: KeyEvent): Boolean
 | 
			
		||||
    fun checkConfigAndUpdateView() {}  // do nothing
 | 
			
		||||
    // fun saveConfig()
 | 
			
		||||
    fun saveProgramConfig()
 | 
			
		||||
    fun getConfigContent(): String
 | 
			
		||||
    fun checkConfigAndUpdateView()
 | 
			
		||||
    fun saveConfig()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override fun onEnabledChanged(value: Boolean) {
 | 
			
		||||
        config.enabled = value
 | 
			
		||||
        binding.config!!.enabled = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 29))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onForceExportResourceChanged(value: Boolean) {
 | 
			
		||||
        config.forceExportResource = value
 | 
			
		||||
        binding.config!!.forceExportResource = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 30))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReplaceFontChanged(value: Boolean) {
 | 
			
		||||
        config.replaceFont = value
 | 
			
		||||
        binding.config!!.replaceFont = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        pushKeyEvent(KeyEvent(1145, 30))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onTextTestChanged(value: Boolean) {
 | 
			
		||||
        config.textTest = value
 | 
			
		||||
        binding.config!!.textTest = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onDumpTextChanged(value: Boolean) {
 | 
			
		||||
        config.dumpText = value
 | 
			
		||||
        binding.config!!.dumpText = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onEnableFreeCameraChanged(value: Boolean) {
 | 
			
		||||
        config.enableFreeCamera = value
 | 
			
		||||
        binding.config!!.enableFreeCamera = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUnlockAllLiveChanged(value: Boolean) {
 | 
			
		||||
        config.unlockAllLive = value
 | 
			
		||||
        binding.config!!.unlockAllLive = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -140,7 +131,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
            } else {
 | 
			
		||||
                valueStr.toInt()
 | 
			
		||||
            }
 | 
			
		||||
            config.targetFrameRate = value
 | 
			
		||||
            binding.config!!.targetFrameRate = value
 | 
			
		||||
            saveConfig()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -149,22 +140,22 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeDressChanged(value: Boolean) {
 | 
			
		||||
        config.enableLiveCustomeDress = value
 | 
			
		||||
        binding.config!!.enableLiveCustomeDress = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.liveCustomeCostumeId = s.toString()
 | 
			
		||||
        binding.config!!.liveCustomeCostumeId = s.toString()
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onUseCustomeGraphicSettingsChanged(value: Boolean) {
 | 
			
		||||
        config.useCustomeGraphicSettings = value
 | 
			
		||||
        binding.config!!.useCustomeGraphicSettings = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onRenderScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.renderScale = try {
 | 
			
		||||
        binding.config!!.renderScale = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +165,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onQualitySettingsLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.qualitySettingsLevel = try {
 | 
			
		||||
        binding.config!!.qualitySettingsLevel = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -184,7 +175,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onVolumeIndexChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.volumeIndex = try {
 | 
			
		||||
        binding.config!!.volumeIndex = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -194,7 +185,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onMaxBufferPixelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.maxBufferPixel = try {
 | 
			
		||||
        binding.config!!.maxBufferPixel = try {
 | 
			
		||||
            s.toString().toInt()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -204,12 +195,12 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.liveCustomeHeadId = s.toString()
 | 
			
		||||
        binding.config!!.liveCustomeHeadId = s.toString()
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onReflectionQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.reflectionQualityLevel = try {
 | 
			
		||||
        binding.config!!.reflectionQualityLevel = try {
 | 
			
		||||
            val value = s.toString().toInt()
 | 
			
		||||
            if (value > 5) 5 else value
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -220,7 +211,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLodQualityLevelChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.lodQualityLevel = try {
 | 
			
		||||
        binding.config!!.lodQualityLevel = try {
 | 
			
		||||
            val value = s.toString().toInt()
 | 
			
		||||
            if (value > 5) 5 else value
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -233,44 +224,44 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    override fun onChangePresetQuality(level: Int) {
 | 
			
		||||
        when (level) {
 | 
			
		||||
            0 -> {
 | 
			
		||||
                config.renderScale = 0.5f
 | 
			
		||||
                config.qualitySettingsLevel = 1
 | 
			
		||||
                config.volumeIndex = 0
 | 
			
		||||
                config.maxBufferPixel = 1024
 | 
			
		||||
                config.lodQualityLevel = 1
 | 
			
		||||
                config.reflectionQualityLevel = 1
 | 
			
		||||
                binding.config!!.renderScale = 0.5f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 1
 | 
			
		||||
                binding.config!!.volumeIndex = 0
 | 
			
		||||
                binding.config!!.maxBufferPixel = 1024
 | 
			
		||||
                binding.config!!.lodQualityLevel = 1
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 1
 | 
			
		||||
            }
 | 
			
		||||
            1 -> {
 | 
			
		||||
                config.renderScale = 0.59f
 | 
			
		||||
                config.qualitySettingsLevel = 1
 | 
			
		||||
                config.volumeIndex = 1
 | 
			
		||||
                config.maxBufferPixel = 1440
 | 
			
		||||
                config.lodQualityLevel = 2
 | 
			
		||||
                config.reflectionQualityLevel = 2
 | 
			
		||||
                binding.config!!.renderScale = 0.59f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 1
 | 
			
		||||
                binding.config!!.volumeIndex = 1
 | 
			
		||||
                binding.config!!.maxBufferPixel = 1440
 | 
			
		||||
                binding.config!!.lodQualityLevel = 2
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 2
 | 
			
		||||
            }
 | 
			
		||||
            2 -> {
 | 
			
		||||
                config.renderScale = 0.67f
 | 
			
		||||
                config.qualitySettingsLevel = 2
 | 
			
		||||
                config.volumeIndex = 2
 | 
			
		||||
                config.maxBufferPixel = 2538
 | 
			
		||||
                config.lodQualityLevel = 3
 | 
			
		||||
                config.reflectionQualityLevel = 3
 | 
			
		||||
                binding.config!!.renderScale = 0.67f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 2
 | 
			
		||||
                binding.config!!.volumeIndex = 2
 | 
			
		||||
                binding.config!!.maxBufferPixel = 2538
 | 
			
		||||
                binding.config!!.lodQualityLevel = 3
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 3
 | 
			
		||||
            }
 | 
			
		||||
            3 -> {
 | 
			
		||||
                config.renderScale = 0.77f
 | 
			
		||||
                config.qualitySettingsLevel = 3
 | 
			
		||||
                config.volumeIndex = 3
 | 
			
		||||
                config.maxBufferPixel = 3384
 | 
			
		||||
                config.lodQualityLevel = 4
 | 
			
		||||
                config.reflectionQualityLevel = 4
 | 
			
		||||
                binding.config!!.renderScale = 0.77f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 3
 | 
			
		||||
                binding.config!!.volumeIndex = 3
 | 
			
		||||
                binding.config!!.maxBufferPixel = 3384
 | 
			
		||||
                binding.config!!.lodQualityLevel = 4
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 4
 | 
			
		||||
            }
 | 
			
		||||
            4 -> {
 | 
			
		||||
                config.renderScale = 1.0f
 | 
			
		||||
                config.qualitySettingsLevel = 5
 | 
			
		||||
                config.volumeIndex = 4
 | 
			
		||||
                config.maxBufferPixel = 8190
 | 
			
		||||
                config.lodQualityLevel = 5
 | 
			
		||||
                config.reflectionQualityLevel = 5
 | 
			
		||||
                binding.config!!.renderScale = 1.0f
 | 
			
		||||
                binding.config!!.qualitySettingsLevel = 5
 | 
			
		||||
                binding.config!!.volumeIndex = 4
 | 
			
		||||
                binding.config!!.maxBufferPixel = 8190
 | 
			
		||||
                binding.config!!.lodQualityLevel = 5
 | 
			
		||||
                binding.config!!.reflectionQualityLevel = 5
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
| 
						 | 
				
			
			@ -278,31 +269,38 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onGameOrientationChanged(checkedId: Int) {
 | 
			
		||||
        if (checkedId in listOf(0, 1, 2)) {
 | 
			
		||||
            config.gameOrientation = checkedId
 | 
			
		||||
        when (checkedId) {
 | 
			
		||||
            R.id.radioButtonGameDefault -> binding.config!!.gameOrientation = 0
 | 
			
		||||
            R.id.radioButtonGamePortrait -> binding.config!!.gameOrientation = 1
 | 
			
		||||
            R.id.radioButtonGameLandscape -> binding.config!!.gameOrientation = 2
 | 
			
		||||
            else -> {
 | 
			
		||||
                if (listOf(0, 1, 2).contains(checkedId)) {
 | 
			
		||||
                    binding.config!!.gameOrientation = checkedId
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onEnableBreastParamChanged(value: Boolean) {
 | 
			
		||||
        config.enableBreastParam = value
 | 
			
		||||
        binding.config!!.enableBreastParam = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseArmCorrectionChanged(value: Boolean) {
 | 
			
		||||
        config.bUseArmCorrection = value
 | 
			
		||||
        binding.config!!.bUseArmCorrection = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseScaleChanged(value: Boolean) {
 | 
			
		||||
        config.bUseScale = value
 | 
			
		||||
        binding.config!!.bUseScale = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBDampingChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bDamping = try {
 | 
			
		||||
        binding.config!!.bDamping = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -312,7 +310,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBStiffnessChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bStiffness = try {
 | 
			
		||||
        binding.config!!.bStiffness = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -322,7 +320,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBSpringChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bSpring = try {
 | 
			
		||||
        binding.config!!.bSpring = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -332,7 +330,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBPendulumChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bPendulum = try {
 | 
			
		||||
        binding.config!!.bPendulum = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -342,7 +340,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBPendulumRangeChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bPendulumRange = try {
 | 
			
		||||
        binding.config!!.bPendulumRange = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -352,7 +350,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBAverageChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bAverage = try {
 | 
			
		||||
        binding.config!!.bAverage = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -362,7 +360,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBRootWeightChanged(s: CharSequence, start: Int, before: Int, count: Int){
 | 
			
		||||
        config.bRootWeight = try {
 | 
			
		||||
        binding.config!!.bRootWeight = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -372,13 +370,13 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBUseLimitChanged(value: Boolean){
 | 
			
		||||
        config.bUseLimit = value
 | 
			
		||||
        binding.config!!.bUseLimit = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitXxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitXx = try {
 | 
			
		||||
        binding.config!!.bLimitXx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -388,7 +386,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitXyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitXy = try {
 | 
			
		||||
        binding.config!!.bLimitXy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -398,7 +396,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitYxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitYx = try {
 | 
			
		||||
        binding.config!!.bLimitYx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -408,7 +406,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitYyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitYy = try {
 | 
			
		||||
        binding.config!!.bLimitYy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -418,7 +416,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitZxChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitZx = try {
 | 
			
		||||
        binding.config!!.bLimitZx = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -428,7 +426,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onBLimitZyChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bLimitZy = try {
 | 
			
		||||
        binding.config!!.bLimitZy = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -439,7 +437,7 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
    override fun onBScaleChanged(s: CharSequence, start: Int, before: Int, count: Int) {
 | 
			
		||||
        config.bScale = try {
 | 
			
		||||
        binding.config!!.bScale = try {
 | 
			
		||||
            s.toString().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: Exception) {
 | 
			
		||||
| 
						 | 
				
			
			@ -469,62 +467,30 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
                1f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        config.bDamping = setData[0]
 | 
			
		||||
        config.bStiffness = setData[1]
 | 
			
		||||
        config.bSpring = setData[2]
 | 
			
		||||
        config.bPendulum = setData[3]
 | 
			
		||||
        config.bPendulumRange = setData[4]
 | 
			
		||||
        config.bAverage = setData[5]
 | 
			
		||||
        config.bRootWeight = setData[6]
 | 
			
		||||
        config.bUseLimit = if (setData[7] == 0f) {
 | 
			
		||||
        binding.config!!.bDamping = setData[0]
 | 
			
		||||
        binding.config!!.bStiffness = setData[1]
 | 
			
		||||
        binding.config!!.bSpring = setData[2]
 | 
			
		||||
        binding.config!!.bPendulum = setData[3]
 | 
			
		||||
        binding.config!!.bPendulumRange = setData[4]
 | 
			
		||||
        binding.config!!.bAverage = setData[5]
 | 
			
		||||
        binding.config!!.bRootWeight = setData[6]
 | 
			
		||||
        binding.config!!.bUseLimit = if (setData[7] == 0f) {
 | 
			
		||||
            false
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            config.bLimitXx = setData[8]
 | 
			
		||||
            config.bLimitXy = setData[9]
 | 
			
		||||
            config.bLimitYx = setData[10]
 | 
			
		||||
            config.bLimitYy = setData[11]
 | 
			
		||||
            config.bLimitZx = setData[12]
 | 
			
		||||
            config.bLimitZy = setData[13]
 | 
			
		||||
            binding.config!!.bLimitXx = setData[8]
 | 
			
		||||
            binding.config!!.bLimitXy = setData[9]
 | 
			
		||||
            binding.config!!.bLimitYx = setData[10]
 | 
			
		||||
            binding.config!!.bLimitYy = setData[11]
 | 
			
		||||
            binding.config!!.bLimitZx = setData[12]
 | 
			
		||||
            binding.config!!.bLimitZy = setData[13]
 | 
			
		||||
            true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        config.bUseArmCorrection = true
 | 
			
		||||
        binding.config!!.bUseArmCorrection = true
 | 
			
		||||
 | 
			
		||||
        checkConfigAndUpdateView()
 | 
			
		||||
        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,30 +11,22 @@ import android.net.Uri
 | 
			
		|||
import android.os.Handler
 | 
			
		||||
import android.os.Looper
 | 
			
		||||
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.ConfigBuilder
 | 
			
		||||
import de.robv.android.xposed.IXposedHookLoadPackage
 | 
			
		||||
import de.robv.android.xposed.IXposedHookZygoteInit
 | 
			
		||||
import de.robv.android.xposed.XC_MethodHook
 | 
			
		||||
import de.robv.android.xposed.XposedBridge
 | 
			
		||||
import de.robv.android.xposed.XposedHelpers
 | 
			
		||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.view.MotionEvent
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
import de.robv.android.xposed.XposedBridge
 | 
			
		||||
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.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"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -48,23 +40,8 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
    private var gkmsDataInited = false
 | 
			
		||||
 | 
			
		||||
    private var getConfigError: Exception? = null
 | 
			
		||||
    private var externalFilesChecked: Boolean = false
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +163,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                        requestConfig(app.applicationContext)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    FilesChecker.initDir(app.filesDir, modulePath)
 | 
			
		||||
                    FilesChecker.initAndCheck(app.filesDir, modulePath)
 | 
			
		||||
                    initHook(
 | 
			
		||||
                        "${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
 | 
			
		||||
                        File(
 | 
			
		||||
| 
						 | 
				
			
			@ -198,74 +175,23 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                    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) {
 | 
			
		||||
        val intent = activity.intent
 | 
			
		||||
        val gkmsData = intent.getStringExtra("gkmsData")
 | 
			
		||||
        val programData = intent.getStringExtra("localData")
 | 
			
		||||
        if (gkmsData != null) {
 | 
			
		||||
            gkmsDataInited = true
 | 
			
		||||
            val initConfig = try {
 | 
			
		||||
                json.decodeFromString<GakumasConfig>(gkmsData)
 | 
			
		||||
                Gson().fromJson(gkmsData, GakumasConfig::class.java)
 | 
			
		||||
            }
 | 
			
		||||
            catch (e: Exception) {
 | 
			
		||||
                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) {
 | 
			
		||||
                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)
 | 
			
		||||
            Log.d(TAG, "gkmsData: $gkmsData")
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -347,7 +273,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
    fun requestConfig(activity: Context) {
 | 
			
		||||
        try {
 | 
			
		||||
            val intent = Intent().apply {
 | 
			
		||||
                setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.TranslucentActivity")
 | 
			
		||||
                setClassName("io.github.chinosk.gakumas.localify", "io.github.chinosk.gakumas.localify.MainActivity")
 | 
			
		||||
                putExtra("gkmsData", "requestConfig")
 | 
			
		||||
                flags = FLAG_ACTIVITY_NEW_TASK
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -411,9 +337,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                Log.e(TAG, "showToast: $message failed: applicationContext is null")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        external fun pluginCallbackLooper()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify
 | 
			
		||||
 | 
			
		||||
import SplashScreen
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
| 
						 | 
				
			
			@ -7,65 +8,69 @@ import android.os.Bundle
 | 
			
		|||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.databinding.DataBindingUtil
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
import com.google.gson.JsonSyntaxException
 | 
			
		||||
import io.github.chinosk.gakumas.localify.databinding.ActivityMainBinding
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.MainKeyEventDispatcher
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
 | 
			
		||||
import java.io.File
 | 
			
		||||
import androidx.activity.ComponentActivity
 | 
			
		||||
import androidx.activity.compose.setContent
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
			
		||||
import androidx.compose.runtime.State
 | 
			
		||||
import androidx.compose.runtime.collectAsState
 | 
			
		||||
import androidx.lifecycle.ViewModelProvider
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
 | 
			
		||||
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.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfigViewModelFactory
 | 
			
		||||
import androidx.navigation.compose.NavHost
 | 
			
		||||
import androidx.navigation.compose.composable
 | 
			
		||||
import androidx.navigation.compose.rememberNavController
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableActivity<MainActivity> {
 | 
			
		||||
    override lateinit var config: GakumasConfig
 | 
			
		||||
    override lateinit var programConfig: ProgramConfig
 | 
			
		||||
class MainActivity : ComponentActivity(), ConfigUpdateListener {
 | 
			
		||||
    override lateinit var binding: ActivityMainBinding
 | 
			
		||||
 | 
			
		||||
    override lateinit var factory: UserConfigViewModelFactory
 | 
			
		||||
    override lateinit var viewModel: UserConfigViewModel
 | 
			
		||||
 | 
			
		||||
    override lateinit var programConfigFactory: ProgramConfigViewModelFactory
 | 
			
		||||
    override lateinit var programConfigViewModel: ProgramConfigViewModel
 | 
			
		||||
 | 
			
		||||
    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 showToast(message: String) {
 | 
			
		||||
        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("检测到第一次启动,初始化配置文件...")
 | 
			
		||||
            "{}"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
            config.pf = false
 | 
			
		||||
            viewModel.configState.value = config.copy( pf = true )  // 更新 UI
 | 
			
		||||
            binding.config!!.pf = false
 | 
			
		||||
            viewModel.configState.value = binding.config!!.copy( pf = true )  // 更新 UI
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: RuntimeException) {
 | 
			
		||||
            Log.d(TAG, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
        val configFile = File(filesDir, "gkms-config.json")
 | 
			
		||||
        configFile.writeText(json.encodeToString(config))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveProgramConfig() {
 | 
			
		||||
        try {
 | 
			
		||||
            programConfig.p = false
 | 
			
		||||
            programConfigViewModel.configState.value = programConfig.copy( p = true )  // 更新 UI
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: RuntimeException) {
 | 
			
		||||
            Log.d(TAG, e.toString())
 | 
			
		||||
        }
 | 
			
		||||
        val configFile = File(filesDir, "localify-config.json")
 | 
			
		||||
        configFile.writeText(json.encodeToString(programConfig))
 | 
			
		||||
        configFile.writeText(Gson().toJson(binding.config!!))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getVersion(): List<String> {
 | 
			
		||||
| 
						 | 
				
			
			@ -92,6 +97,23 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
 | 
			
		|||
        startActivity(intent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadConfig() {
 | 
			
		||||
        val configStr = getConfigContent()
 | 
			
		||||
        binding.config = try {
 | 
			
		||||
            Gson().fromJson(configStr, GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: JsonSyntaxException) {
 | 
			
		||||
            showToast("配置文件异常,已重置: $e")
 | 
			
		||||
            Gson().fromJson("{}", GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun checkConfigAndUpdateView() {
 | 
			
		||||
        binding.config = binding.config
 | 
			
		||||
        binding.notifyChange()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pushKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        return dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -100,11 +122,13 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
 | 
			
		|||
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        // Log.d(TAG, "${event.keyCode}, ${event.action}")
 | 
			
		||||
        if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
 | 
			
		||||
            val origDbg = config.dbgMode
 | 
			
		||||
            config.dbgMode = !origDbg
 | 
			
		||||
            checkConfigAndUpdateView()
 | 
			
		||||
            saveConfig()
 | 
			
		||||
            showToast("TestMode: ${!origDbg}")
 | 
			
		||||
            val origDbg = binding.config?.dbgMode
 | 
			
		||||
            if (origDbg != null) {
 | 
			
		||||
                binding.config!!.dbgMode = !origDbg
 | 
			
		||||
                checkConfigAndUpdateView()
 | 
			
		||||
                saveConfig()
 | 
			
		||||
                showToast("TestMode: ${!origDbg}")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return if (event.action == 1145) true else super.dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -112,20 +136,36 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
 | 
			
		|||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
 | 
			
		||||
        loadConfig()
 | 
			
		||||
        binding.listener = this
 | 
			
		||||
 | 
			
		||||
        factory = UserConfigViewModelFactory(config)
 | 
			
		||||
        val requestData = intent.getStringExtra("gkmsData")
 | 
			
		||||
        if (requestData != null) {
 | 
			
		||||
            if (requestData == "requestConfig") {
 | 
			
		||||
                onClickStartGame()
 | 
			
		||||
                finish()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        factory = UserConfigViewModelFactory(binding.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) {
 | 
			
		||||
            GakumasLocalifyTheme(dynamicColor = false) {
 | 
			
		||||
                MainUI(context = this)
 | 
			
		||||
                /*
 | 
			
		||||
                val navController = rememberNavController()
 | 
			
		||||
                NavHost(navController, startDestination = "splash") {
 | 
			
		||||
                    composable("splash") {
 | 
			
		||||
                        SplashScreen(navController)
 | 
			
		||||
                    }
 | 
			
		||||
                    composable("main") {
 | 
			
		||||
                        MainUI(context = this@MainActivity)
 | 
			
		||||
                    }
 | 
			
		||||
                }*/
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -142,57 +182,149 @@ fun getConfigState(context: MainActivity?, previewData: GakumasConfig?): State<G
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
/*
 | 
			
		||||
class OldActivity : AppCompatActivity(), ConfigUpdateListener {
 | 
			
		||||
    override lateinit var binding: ActivityMainBinding
 | 
			
		||||
    private val TAG = "GakumasLocalify"
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadState(context: MainActivity?): State<Float> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.downloadProgress.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(0f)
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    override lateinit var factory: UserConfigViewModelFactory  // No usage
 | 
			
		||||
    override lateinit var viewModel: UserConfigViewModel  // No usage
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadAbleState(context: MainActivity?): State<Boolean> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.downloadAble.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow(true)
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        setContentView(R.layout.activity_main)
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramLocalResourceVersionState(context: MainActivity?): State<String> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.localResourceVersion.collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow("null")
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
 | 
			
		||||
        loadConfig()
 | 
			
		||||
        binding.listener = this
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun getProgramDownloadErrorStringState(context: MainActivity?): State<String> {
 | 
			
		||||
    return if (context != null) {
 | 
			
		||||
        context.programConfigViewModel.errorString.collectAsState()
 | 
			
		||||
        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)
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        val configMSF = MutableStateFlow("")
 | 
			
		||||
        configMSF.asStateFlow().collectAsState()
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
        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("检测到第一次启动,初始化配置文件...")
 | 
			
		||||
            "{}"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun saveConfig() {
 | 
			
		||||
        val configFile = File(filesDir, "gkms-config.json")
 | 
			
		||||
        configFile.writeText(Gson().toJson(binding.config!!))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    private fun showVersion() {
 | 
			
		||||
        val titleLabel = findViewById<TextView>(R.id.textViewTitle)
 | 
			
		||||
        val versionLabel = findViewById<TextView>(R.id.textViewResVersion)
 | 
			
		||||
        var versionText = "unknown"
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt")
 | 
			
		||||
            versionText = FilesChecker.convertToString(stream)
 | 
			
		||||
 | 
			
		||||
            val packInfo = packageManager.getPackageInfo(packageName, 0)
 | 
			
		||||
            val version = packInfo.versionName
 | 
			
		||||
            val versionCode = packInfo.longVersionCode
 | 
			
		||||
            titleLabel.text = "${titleLabel.text} $version ($versionCode)"
 | 
			
		||||
        }
 | 
			
		||||
        catch (_: Exception) {}
 | 
			
		||||
        versionLabel.text = "Assets Version: $versionText"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun loadConfig() {
 | 
			
		||||
        val configStr = getConfigContent()
 | 
			
		||||
        binding.config = try {
 | 
			
		||||
            Gson().fromJson(configStr, GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        catch (e: JsonSyntaxException) {
 | 
			
		||||
            showToast("配置文件异常,已重置: $e")
 | 
			
		||||
            Gson().fromJson("{}", GakumasConfig::class.java)
 | 
			
		||||
        }
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun checkConfigAndUpdateView() {
 | 
			
		||||
        binding.config = binding.config
 | 
			
		||||
        binding.notifyChange()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun pushKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        return dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
 | 
			
		||||
        // Log.d(TAG, "${event.keyCode}, ${event.action}")
 | 
			
		||||
        if (MainKeyEventDispatcher.checkDbgKey(event.keyCode, event.action)) {
 | 
			
		||||
            val origDbg = binding.config?.dbgMode
 | 
			
		||||
            if (origDbg != null) {
 | 
			
		||||
                binding.config!!.dbgMode = !origDbg
 | 
			
		||||
                checkConfigAndUpdateView()
 | 
			
		||||
                saveConfig()
 | 
			
		||||
                showToast("TestMode: ${!origDbg}")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return if (event.action == 1145) true else super.dispatchKeyEvent(event)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +0,0 @@
 | 
			
		|||
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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,181 +0,0 @@
 | 
			
		|||
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,14 +16,10 @@ object FilesChecker {
 | 
			
		|||
    var filesUpdated = false
 | 
			
		||||
 | 
			
		||||
    fun initAndCheck(fileDir: File, modulePath: String) {
 | 
			
		||||
        initDir(fileDir, modulePath)
 | 
			
		||||
 | 
			
		||||
        checkFiles()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun initDir(fileDir: File, modulePath: String) {
 | 
			
		||||
        this.filesDir = fileDir
 | 
			
		||||
        this.modulePath = modulePath
 | 
			
		||||
 | 
			
		||||
        checkFiles()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun checkFiles() {
 | 
			
		||||
| 
						 | 
				
			
			@ -122,45 +118,4 @@ object FilesChecker {
 | 
			
		|||
        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,10 +1,11 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.hookUtils
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.KeyEvent
 | 
			
		||||
 | 
			
		||||
object MainKeyEventDispatcher {
 | 
			
		||||
    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 {
 | 
			
		||||
        if (action == KeyEvent.ACTION_UP) return false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,137 +0,0 @@
 | 
			
		|||
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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +0,0 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.mainUtils
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
 | 
			
		||||
val json = Json {
 | 
			
		||||
    encodeDefaults = true
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,31 +1,24 @@
 | 
			
		|||
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(
 | 
			
		||||
data class AboutPageConfig (
 | 
			
		||||
    var plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify",
 | 
			
		||||
    var main_contributors: List<MainContributors> = listOf(),
 | 
			
		||||
    var contrib_img: ContribImg = ContribImg(
 | 
			
		||||
        "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
 | 
			
		||||
        "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
 | 
			
		||||
    )
 | 
			
		||||
        "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class MainContributors(
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val links: List<Links>
 | 
			
		||||
data class MainContributors (
 | 
			
		||||
    var name: String,
 | 
			
		||||
    var links: List<Links>
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class ContribImg(
 | 
			
		||||
    val plugin: String,
 | 
			
		||||
    val translation: String
 | 
			
		||||
data class ContribImg (
 | 
			
		||||
    var plugin: String,
 | 
			
		||||
    var translation: String
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class Links(
 | 
			
		||||
    val name: String,
 | 
			
		||||
    val link: String
 | 
			
		||||
data class Links (
 | 
			
		||||
    var name: String,
 | 
			
		||||
    var link: String
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class GakumasConfig (
 | 
			
		||||
    var dbgMode: Boolean = false,
 | 
			
		||||
    var enabled: Boolean = true,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,41 +0,0 @@
 | 
			
		|||
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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,67 +5,18 @@ 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 CollapsibleBoxViewModel(initiallyExpanded: Boolean = false) : ViewModel() {
 | 
			
		||||
    var expanded by mutableStateOf(initiallyExpanded)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
class CollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory {
 | 
			
		||||
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
 | 
			
		||||
        if (modelClass.isAssignableFrom(BreastCollapsibleBoxViewModel::class.java)) {
 | 
			
		||||
        if (modelClass.isAssignableFrom(CollapsibleBoxViewModel::class.java)) {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return BreastCollapsibleBoxViewModel(initiallyExpanded) as T
 | 
			
		||||
            return CollapsibleBoxViewModel(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()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,15 +34,13 @@ fun GakuButton(
 | 
			
		|||
    shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角
 | 
			
		||||
    shadowElevation: Dp = 8.dp, // 阴影的高度
 | 
			
		||||
    borderWidth: Dp = 1.dp, // 描边的宽度
 | 
			
		||||
    borderColor: Color = Color.Transparent, // 描边的颜色
 | 
			
		||||
    enabled: Boolean = true
 | 
			
		||||
    borderColor: Color = Color.Transparent // 描边的颜色
 | 
			
		||||
) {
 | 
			
		||||
    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)),
 | 
			
		||||
            colors = listOf(Color(0xFFFF5F19), Color(0xFFFFA028)),
 | 
			
		||||
            start = Offset(0f, 0f),
 | 
			
		||||
            end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			@ -50,7 +48,6 @@ fun GakuButton(
 | 
			
		|||
 | 
			
		||||
    Button(
 | 
			
		||||
        onClick = onClick,
 | 
			
		||||
        enabled = enabled,
 | 
			
		||||
        colors = ButtonDefaults.buttonColors(
 | 
			
		||||
            containerColor = Color.Transparent
 | 
			
		||||
        ),
 | 
			
		||||
| 
						 | 
				
			
			@ -64,7 +61,7 @@ fun GakuButton(
 | 
			
		|||
            .border(borderWidth, borderColor, shape),
 | 
			
		||||
        contentPadding = PaddingValues(0.dp)
 | 
			
		||||
    ) {
 | 
			
		||||
        Text(text = text, color = if (enabled) Color.White else Color(0xFF111111))
 | 
			
		||||
        Text(text = text)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -72,6 +69,5 @@ fun GakuButton(
 | 
			
		|||
@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)
 | 
			
		||||
    GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {})
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,3 @@
 | 
			
		|||
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.*
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,55 +0,0 @@
 | 
			
		|||
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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ 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.Text
 | 
			
		||||
import androidx.compose.material3.Switch
 | 
			
		||||
import androidx.compose.material3.SwitchDefaults
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +34,7 @@ fun GakuSwitch(modifier: Modifier = Modifier,
 | 
			
		|||
            modifier = Modifier,
 | 
			
		||||
            colors = SwitchDefaults.colors(
 | 
			
		||||
                checkedThumbColor = Color(0xFFFFFFFF),
 | 
			
		||||
                checkedTrackColor = Color(0xFFF9C114),
 | 
			
		||||
                checkedTrackColor = Color(0xFFF89400),
 | 
			
		||||
 | 
			
		||||
                uncheckedThumbColor = Color(0xFFFFFFFF),
 | 
			
		||||
                uncheckedTrackColor = Color(0xFFCFD8DC),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,6 +2,7 @@ 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.Image
 | 
			
		||||
import androidx.compose.foundation.background
 | 
			
		||||
import androidx.compose.foundation.layout.Box
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,6 @@ 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.*
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +29,6 @@ fun CollapsibleBox(
 | 
			
		|||
    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
 | 
			
		||||
| 
						 | 
				
			
			@ -68,8 +65,6 @@ fun CollapsibleBox(
 | 
			
		|||
            modifier = Modifier
 | 
			
		||||
                .height(animatedHeight)
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(start = innerPaddingLeftRight, end = innerPaddingLeftRight,
 | 
			
		||||
                    top = innerPaddingTopBottom, bottom = innerPaddingTopBottom)
 | 
			
		||||
                // .fillMaxSize()
 | 
			
		||||
                .clickable {
 | 
			
		||||
                    if (!expanded && showExpand) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,6 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,10 +28,10 @@ import coil.compose.rememberAsyncImagePainter
 | 
			
		|||
import coil.decode.SvgDecoder
 | 
			
		||||
import coil.request.ImageRequest
 | 
			
		||||
import coil.size.Size
 | 
			
		||||
import com.google.gson.Gson
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -48,7 +48,7 @@ fun AboutPage(modifier: Modifier = Modifier,
 | 
			
		|||
        val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let {
 | 
			
		||||
            convertToString(context.assets?.open(it))
 | 
			
		||||
        }
 | 
			
		||||
        dataJsonString?.let { json.decodeFromString<AboutPageConfig>(it) }
 | 
			
		||||
        Gson().fromJson(dataJsonString, AboutPageConfig::class.java)
 | 
			
		||||
            ?: AboutPageConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages.subPages
 | 
			
		||||
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
 | 
			
		||||
import GakuGroupBox
 | 
			
		||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
| 
						 | 
				
			
			@ -29,8 +29,8 @@ 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.CollapsibleBoxViewModel
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModelFactory
 | 
			
		||||
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
 | 
			
		||||
| 
						 | 
				
			
			@ -47,8 +47,8 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
 | 
			
		|||
    val config = getConfigState(context, previewData)
 | 
			
		||||
    // val scrollState = rememberScrollState()
 | 
			
		||||
 | 
			
		||||
    val breastParamViewModel: BreastCollapsibleBoxViewModel =
 | 
			
		||||
        viewModel(factory = BreastCollapsibleBoxViewModelFactory(initiallyExpanded = false))
 | 
			
		||||
    val breastParamViewModel: CollapsibleBoxViewModel =
 | 
			
		||||
        viewModel(factory = CollapsibleBoxViewModelFactory(initiallyExpanded = false))
 | 
			
		||||
    val keyBoardOptionsDecimal = remember {
 | 
			
		||||
        KeyboardOptions(keyboardType = KeyboardType.Decimal)
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,7 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.pages.subPages
 | 
			
		||||
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.components.GakuGroupBox
 | 
			
		||||
import 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
 | 
			
		||||
| 
						 | 
				
			
			@ -17,39 +16,24 @@ 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
 | 
			
		||||
| 
						 | 
				
			
			@ -59,13 +43,6 @@ fun HomePage(modifier: Modifier = Modifier,
 | 
			
		|||
             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)
 | 
			
		||||
| 
						 | 
				
			
			@ -74,57 +51,6 @@ fun HomePage(modifier: Modifier = Modifier,
 | 
			
		|||
        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)
 | 
			
		||||
| 
						 | 
				
			
			@ -149,138 +75,6 @@ fun HomePage(modifier: Modifier = Modifier,
 | 
			
		|||
            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
 | 
			
		||||
| 
						 | 
				
			
			@ -469,7 +263,7 @@ fun HomePage(modifier: Modifier = Modifier,
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
 | 
			
		||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 880)
 | 
			
		||||
@Composable
 | 
			
		||||
fun HomePagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) {
 | 
			
		||||
    HomePage(modifier, previewData = data)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 1.4 MiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 650 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 2.8 MiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 5.1 MiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 2.6 MiB  | 
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
<?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>
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -63,17 +63,6 @@
 | 
			
		|||
    <string name="plugin_code">插件本体</string>
 | 
			
		||||
    <string name="contributors">贡献者列表</string>
 | 
			
		||||
    <string name="translation_repository">译文仓库</string>
 | 
			
		||||
    <string name="resource_settings">资源设置</string>
 | 
			
		||||
    <string name="check_built_in_resource">检查内置数据更新</string>
 | 
			
		||||
    <string name="delete_plugin_resource">清除游戏目录内的插件资源</string>
 | 
			
		||||
    <string name="use_remote_zip_resource">使用远程 ZIP 数据</string>
 | 
			
		||||
    <string name="resource_url">资源地址</string>
 | 
			
		||||
    <string name="download">下载</string>
 | 
			
		||||
    <string name="invalid_zip_file">文件解析失败</string>
 | 
			
		||||
    <string name="invalid_zip_file_warn">此文件不是一个有效的 ZIP 翻译资源包</string>
 | 
			
		||||
    <string name="cancel">取消</string>
 | 
			
		||||
    <string name="downloaded_resource_version">已下载资源版本</string>
 | 
			
		||||
    <string name="del_remote_after_update">替换文件后删除下载缓存</string>
 | 
			
		||||
 | 
			
		||||
    <string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
 | 
			
		||||
</resources>
 | 
			
		||||
| 
						 | 
				
			
			@ -63,17 +63,6 @@
 | 
			
		|||
    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,30 +1,5 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" >
 | 
			
		||||
        <item name="android:windowBackground">@drawable/splash_style</item>
 | 
			
		||||
    </style>
 | 
			
		||||
    <style name="Theme.GakumasLocalify" parent="@style/Theme.MaterialComponents.Light.NoActionBar" />
 | 
			
		||||
 | 
			
		||||
    <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>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +0,0 @@
 | 
			
		|||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<paths>
 | 
			
		||||
    <files-path name="files" path="." />
 | 
			
		||||
</paths>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
 | 
			
		||||
plugins {
 | 
			
		||||
    alias(libs.plugins.androidApplication) apply false
 | 
			
		||||
    alias(libs.plugins.kotlinAndroid) apply false
 | 
			
		||||
id 'com.android.application' version '8.2.0' apply false
 | 
			
		||||
    id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,54 +0,0 @@
 | 
			
		|||
[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" }
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
						 | 
				
			
			@ -1,7 +1,6 @@
 | 
			
		|||
#Fri May 17 13:19:52 CST 2024
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
 | 
			
		||||
networkTimeout=10000
 | 
			
		||||
validateDistributionUrl=true
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
#!/bin/sh
 | 
			
		||||
#!/usr/bin/env sh
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Copyright © 2015-2021 the original authors.
 | 
			
		||||
# Copyright 2015 the original author or authors.
 | 
			
		||||
#
 | 
			
		||||
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
			
		||||
# you may not use this file except in compliance with the License.
 | 
			
		||||
| 
						 | 
				
			
			@ -17,99 +17,67 @@
 | 
			
		|||
#
 | 
			
		||||
 | 
			
		||||
##############################################################################
 | 
			
		||||
#
 | 
			
		||||
#   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/.
 | 
			
		||||
#
 | 
			
		||||
##
 | 
			
		||||
##  Gradle start up script for UN*X
 | 
			
		||||
##
 | 
			
		||||
##############################################################################
 | 
			
		||||
 | 
			
		||||
# Attempt to set APP_HOME
 | 
			
		||||
 | 
			
		||||
# Resolve links: $0 may be a link
 | 
			
		||||
app_path=$0
 | 
			
		||||
 | 
			
		||||
# Need this for daisy-chained symlinks.
 | 
			
		||||
while
 | 
			
		||||
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
 | 
			
		||||
    [ -h "$app_path" ]
 | 
			
		||||
do
 | 
			
		||||
    ls=$( ls -ld "$app_path" )
 | 
			
		||||
    link=${ls#*' -> '}
 | 
			
		||||
    case $link in             #(
 | 
			
		||||
      /*)   app_path=$link ;; #(
 | 
			
		||||
      *)    app_path=$APP_HOME$link ;;
 | 
			
		||||
    esac
 | 
			
		||||
PRG="$0"
 | 
			
		||||
# Need this for relative symlinks.
 | 
			
		||||
while [ -h "$PRG" ] ; do
 | 
			
		||||
    ls=`ls -ld "$PRG"`
 | 
			
		||||
    link=`expr "$ls" : '.*-> \(.*\)$'`
 | 
			
		||||
    if expr "$link" : '/.*' > /dev/null; then
 | 
			
		||||
        PRG="$link"
 | 
			
		||||
    else
 | 
			
		||||
        PRG=`dirname "$PRG"`"/$link"
 | 
			
		||||
    fi
 | 
			
		||||
done
 | 
			
		||||
SAVED="`pwd`"
 | 
			
		||||
cd "`dirname \"$PRG\"`/" >/dev/null
 | 
			
		||||
APP_HOME="`pwd -P`"
 | 
			
		||||
cd "$SAVED" >/dev/null
 | 
			
		||||
 | 
			
		||||
# This is normally unused
 | 
			
		||||
# shellcheck disable=SC2034
 | 
			
		||||
APP_BASE_NAME=${0##*/}
 | 
			
		||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
 | 
			
		||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
 | 
			
		||||
APP_NAME="Gradle"
 | 
			
		||||
APP_BASE_NAME=`basename "$0"`
 | 
			
		||||
 | 
			
		||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
 | 
			
		||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
 | 
			
		||||
 | 
			
		||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
 | 
			
		||||
MAX_FD=maximum
 | 
			
		||||
MAX_FD="maximum"
 | 
			
		||||
 | 
			
		||||
warn () {
 | 
			
		||||
    echo "$*"
 | 
			
		||||
} >&2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
die () {
 | 
			
		||||
    echo
 | 
			
		||||
    echo "$*"
 | 
			
		||||
    echo
 | 
			
		||||
    exit 1
 | 
			
		||||
} >&2
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# OS specific support (must be 'true' or 'false').
 | 
			
		||||
cygwin=false
 | 
			
		||||
msys=false
 | 
			
		||||
darwin=false
 | 
			
		||||
nonstop=false
 | 
			
		||||
case "$( uname )" in                #(
 | 
			
		||||
  CYGWIN* )         cygwin=true  ;; #(
 | 
			
		||||
  Darwin* )         darwin=true  ;; #(
 | 
			
		||||
  MSYS* | MINGW* )  msys=true    ;; #(
 | 
			
		||||
  NONSTOP* )        nonstop=true ;;
 | 
			
		||||
case "`uname`" in
 | 
			
		||||
  CYGWIN* )
 | 
			
		||||
    cygwin=true
 | 
			
		||||
    ;;
 | 
			
		||||
  Darwin* )
 | 
			
		||||
    darwin=true
 | 
			
		||||
    ;;
 | 
			
		||||
  MINGW* )
 | 
			
		||||
    msys=true
 | 
			
		||||
    ;;
 | 
			
		||||
  NONSTOP* )
 | 
			
		||||
    nonstop=true
 | 
			
		||||
    ;;
 | 
			
		||||
esac
 | 
			
		||||
 | 
			
		||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
| 
						 | 
				
			
			@ -119,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
 | 
			
		|||
if [ -n "$JAVA_HOME" ] ; then
 | 
			
		||||
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
 | 
			
		||||
        # IBM's JDK on AIX uses strange locations for the executables
 | 
			
		||||
        JAVACMD=$JAVA_HOME/jre/sh/java
 | 
			
		||||
        JAVACMD="$JAVA_HOME/jre/sh/java"
 | 
			
		||||
    else
 | 
			
		||||
        JAVACMD=$JAVA_HOME/bin/java
 | 
			
		||||
        JAVACMD="$JAVA_HOME/bin/java"
 | 
			
		||||
    fi
 | 
			
		||||
    if [ ! -x "$JAVACMD" ] ; then
 | 
			
		||||
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
 | 
			
		||||
| 
						 | 
				
			
			@ -130,120 +98,88 @@ Please set the JAVA_HOME variable in your environment to match the
 | 
			
		|||
location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
else
 | 
			
		||||
    JAVACMD=java
 | 
			
		||||
    if ! command -v java >/dev/null 2>&1
 | 
			
		||||
    then
 | 
			
		||||
        die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
    JAVACMD="java"
 | 
			
		||||
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
 | 
			
		||||
Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
location of your Java installation."
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Increase the maximum file descriptors if we can.
 | 
			
		||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
 | 
			
		||||
    case $MAX_FD in #(
 | 
			
		||||
      max*)
 | 
			
		||||
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC2039,SC3045
 | 
			
		||||
        MAX_FD=$( ulimit -H -n ) ||
 | 
			
		||||
            warn "Could not query maximum file descriptor limit"
 | 
			
		||||
    esac
 | 
			
		||||
    case $MAX_FD in  #(
 | 
			
		||||
      '' | soft) :;; #(
 | 
			
		||||
      *)
 | 
			
		||||
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
 | 
			
		||||
        # shellcheck disable=SC2039,SC3045
 | 
			
		||||
        ulimit -n "$MAX_FD" ||
 | 
			
		||||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
 | 
			
		||||
    esac
 | 
			
		||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
 | 
			
		||||
    MAX_FD_LIMIT=`ulimit -H -n`
 | 
			
		||||
    if [ $? -eq 0 ] ; then
 | 
			
		||||
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
 | 
			
		||||
            MAX_FD="$MAX_FD_LIMIT"
 | 
			
		||||
        fi
 | 
			
		||||
        ulimit -n $MAX_FD
 | 
			
		||||
        if [ $? -ne 0 ] ; then
 | 
			
		||||
            warn "Could not set maximum file descriptor limit: $MAX_FD"
 | 
			
		||||
        fi
 | 
			
		||||
    else
 | 
			
		||||
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
 | 
			
		||||
    fi
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Collect all arguments for the java command, stacking in reverse order:
 | 
			
		||||
#   * args from the command line
 | 
			
		||||
#   * the main class name
 | 
			
		||||
#   * -classpath
 | 
			
		||||
#   * -D...appname settings
 | 
			
		||||
#   * --module-path (only if needed)
 | 
			
		||||
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
 | 
			
		||||
# 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" || "$msys" ; then
 | 
			
		||||
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
 | 
			
		||||
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
 | 
			
		||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
 | 
			
		||||
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
 | 
			
		||||
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
 | 
			
		||||
 | 
			
		||||
    JAVACMD=$( cygpath --unix "$JAVACMD" )
 | 
			
		||||
    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
 | 
			
		||||
    # We build the pattern for arguments to be converted via cygpath
 | 
			
		||||
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
 | 
			
		||||
    SEP=""
 | 
			
		||||
    for dir in $ROOTDIRSRAW ; do
 | 
			
		||||
        ROOTDIRS="$ROOTDIRS$SEP$dir"
 | 
			
		||||
        SEP="|"
 | 
			
		||||
    done
 | 
			
		||||
    OURCYGPATTERN="(^($ROOTDIRS))"
 | 
			
		||||
    # Add a user-defined pattern to the cygpath arguments
 | 
			
		||||
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
 | 
			
		||||
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
 | 
			
		||||
    fi
 | 
			
		||||
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
 | 
			
		||||
    i=0
 | 
			
		||||
    for arg in "$@" ; do
 | 
			
		||||
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
 | 
			
		||||
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
 | 
			
		||||
 | 
			
		||||
        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
 | 
			
		||||
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
 | 
			
		||||
        else
 | 
			
		||||
            eval `echo args$i`="\"$arg\""
 | 
			
		||||
        fi
 | 
			
		||||
        i=`expr $i + 1`
 | 
			
		||||
    done
 | 
			
		||||
    case $i in
 | 
			
		||||
        0) set -- ;;
 | 
			
		||||
        1) set -- "$args0" ;;
 | 
			
		||||
        2) set -- "$args0" "$args1" ;;
 | 
			
		||||
        3) set -- "$args0" "$args1" "$args2" ;;
 | 
			
		||||
        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
 | 
			
		||||
        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
 | 
			
		||||
        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
 | 
			
		||||
        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
 | 
			
		||||
        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
 | 
			
		||||
        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
 | 
			
		||||
    esac
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Escape application args
 | 
			
		||||
save () {
 | 
			
		||||
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
 | 
			
		||||
    echo " "
 | 
			
		||||
}
 | 
			
		||||
APP_ARGS=`save "$@"`
 | 
			
		||||
 | 
			
		||||
# 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' ' '
 | 
			
		||||
    )" '"$@"'
 | 
			
		||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
 | 
			
		||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
 | 
			
		||||
 | 
			
		||||
exec "$JAVACMD" "$@"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
@rem limitations under the License.
 | 
			
		||||
@rem
 | 
			
		||||
 | 
			
		||||
@if "%DEBUG%"=="" @echo off
 | 
			
		||||
@if "%DEBUG%" == "" @echo off
 | 
			
		||||
@rem ##########################################################################
 | 
			
		||||
@rem
 | 
			
		||||
@rem  Gradle startup script for Windows
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +25,7 @@
 | 
			
		|||
if "%OS%"=="Windows_NT" setlocal
 | 
			
		||||
 | 
			
		||||
set DIRNAME=%~dp0
 | 
			
		||||
if "%DIRNAME%"=="" set DIRNAME=.
 | 
			
		||||
@rem This is normally unused
 | 
			
		||||
if "%DIRNAME%" == "" set DIRNAME=.
 | 
			
		||||
set APP_BASE_NAME=%~n0
 | 
			
		||||
set APP_HOME=%DIRNAME%
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -41,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
 | 
			
		|||
 | 
			
		||||
set JAVA_EXE=java.exe
 | 
			
		||||
%JAVA_EXE% -version >NUL 2>&1
 | 
			
		||||
if %ERRORLEVEL% equ 0 goto execute
 | 
			
		||||
if "%ERRORLEVEL%" == "0" goto execute
 | 
			
		||||
 | 
			
		||||
echo. 1>&2
 | 
			
		||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
 | 
			
		||||
echo. 1>&2
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
 | 
			
		||||
echo location of your Java installation. 1>&2
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
 | 
			
		|||
 | 
			
		||||
if exist "%JAVA_EXE%" goto execute
 | 
			
		||||
 | 
			
		||||
echo. 1>&2
 | 
			
		||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
 | 
			
		||||
echo. 1>&2
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
 | 
			
		||||
echo location of your Java installation. 1>&2
 | 
			
		||||
echo.
 | 
			
		||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
 | 
			
		||||
echo.
 | 
			
		||||
echo Please set the JAVA_HOME variable in your environment to match the
 | 
			
		||||
echo location of your Java installation.
 | 
			
		||||
 | 
			
		||||
goto fail
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -76,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
 | 
			
		|||
 | 
			
		||||
:end
 | 
			
		||||
@rem End local scope for the variables with windows NT shell
 | 
			
		||||
if %ERRORLEVEL% equ 0 goto mainEnd
 | 
			
		||||
if "%ERRORLEVEL%"=="0" goto mainEnd
 | 
			
		||||
 | 
			
		||||
:fail
 | 
			
		||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
 | 
			
		||||
rem the _cmd.exe /c_ return code!
 | 
			
		||||
set EXIT_CODE=%ERRORLEVEL%
 | 
			
		||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
 | 
			
		||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
 | 
			
		||||
exit /b %EXIT_CODE%
 | 
			
		||||
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
 | 
			
		||||
exit /b 1
 | 
			
		||||
 | 
			
		||||
:mainEnd
 | 
			
		||||
if "%OS%"=="Windows_NT" endlocal
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,10 @@
 | 
			
		|||
pluginManagement {
 | 
			
		||||
    repositories {
 | 
			
		||||
        google {
 | 
			
		||||
            content {
 | 
			
		||||
                includeGroupByRegex("com\\.android.*")
 | 
			
		||||
                includeGroupByRegex("com\\.google.*")
 | 
			
		||||
                includeGroupByRegex("androidx.*")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        google()
 | 
			
		||||
        mavenCentral()
 | 
			
		||||
        gradlePluginPortal()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencyResolutionManagement {
 | 
			
		||||
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
 | 
			
		||||
    repositories {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue