UI rewrite (#27)
* rewrite UI * update submodule and ci * update submodule * AboutPage - use config
This commit is contained in:
parent
64f147586f
commit
d36666f436
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -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: |
|
||||
|
@ -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'
|
||||
}
|
@ -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"
|
||||
|
29
app/src/main/assets/about_contributors_en.json
Normal file
29
app/src/main/assets/about_contributors_en.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
|
||||
"main_contributors": [
|
||||
{
|
||||
"name": "chinosk (Plugin code)",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/chinosk6"},
|
||||
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DarwinTree (Translation Workflow)",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/darwintree"},
|
||||
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "And all other translators",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"contrib_img": {
|
||||
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||
}
|
||||
}
|
29
app/src/main/assets/about_contributors_zh_cn.json
Normal file
29
app/src/main/assets/about_contributors_zh_cn.json
Normal file
@ -0,0 +1,29 @@
|
||||
{
|
||||
"plugin_repo": "https://github.com/chinosk6/gakuen-imas-localify",
|
||||
"main_contributors": [
|
||||
{
|
||||
"name": "chinosk(插件本体)",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/chinosk6"},
|
||||
{"name": "Bilibili", "link": "https://space.bilibili.com/287061163"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "DarwinTree(译文工作流)",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/darwintree"},
|
||||
{"name": "Bilibili", "link": "https://space.bilibili.com/6069705"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "以及其他所有翻译贡献者",
|
||||
"links": [
|
||||
{"name": "Github", "link": "https://github.com/chinosk6/GakumasTranslationData/graphs/contributors"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"contrib_img": {
|
||||
"plugin": "https://contrib.rocks/image?repo=chinosk6/gakuen-imas-localify",
|
||||
"translation": "https://contrib.rocks/image?repo=chinosk6/GakumasTranslationData"
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 0ffe615be44269500cfb9fa78b967a791724535c
|
||||
Subproject commit cdd0ad064cf6d3f13107e19b5d08c582d8d0664e
|
@ -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",
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
@ -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
|
||||
)
|
@ -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
|
||||
)
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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 = {})
|
||||
}
|
@ -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.")
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package io.github.chinosk.gakumas.localify.ui.components
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.RadioButtonDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.github.chinosk.gakumas.localify.ui.components.base.AutoSizeText
|
||||
|
||||
@Composable
|
||||
fun GakuRadio(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
selected: Boolean,
|
||||
fontSize: TextUnit = 14.sp,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val backgroundColor = if (selected) Color(0xFFFFEEC3) else Color(0xFFF8F7F5)
|
||||
val radioButtonColor = if (selected) Color(0xFFFF7601) else MaterialTheme.colorScheme.onSurface
|
||||
|
||||
Surface(
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 4.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomEnd = 4.dp,
|
||||
bottomStart = 16.dp
|
||||
),
|
||||
color = backgroundColor,
|
||||
modifier = modifier
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(onTap = {
|
||||
onClick()
|
||||
})
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier.padding(start = 0.dp, end = 4.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
modifier = Modifier.padding(start = 0.dp),
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
colors = RadioButtonDefaults.colors(
|
||||
selectedColor = radioButtonColor,
|
||||
unselectedColor = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
)
|
||||
// Spacer(modifier = modifier.width(16.dp))
|
||||
AutoSizeText(text = text,
|
||||
textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize = fontSize))
|
||||
// Text(text = text, color = MaterialTheme.colorScheme.onSurface, fontSize = fontSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 100, heightDp = 40)
|
||||
@Composable
|
||||
fun GakuRadioPreview() {
|
||||
GakuRadio(text = "GakuRadioooo", selected = true, onClick = {})
|
||||
}
|
@ -0,0 +1,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)
|
||||
}
|
@ -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")) { _ -> }
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package io.github.chinosk.gakumas.localify.ui.components
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
@Composable
|
||||
fun GakuTextInput(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
fontSize: Float = 16f,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default
|
||||
) {
|
||||
val shape: Shape = remember {
|
||||
RoundedCornerShape(
|
||||
topStart = 4.dp,
|
||||
topEnd = 16.dp,
|
||||
bottomEnd = 4.dp,
|
||||
bottomStart = 16.dp
|
||||
)
|
||||
}
|
||||
|
||||
var localValue by remember { mutableStateOf(value) }
|
||||
var isUserInput by remember { mutableStateOf(false) }
|
||||
val textStyle = remember {
|
||||
TextStyle(fontSize = fontSize.sp)
|
||||
}
|
||||
|
||||
LaunchedEffect(value) {
|
||||
if (!isUserInput) {
|
||||
localValue = value
|
||||
}
|
||||
isUserInput = false
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
OutlinedTextFieldNoPadding(
|
||||
singleLine = true,
|
||||
value = localValue,
|
||||
onValueChange = { newValue ->
|
||||
isUserInput = true
|
||||
localValue = newValue
|
||||
onValueChange(newValue)
|
||||
},
|
||||
label = label,
|
||||
modifier = modifier,
|
||||
textStyle = textStyle,
|
||||
shape = shape,
|
||||
keyboardOptions = keyboardOptions
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun OutlinedTextFieldNoPadding(
|
||||
value: String,
|
||||
onValueChange: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
readOnly: Boolean = false,
|
||||
textStyle: TextStyle = LocalTextStyle.current,
|
||||
label: @Composable (() -> Unit)? = null,
|
||||
placeholder: @Composable (() -> Unit)? = null,
|
||||
leadingIcon: @Composable (() -> Unit)? = null,
|
||||
trailingIcon: @Composable (() -> Unit)? = null,
|
||||
prefix: @Composable (() -> Unit)? = null,
|
||||
suffix: @Composable (() -> Unit)? = null,
|
||||
supportingText: @Composable (() -> Unit)? = null,
|
||||
isError: Boolean = false,
|
||||
visualTransformation: VisualTransformation = VisualTransformation.None,
|
||||
keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
|
||||
keyboardActions: KeyboardActions = KeyboardActions.Default,
|
||||
singleLine: Boolean = false,
|
||||
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
|
||||
minLines: Int = 1,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
shape: Shape = OutlinedTextFieldDefaults.shape,
|
||||
colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
|
||||
) {
|
||||
// If color is not provided via the text style, use content color as a default
|
||||
val textColor = textStyle.color
|
||||
val mergedTextStyle = textStyle.merge(TextStyle(color = textColor))
|
||||
|
||||
CompositionLocalProvider {
|
||||
BasicTextField(
|
||||
value = value,
|
||||
modifier = if (label != null) {
|
||||
modifier
|
||||
// Merge semantics at the beginning of the modifier chain to ensure padding is
|
||||
// considered part of the text field.
|
||||
.semantics(mergeDescendants = true) {}
|
||||
.padding(top = 8.dp)
|
||||
} else {
|
||||
modifier
|
||||
}
|
||||
.defaultMinSize(
|
||||
minWidth = OutlinedTextFieldDefaults.MinWidth,
|
||||
minHeight = OutlinedTextFieldDefaults.MinHeight
|
||||
),
|
||||
onValueChange = onValueChange,
|
||||
enabled = enabled,
|
||||
readOnly = readOnly,
|
||||
textStyle = mergedTextStyle,
|
||||
cursorBrush = SolidColor(if (!isError) MaterialTheme.colorScheme.primary else Color.Red),
|
||||
visualTransformation = visualTransformation,
|
||||
keyboardOptions = keyboardOptions,
|
||||
keyboardActions = keyboardActions,
|
||||
interactionSource = interactionSource,
|
||||
singleLine = singleLine,
|
||||
maxLines = maxLines,
|
||||
minLines = minLines,
|
||||
decorationBox = @Composable { innerTextField ->
|
||||
OutlinedTextFieldDefaults.DecorationBox(
|
||||
contentPadding = PaddingValues.Absolute(left = 16.dp, right = 16.dp),
|
||||
value = value,
|
||||
visualTransformation = visualTransformation,
|
||||
innerTextField = innerTextField,
|
||||
placeholder = placeholder,
|
||||
label = label,
|
||||
leadingIcon = leadingIcon,
|
||||
trailingIcon = trailingIcon,
|
||||
prefix = prefix,
|
||||
suffix = suffix,
|
||||
supportingText = supportingText,
|
||||
singleLine = singleLine,
|
||||
enabled = enabled,
|
||||
isError = isError,
|
||||
interactionSource = interactionSource,
|
||||
colors = colors,
|
||||
container = {
|
||||
OutlinedTextFieldDefaults.ContainerBox(
|
||||
enabled,
|
||||
isError,
|
||||
interactionSource,
|
||||
colors,
|
||||
shape
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
|
||||
@Composable
|
||||
fun GakuTextInputPreview() {
|
||||
GakuTextInput(modifier = Modifier.height(50.dp),
|
||||
fontSize = 16f,
|
||||
value = "123456", onValueChange = { }, label = { Text("Label") })
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package io.github.chinosk.gakumas.localify.ui.components.base
|
||||
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
|
||||
@Composable
|
||||
fun AutoSizeText(
|
||||
modifier: Modifier = Modifier,
|
||||
text: String,
|
||||
color: Color = MaterialTheme.colorScheme.onSurface,
|
||||
fontSize: TextUnit = TextUnit.Unspecified,
|
||||
textStyle: TextStyle? = null,
|
||||
minSize: TextUnit = 8.sp
|
||||
) {
|
||||
var scaledTextStyle by remember { mutableStateOf(textStyle ?: TextStyle(color = color, fontSize = fontSize)) }
|
||||
var readyToDraw by remember { mutableStateOf(false) }
|
||||
|
||||
if (LocalInspectionMode.current) {
|
||||
Text(
|
||||
text,
|
||||
modifier,
|
||||
style = scaledTextStyle
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
Text(
|
||||
text,
|
||||
modifier.drawWithContent {
|
||||
if (readyToDraw) {
|
||||
drawContent()
|
||||
}
|
||||
},
|
||||
style = scaledTextStyle,
|
||||
softWrap = false,
|
||||
onTextLayout = { textLayoutResult ->
|
||||
if (textLayoutResult.didOverflowWidth) {
|
||||
val newSize = (scaledTextStyle.fontSize.value - 1.sp.value).sp
|
||||
if (minSize <= newSize) {
|
||||
scaledTextStyle = scaledTextStyle.copy(fontSize = newSize)
|
||||
}
|
||||
else {
|
||||
readyToDraw = true
|
||||
}
|
||||
} else {
|
||||
readyToDraw = true
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
@ -0,0 +1,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) {}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
package io.github.chinosk.gakumas.localify.ui.pages
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.github.chinosk.gakumas.localify.MainActivity
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
import io.github.chinosk.gakumas.localify.models.GakumasConfig
|
||||
import io.github.chinosk.gakumas.localify.ui.theme.GakumasLocalifyTheme
|
||||
|
||||
|
||||
@Composable
|
||||
fun MainUI(modifier: Modifier = Modifier, context: MainActivity? = null,
|
||||
previewData: GakumasConfig? = null) {
|
||||
val imagePainter = painterResource(R.drawable.bg_pattern)
|
||||
val versionInfo = remember {
|
||||
context?.getVersion() ?: listOf("", "Unknown")
|
||||
}
|
||||
// val config = getConfigState(context, previewData)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFFDFDFD))
|
||||
) {
|
||||
val screenH = imageRepeater(
|
||||
painter = imagePainter,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.TopCenter)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.padding(10.dp, 10.dp, 10.dp, 0.dp),
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Text(text = "Gakumas Localify ${versionInfo[0]}", fontSize = 18.sp)
|
||||
Text(text = "Assets version: ${versionInfo[1]}", fontSize = 13.sp)
|
||||
|
||||
SettingsTabs(modifier, listOf(stringResource(R.string.about), stringResource(R.string.home),
|
||||
stringResource(R.string.advanced_settings)),
|
||||
context = context, previewData = previewData, screenH = screenH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun imageRepeater(
|
||||
painter: Painter,
|
||||
modifier: Modifier = Modifier
|
||||
): Dp {
|
||||
val density = LocalDensity.current
|
||||
val imageHeightPx = painter.intrinsicSize.height
|
||||
val imageHeightDp = with(density) { imageHeightPx.toDp() }
|
||||
var retMaxH = 1080.dp
|
||||
BoxWithConstraints(modifier = modifier) {
|
||||
retMaxH = maxHeight
|
||||
val screenHeight = maxHeight
|
||||
val repeatCount = (screenHeight / imageHeightDp).toInt() + 1
|
||||
|
||||
Column {
|
||||
repeat(repeatCount) {
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(imageHeightDp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return retMaxH
|
||||
}
|
||||
|
||||
|
||||
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO, widthDp = 380)
|
||||
@Composable
|
||||
fun MainUIPreview(modifier: Modifier = Modifier) {
|
||||
val previewConfig = GakumasConfig()
|
||||
previewConfig.enabled = true
|
||||
|
||||
GakumasLocalifyTheme {
|
||||
MainUI(previewData = previewConfig)
|
||||
}
|
||||
}
|
@ -0,0 +1,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())
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.navigation.NavController
|
||||
import io.github.chinosk.gakumas.localify.R
|
||||
|
||||
@Composable
|
||||
fun SplashScreen(navController: NavController) {
|
||||
/*Image(
|
||||
painter = painterResource(id = R.drawable.splash_image),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.FillHeight
|
||||
)*/
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
kotlinx.coroutines.delay(100)
|
||||
|
||||
navController.navigate("main") {
|
||||
popUpTo("splash") { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
package io.github.chinosk.gakumas.localify.ui.pages.subPages
|
||||
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.ImageLoader
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Size
|
||||
import 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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
@ -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 {
|
||||
|
BIN
app/src/main/res/drawable/bg_h1.png
Normal file
BIN
app/src/main/res/drawable/bg_h1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable/bg_pattern.png
Normal file
BIN
app/src/main/res/drawable/bg_pattern.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
@ -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>
|
@ -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>
|
Loading…
x
Reference in New Issue
Block a user