diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 849f32e..dad21a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,11 +37,13 @@ jobs: git clone https://${{ secrets.ACCESS_TOKEN_GITHUB }}@github.com/imas-tools/gakumas-raw-txts.git app/src/main/assets/gakumas-local/gakumas-raw-txts mv app/src/main/assets/gakumas-local/gakumas-raw-txts/Resource app/src/main/assets/gakumas-local/raw rm -rf app/src/main/assets/gakumas-local/gakumas-raw-txts + continue-on-error: true - name: Build Assets run: | mv app/src/main/assets/gakumas-local/GakumasPreTranslation/.env.sample app/src/main/assets/gakumas-local/GakumasPreTranslation/.env cd app/src/main/assets/gakumas-local && make build-resource + continue-on-error: true - name: Write branch and commit info run: | diff --git a/app/build.gradle b/app/build.gradle index 97ffc5e..0838e55 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,16 +84,37 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' - implementation platform('androidx.compose:compose-bom:2023.08.00') + 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' + 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 "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 "io.coil-kt:coil-compose:2.6.0" + implementation "io.coil-kt:coil-svg:2.6.0" 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.0" + implementation "org.jetbrains.kotlin:kotlin-reflect:1.9.22" implementation 'com.google.code.gson:gson:2.11.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a2f6ff8..4481f0b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + Invoke(obj); @@ -109,18 +109,18 @@ namespace GakumasLocal::HookMain { return GetResolution->Invoke(); } - DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* _this, float value)) { + DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) { if (Config::enableFreeCamera) { - if (_this == mainCameraCache) { + if (self == mainCameraCache) { value = GKCamera::baseCamera.fov; } } - Unity_set_fieldOfView_Orig(_this, value); + Unity_set_fieldOfView_Orig(self, value); } - DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* _this)) { + DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* self)) { if (Config::enableFreeCamera) { - if (_this == mainCameraCache) { + if (self == mainCameraCache) { static auto get_orthographic = reinterpret_cast(Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.Camera::get_orthographic()" )); @@ -133,30 +133,30 @@ namespace GakumasLocal::HookMain { // set_orthographic(i, false); Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov); } - Unity_set_fieldOfView_Orig(_this, GKCamera::baseCamera.fov); + Unity_set_fieldOfView_Orig(self, GKCamera::baseCamera.fov); - // Log::DebugFmt("main - get_orthographic: %d", get_orthographic(_this)); + // Log::DebugFmt("main - get_orthographic: %d", get_orthographic(self)); return GKCamera::baseCamera.fov; } } - return Unity_get_fieldOfView_Orig(_this); + return Unity_get_fieldOfView_Orig(self); } - UnityResolve::UnityType::Transform* cacheTrans = NULL; + UnityResolve::UnityType::Transform* cacheTrans = nullptr; UnityResolve::UnityType::Quaternion cacheRotation{}; UnityResolve::UnityType::Vector3 cachePosition{}; UnityResolve::UnityType::Vector3 cacheForward{}; UnityResolve::UnityType::Vector3 cacheLookAt{}; - DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Quaternion* value)) { + DEFINE_HOOK(void, Unity_set_rotation_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Quaternion* value)) { if (Config::enableFreeCamera) { - static auto lookat_injected = reinterpret_cast( Il2cppUtils::il2cpp_resolve_icall( "UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)")); static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0); - if (cameraTransformCache == _this) { + if (cameraTransformCache == self) { const auto cameraMode = GKCamera::GetCameraMode(); if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) { if (cacheTrans && IsNativeObjectAlive(cacheTrans)) { @@ -167,7 +167,7 @@ namespace GakumasLocal::HookMain { static GakumasLocal::Misc::FixedSizeQueue recordsY(60); const auto newY = GKCamera::CheckNewY(cacheLookAt, true, recordsY); UnityResolve::UnityType::Vector3 newCacheLookAt{cacheLookAt.x, newY, cacheLookAt.z}; - lookat_injected(_this, &newCacheLookAt, &worldUp); + lookat_injected(self, &newCacheLookAt, &worldUp); return; } } @@ -175,25 +175,25 @@ namespace GakumasLocal::HookMain { else if (cameraMode == GKCamera::CameraMode::FOLLOW) { auto newLookAtPos = GKCamera::CalcFollowModeLookAt(cachePosition, GKCamera::followPosOffset, true); - lookat_injected(_this, &newLookAtPos, &worldUp); + lookat_injected(self, &newLookAtPos, &worldUp); return; } else { auto& origCameraLookat = GKCamera::baseCamera.lookAt; - lookat_injected(_this, &origCameraLookat, &worldUp); + lookat_injected(self, &origCameraLookat, &worldUp); // Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov); return; } } } - return Unity_set_rotation_Injected_Orig(_this, value); + return Unity_set_rotation_Injected_Orig(self, value); } - DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Vector3* data)) { + DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* self, UnityResolve::UnityType::Vector3* data)) { if (Config::enableFreeCamera) { CheckAndUpdateMainCamera(); - if (cameraTransformCache == _this) { + if (cameraTransformCache == self) { const auto cameraMode = GKCamera::GetCameraMode(); if (cameraMode == GKCamera::CameraMode::FIRST_PERSON) { if (cacheTrans && IsNativeObjectAlive(cacheTrans)) { @@ -218,16 +218,16 @@ namespace GakumasLocal::HookMain { } } - return Unity_set_position_Injected_Orig(_this, data); + return Unity_set_position_Injected_Orig(self, data); } - DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* _this, int type, void* c, void* tc, void* mtd)) { + DEFINE_HOOK(void*, InternalSetOrientationAsync, (void* self, int type, void* c, void* tc, void* mtd)) { switch (Config::gameOrientation) { case 1: type = 0x2; break; // FixedPortrait case 2: type = 0x3; break; // FixedLandscape default: break; } - return InternalSetOrientationAsync_Orig(_this, type, c, tc, mtd); + return InternalSetOrientationAsync_Orig(self, type, c, tc, mtd); } DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) { @@ -248,16 +248,16 @@ namespace GakumasLocal::HookMain { std::unordered_map loadHistory{}; - DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) { + DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* self, Il2cppString* name, void* type)) { // Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str()); - auto ret = AssetBundle_LoadAssetAsync_Orig(_this, name, type); + auto ret = AssetBundle_LoadAssetAsync_Orig(self, name, type); loadHistory.emplace(ret, name->ToString()); return ret; } - DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* _this)) { - auto result = AssetBundleRequest_GetResult_Orig(_this); - if (const auto iter = loadHistory.find(_this); iter != loadHistory.end()) { + DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* self)) { + auto result = AssetBundleRequest_GetResult_Orig(self); + if (const auto iter = loadHistory.find(self); iter != loadHistory.end()) { const auto name = iter->second; loadHistory.erase(iter); @@ -275,25 +275,25 @@ namespace GakumasLocal::HookMain { return ret; } - DEFINE_HOOK(void, I18nHelper_SetUpI18n, (void* _this, Il2cppString* lang, Il2cppString* localizationText, int keyComparison)) { + DEFINE_HOOK(void, I18nHelper_SetUpI18n, (void* self, Il2cppString* lang, Il2cppString* localizationText, int keyComparison)) { // Log::InfoFmt("SetUpI18n lang: %s, key: %d text: %s", lang->ToString().c_str(), keyComparison, localizationText->ToString().c_str()); // TODO 此处为 dump 原文 csv - I18nHelper_SetUpI18n_Orig(_this, lang, localizationText, keyComparison); + I18nHelper_SetUpI18n_Orig(self, lang, localizationText, keyComparison); } - DEFINE_HOOK(void, I18nHelper_SetValue, (void* _this, Il2cppString* key, Il2cppString* value)) { + DEFINE_HOOK(void, I18nHelper_SetValue, (void* self, Il2cppString* key, Il2cppString* value)) { // Log::InfoFmt("I18nHelper_SetValue: %s - %s", key->ToString().c_str(), value->ToString().c_str()); std::string local; if (Local::GetI18n(key->ToString(), &local)) { - I18nHelper_SetValue_Orig(_this, key, UnityResolve::UnityType::String::New(local)); + I18nHelper_SetValue_Orig(self, key, UnityResolve::UnityType::String::New(local)); return; } Local::DumpI18nItem(key->ToString(), value->ToString()); if (Config::textTest) { - I18nHelper_SetValue_Orig(_this, key, Il2cppString::New("[I18]" + value->ToString())); + I18nHelper_SetValue_Orig(self, key, Il2cppString::New("[I18]" + value->ToString())); } else { - I18nHelper_SetValue_Orig(_this, key, value); + I18nHelper_SetValue_Orig(self, key, value); } } @@ -322,7 +322,7 @@ namespace GakumasLocal::HookMain { } std::unordered_set updatedFontPtrs{}; - void UpdateFont(void* TMP_Text_this) { + void UpdateFont(void* TMP_Textself) { if (!Config::replaceFont) return; static auto get_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro", "TMP_Text", "get_font"); @@ -334,7 +334,7 @@ namespace GakumasLocal::HookMain { static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro", "TMP_FontAsset", "UpdateFontAssetData"); - auto fontAsset = get_font->Invoke(TMP_Text_this); + auto fontAsset = get_font->Invoke(TMP_Textself); auto newFont = GetReplaceFont(); if (fontAsset && newFont) { set_sourceFontFile->Invoke(fontAsset, newFont); @@ -344,12 +344,12 @@ namespace GakumasLocal::HookMain { } if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear(); } - set_font->Invoke(TMP_Text_this, fontAsset); + set_font->Invoke(TMP_Textself, fontAsset); } - DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* _this, UnityResolve::UnityType::String* text, int start, int length)) { + DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* self, UnityResolve::UnityType::String* text, int start, int length)) { if (!text) { - return TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length); + return TMP_Text_PopulateTextBackingArray_Orig(self, text, start, length); } static auto Substring = Il2cppUtils::GetMethod("mscorlib.dll", "System", "String", "Substring", @@ -359,48 +359,48 @@ namespace GakumasLocal::HookMain { std::string transText; if (Local::GetGenericText(origText, &transText)) { const auto newText = UnityResolve::UnityType::String::New(transText); - return TMP_Text_PopulateTextBackingArray_Orig(_this, newText, 0, newText->length); + return TMP_Text_PopulateTextBackingArray_Orig(self, newText, 0, newText->length); } if (Config::textTest) { - TMP_Text_PopulateTextBackingArray_Orig(_this, UnityResolve::UnityType::String::New("[TP]" + text->ToString()), start, length + 4); + TMP_Text_PopulateTextBackingArray_Orig(self, UnityResolve::UnityType::String::New("[TP]" + text->ToString()), start, length + 4); } else { - TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length); + TMP_Text_PopulateTextBackingArray_Orig(self, text, start, length); } - UpdateFont(_this); + UpdateFont(self); } - DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* _this, void* method)) { - // Log::InfoFmt("TextMeshProUGUI_Awake at %p, _this at %p", TextMeshProUGUI_Awake_Orig, _this); + DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* self, void* method)) { + // Log::InfoFmt("TextMeshProUGUI_Awake at %p, self at %p", TextMeshProUGUI_Awake_Orig, self); const auto TMP_Text_klass = Il2cppUtils::GetClass("Unity.TextMeshPro.dll", "TMPro", "TMP_Text"); const auto get_Text_method = TMP_Text_klass->Get("get_text"); const auto set_Text_method = TMP_Text_klass->Get("set_text"); - const auto currText = get_Text_method->Invoke(_this); + const auto currText = get_Text_method->Invoke(self); if (currText) { //Log::InfoFmt("TextMeshProUGUI_Awake: %s", currText->ToString().c_str()); std::string transText; if (Local::GetGenericText(currText->ToString(), &transText)) { if (Config::textTest) { - set_Text_method->Invoke(_this, UnityResolve::UnityType::String::New("[TA]" + transText)); + set_Text_method->Invoke(self, UnityResolve::UnityType::String::New("[TA]" + transText)); } else { - set_Text_method->Invoke(_this, UnityResolve::UnityType::String::New(transText)); + set_Text_method->Invoke(self, UnityResolve::UnityType::String::New(transText)); } } } - // set_font->Invoke(_this, font); - UpdateFont(_this); - TextMeshProUGUI_Awake_Orig(_this, method); + // set_font->Invoke(self, font); + UpdateFont(self); + TextMeshProUGUI_Awake_Orig(self, method); } // TODO 文本未hook完整 - DEFINE_HOOK(void, TextField_set_value, (void* _this, Il2cppString* value)) { + DEFINE_HOOK(void, TextField_set_value, (void* self, Il2cppString* value)) { Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str()); - TextField_set_value_Orig(_this, value); + TextField_set_value_Orig(self, value); } DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) { @@ -410,7 +410,7 @@ namespace GakumasLocal::HookMain { } DEFINE_HOOK(void, OctoResourceLoader_LoadFromCacheOrDownload, - (void* _this, Il2cppString* resourceName, void* onComplete, void* onProgress, void* method)) { + (void* self, Il2cppString* resourceName, void* onComplete, void* onProgress, void* method)) { Log::DebugFmt("OctoResourceLoader_LoadFromCacheOrDownload: %s\n", resourceName->ToString().c_str()); @@ -423,24 +423,24 @@ namespace GakumasLocal::HookMain { const auto onComplete_invoke = reinterpret_cast( onComplete_invoke_mtd->methodPointer ); - onComplete_invoke(onComplete, UnityResolve::UnityType::String::New(replaceStr), NULL); + onComplete_invoke(onComplete, UnityResolve::UnityType::String::New(replaceStr), nullptr); return; } } - return OctoResourceLoader_LoadFromCacheOrDownload_Orig(_this, resourceName, onComplete, onProgress, method); + return OctoResourceLoader_LoadFromCacheOrDownload_Orig(self, resourceName, onComplete, onProgress, method); } - DEFINE_HOOK(void, OnDownloadProgress_Invoke, (void* _this, Il2cppString* name, uint64_t receivedLength, uint64_t contentLength)) { + DEFINE_HOOK(void, OnDownloadProgress_Invoke, (void* self, Il2cppString* name, uint64_t receivedLength, uint64_t contentLength)) { Log::DebugFmt("OnDownloadProgress_Invoke: %s, %lu/%lu", name->ToString().c_str(), receivedLength, contentLength); - OnDownloadProgress_Invoke_Orig(_this, name, receivedLength, contentLength); + OnDownloadProgress_Invoke_Orig(self, name, receivedLength, contentLength); } // UnHooked - DEFINE_HOOK(UnityResolve::UnityType::String*, UI_I18n_GetOrDefault, (void* _this, + DEFINE_HOOK(UnityResolve::UnityType::String*, UI_I18n_GetOrDefault, (void* self, UnityResolve::UnityType::String* key, UnityResolve::UnityType::String* defaultKey, void* method)) { - auto ret = UI_I18n_GetOrDefault_Orig(_this, key, defaultKey, method); + auto ret = UI_I18n_GetOrDefault_Orig(self, key, defaultKey, method); // Log::DebugFmt("UI_I18n_GetOrDefault: key: %s, default: %s, result: %s", key->ToString().c_str(), defaultKey->ToString().c_str(), ret->ToString().c_str()); @@ -448,16 +448,16 @@ namespace GakumasLocal::HookMain { // return UnityResolve::UnityType::String::New("[I18]" + ret->ToString()); } - DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* _this, void* liveData, bool isUnlocked, bool isNew)) { + DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* self, void* liveData, bool isUnlocked, bool isNew, void* ct, void* mtd)) { // Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew); - if (Config::unlockAllLive) { + if (Config::dbgMode && Config::unlockAllLive) { isUnlocked = true; } - PictureBookLiveThumbnailView_SetData_Orig(_this, liveData, isUnlocked, isNew); + PictureBookLiveThumbnailView_SetData_Orig(self, liveData, isUnlocked, isNew, ct, mtd); } bool needRestoreHides = false; - DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* _this, void* produceLive, + DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive, Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) { needRestoreHides = false; Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,", @@ -468,18 +468,18 @@ namespace GakumasLocal::HookMain { characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006, */ - if (Config::enableLiveCustomeDress) { + if (Config::dbgMode && Config::enableLiveCustomeDress) { // 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人 - return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, + return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId), Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId)); } - return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, costumeId, costumeHeadId); + return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, costumeId, costumeHeadId); } // std::string lastMusicId; - DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* _this, void* itemModel, bool isFirst, void* mtd)) { + DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* self, void* itemModel, void* ct, void* mtd)) { /* // 修改角色后,Live 结束返回时, itemModel 为 null Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel); @@ -501,7 +501,7 @@ namespace GakumasLocal::HookMain { auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New(); PictureBookLiveSelectMusicListItemModel_ctor->Invoke(newItemModel, music, false); - return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd); + return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, newItemModel, isFirst, mtd); } if (itemModel) { @@ -510,23 +510,23 @@ namespace GakumasLocal::HookMain { lastMusicId = musicId->ToString(); }*/ if (!itemModel) return; - return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, itemModel, isFirst, mtd); + return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, itemModel, ct, mtd); } - DEFINE_HOOK(bool, VLDOF_IsActive, (void* _this)) { + DEFINE_HOOK(bool, VLDOF_IsActive, (void* self)) { if (Config::enableFreeCamera) return false; - return VLDOF_IsActive_Orig(_this); + return VLDOF_IsActive_Orig(self); } - DEFINE_HOOK(void, CampusQualityManager_set_TargetFrameRate, (void* _this, float value)) { + DEFINE_HOOK(void, CampusQualityManager_set_TargetFrameRate, (void* self, float value)) { // Log::InfoFmt("CampusQualityManager_set_TargetFrameRate: %f", value); const auto configFps = Config::targetFrameRate; - CampusQualityManager_set_TargetFrameRate_Orig(_this, configFps == 0 ? value : (float)configFps); + CampusQualityManager_set_TargetFrameRate_Orig(self, configFps == 0 ? value : (float)configFps); } - DEFINE_HOOK(void, CampusQualityManager_ApplySetting, (void* _this, int qualitySettingsLevel, int maxBufferPixel, float renderScale, int volumeIndex)) { + DEFINE_HOOK(void, CampusQualityManager_ApplySetting, (void* self, int qualitySettingsLevel, int maxBufferPixel, float renderScale, int volumeIndex)) { if (Config::targetFrameRate != 0) { - CampusQualityManager_set_TargetFrameRate_Orig(_this, Config::targetFrameRate); + CampusQualityManager_set_TargetFrameRate_Orig(self, Config::targetFrameRate); } if (Config::useCustomeGraphicSettings) { static auto SetReflectionQuality = Il2cppUtils::GetMethod("campus-submodule.Runtime.dll", "Campus.Common", @@ -545,8 +545,8 @@ namespace GakumasLocal::HookMain { if (Config::lodQualityLevel >= values.size()) Config::lodQualityLevel = values.size() - 1; if (Config::reflectionQualityLevel >= values.size()) Config::reflectionQualityLevel = values.size() - 1; - SetLODQuality->Invoke(_this, values[Config::lodQualityLevel]); - SetReflectionQuality->Invoke(_this, values[Config::reflectionQualityLevel]); + SetLODQuality->Invoke(self, values[Config::lodQualityLevel]); + SetReflectionQuality->Invoke(self, values[Config::reflectionQualityLevel]); qualitySettingsLevel = Config::qualitySettingsLevel; maxBufferPixel = Config::maxBufferPixel; @@ -557,7 +557,7 @@ namespace GakumasLocal::HookMain { qualitySettingsLevel, maxBufferPixel, renderScale, volumeIndex, Config::lodQualityLevel, Config::reflectionQualityLevel); } - CampusQualityManager_ApplySetting_Orig(_this, qualitySettingsLevel, maxBufferPixel, renderScale, volumeIndex); + CampusQualityManager_ApplySetting_Orig(self, qualitySettingsLevel, maxBufferPixel, renderScale, volumeIndex); } DEFINE_HOOK(void, UIManager_UpdateRenderTarget, (UnityResolve::UnityType::Vector2 ratio, void* mtd)) { @@ -566,10 +566,10 @@ namespace GakumasLocal::HookMain { return UIManager_UpdateRenderTarget_Orig(ratio, mtd); } - DEFINE_HOOK(void, VLSRPCameraController_UpdateRenderTarget, (void* _this, int width, int height, bool forceAlpha, void* method)) { + DEFINE_HOOK(void, VLSRPCameraController_UpdateRenderTarget, (void* self, int width, int height, bool forceAlpha, void* method)) { // const auto resolution = GetResolution(); // Log::DebugFmt("VLSRPCameraController_UpdateRenderTarget: %d, %d", width, height); - return VLSRPCameraController_UpdateRenderTarget_Orig(_this, width, height, forceAlpha, method); + return VLSRPCameraController_UpdateRenderTarget_Orig(self, width, height, forceAlpha, method); } DEFINE_HOOK(void*, VLUtility_GetLimitedResolution, (int32_t screenWidth, int32_t screenHeight, @@ -584,8 +584,8 @@ namespace GakumasLocal::HookMain { } - DEFINE_HOOK(void, CampusActorModelParts_OnRegisterBone, (void* _this, Il2cppString** name, UnityResolve::UnityType::Transform* bone)) { - CampusActorModelParts_OnRegisterBone_Orig(_this, name, bone); + DEFINE_HOOK(void, CampusActorModelParts_OnRegisterBone, (void* self, Il2cppString** name, UnityResolve::UnityType::Transform* bone)) { + CampusActorModelParts_OnRegisterBone_Orig(self, name, bone); // Log::DebugFmt("CampusActorModelParts_OnRegisterBone: %s, %p", (*name)->ToString().c_str(), bone); } @@ -607,6 +607,7 @@ namespace GakumasLocal::HookMain { } std::vector namesVec{}; + namesVec.reserve(names.size()); for (auto i :names) { namesVec.push_back(i->ToString()); } @@ -645,7 +646,7 @@ namespace GakumasLocal::HookMain { } } - DEFINE_HOOK(void, CampusActorController_LateUpdate, (void* _this, void* mtd)) { + DEFINE_HOOK(void, CampusActorController_LateUpdate, (void* self, void* mtd)) { static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll", "Campus.Common", "CampusActorController"); static auto rootBody_field = CampusActorController_klass->Get("_rootBody"); @@ -654,10 +655,10 @@ namespace GakumasLocal::HookMain { if (!Config::enableFreeCamera || (GKCamera::GetCameraMode() == GKCamera::CameraMode::FREE)) { if (needRestoreHides) { needRestoreHides = false; - HideHead(NULL, false); - HideHead(NULL, true); + HideHead(nullptr, false); + HideHead(nullptr, true); } - return CampusActorController_LateUpdate_Orig(_this, mtd); + return CampusActorController_LateUpdate_Orig(self, mtd); } static auto GetHumanBodyBoneTransform_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(parentKlass, "GetHumanBodyBoneTransform", 1); @@ -668,13 +669,13 @@ namespace GakumasLocal::HookMain { static auto get_Index = get_index_mtd ? reinterpret_cast( get_index_mtd->methodPointer) : [](void*){return 0;}; - const auto currIndex = get_Index(_this); + const auto currIndex = get_Index(self); if (currIndex == GKCamera::followCharaIndex) { static auto initPartsSuccess = InitBodyParts(); static auto headBodyId = initPartsSuccess ? GKCamera::bodyPartsEnum.GetValueByName("Head") : 0xA; const auto isFirstPerson = GKCamera::GetCameraMode() == GKCamera::CameraMode::FIRST_PERSON; - auto targetTrans = GetHumanBodyBoneTransform(_this, + auto targetTrans = GetHumanBodyBoneTransform(self, isFirstPerson ? headBodyId : GKCamera::bodyPartsEnum.GetCurrent().second); if (targetTrans) { @@ -684,7 +685,7 @@ namespace GakumasLocal::HookMain { cacheForward = cacheTrans->GetForward(); cacheLookAt = cacheTrans->GetPosition() + cacheTrans->GetForward() * 3; - auto rootBody = Il2cppUtils::ClassGetFieldValue(_this, rootBody_field); + auto rootBody = Il2cppUtils::ClassGetFieldValue(self, rootBody_field); auto rootModel = rootBody->GetParent(); auto rootModelChildCount = rootModel->GetChildCount(); for (int i = 0; i < rootModelChildCount; i++) { @@ -706,12 +707,12 @@ namespace GakumasLocal::HookMain { } } else { - cacheTrans = NULL; + cacheTrans = nullptr; } } - CampusActorController_LateUpdate_Orig(_this, mtd); + CampusActorController_LateUpdate_Orig(self, mtd); } void UpdateSwingBreastBonesData(void* initializeData) { @@ -814,9 +815,9 @@ namespace GakumasLocal::HookMain { // Log::DebugFmt("\n"); } - DEFINE_HOOK(void, CampusActorAnimation_Setup, (void* _this, void* rootTrans, void* initializeData)) { + DEFINE_HOOK(void, CampusActorAnimation_Setup, (void* self, void* rootTrans, void* initializeData)) { UpdateSwingBreastBonesData(initializeData); - return CampusActorAnimation_Setup_Orig(_this, rootTrans, initializeData); + return CampusActorAnimation_Setup_Orig(self, rootTrans, initializeData); } void StartInjectFunctions() { @@ -862,14 +863,14 @@ namespace GakumasLocal::HookMain { ADD_HOOK(PictureBookLiveThumbnailView_SetData, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook", - "PictureBookLiveThumbnailView", "SetData")); + "PictureBookLiveThumbnailView", "SetDataAsync", {"*", "*", "*", "*"})); ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", "PictureBookLiveSelectScreenPresenter", "MoveLiveScene")); ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", - "PictureBookLiveSelectScreenPresenter", "OnSelectMusic")); + "PictureBookLiveSelectScreenPresenter", "OnSelectMusicAsync")); ADD_HOOK(VLDOF_IsActive, Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering", diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt index 457fdf4..9606238 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt @@ -1,7 +1,16 @@ 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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow interface ConfigListener { @@ -48,9 +57,26 @@ interface ConfigListener { fun onBClickPresetChanged(index: Int) } +class UserConfigViewModelFactory(private val initialValue: GakumasConfig) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(UserConfigViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return UserConfigViewModel(initialValue) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + +class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() { + val configState = MutableStateFlow(initValue) + val config: StateFlow = configState.asStateFlow() +} + interface ConfigUpdateListener: ConfigListener { var binding: ActivityMainBinding + var factory: UserConfigViewModelFactory + var viewModel: UserConfigViewModel fun pushKeyEvent(event: KeyEvent): Boolean fun getConfigContent(): String @@ -247,6 +273,11 @@ interface ConfigUpdateListener: ConfigListener { 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() } diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt index 094d222..cedd43a 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/MainActivity.kt @@ -1,32 +1,195 @@ package io.github.chinosk.gakumas.localify - +import SplashScreen import android.annotation.SuppressLint import android.content.Intent +import android.net.Uri import android.os.Bundle +import android.util.Log import android.view.KeyEvent -import android.view.View -import android.view.ViewTreeObserver -import android.widget.ScrollView -import android.widget.TextView import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity +import androidx.compose.runtime.Composable import androidx.databinding.DataBindingUtil -import com.google.android.material.button.MaterialButton -import com.google.android.material.floatingactionbutton.FloatingActionButton 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 kotlinx.coroutines.flow.MutableStateFlow +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.lifecycle.ViewModelProvider +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 kotlinx.coroutines.flow.asStateFlow -class MainActivity : AppCompatActivity(), ConfigUpdateListener { +class MainActivity : ComponentActivity(), ConfigUpdateListener { + override lateinit var binding: ActivityMainBinding + + override lateinit var factory: UserConfigViewModelFactory + override lateinit var viewModel: UserConfigViewModel + + 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 { + 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(Gson().toJson(binding.config!!)) + } + + fun getVersion(): List { + var versionText = "" + var resVersionText = "unknown" + + try { + val stream = assets.open("${FilesChecker.localizationFilesDir}/version.txt") + resVersionText = FilesChecker.convertToString(stream) + + val packInfo = packageManager.getPackageInfo(packageName, 0) + val version = packInfo.versionName + val versionCode = packInfo.longVersionCode + versionText = "$version ($versionCode)" + } + catch (_: Exception) {} + + return listOf(versionText, resVersionText) + } + + fun openUrl(url: String) { + val webpage = Uri.parse(url) + val intent = Intent(Intent.ACTION_VIEW, webpage) + 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) + } + + @SuppressLint("RestrictedApi") + 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) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + loadConfig() + binding.listener = this + + val requestData = intent.getStringExtra("gkmsData") + if (requestData != null) { + if (requestData == "requestConfig") { + onClickStartGame() + finish() + } + } + + factory = UserConfigViewModelFactory(binding.config!!) + viewModel = ViewModelProvider(this, factory)[UserConfigViewModel::class.java] + + setContent { + GakumasLocalifyTheme(dynamicColor = false) { + MainUI(context = this) + /* + val navController = rememberNavController() + NavHost(navController, startDestination = "splash") { + composable("splash") { + SplashScreen(navController) + } + composable("main") { + MainUI(context = this@MainActivity) + } + }*/ + } + + } + } +} + + +@Composable +fun getConfigState(context: MainActivity?, previewData: GakumasConfig?): State { + return if (context != null) { + context.viewModel.config.collectAsState() + } + else { + val configMSF = MutableStateFlow(previewData!!) + configMSF.asStateFlow().collectAsState() + } +} + +/* +class OldActivity : AppCompatActivity(), ConfigUpdateListener { override lateinit var binding: ActivityMainBinding private val TAG = "GakumasLocalify" + override lateinit var factory: UserConfigViewModelFactory // No usage + override lateinit var viewModel: UserConfigViewModel // No usage + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -163,4 +326,5 @@ class MainActivity : AppCompatActivity(), ConfigUpdateListener { } return if (event.action == 1145) true else super.dispatchKeyEvent(event) } -} \ No newline at end of file +} + */ \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/models/AboutPageConfig.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/AboutPageConfig.kt new file mode 100644 index 0000000..b26af40 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/AboutPageConfig.kt @@ -0,0 +1,24 @@ +package io.github.chinosk.gakumas.localify.models + +data class AboutPageConfig ( + var plugin_repo: String = "https://github.com/chinosk6/gakuen-imas-localify", + var main_contributors: List = listOf(), + var contrib_img: ContribImg = ContribImg( + "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify", + "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData") +) + +data class MainContributors ( + var name: String, + var links: List +) + +data class ContribImg ( + var plugin: String, + var translation: String +) + +data class Links ( + var name: String, + var link: String +) \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt index af45b52..1a72e7c 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt @@ -42,4 +42,6 @@ data class GakumasConfig ( var bLimitYy: Float = 1.0f, var bLimitZx: Float = 1.0f, var bLimitZy: Float = 1.0f, + + var pf: Boolean = false ) diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/models/ViewModels.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/ViewModels.kt new file mode 100644 index 0000000..a6c190f --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/ViewModels.kt @@ -0,0 +1,22 @@ +package io.github.chinosk.gakumas.localify.models + +import androidx.lifecycle.ViewModel +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModelProvider + + +class CollapsibleBoxViewModel(initiallyExpanded: Boolean = false) : ViewModel() { + var expanded by mutableStateOf(initiallyExpanded) +} + +class CollapsibleBoxViewModelFactory(private val initiallyExpanded: Boolean) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(CollapsibleBoxViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return CollapsibleBoxViewModel(initiallyExpanded) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuButton.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuButton.kt new file mode 100644 index 0000000..5f9b446 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuButton.kt @@ -0,0 +1,73 @@ +package io.github.chinosk.gakumas.localify.ui.components + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.ui.tooling.preview.Preview + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp + + +@Composable +fun GakuButton( + onClick: () -> Unit, + text: String, + modifier: Modifier = Modifier, + shape: Shape = RoundedCornerShape(50.dp), // 用于实现左右两边的半圆角 + shadowElevation: Dp = 8.dp, // 阴影的高度 + borderWidth: Dp = 1.dp, // 描边的宽度 + borderColor: Color = Color.Transparent // 描边的颜色 +) { + var buttonSize by remember { mutableStateOf(IntSize.Zero) } + + val gradient = remember(buttonSize) { + Brush.linearGradient( + colors = listOf(Color(0xFFFF5F19), Color(0xFFFFA028)), + start = Offset(0f, 0f), + end = Offset(buttonSize.width.toFloat(), buttonSize.height.toFloat()) // 动态终点 + ) + } + + Button( + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent + ), + modifier = modifier + .onGloballyPositioned { layoutCoordinates -> + buttonSize = layoutCoordinates.size + } + .shadow(elevation = shadowElevation, shape = shape) + .clip(shape) + .background(gradient) + .border(borderWidth, borderColor, shape), + contentPadding = PaddingValues(0.dp) + ) { + Text(text = text) + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun GakuButtonPreview() { + GakuButton(modifier = Modifier.width(80.dp).height(40.dp), text = "Button", onClick = {}) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuGroupBox.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuGroupBox.kt new file mode 100644 index 0000000..a0726d1 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuGroupBox.kt @@ -0,0 +1,108 @@ +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.github.chinosk.gakumas.localify.R + +@Composable +fun GakuGroupBox( + modifier: Modifier = Modifier, + title: String = "Title", + maxWidth: Dp = 500.dp, + contentPadding: Dp = 8.dp, + rightHead: @Composable (() -> Unit)? = null, + onHeadClick: () -> Unit = {}, + content: @Composable () -> Unit +) { + + Box( + modifier = Modifier + .shadow(4.dp, RoundedCornerShape( + bottomStart = 16.dp, + bottomEnd = 8.dp, + topEnd = 16.dp, + topStart = 0.dp + )) + // .background(Color.White, RoundedCornerShape(8.dp)) + ) { + Column(modifier = modifier.widthIn(max = maxWidth)) { + // Header + Box( + modifier = modifier + .fillMaxWidth() + .background(Color.Transparent) + .height(23.dp) + .clickable { + onHeadClick() + } + ) { + Image( + painter = painterResource(id = R.drawable.bg_h1), + contentDescription = null, + // modifier = Modifier.fillMaxSize(), + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.FillBounds + ) + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + color = Color.White, + modifier = modifier + .align(Alignment.CenterStart) + .padding(start = (maxWidth.value * 0.043f).dp) + ) + if (rightHead != null) { + Box(modifier = Modifier + .align(Alignment.CenterEnd) + .padding(end = (maxWidth.value * 0.1f).dp)) { + rightHead() + } + } + } + + // Content + Box( + modifier = modifier + .background( + color = Color.White, + shape = RoundedCornerShape( + bottomStart = 16.dp, + bottomEnd = 8.dp + ) + ) + .padding(contentPadding) + .fillMaxWidth() + ) { + content() + } + } + } + + +} + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun PreviewGakuGroupBox() { + GakuGroupBox( + title = "GroupBox Title" + ) { + Column { + Text("This is the content of the GroupBox.") + Text("This is the content of the GroupBox.") + } + } +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuRadio.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuRadio.kt new file mode 100644 index 0000000..2f430e1 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuRadio.kt @@ -0,0 +1,77 @@ +package io.github.chinosk.gakumas.localify.ui.components + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText + +@Composable +fun GakuRadio( + modifier: Modifier = Modifier, + text: String, + selected: Boolean, + fontSize: TextUnit = 14.sp, + onClick: () -> Unit +) { + val backgroundColor = if (selected) Color(0xFFFFEEC3) else Color(0xFFF8F7F5) + val radioButtonColor = if (selected) Color(0xFFFF7601) else MaterialTheme.colorScheme.onSurface + + Surface( + shape = RoundedCornerShape( + topStart = 4.dp, + topEnd = 16.dp, + bottomEnd = 4.dp, + bottomStart = 16.dp + ), + color = backgroundColor, + modifier = modifier + .pointerInput(Unit) { + detectTapGestures(onTap = { + onClick() + }) + } + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = modifier.padding(start = 0.dp, end = 4.dp) + ) { + RadioButton( + modifier = Modifier.padding(start = 0.dp), + selected = selected, + onClick = onClick, + colors = RadioButtonDefaults.colors( + selectedColor = radioButtonColor, + unselectedColor = MaterialTheme.colorScheme.onSurface + ) + ) + // Spacer(modifier = modifier.width(16.dp)) + AutoSizeText(text = text, + textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface, + fontSize = fontSize)) + // Text(text = text, color = MaterialTheme.colorScheme.onSurface, fontSize = fontSize) + } + } +} + + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 100, heightDp = 40) +@Composable +fun GakuRadioPreview() { + GakuRadio(text = "GakuRadioooo", selected = true, onClick = {}) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuSwitch.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuSwitch.kt new file mode 100644 index 0000000..aa1b0ef --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuSwitch.kt @@ -0,0 +1,51 @@ +package io.github.chinosk.gakumas.localify.ui.components + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.ui.Alignment +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText + + +@Composable +fun GakuSwitch(modifier: Modifier = Modifier, + text: String = "", + checked: Boolean = false, + leftPart: @Composable (() -> Unit)? = null, + onCheckedChange: (Boolean) -> Unit = {}) { + Row(modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically) { + if (text.isNotEmpty()) { + AutoSizeText(text = text, fontSize = 16.sp) + } + leftPart?.invoke() + Switch(checked = checked, + onCheckedChange = { value -> onCheckedChange(value) }, + modifier = Modifier, + colors = SwitchDefaults.colors( + checkedThumbColor = Color(0xFFFFFFFF), + checkedTrackColor = Color(0xFFF89400), + + uncheckedThumbColor = Color(0xFFFFFFFF), + uncheckedTrackColor = Color(0xFFCFD8DC), + uncheckedBorderColor = Color(0xFFCFD8DC), + )) + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun GakuSwitchPreview() { + GakuSwitch(text = "Switch", checked = true) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTableRow.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTableRow.kt new file mode 100644 index 0000000..d263bf1 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTableRow.kt @@ -0,0 +1,103 @@ +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 +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import androidx.compose.material3.Surface +import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.runtime.* +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState + + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun GakuTabRow( + modifier: Modifier = Modifier, + pagerState: PagerState, + tabs: List, + onTabSelected: (index: Int) -> Unit +) { + val coroutineScope = rememberCoroutineScope() + LaunchedEffect(pagerState.currentPage) { + onTabSelected(pagerState.currentPage) + } + + Box( + modifier = Modifier + .shadow(4.dp, RoundedCornerShape(16.dp)) + ) { + Surface( + modifier = modifier + .clip(RoundedCornerShape(16.dp)) + .shadow(4.dp), + shape = RoundedCornerShape(16.dp), + ) { + Column { + TabRow( + modifier = modifier.background(Color.Transparent), + containerColor = Color.Transparent, + selectedTabIndex = pagerState.currentPage, + indicator = @Composable { tabPositions -> + Box( + Modifier + .tabIndicatorOffset(tabPositions[pagerState.currentPage]) + .height(4.dp) + .background(Color(0xFFFFA500)) + .padding(horizontal = 4.dp) + ) + } + ) { + tabs.forEachIndexed { index, title -> + Tab( + selected = pagerState.currentPage == index, + onClick = { + coroutineScope.launch { + pagerState.scrollToPage(index) +// pagerState.animateScrollToPage( +// page = index, +// animationSpec = tween(durationMillis = 250) +// ) + } + }, + text = { + Text( + text = title, + color = if (pagerState.currentPage == index) Color(0xFFFFA500) else Color.Black + ) + } + ) + } + } + + + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun GakuTabRowPreview(modifier: Modifier = Modifier) { + val pagerState = rememberPagerState(initialPage = 1, pageCount = { 3 }) + GakuTabRow(modifier, pagerState, listOf("TAB 1", "TAB 2", "TAB 3")) { _ -> } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTextInput.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTextInput.kt new file mode 100644 index 0000000..8a8aa33 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/GakuTextInput.kt @@ -0,0 +1,188 @@ +package io.github.chinosk.gakumas.localify.ui.components + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun GakuTextInput( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + label: @Composable (() -> Unit)? = null, + fontSize: Float = 16f, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default +) { + val shape: Shape = remember { + RoundedCornerShape( + topStart = 4.dp, + topEnd = 16.dp, + bottomEnd = 4.dp, + bottomStart = 16.dp + ) + } + + var localValue by remember { mutableStateOf(value) } + var isUserInput by remember { mutableStateOf(false) } + val textStyle = remember { + TextStyle(fontSize = fontSize.sp) + } + + LaunchedEffect(value) { + if (!isUserInput) { + localValue = value + } + isUserInput = false + } + + Box( + modifier = modifier + ) { + OutlinedTextFieldNoPadding( + singleLine = true, + value = localValue, + onValueChange = { newValue -> + isUserInput = true + localValue = newValue + onValueChange(newValue) + }, + label = label, + modifier = modifier, + textStyle = textStyle, + shape = shape, + keyboardOptions = keyboardOptions + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun OutlinedTextFieldNoPadding( + value: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + readOnly: Boolean = false, + textStyle: TextStyle = LocalTextStyle.current, + label: @Composable (() -> Unit)? = null, + placeholder: @Composable (() -> Unit)? = null, + leadingIcon: @Composable (() -> Unit)? = null, + trailingIcon: @Composable (() -> Unit)? = null, + prefix: @Composable (() -> Unit)? = null, + suffix: @Composable (() -> Unit)? = null, + supportingText: @Composable (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions.Default, + keyboardActions: KeyboardActions = KeyboardActions.Default, + singleLine: Boolean = false, + maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, + minLines: Int = 1, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + shape: Shape = OutlinedTextFieldDefaults.shape, + colors: TextFieldColors = OutlinedTextFieldDefaults.colors() +) { + // If color is not provided via the text style, use content color as a default + val textColor = textStyle.color + val mergedTextStyle = textStyle.merge(TextStyle(color = textColor)) + + CompositionLocalProvider { + BasicTextField( + value = value, + modifier = if (label != null) { + modifier + // Merge semantics at the beginning of the modifier chain to ensure padding is + // considered part of the text field. + .semantics(mergeDescendants = true) {} + .padding(top = 8.dp) + } else { + modifier + } + .defaultMinSize( + minWidth = OutlinedTextFieldDefaults.MinWidth, + minHeight = OutlinedTextFieldDefaults.MinHeight + ), + onValueChange = onValueChange, + enabled = enabled, + readOnly = readOnly, + textStyle = mergedTextStyle, + cursorBrush = SolidColor(if (!isError) MaterialTheme.colorScheme.primary else Color.Red), + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions, + interactionSource = interactionSource, + singleLine = singleLine, + maxLines = maxLines, + minLines = minLines, + decorationBox = @Composable { innerTextField -> + OutlinedTextFieldDefaults.DecorationBox( + contentPadding = PaddingValues.Absolute(left = 16.dp, right = 16.dp), + value = value, + visualTransformation = visualTransformation, + innerTextField = innerTextField, + placeholder = placeholder, + label = label, + leadingIcon = leadingIcon, + trailingIcon = trailingIcon, + prefix = prefix, + suffix = suffix, + supportingText = supportingText, + singleLine = singleLine, + enabled = enabled, + isError = isError, + interactionSource = interactionSource, + colors = colors, + container = { + OutlinedTextFieldDefaults.ContainerBox( + enabled, + isError, + interactionSource, + colors, + shape + ) + } + ) + } + ) + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun GakuTextInputPreview() { + GakuTextInput(modifier = Modifier.height(50.dp), + fontSize = 16f, + value = "123456", onValueChange = { }, label = { Text("Label") }) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/AutoSizeText.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/AutoSizeText.kt new file mode 100644 index 0000000..2b58b82 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/AutoSizeText.kt @@ -0,0 +1,63 @@ +package io.github.chinosk.gakumas.localify.ui.components.base + +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalInspectionMode +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + + +@Composable +fun AutoSizeText( + modifier: Modifier = Modifier, + text: String, + color: Color = MaterialTheme.colorScheme.onSurface, + fontSize: TextUnit = TextUnit.Unspecified, + textStyle: TextStyle? = null, + minSize: TextUnit = 8.sp +) { + var scaledTextStyle by remember { mutableStateOf(textStyle ?: TextStyle(color = color, fontSize = fontSize)) } + var readyToDraw by remember { mutableStateOf(false) } + + if (LocalInspectionMode.current) { + Text( + text, + modifier, + style = scaledTextStyle + ) + return + } + + Text( + text, + modifier.drawWithContent { + if (readyToDraw) { + drawContent() + } + }, + style = scaledTextStyle, + softWrap = false, + onTextLayout = { textLayoutResult -> + if (textLayoutResult.didOverflowWidth) { + val newSize = (scaledTextStyle.fontSize.value - 1.sp.value).sp + if (minSize <= newSize) { + scaledTextStyle = scaledTextStyle.copy(fontSize = newSize) + } + else { + readyToDraw = true + } + } else { + readyToDraw = true + } + } + ) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/CollapsibleBox.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/CollapsibleBox.kt new file mode 100644 index 0000000..ffcd4a7 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/components/base/CollapsibleBox.kt @@ -0,0 +1,94 @@ +package io.github.chinosk.gakumas.localify.ui.components.base + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.material3.Text +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import io.github.chinosk.gakumas.localify.models.CollapsibleBoxViewModel + +@Composable +fun CollapsibleBox( + modifier: Modifier = Modifier, + collapsedHeight: Dp = 28.dp, + viewModel: CollapsibleBoxViewModel = viewModel(), + showExpand: Boolean = true, + expandState: Boolean? = null, + content: @Composable () -> Unit +) { + val expanded by viewModel::expanded + + // var offsetY by remember { mutableFloatStateOf(0f) } + + val animatedHeight by animateDpAsState( + targetValue = if (expandState ?: expanded) Dp.Unspecified else collapsedHeight, + label = "CollapsibleBox$collapsedHeight" + ) + + Box( + modifier = modifier + .animateContentSize()/* + .pointerInput(Unit) { + detectVerticalDragGestures( + onVerticalDrag = { change, dragAmount -> + change.consume() + offsetY += dragAmount + if (expanded && offsetY > 0) { + viewModel.expanded = false + } else if (!expanded && offsetY < 0) { + viewModel.expanded = true + } + }, + onDragEnd = { + offsetY = 0f + } + ) + }*/ + .background(MaterialTheme.colorScheme.background) + ) { + Column( + modifier = Modifier + .height(animatedHeight) + .fillMaxWidth() + // .fillMaxSize() + .clickable { + if (!expanded && showExpand) { + viewModel.expanded = expandState ?: true + } + }, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + //item { + if (expandState ?: expanded) { + content() + } + else if (showExpand) { + Text(text = "Details ↓", color = Color.Gray) + } + //} + + } + } +} + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun CollapsibleBoxPreview() { + CollapsibleBox(showExpand = true) {} +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/MainPage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/MainPage.kt new file mode 100644 index 0000000..d667ca5 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/MainPage.kt @@ -0,0 +1,113 @@ +package io.github.chinosk.gakumas.localify.ui.pages + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import io.github.chinosk.gakumas.localify.MainActivity +import io.github.chinosk.gakumas.localify.R +import io.github.chinosk.gakumas.localify.models.GakumasConfig +import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme + + +@Composable +fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null, + previewData: GakumasConfig? = null) { + val imagePainter = painterResource(R.drawable.bg_pattern) + val versionInfo = remember { + context?.getVersion() ?: listOf("", "Unknown") + } + // val config = getConfigState(context, previewData) + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFFDFDFD)) + ) { + val screenH = imageRepeater( + painter = imagePainter, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopCenter) + ) + + Column( + modifier = modifier + .fillMaxSize() + .padding(10.dp, 10.dp, 10.dp, 0.dp), + verticalArrangement = Arrangement.Top + ) { + Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp) + Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp) + + SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home), + stringResource(R.string.advanced_settings)), + context = context, previewData = previewData, screenH = screenH) + } + } +} + + +@Composable +fun imageRepeater( + painter: Painter, + modifier: Modifier = Modifier +): Dp { + val density = LocalDensity.current + val imageHeightPx = painter.intrinsicSize.height + val imageHeightDp = with(density) { imageHeightPx.toDp() } + var retMaxH = 1080.dp + BoxWithConstraints(modifier = modifier) { + retMaxH = maxHeight + val screenHeight = maxHeight + val repeatCount = (screenHeight / imageHeightDp).toInt() + 1 + + Column { + repeat(repeatCount) { + Image( + painter = painter, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxWidth() + .height(imageHeightDp) + ) + } + } + } + return retMaxH +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380) +@Composable +fun MainUIPreview(modifier: Modifier = Modifier) { + val previewConfig = GakumasConfig() + previewConfig.enabled = true + + GakumasLocalifyTheme { + MainUI(previewData = previewConfig) + } +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SettingsTab.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SettingsTab.kt new file mode 100644 index 0000000..b6b8f80 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SettingsTab.kt @@ -0,0 +1,89 @@ +package io.github.chinosk.gakumas.localify.ui.pages + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import io.github.chinosk.gakumas.localify.MainActivity +import io.github.chinosk.gakumas.localify.models.GakumasConfig +import io.github.chinosk.gakumas.localify.ui.components.GakuTabRow +import io.github.chinosk.gakumas.localify.ui.pages.subPages.AboutPage +import io.github.chinosk.gakumas.localify.ui.pages.subPages.AdvanceSettingsPage +import io.github.chinosk.gakumas.localify.ui.pages.subPages.HomePage + + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun SettingsTabs(modifier: Modifier = Modifier, + titles: List, + context: MainActivity? = null, + previewData: GakumasConfig? = null, + screenH: Dp = 1080.dp +) { + + val pagerState = rememberPagerState(initialPage = 1, pageCount = { titles.size }) + + Box { + HorizontalPager( + state = pagerState, + modifier = modifier.fillMaxSize(), + pageSpacing = 10.dp + ) { page -> + Column(modifier = modifier + .padding(5.dp) + .fillMaxHeight(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally) { + when (page) { + 0 -> AboutPage(modifier, context = context, previewData = previewData, screenH = screenH) + 1 -> HomePage(modifier, context = context, previewData = previewData, screenH = screenH) + 2 -> AdvanceSettingsPage(modifier, context = context, previewData = previewData, screenH = screenH) + } + } + } + + Box( + Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 6.dp)) { + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + FloatingActionButton( + onClick = { context?.onClickStartGame() }, + modifier = Modifier.align(Alignment.End), + containerColor = MaterialTheme.colorScheme.primary, + shape = CircleShape + ) { + Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = "StartGame") + } + + GakuTabRow(modifier, pagerState, titles) { } + } + } + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, heightDp = 760) +@Composable +fun SettingTabsPreview(modifier: Modifier = Modifier) { + SettingsTabs(titles = listOf("TAB 1", "TAB 2", "TAB 3"), previewData = GakumasConfig()) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SplashScreen.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SplashScreen.kt new file mode 100644 index 0000000..c67b52c --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/SplashScreen.kt @@ -0,0 +1,27 @@ +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.navigation.NavController +import io.github.chinosk.gakumas.localify.R + +@Composable +fun SplashScreen(navController: NavController) { + /*Image( + painter = painterResource(id = R.drawable.splash_image), + contentDescription = null, + modifier = Modifier.fillMaxSize(), + contentScale = ContentScale.FillHeight + )*/ + + LaunchedEffect(Unit) { + kotlinx.coroutines.delay(100) + + navController.navigate("main") { + popUpTo("splash") { inclusive = true } + } + } +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AboutPage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AboutPage.kt new file mode 100644 index 0000000..7c0d346 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AboutPage.kt @@ -0,0 +1,214 @@ +package io.github.chinosk.gakumas.localify.ui.pages.subPages + +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.ImageLoader +import coil.compose.rememberAsyncImagePainter +import coil.decode.SvgDecoder +import coil.request.ImageRequest +import coil.size.Size +import 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.models.AboutPageConfig +import io.github.chinosk.gakumas.localify.models.GakumasConfig +import io.github.chinosk.gakumas.localify.ui.components.GakuButton + + +@Composable +fun AboutPage(modifier: Modifier = Modifier, + context: MainActivity? = null, + previewData: GakumasConfig? = null, + bottomSpacerHeight: Dp = 120.dp, + screenH: Dp = 1080.dp) { + // val config = getConfigState(context, previewData) + val contributorInfo = remember { + val dataJsonString = context?.getString(R.string.about_contributors_asset_file)?.let { + convertToString(context.assets?.open(it)) + } + Gson().fromJson(dataJsonString, AboutPageConfig::class.java) + ?: AboutPageConfig() + } + + LazyColumn(modifier = modifier + .sizeIn(maxHeight = screenH) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + Text(stringResource(R.string.about_warn_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.error) + Text(stringResource(R.string.about_warn_p1)) + Text(stringResource(R.string.about_warn_p2)) + } + + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + Text(stringResource(R.string.about_about_title), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer) + Text(stringResource(R.string.about_about_p1)) + Text(stringResource(R.string.about_about_p2)) + Row(modifier = Modifier + .fillMaxWidth() + .padding( + start = 8.dp, end = 8.dp, top = 8.dp, bottom = 0.dp + )) { + GakuButton(text = "Github", modifier = modifier + .weight(1f) + .sizeIn(maxWidth = 600.dp), onClick = { + context?.openUrl(contributorInfo.plugin_repo) + }) + } + } + + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + LazyColumn(modifier = modifier + .sizeIn(maxHeight = screenH) + .fillMaxWidth()) { + item { + Text(stringResource(R.string.project_contribution), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer) + } + for (contributor in contributorInfo.main_contributors) { + item { + Row(modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 8.dp, 8.dp, 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically) { + Text(contributor.name, fontSize = 16.sp) + for (link in contributor.links) { + GakuButton(text = link.name, modifier = modifier.height(40.dp), + onClick = { + context?.openUrl(link.link) + }) + } + } + } + + } + } + } + + item { + Text(stringResource(R.string.contributors), fontSize = 24.sp, color = MaterialTheme.colorScheme.onPrimaryContainer) + + Text(stringResource(R.string.plugin_code), fontSize = 16.sp) + NetworkSvgImage( + url = contributorInfo.contrib_img.plugin, + contentDescription = "plugin-contrib" + ) + + Spacer(modifier = Modifier.height(4.dp)) + Text(stringResource(R.string.translation_repository), fontSize = 16.sp) + NetworkSvgImage( + url = contributorInfo.contrib_img.translation, + contentDescription = "translation-contrib" + ) + } + + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + Spacer(modifier = modifier.height(bottomSpacerHeight)) + } + } +} + + +@Composable +fun NetworkImage( + url: String, + contentDescription: String?, + modifier: Modifier = Modifier +) { + val painter = rememberAsyncImagePainter(model = ImageRequest.Builder(LocalContext.current) + .data(url) + .crossfade(true) + .size(Size.ORIGINAL) + .build()) + + Image( + painter = painter, + contentDescription = contentDescription, + modifier = modifier + ) +} + +@Composable +fun NetworkSvgImage( + url: String, + contentDescription: String?, + modifier: Modifier = Modifier +) { + val imageLoader = ImageLoader.Builder(LocalContext.current) + .components { + add(SvgDecoder.Factory()) + } + .build() + + val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data(url) + .size(Size.ORIGINAL) + .build(), + imageLoader = imageLoader + ) + + Image( + painter = painter, + contentDescription = contentDescription, + modifier = modifier + ) +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun AboutPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) { + AboutPage(modifier, previewData = data) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AdvancedSettingsPage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AdvancedSettingsPage.kt new file mode 100644 index 0000000..1037ddd --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/AdvancedSettingsPage.kt @@ -0,0 +1,391 @@ +package io.github.chinosk.gakumas.localify.ui.pages.subPages + +import GakuGroupBox +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import io.github.chinosk.gakumas.localify.MainActivity +import io.github.chinosk.gakumas.localify.R +import io.github.chinosk.gakumas.localify.getConfigState +import io.github.chinosk.gakumas.localify.models.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 +import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch +import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput + + +@Composable +fun AdvanceSettingsPage(modifier: Modifier = Modifier, + context: MainActivity? = null, + previewData: GakumasConfig? = null, + bottomSpacerHeight: Dp = 120.dp, + screenH: Dp = 1080.dp) { + val config = getConfigState(context, previewData) + // val scrollState = rememberScrollState() + + val breastParamViewModel: CollapsibleBoxViewModel = + viewModel(factory = CollapsibleBoxViewModelFactory(initiallyExpanded = false)) + val keyBoardOptionsDecimal = remember { + KeyboardOptions(keyboardType = KeyboardType.Decimal) + } + + LazyColumn(modifier = modifier + .sizeIn(maxHeight = screenH) + // .fillMaxHeight() + // .verticalScroll(scrollState) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + GakuGroupBox(modifier, stringResource(R.string.camera_settings)) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + GakuSwitch(modifier, stringResource(R.string.enable_free_camera), checked = config.value.enableFreeCamera) { + v -> context?.onEnableFreeCameraChanged(v) + } + } + } + + Spacer(Modifier.height(6.dp)) + } + + item { + GakuGroupBox(modifier, stringResource(R.string.debug_settings)) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) { + v -> context?.onTextTestChanged(v) + } + + GakuSwitch(modifier, stringResource(R.string.export_text), checked = config.value.dumpText) { + v -> context?.onDumpTextChanged(v) + } + + GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) { + v -> context?.onForceExportResourceChanged(v) + } + } + } + + Spacer(Modifier.height(6.dp)) + } + + item { + GakuGroupBox(modifier, stringResource(R.string.breast_param), + contentPadding = 0.dp, + onHeadClick = { + breastParamViewModel.expanded = !breastParamViewModel.expanded + }) { + CollapsibleBox(modifier = modifier, + viewModel = breastParamViewModel + ) { + LazyColumn(modifier = modifier + .padding(8.dp) + .sizeIn(maxHeight = screenH), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + item { + Row(modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(2.dp)) { + val buttonModifier = remember { + modifier + .height(40.dp) + .weight(1f) + } + + GakuButton(modifier = buttonModifier, + text = "??", onClick = { context?.onBClickPresetChanged(5) }) + + GakuButton(modifier = buttonModifier, + text = "+5", onClick = { context?.onBClickPresetChanged(4) }) + + GakuButton(modifier = buttonModifier, + text = "+4", onClick = { context?.onBClickPresetChanged(3) }) + + GakuButton(modifier = buttonModifier, + text = "+3", onClick = { context?.onBClickPresetChanged(2) }) + + GakuButton(modifier = buttonModifier, + text = "+2", onClick = { context?.onBClickPresetChanged(1) }) + + GakuButton(modifier = buttonModifier, + text = "+1", onClick = { context?.onBClickPresetChanged(0) }) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bDamping.toString(), + onValueChange = { c -> context?.onBDampingChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.damping)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bStiffness.toString(), + onValueChange = { c -> context?.onBStiffnessChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.stiffness)) }, + keyboardOptions = keyBoardOptionsDecimal) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bSpring.toString(), + onValueChange = { c -> context?.onBSpringChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.spring)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bPendulum.toString(), + onValueChange = { c -> context?.onBPendulumChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.pendulum)) }, + keyboardOptions = keyBoardOptionsDecimal) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bPendulumRange.toString(), + onValueChange = { c -> context?.onBPendulumRangeChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.pendulumrange)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = modifier + .height(45.dp) + .weight(1f), + fontSize = 14f, + value = config.value.bAverage.toString(), + onValueChange = { c -> context?.onBAverageChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.average)) }, + keyboardOptions = keyBoardOptionsDecimal) + } + } + + item { + GakuTextInput(modifier = modifier + .height(45.dp) + .fillMaxWidth(), + fontSize = 14f, + value = config.value.bRootWeight.toString(), + onValueChange = { c -> context?.onBRootWeightChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.rootweight)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + } + + item { + GakuSwitch(modifier = modifier, + checked = config.value.bUseScale, + leftPart = { + GakuTextInput(modifier = modifier + .height(45.dp), + fontSize = 14f, + value = config.value.bScale.toString(), + onValueChange = { c -> context?.onBScaleChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.breast_scale)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + } + ) { v -> context?.onBUseScaleChanged(v) } + } + + item { + GakuSwitch(modifier = modifier, + checked = config.value.bUseArmCorrection, + text = stringResource(R.string.usearmcorrection) + ) { v -> context?.onBUseArmCorrectionChanged(v) } + } + + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + GakuSwitch(modifier = modifier, + checked = config.value.bUseLimit, + text = stringResource(R.string.uselimit_0_1) + ) { v -> + context?.onBUseLimitChanged(v) + } + } + + item { + CollapsibleBox(modifier = modifier, + expandState = config.value.bUseLimit, + collapsedHeight = 0.dp, + showExpand = false + ){ + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val textInputModifier = remember { + modifier + .height(45.dp) + .weight(1f) + } + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitXx.toString(), + onValueChange = { c -> context?.onBLimitXxChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisx_x)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitYx.toString(), + onValueChange = { c -> context?.onBLimitYxChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisy_x)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitZx.toString(), + onValueChange = { c -> context?.onBLimitZxChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisz_x)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + } + + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val textInputModifier = remember { + modifier + .height(45.dp) + .weight(1f) + } + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitXy.toString(), + onValueChange = { c -> context?.onBLimitXyChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisx_y)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitYy.toString(), + onValueChange = { c -> context?.onBLimitYyChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisy_y)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.bLimitZy.toString(), + onValueChange = { c -> context?.onBLimitZyChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.axisz_y)) }, + keyboardOptions = keyBoardOptionsDecimal + ) + } + } + } + + } + } + } + } + + item { + if (config.value.dbgMode) { + Spacer(Modifier.height(6.dp)) + + GakuGroupBox(modifier, stringResource(R.string.test_mode_live)) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + GakuSwitch(modifier, stringResource(R.string.unlockAllLive), + checked = config.value.unlockAllLive) { + v -> context?.onUnlockAllLiveChanged(v) + } + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + GakuSwitch(modifier, stringResource(R.string.liveUseCustomeDress), + checked = config.value.enableLiveCustomeDress) { + v -> context?.onLiveCustomeDressChanged(v) + } + GakuTextInput(modifier = modifier + .height(45.dp) + .fillMaxWidth(), + fontSize = 14f, + value = config.value.liveCustomeHeadId, + onValueChange = { c -> context?.onLiveCustomeHeadIdChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.live_costume_head_id), + fontSize = 12.sp) } + ) + GakuTextInput(modifier = modifier + .height(45.dp) + .fillMaxWidth(), + fontSize = 14f, + value = config.value.liveCustomeCostumeId, + onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.live_custome_dress_id)) } + ) + } + } + } + } + + item { + Spacer(modifier = modifier.height(bottomSpacerHeight)) + } + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Composable +fun AdvanceSettingsPagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) { + AdvanceSettingsPage(modifier, previewData = data) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt new file mode 100644 index 0000000..f818ea6 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt @@ -0,0 +1,270 @@ +package io.github.chinosk.gakumas.localify.ui.pages.subPages + +import GakuGroupBox +import android.content.res.Configuration.UI_MODE_NIGHT_NO +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import 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.GakumasConfig +import io.github.chinosk.gakumas.localify.ui.components.base.CollapsibleBox +import io.github.chinosk.gakumas.localify.ui.components.GakuButton +import io.github.chinosk.gakumas.localify.ui.components.GakuRadio +import io.github.chinosk.gakumas.localify.ui.components.GakuSwitch +import io.github.chinosk.gakumas.localify.ui.components.GakuTextInput + + +@Composable +fun HomePage(modifier: Modifier = Modifier, + context: MainActivity? = null, + previewData: GakumasConfig? = null, + bottomSpacerHeight: Dp = 120.dp, + screenH: Dp = 1080.dp) { + val config = getConfigState(context, previewData) + // val scrollState = rememberScrollState() + val keyboardOptionsNumber = remember { + KeyboardOptions(keyboardType = KeyboardType.Number) + } + val keyBoardOptionsDecimal = remember { + KeyboardOptions(keyboardType = KeyboardType.Decimal) + } + + + LazyColumn(modifier = modifier + .sizeIn(maxHeight = screenH) + // .fillMaxHeight() + // .verticalScroll(scrollState) + // .width(IntrinsicSize.Max) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + GakuGroupBox(modifier = modifier, stringResource(R.string.basic_settings)) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + GakuSwitch(modifier, stringResource(R.string.enable_plugin), checked = config.value.enabled) { + v -> context?.onEnabledChanged(v) + } + + GakuSwitch(modifier, stringResource(R.string.replace_font), checked = config.value.replaceFont) { + v -> context?.onReplaceFontChanged(v) + } + } + } + Spacer(Modifier.height(6.dp)) + } + + item { + GakuGroupBox(modifier = modifier, contentPadding = 0.dp, title = stringResource(R.string.graphic_settings)) { + LazyColumn(modifier = Modifier + .sizeIn(maxHeight = screenH), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + item { + Spacer(modifier = Modifier.height(8.dp)) + GakuTextInput(modifier = modifier + .padding(start = 4.dp, end = 4.dp) + .height(45.dp) + .fillMaxWidth(), + fontSize = 14f, + value = config.value.targetFrameRate.toString(), + onValueChange = { c -> context?.onTargetFpsChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.setFpsTitle)) }, + keyboardOptions = keyboardOptionsNumber) + } + + item { + Column(modifier = Modifier.padding(start = 8.dp, end = 8.dp), + verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(stringResource(R.string.orientation_lock)) + Row(modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(6.dp)) { + val radioModifier = remember { + modifier + .height(40.dp) + .weight(1f) + } + + GakuRadio(modifier = radioModifier, + text = stringResource(R.string.orientation_orig), selected = config.value.gameOrientation == 0, + onClick = { context?.onGameOrientationChanged(0) }) + + GakuRadio(modifier = radioModifier, + text = stringResource(R.string.orientation_portrait), selected = config.value.gameOrientation == 1, + onClick = { context?.onGameOrientationChanged(1) }) + + GakuRadio(modifier = radioModifier, + text = stringResource(R.string.orientation_landscape), selected = config.value.gameOrientation == 2, + onClick = { context?.onGameOrientationChanged(2) }) + } + } + } + + item { + HorizontalDivider( + thickness = 1.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) + ) + } + + item { + GakuSwitch(modifier.padding(start = 8.dp, end = 8.dp), + stringResource(R.string.useCustomeGraphicSettings), + checked = config.value.useCustomeGraphicSettings) { + v -> context?.onUseCustomeGraphicSettingsChanged(v) + } + + CollapsibleBox(modifier = modifier, + expandState = config.value.useCustomeGraphicSettings, + collapsedHeight = 0.dp, + showExpand = false + ) { + LazyColumn(modifier = modifier + .padding(8.dp) + .sizeIn(maxHeight = screenH) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + item { + Row(modifier = modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val buttonModifier = remember { + modifier + .height(40.dp) + .weight(1f) + } + + GakuButton(modifier = buttonModifier, + text = stringResource(R.string.max_high), onClick = { context?.onChangePresetQuality(4) }) + + GakuButton(modifier = buttonModifier, + text = stringResource(R.string.very_high), onClick = { context?.onChangePresetQuality(3) }) + + GakuButton(modifier = buttonModifier, + text = stringResource(R.string.hign), onClick = { context?.onChangePresetQuality(2) }) + + GakuButton(modifier = buttonModifier, + text = stringResource(R.string.middle), onClick = { context?.onChangePresetQuality(1) }) + + GakuButton(modifier = buttonModifier, + text = stringResource(R.string.low), onClick = { context?.onChangePresetQuality(0) }) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val textInputModifier = remember { + modifier + .height(45.dp) + .weight(1f) + } + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.renderScale.toString(), + onValueChange = { c -> context?.onRenderScaleChanged(c, 0, 0, 0)}, + label = { Text(stringResource(R.string.renderscale)) }, + keyboardOptions = keyBoardOptionsDecimal) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.qualitySettingsLevel.toString(), + onValueChange = { c -> context?.onQualitySettingsLevelChanged(c, 0, 0, 0)}, + label = { Text("QualityLevel (1/1/2/3/5)") }, + keyboardOptions = keyboardOptionsNumber) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val textInputModifier = remember { + modifier + .height(45.dp) + .weight(1f) + } + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.volumeIndex.toString(), + onValueChange = { c -> context?.onVolumeIndexChanged(c, 0, 0, 0)}, + label = { Text("VolumeIndex (0/1/2/3/4)") }, + keyboardOptions = keyboardOptionsNumber) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.maxBufferPixel.toString(), + onValueChange = { c -> context?.onMaxBufferPixelChanged(c, 0, 0, 0)}, + label = { Text("MaxBufferPixel (1024/1440/2538/3384/8190)", fontSize = 10.sp) }, + keyboardOptions = keyboardOptionsNumber) + } + } + + item { + Row(modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp)) { + val textInputModifier = remember { + modifier + .height(45.dp) + .weight(1f) + } + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.reflectionQualityLevel.toString(), + onValueChange = { c -> context?.onReflectionQualityLevelChanged(c, 0, 0, 0)}, + label = { Text( text = "ReflectionLevel (0~5)") }, + keyboardOptions = keyboardOptionsNumber) + + GakuTextInput(modifier = textInputModifier, + fontSize = 14f, + value = config.value.lodQualityLevel.toString(), + onValueChange = { c -> context?.onLodQualityLevelChanged(c, 0, 0, 0)}, + label = { Text("LOD Level (0~5)") }, + keyboardOptions = keyboardOptionsNumber) + } + } + } + } + + } + + } + + } + } + + item { + Spacer(modifier = modifier.height(bottomSpacerHeight)) + } + } +} + + +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 880) +@Composable +fun HomePagePreview(modifier: Modifier = Modifier, data: GakumasConfig = GakumasConfig()) { + HomePage(modifier, previewData = data) +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/theme/Theme.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/theme/Theme.kt index afe5886..ce8435b 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/theme/Theme.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/theme/Theme.kt @@ -10,6 +10,7 @@ import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.lightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView @@ -22,7 +23,7 @@ private val DarkColorScheme = darkColorScheme( ) private val LightColorScheme = lightColorScheme( - primary = Purple40, + primary = Color(0xFFF89400), secondary = PurpleGrey40, tertiary = Pink40 @@ -53,6 +54,7 @@ fun GakumasLocalifyTheme( darkTheme -> DarkColorScheme else -> LightColorScheme } + val view = LocalView.current if (!view.isInEditMode) { SideEffect { diff --git a/app/src/main/res/drawable/bg_h1.png b/app/src/main/res/drawable/bg_h1.png new file mode 100644 index 0000000..7f14d2b Binary files /dev/null and b/app/src/main/res/drawable/bg_h1.png differ diff --git a/app/src/main/res/drawable/bg_pattern.png b/app/src/main/res/drawable/bg_pattern.png new file mode 100644 index 0000000..5fcc333 Binary files /dev/null and b/app/src/main/res/drawable/bg_pattern.png differ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 76a9cd1..2b4dfdf 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -20,7 +20,7 @@ - 游戏原版 + 原版 竖屏 横屏 方向锁定 @@ -44,4 +44,25 @@ axisX.y axisY.y axisZ.y + 基础设置 + 画面设置 + 摄像机设置 + 测试模式 - LIVE + 调试设置 + 胸部参数 + 关于 + 主页 + 高级设置 + 使用前警告 + 本插件仅供学习和交流使用。 + 使用外部插件属于违反游戏条款的行为。若使用插件后账号被封禁,造成的后果由用户自行承担。 + 关于本插件 + 本插件完全免费。若您付费购买了本插件,请举报店家。 + 插件交流群: 975854705 + 项目贡献 + 插件本体 + 贡献者列表 + 译文仓库 + + about_contributors_zh_cn.json \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0de9161..be1df2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -44,5 +44,25 @@ axisX.y axisY.y axisZ.y + Basic Ssettings + Graphic Settings + Camera Settings + Test Mode - LIVE + Debug Settings + Breast Parameters + About + Home + Advanced + WARNING + This plugin is for learning and communication only. + Using external plugin against the relevant TOS so proceed at your own risk. + About This Plugin + This plugin is completely free. If you paid for this plugin, please report the seller. + Plugin QQ group: 975854705 + Project Contribution + Plugin Code + Contributors + Translation Repository + about_contributors_en.json \ No newline at end of file