1
0

UI rewrite (#27)

* rewrite UI

* update submodule and ci

* update submodule

* AboutPage - use config
This commit is contained in:
chinosk 2024-06-24 03:20:00 -05:00 committed by GitHub
parent 64f147586f
commit d36666f436
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 2343 additions and 112 deletions

View File

@ -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: |

View File

@ -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'
}

View File

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -0,0 +1,29 @@
{
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
"main_contributors": [
{
"name": "chinosk (Plugin code)",
"links": [
{"name": "Github", "link": "https://github.com/chinosk6"},
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
]
},
{
"name": "DarwinTree (Translation Workflow)",
"links": [
{"name": "Github", "link": "https://github.com/darwintree"},
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
]
},
{
"name": "And all other translators",
"links": [
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
]
}
],
"contrib_img": {
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
}
}

View File

@ -0,0 +1,29 @@
{
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
"main_contributors": [
{
"name": "chinosk插件本体",
"links": [
{"name": "Github", "link": "https://github.com/chinosk6"},
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
]
},
{
"name": "DarwinTree译文工作流",
"links": [
{"name": "Github", "link": "https://github.com/darwintree"},
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
]
},
{
"name": "以及其他所有翻译贡献者",
"links": [
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
]
}
],
"contrib_img": {
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
}
}

@ -1 +1 @@
Subproject commit 0ffe615be44269500cfb9fa78b967a791724535c
Subproject commit cdd0ad064cf6d3f13107e19b5d08c582d8d0664e

View File

@ -87,7 +87,7 @@ namespace GakumasLocal::HookMain {
}
bool IsNativeObjectAlive(void* obj) {
static UnityResolve::Method* IsNativeObjectAliveMtd = NULL;
static UnityResolve::Method* IsNativeObjectAliveMtd = nullptr;
if (!IsNativeObjectAliveMtd) IsNativeObjectAliveMtd = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
"Object", "IsNativeObjectAlive");
return IsNativeObjectAliveMtd->Invoke<bool>(obj);
@ -109,18 +109,18 @@ namespace GakumasLocal::HookMain {
return GetResolution->Invoke<Il2cppUtils::Resolution_t>();
}
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* _this, float value)) {
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) {
if (Config::enableFreeCamera) {
if (_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<bool (*)(void*)>(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<void (*)(void*_this,
static auto lookat_injected = reinterpret_cast<void (*)(void*self,
UnityResolve::UnityType::Vector3* worldPosition, UnityResolve::UnityType::Vector3* worldUp)>(
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<float> 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<void*, std::string> loadHistory{};
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* self, Il2cppString* name, void* type)) {
// Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str());
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<void*> 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<void*>(TMP_Text_this);
auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
auto newFont = GetReplaceFont();
if (fontAsset && newFont) {
set_sourceFontFile->Invoke<void>(fontAsset, newFont);
@ -344,12 +344,12 @@ namespace GakumasLocal::HookMain {
}
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
}
set_font->Invoke<void>(TMP_Text_this, fontAsset);
set_font->Invoke<void>(TMP_Textself, fontAsset);
}
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* _this, UnityResolve::UnityType::String* text, int start, int length)) {
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* self, UnityResolve::UnityType::String* text, int start, int length)) {
if (!text) {
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<UnityResolve::Method>("get_text");
const auto set_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("set_text");
const auto currText = get_Text_method->Invoke<UnityResolve::UnityType::String*>(_this);
const auto currText = get_Text_method->Invoke<UnityResolve::UnityType::String*>(self);
if (currText) {
//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<void>(_this, UnityResolve::UnityType::String::New("[TA]" + transText));
set_Text_method->Invoke<void>(self, UnityResolve::UnityType::String::New("[TA]" + transText));
}
else {
set_Text_method->Invoke<void>(_this, UnityResolve::UnityType::String::New(transText));
set_Text_method->Invoke<void>(self, UnityResolve::UnityType::String::New(transText));
}
}
}
// set_font->Invoke<void>(_this, font);
UpdateFont(_this);
TextMeshProUGUI_Awake_Orig(_this, method);
// set_font->Invoke<void>(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<void (*)(void*, Il2cppString*, void*)>(
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<void*>();
PictureBookLiveSelectMusicListItemModel_ctor->Invoke<void>(newItemModel, music, false);
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd);
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(self, newItemModel, isFirst, mtd);
}
if (itemModel) {
@ -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<void>(_this, values[Config::lodQualityLevel]);
SetReflectionQuality->Invoke<void>(_this, values[Config::reflectionQualityLevel]);
SetLODQuality->Invoke<void>(self, values[Config::lodQualityLevel]);
SetReflectionQuality->Invoke<void>(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<std::string> 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<UnityResolve::Field>("_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<int (*)(void*)>(
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<UnityResolve::UnityType::Transform*>(_this, rootBody_field);
auto rootBody = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::Transform*>(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",

View File

@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(UserConfigViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return UserConfigViewModel(initialValue) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
class UserConfigViewModel(initValue: GakumasConfig) : ViewModel() {
val configState = MutableStateFlow(initValue)
val config: StateFlow<GakumasConfig> = configState.asStateFlow()
}
interface ConfigUpdateListener: ConfigListener {
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()
}

View File

@ -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<String> {
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<ActivityMainBinding>(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<GakumasConfig> {
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)
}
}
}
*/

View File

@ -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<MainContributors> = 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<Links>
)
data class ContribImg (
var plugin: String,
var translation: String
)
data class Links (
var name: String,
var link: String
)

View File

@ -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
)

View File

@ -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 <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CollapsibleBoxViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return CollapsibleBoxViewModel(initiallyExpanded) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

View File

@ -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 = {})
}

View File

@ -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.")
}
}
}

View File

@ -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 = {})
}

View File

@ -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)
}

View File

@ -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<String>,
onTabSelected: (index: Int) -> Unit
) {
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(pagerState.currentPage) {
onTabSelected(pagerState.currentPage)
}
Box(
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(16.dp))
) {
Surface(
modifier = modifier
.clip(RoundedCornerShape(16.dp))
.shadow(4.dp),
shape = RoundedCornerShape(16.dp),
) {
Column {
TabRow(
modifier = modifier.background(Color.Transparent),
containerColor = Color.Transparent,
selectedTabIndex = pagerState.currentPage,
indicator = @Composable { tabPositions ->
Box(
Modifier
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
.height(4.dp)
.background(Color(0xFFFFA500))
.padding(horizontal = 4.dp)
)
}
) {
tabs.forEachIndexed { index, title ->
Tab(
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
pagerState.scrollToPage(index)
// pagerState.animateScrollToPage(
// page = index,
// animationSpec = tween(durationMillis = 250)
// )
}
},
text = {
Text(
text = title,
color = if (pagerState.currentPage == index) Color(0xFFFFA500) else Color.Black
)
}
)
}
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@Composable
fun GakuTabRowPreview(modifier: Modifier = Modifier) {
val pagerState = rememberPagerState(initialPage = 1, pageCount = { 3 })
GakuTabRow(modifier, pagerState, listOf("TAB 1", "TAB 2", "TAB 3")) { _ -> }
}

View File

@ -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") })
}

View File

@ -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
}
}
)
}

View File

@ -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) {}
}

View File

@ -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)
}
}

View File

@ -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<String>,
context: MainActivity? = null,
previewData: GakumasConfig? = null,
screenH: Dp = 1080.dp
) {
val pagerState = rememberPagerState(initialPage = 1, pageCount = { titles.size })
Box {
HorizontalPager(
state = pagerState,
modifier = modifier.fillMaxSize(),
pageSpacing = 10.dp
) { page ->
Column(modifier = modifier
.padding(5.dp)
.fillMaxHeight(),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally) {
when (page) {
0 -> AboutPage(modifier, context = context, previewData = previewData, screenH = screenH)
1 -> HomePage(modifier, context = context, previewData = previewData, screenH = screenH)
2 -> AdvanceSettingsPage(modifier, context = context, previewData = previewData, screenH = screenH)
}
}
}
Box(
Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 6.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(6.dp)) {
FloatingActionButton(
onClick = { context?.onClickStartGame() },
modifier = Modifier.align(Alignment.End),
containerColor = MaterialTheme.colorScheme.primary,
shape = CircleShape
) {
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
contentDescription = "StartGame")
}
GakuTabRow(modifier, pagerState, titles) { }
}
}
}
}
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, heightDp = 760)
@Composable
fun SettingTabsPreview(modifier: Modifier = Modifier) {
SettingsTabs(titles = listOf("TAB 1", "TAB 2", "TAB 3"), previewData = GakumasConfig())
}

View File

@ -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 }
}
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -20,7 +20,7 @@
<string name="hign"></string>
<string name="middle"></string>
<string name="low"></string>
<string name="orientation_orig">游戏原版</string>
<string name="orientation_orig">原版</string>
<string name="orientation_portrait">竖屏</string>
<string name="orientation_landscape">横屏</string>
<string name="orientation_lock">方向锁定</string>
@ -44,4 +44,25 @@
<string name="axisx_y">axisX.y</string>
<string name="axisy_y">axisY.y</string>
<string name="axisz_y">axisZ.y</string>
<string name="basic_settings">基础设置</string>
<string name="graphic_settings">画面设置</string>
<string name="camera_settings">摄像机设置</string>
<string name="test_mode_live">测试模式 - LIVE</string>
<string name="debug_settings">调试设置</string>
<string name="breast_param">胸部参数</string>
<string name="about">关于</string>
<string name="home">主页</string>
<string name="advanced_settings">高级设置</string>
<string name="about_warn_title">使用前警告</string>
<string name="about_warn_p1">本插件仅供学习和交流使用。</string>
<string name="about_warn_p2">使用外部插件属于违反游戏条款的行为。若使用插件后账号被封禁,造成的后果由用户自行承担。</string>
<string name="about_about_title">关于本插件</string>
<string name="about_about_p1">本插件完全免费。若您付费购买了本插件,请举报店家。</string>
<string name="about_about_p2">插件交流群: 975854705</string>
<string name="project_contribution">项目贡献</string>
<string name="plugin_code">插件本体</string>
<string name="contributors">贡献者列表</string>
<string name="translation_repository">译文仓库</string>
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
</resources>

View File

@ -44,5 +44,25 @@
<string name="axisx_y">axisX.y</string>
<string name="axisy_y">axisY.y</string>
<string name="axisz_y">axisZ.y</string>
<string name="basic_settings">Basic Ssettings</string>
<string name="graphic_settings">Graphic Settings</string>
<string name="camera_settings">Camera Settings</string>
<string name="test_mode_live">Test Mode - LIVE</string>
<string name="debug_settings">Debug Settings</string>
<string name="breast_param">Breast Parameters</string>
<string name="about">About</string>
<string name="home">Home</string>
<string name="advanced_settings">Advanced</string>
<string name="about_warn_title">WARNING</string>
<string name="about_warn_p1">This plugin is for learning and communication only.</string>
<string name="about_warn_p2">Using external plugin against the relevant TOS so proceed at your own risk.</string>
<string name="about_about_title">About This Plugin</string>
<string name="about_about_p1">This plugin is completely free. If you paid for this plugin, please report the seller.</string>
<string name="about_about_p2">Plugin QQ group: 975854705</string>
<string name="project_contribution">Project Contribution</string>
<string name="plugin_code">Plugin Code</string>
<string name="contributors">Contributors</string>
<string name="translation_repository">Translation Repository</string>
<string name="about_contributors_asset_file">about_contributors_en.json</string>
</resources>