Compare commits

..

7 Commits

Author SHA1 Message Date
Kain344 68747fc5d1 feat: update versioning and changelog for release 4.3.5-01
Build / build (push) Successful in 5m47s
2026-06-27 12:29:34 +07:00
Kain344 1a26f98b02 refactor: remove ServerControlScreen composable and associated utility functions from MainActivity
Build / build (push) Successful in 5m32s
2026-06-26 11:05:48 +07:00
Kain344 c2bd81fc75 feat: implement authentication management and deep link handling in MainActivity and GolangServerService
Build / build (push) Failing after 3m36s
2026-06-25 23:55:16 +07:00
Kain344 1ae12e94bc fix: resolve merge conflict in build_apk.yml
Build / build (push) Failing after 2m15s
2026-06-25 23:19:47 +07:00
Kain344 ee8207dc79 feat: new data 2026-06-25 23:18:12 +07:00
Kain344 c468384f04 Update .gitignore 2026-06-25 23:15:47 +07:00
Kain344 6f4604c5f5 feat: v4 2026-06-25 22:52:26 +07:00
54 changed files with 16625 additions and 7248 deletions
+61
View File
@@ -0,0 +1,61 @@
name: Build
run-name: ${{ gitea.actor }} build 🚀
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set Up JDK
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
- name: Setup Android SDK
uses: amyu/setup-android@v5
with:
cache-disabled: true
- name: Grant execute permissions
run: |
chmod +x ./gradlew
chmod +x ./script/release-uploader
- name: Download AAR manually
run: |
FILE="app/libs/firefly-go.aar"
URL="https://git.kain.io.vn/Firefly-Shelter/FireflyGo_Android/media/branch/master/app/libs/firefly-go.aar"
echo "📥 Downloading $FILE from $URL"
curl -L -o "$FILE" "$URL"
- name: Build signed release APK
env:
KEYSTORE_PATH: ${{ github.workspace }}/KeyStore.jks
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
run: |
./gradlew assembleRelease \
-Pandroid.injected.signing.store.file=$KEYSTORE_PATH \
-Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \
-Pandroid.injected.signing.key.alias=$KEY_ALIAS \
-Pandroid.injected.signing.key.password=$KEY_PASSWORD
- name: Find and rename release APK
run: |
APK_FILE=$(ls app/build/outputs/apk/release/*.apk | head -n 1)
echo "Found APK: $APK_FILE"
mv "$APK_FILE" app/build/outputs/apk/release/firefly_go_android.apk
- name: Upload release
env:
REPO_TOKEN: ${{ secrets.REPO_TOKEN }}
run: script/release-uploader -token=$REPO_TOKEN -release-url="https://git.kain.io.vn/api/v1/repos/Firefly-Shelter/FireflyGo_Android/releases" -files="app/build/outputs/apk/release/firefly_go_android.apk"
+2
View File
@@ -15,3 +15,5 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
.history/
.idea/
Generated
+1 -1
View File
@@ -1 +1 @@
FireflyPsAndorid FireflyGoAndroid
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" /> <bytecodeTargetLevel target="17" />
</component> </component>
</project> </project>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>
+1 -1
View File
@@ -6,7 +6,6 @@
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
@@ -15,5 +14,6 @@
</option> </option>
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
<option name="parallelModelFetch" value="true" />
</component> </component>
</project> </project>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>
+1 -1
View File
@@ -1,6 +1,6 @@
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
BIN
View File
Binary file not shown.
+76 -28
View File
@@ -1,60 +1,108 @@
@file:Suppress("UnstableApiUsage")
plugins { plugins {
alias(libs.plugins.android.application) alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
} }
kotlin {
jvmToolchain(17)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
android { android {
namespace = "com.example.fireflypsandorid" namespace = "com.example.firefly_go_android"
compileSdk = 35 compileSdk = 36
defaultConfig { defaultConfig {
applicationId = "com.example.fireflypsandorid" applicationId = "com.kain344.firefly_go_android"
minSdk = 24 minSdk = 24
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 2
versionName = "1.0" versionName = "1.0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
release { release {
isMinifyEnabled = false isMinifyEnabled = true
isShrinkResources = true
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
ndk {
abiFilters.addAll(listOf("arm64-v8a"))
}
} }
} }
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures { buildFeatures {
compose = true compose = true
viewBinding = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
buildToolsVersion = "36.0.0"
ndkVersion = "27.2.12479018"
dependenciesInfo {
includeInApk = false
includeInBundle = false
} }
} }
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx.v293)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose.v1101)
implementation(platform(libs.androidx.compose.bom)) // Compose UI
implementation(libs.androidx.ui) implementation(libs.ui)
implementation(libs.androidx.ui.graphics) implementation(libs.ui.graphics)
implementation(libs.androidx.ui.tooling.preview) implementation(libs.ui.tooling.preview)
implementation(libs.androidx.material3)
// Foundation & Animation
implementation(libs.androidx.foundation)
implementation(libs.androidx.animation)
implementation(libs.androidx.animation.core)
// Material & Material3
implementation(libs.androidx.material)
implementation(libs.androidx.material.icons.extended)
implementation(libs.material3)
implementation(libs.androidx.material3.window.size.class1)
// Auto updater library
implementation(libs.autoupdater)
// OkHttp Client
implementation("com.squareup.okhttp3:okhttp:4.12.0")
// Chrome Custom Tabs
implementation("androidx.browser:browser:1.8.0")
// Unit Test
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) // Android Instrumentation Test
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.junit.v130)
androidTestImplementation(libs.androidx.ui.test.junit4) androidTestImplementation(libs.androidx.espresso.core.v370)
debugImplementation(libs.androidx.ui.tooling) androidTestImplementation(libs.ui.test.junit4)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(files("../library/firefly-go.aar")) // Debug
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
// Local AAR library
implementation(files("libs/firefly-go.aar"))
implementation(libs.slf4j.android)
} }
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
package com.example.fireflypsandorid package com.example.firefly_go_android
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
+40 -10
View File
@@ -2,14 +2,22 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"
<uses-permission android:name="android.permission.INTERNET" /> tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -20,23 +28,45 @@
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:theme="@style/Theme.FireflyPsAndorid" android:theme="@style/Theme.FireflyGoAndroid"
tools:targetApi="31"> tools:targetApi="31">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:launchMode="singleTask"
android:theme="@style/Theme.FireflyPsAndorid"> android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.FireflyGoAndroid">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="firefly-launcher" android:host="auth" android:path="/discord" />
</intent-filter>
</activity> </activity>
<service <service
android:name=".GolangServerService" android:name=".GolangServerService"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="specialUse"
android:exported="false">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Running local Golang TCP/UDP Server" />
</service>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>
+588
View File
@@ -0,0 +1,588 @@
{
"leader": 2,
"lineups": {
"0": 1409,
"1": 1407,
"2": 1406,
"3": 1403
},
"position": {
"x": -4030,
"z": -13006,
"y": 0,
"rot_y": 270000
},
"scene": {
"plane_id": 10000,
"floor_id": 10000000,
"entry_id": 100000104
},
"player_outfit": [
1003
],
"char_path": {
"main": 8008,
"march_7": 1224
},
"char_enhanced": {
"1004": 1,
"1005": 0,
"1006": 1,
"1102": 1,
"1205": 1,
"1212": 1,
"1217": 1,
"1306": 1,
"1307": 1,
"1310": 1
},
"challenge": {
"challenge_id": 0,
"skip_half": 0,
"random_seed": 0,
"blessings": [],
"is_in_challenge": false,
"current_stage_id": 0,
"path_resonance_id": 0,
"maze_buff": 0,
"first_lineup": [],
"second_lineup": []
},
"challenge_tierce": {
"challenge_id": 0,
"current_stage_id": 0,
"maze_buff": 0,
"path_resonance_id": 0,
"is_in_challenge": false,
"is_single_stage": false,
"challenge_tierce_data": {
"20245": {
"stage_1": {
"blessing": 3031361,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 40000,
"lineup": [
1407,
1413,
1415,
1409
]
},
"stage_2": {
"blessing": 3031361,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 40000,
"lineup": [
1310,
1225,
1321,
1222
]
},
"stage_3": {
"blessing": 3031362,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 40000,
"lineup": [
1506,
1502,
1501,
1217
]
}
},
"30185": {
"stage_1": {
"blessing": 3111090,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 3932,
"lineup": [
1501,
1502,
1414,
1506
]
},
"stage_2": {
"blessing": 3111085,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 3913,
"lineup": [
1310,
1225,
1303,
1321
]
},
"stage_3": {
"blessing": 3111071,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 4000,
"lineup": [
1415,
1413,
1407,
1409
]
}
},
"5213": {
"stage_1": {
"blessing": 0,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 0,
"lineup": [
1407,
1413,
1415,
1409
]
},
"stage_2": {
"blessing": 0,
"cycle_count": 1,
"battle_status": 1,
"DeadCount": 0,
"score": 0,
"lineup": [
1310,
1321,
1225,
1222
]
},
"stage_3": {
"blessing": 0,
"cycle_count": 0,
"battle_status": 1,
"DeadCount": 0,
"score": 0,
"lineup": [
1506,
1502,
1501,
1414
]
}
},
"5313": {
"stage_1": {
"blessing": 0,
"cycle_count": 0,
"battle_status": 3,
"DeadCount": 0,
"score": 0,
"lineup": [
1510,
8001,
1004,
1414
]
},
"stage_2": {
"blessing": 0,
"cycle_count": 0,
"battle_status": 0,
"DeadCount": 0,
"score": 0,
"lineup": [
1302
]
},
"stage_3": {
"blessing": 0,
"cycle_count": 0,
"battle_status": 0,
"DeadCount": 0,
"score": 0,
"lineup": [
1321
]
}
}
}
},
"challenge_peak": {
"current_mode": "Knight",
"group_id": 8,
"is_in_challenge_peak": false,
"challenge_peak_data": {
"1": {
"checkmate_data": {
"challenge_id": 104,
"blessing": 3033006,
"lineup": [
1413,
1409,
1407,
1403
],
"stage_id": 30501022,
"is_hard_mode": true
},
"knight_data": {
"current_challenge_id": 103,
"details_data": [
{
"lineup": [
1222,
1225,
1310,
1303
],
"stage_id": 30501011,
"challenge_id": 101
},
{
"lineup": [
1412,
1414,
1408,
1313
],
"stage_id": 30501012,
"challenge_id": 102
},
{
"lineup": [
1407,
1403,
1409,
1413
],
"stage_id": 30501013,
"challenge_id": 103
}
]
}
},
"2": {
"checkmate_data": {
"challenge_id": 204,
"blessing": 3033021,
"lineup": [
1415,
1413,
1409,
1407
],
"stage_id": 30502022,
"is_hard_mode": true
},
"knight_data": {
"current_challenge_id": 203,
"details_data": [
{
"lineup": [
1302,
1309,
1410
],
"stage_id": 30502011,
"challenge_id": 201
},
{
"lineup": [
1221,
1222
],
"stage_id": 30502012,
"challenge_id": 202
},
{
"lineup": [
1415,
8001,
1414,
1313
],
"stage_id": 30502013,
"challenge_id": 203
}
]
}
},
"3": {
"checkmate_data": {
"challenge_id": 304,
"blessing": 3033032,
"lineup": [
1222,
1225,
1310,
1321
],
"stage_id": 30503021,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 302,
"details_data": [
{
"lineup": [
1003
],
"stage_id": 30503011,
"challenge_id": 301
},
{
"lineup": [
1315
],
"stage_id": 30503012,
"challenge_id": 302
},
{
"lineup": [
8001
],
"stage_id": 30503013,
"challenge_id": 303
}
]
}
},
"4": {
"checkmate_data": {
"challenge_id": 404,
"blessing": 3033045,
"lineup": [
1310
],
"stage_id": 30504021,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 401,
"details_data": [
{
"lineup": [
1302
],
"stage_id": 30504011,
"challenge_id": 401
},
{
"lineup": [
1321
],
"stage_id": 30504012,
"challenge_id": 402
},
{
"lineup": [
1218
],
"stage_id": 30504013,
"challenge_id": 403
}
]
}
},
"5": {
"checkmate_data": {
"challenge_id": 504,
"blessing": 3033050,
"lineup": [
1415,
1413,
1407,
1409
],
"stage_id": 30505021,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 501,
"details_data": [
{
"lineup": [
1310
],
"stage_id": 30505011,
"challenge_id": 501
},
{
"lineup": [
1407
],
"stage_id": 30505012,
"challenge_id": 502
},
{
"lineup": [
1502
],
"stage_id": 30505013,
"challenge_id": 503
}
]
}
},
"6": {
"checkmate_data": {
"challenge_id": 604,
"blessing": 3033053,
"lineup": [
1505,
1217,
1502,
8001
],
"stage_id": 30506021,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 602,
"details_data": [
{
"lineup": [
1309
],
"stage_id": 30506011,
"challenge_id": 601
},
{
"lineup": [
1321,
1310,
1225,
1303
],
"stage_id": 30506012,
"challenge_id": 602
},
{
"lineup": [
1415,
1413,
1409,
1407
],
"stage_id": 30506013,
"challenge_id": 603
}
]
}
},
"7": {
"checkmate_data": {
"challenge_id": 704,
"blessing": 3033061,
"lineup": [
1505,
1502,
1414,
1410
],
"stage_id": 30507021,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 703,
"details_data": [
{
"lineup": [
1414
],
"stage_id": 30507011,
"challenge_id": 701
},
{
"lineup": [
1501
],
"stage_id": 30507012,
"challenge_id": 702
},
{
"lineup": [
1507,
1222
],
"stage_id": 30507013,
"challenge_id": 703
}
]
}
},
"8": {
"checkmate_data": {
"challenge_id": 0,
"blessing": 0,
"lineup": null,
"stage_id": 0,
"is_hard_mode": false
},
"knight_data": {
"current_challenge_id": 0,
"details_data": null
}
}
}
},
"theory_craft": {
"hp": {
"1": [
200000,
1000000,
200000
],
"2": [
2000000000,
2000000000,
2000000000
],
"3": []
},
"cycle_count": 1,
"stage_id": 30118121,
"mode": false,
"custom_lineup": []
},
"profile_data": {
"cur_chat_bubble_id": 220009,
"cur_phone_theme_id": 221012,
"cur_phone_case_id": 254001,
"cur_pam_skin_id": 252000,
"cur_pet_id": 0,
"cur_avatar_player_icon": 202034,
"cur_player_personal_card": 253001,
"cur_signature": "Firefly GO By Kain",
"cur_nickname": "Firefly GO",
"cur_display_avatar": [
1310,
1309,
1407,
1413,
1412
],
"cur_is_display_avatar": true
},
"skin_data": {
"1001": 1100101,
"1310": 1131001,
"1407": 1140701,
"1415": 1141501
},
"extra_setting": {
"censorship": false,
"cm": false,
"first_person": false,
"hide_ui": false
}
}
File diff suppressed because it is too large Load Diff
+212
View File
@@ -0,0 +1,212 @@
{
"CNBETAAndroid4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAAndroid4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAAndroid4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"CNBETAAndroid4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAAndroid4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAWin4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAWin4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAWin4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"CNBETAWin4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAWin4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAiOS4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAiOS4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"CNBETAiOS4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"CNBETAiOS4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAiOS4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAAndroid4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAAndroid4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAAndroid4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"OSBETAAndroid4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAAndroid4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAWin4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAWin4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAWin4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"OSBETAWin4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAWin4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAiOS4.3.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15261247_f6e6db2125cf_369da465b36faf",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15235885_6091fd15561a_83828f542dc1f3",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15265964_c2fbb1eb05fc_f2238199ee2b6e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15242148_d40f856defc0_599b68a0adf7bd",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAiOS4.3.52": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15305566_fea016d35145_54bbf8ab4009f5",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15318724_a7af31327e74_b3328eb95329b2",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15305751_09783637ccc4_d16f9c81138ab3",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15265964_799df4f0ecef_5a94550ba64cff"
},
"OSBETAiOS4.3.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15378434_99bffafdeff7_5d97713dcef07f",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15385668_07011a79dadf_1111728953724e",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15378559_7514340ac82f_70dad6c56b8bc7",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_15384138_4e4be8cf97ac_1e3fcfa0a93bef"
},
"OSBETAiOS4.3.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15440751_fb99372e1e48_fd11e486274779",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15497648_6ef97acf9b45_ea8ed0ff303308",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15440843_4201511c48d0_34717e44c82e30",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"OSBETAiOS4.3.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"asset_bundle_url_b": "https://autopatchos.starrails.com/asb/BetaLive/output_15507822_f63959b7920b_2231084d979019",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15537653_09630bf7c4f3_a1967fb1d8635c",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15507972_05909e009ba9_54e5377976705e",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
}
}
@@ -0,0 +1,174 @@
package com.example.firefly_go_android
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.content.pm.ServiceInfo
import android.graphics.BitmapFactory
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import libandroid.Libandroid
import androidx.core.content.edit
class GolangServerService : Service() {
companion object {
const val CHANNEL_ID = "GolangServerChannel"
const val NOTIFICATION_ID = 1
private const val TAG = "GolangServerService"
var isRunning by mutableStateOf(false)
}
private var wakeLock: PowerManager.WakeLock? = null
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
@SuppressLint("WakelockTimeout")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (isRunning) {
Log.d(TAG, "Server is already running")
return START_STICKY
}
isRunning = true
Log.d(TAG, "onStartCommand called")
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val largeIcon = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.mipmap.ic_launcher_round)
.setLargeIcon(largeIcon)
.setContentTitle("FireflyGO Server")
.setContentText("Server is running...")
.setColor(ContextCompat.getColor(this, R.color.teal_700))
.setOngoing(true)
.setOnlyAlertOnce(true)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setShowWhen(false)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
if (Build.VERSION.SDK_INT >= 34) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
} else if (Build.VERSION.SDK_INT >= 29) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
)
} else {
startForeground(NOTIFICATION_ID, notification)
}
// Khởi tạo WakeLock và WifiLock
try {
val powerManager = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "GolangServer::WakeLock")
wakeLock?.acquire()
Log.d(TAG, "WakeLock acquired")
} catch (e: Exception) {
Log.e(TAG, "Lock failed", e)
}
Thread {
try {
val sharedPrefs = getSharedPreferences("AppPrefs", MODE_PRIVATE)
var appDataPath = intent?.getStringExtra("appDataPath")
if (appDataPath != null) {
sharedPrefs.edit { putString("saved_app_data_path", appDataPath) }
} else {
appDataPath = sharedPrefs.getString("saved_app_data_path", null)
}
if (appDataPath != null) {
Libandroid.setPathDataLocal(appDataPath)
Log.d(TAG, "Set path data: $appDataPath")
} else {
isRunning = false
Log.e(TAG, "appDataPath not received and not found in SharedPreferences")
stopSelf()
return@Thread
}
Libandroid.setServerRunning(true)
isRunning = true
Log.d(TAG, "Server started")
} catch (e: Exception) {
isRunning = false
Log.e(TAG, "Error starting server", e)
stopSelf()
}
}.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy called")
try {
val result = Libandroid.setServerRunning(false)
isRunning = false
Log.d(TAG, "Server shutdown result: $result")
} catch (e: Exception) {
Log.e(TAG, "Error shutting down server", e)
}
// Nhả các khóa tài nguyên
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
Log.d(TAG, "WakeLock released")
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to release Locks", e)
}
isRunning = false
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Golang Server Channel",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Channel for running Golang backend in foreground"
}
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
Log.d(TAG, "Notification channel created")
}
}
}
@@ -0,0 +1,356 @@
package com.example.firefly_go_android
import AutoUpdaterManager
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.autoupdater.UpdateFeatures
import com.example.firefly_go_android.ui.theme.FireflyPsAndoridTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.*
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.border
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.CloudDownload
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.PlayCircleFilled
import androidx.compose.material.icons.filled.RestartAlt
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material.icons.filled.StopCircle
import androidx.compose.material.icons.rounded.AutoAwesome
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Download
import androidx.compose.material.icons.rounded.InstallMobile
import androidx.compose.material.icons.rounded.SystemUpdate
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import kotlinx.coroutines.delay
import org.json.JSONObject
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import android.os.PowerManager
import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import android.provider.Settings
import com.example.firefly_go_android.network.AuthManager
import com.example.firefly_go_android.ui.ScamWarningDialog
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
data class AppVersion(
val latestVersion: String,
val changelog: String,
val apkUrl: String
)
class MainActivity : ComponentActivity() {
private lateinit var authManager: AuthManager
private var onAuthStatusChanged: (() -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestBatteryExemption(this)
requestInstallPermission(this)
requestStoragePermission(this)
authManager = AuthManager(this)
val appDataPath = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "FireflyGo").absolutePath
val dataDir = File("$appDataPath/data")
if (!dataDir.exists()) dataDir.mkdirs()
val sharedPrefs = getSharedPreferences("AppPrefs", MODE_PRIVATE)
val packageInfo = if (Build.VERSION.SDK_INT >= 33) {
packageManager.getPackageInfo(packageName, android.content.pm.PackageManager.PackageInfoFlags.of(0))
} else {
@Suppress("DEPRECATION")
packageManager.getPackageInfo(packageName, 0)
}
val currentVersionCode = if (Build.VERSION.SDK_INT >= 33) packageInfo.longVersionCode else packageInfo.versionCode.toLong()
val currentLastUpdateTime = packageInfo.lastUpdateTime
val savedVersionCode = sharedPrefs.getLong("last_version_code", 0L)
val savedLastUpdateTime = sharedPrefs.getLong("last_update_time", 0L)
val isFolderEmpty = dataDir.listFiles()?.isEmpty() ?: true
val shouldOverride = currentVersionCode > savedVersionCode ||
currentLastUpdateTime > savedLastUpdateTime ||
isFolderEmpty
Log.i("AppUpdate", "Code: $currentVersionCode, LastUpdate: $currentLastUpdateTime")
Log.i("AppUpdate", "SavedCode: $savedVersionCode, SavedUpdate: $savedLastUpdateTime")
Log.i("AppUpdate", "Should Override: $shouldOverride")
if (copyRawToFile(this, dataDir, shouldOverride)) {
if (shouldOverride) {
sharedPrefs.edit()
.putLong("last_version_code", currentVersionCode)
.putLong("last_update_time", currentLastUpdateTime)
.apply()
Log.i("AppUpdate", "Updated SharedPreferences with new version and time")
}
}
val jsonString = try {
resources.openRawResource(R.raw.app_version_json).use { input ->
input.bufferedReader().use { it.readText() }
}
} catch (e: Exception) {
"{}"
}
val jsonObject = if (jsonString.isNotEmpty()) JSONObject(jsonString) else JSONObject()
val latestVersion = jsonObject.optString("latest_version", "1.0.0")
val changelog = jsonObject.optString("changelog", "")
val apkUrl = jsonObject.optString("apk_url", "")
val appVersion = AppVersion(latestVersion, changelog, apkUrl)
handleDeepLink(intent)
enableEdgeToEdge()
setContent {
FireflyPsAndoridTheme {
var isCheckingAuth by remember { mutableStateOf(authManager.isLoggedIn) }
var authStateTrigger by remember { mutableIntStateOf(0) }
var hasCheckedUpdate by remember { mutableStateOf(false) }
var showScamWarning by remember { mutableStateOf(true) }
onAuthStatusChanged = {
authStateTrigger++
}
LaunchedEffect(Unit) {
if (authManager.isLoggedIn) {
authManager.accessToken?.let { token ->
val result = authManager.fetchProfileAndVerifyRole(token)
if (result.isFailure) {
onAuthStatusChanged?.invoke()
}
}
}
isCheckingAuth = false
}
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Box(modifier = Modifier.fillMaxSize()) {
key(authStateTrigger) {
ServerControlScreen(appDataPath, dataDir, appVersion, authManager, Modifier.padding(innerPadding))
if (!showScamWarning && !hasCheckedUpdate) {
AutoUpdateDialog(
onDismiss = { hasCheckedUpdate = true },
appVersion,
dataDir,
true
)
}
}
if (showScamWarning) {
ScamWarningDialog(onDismiss = { showScamWarning = false })
}
}
}
}
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
}
private fun handleDeepLink(intent: Intent?) {
val data = intent?.data ?: return
if (data.scheme == "firefly-launcher" && data.host == "auth" && data.path == "/discord") {
val token = data.getQueryParameter("token")
if (!token.isNullOrBlank()) {
authManager.accessToken = token
lifecycleScope.launch {
val result = authManager.fetchProfileAndVerifyRole(token)
if (result.isSuccess) {
Toast.makeText(this@MainActivity, "Logged in via Discord successfully!", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(this@MainActivity, "Failed to get Discord profile: ${result.exceptionOrNull()?.message}", Toast.LENGTH_SHORT).show()
}
onAuthStatusChanged?.invoke()
}
}
}
}
}
@SuppressLint("BatteryLife")
fun requestBatteryExemption(context: Context) {
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
if (!powerManager.isIgnoringBatteryOptimizations(context.packageName)) {
val intent = Intent().apply {
action = ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
data = "package:${context.packageName}".toUri()
}
context.startActivity(intent)
}
}
fun requestInstallPermission(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!context.packageManager.canRequestPackageInstalls()) {
val intent = Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES).apply {
data = "package:${context.packageName}".toUri()
}
context.startActivity(intent)
Toast.makeText(context, "Please allow installing unknown apps to update", Toast.LENGTH_LONG).show()
}
}
}
fun requestStoragePermission(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
try {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
data = "package:${context.packageName}".toUri()
}
context.startActivity(intent)
Toast.makeText(context, "Please allow All Files Access to load game data", Toast.LENGTH_LONG).show()
} catch (e: Exception) {
try {
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
context.startActivity(intent)
} catch (ex: Exception) {
Log.e("StoragePermission", "Failed to open settings", ex)
}
}
}
}
}
fun copyRawToFile(context: Context, targetDir: File, override: Boolean = false): Boolean {
val files = listOf(
"data-in-game.json" to "data-in-game.json",
"freesr-data.json" to "freesr-data.json",
"version.json" to "version.json"
)
return try {
if (!targetDir.exists()) targetDir.mkdirs()
for ((assetFile, outName) in files) {
val outFile = File(targetDir, outName)
if (outFile.exists() && !override) {
Log.i("CopyRaw", "Skipping $outName (already exists and no override)")
continue
}
Log.i("CopyRaw", "Copying $assetFile to ${outFile.absolutePath} (Override: $override)")
context.assets.open(assetFile).use { input ->
FileOutputStream(outFile).use { output ->
input.copyTo(output)
output.fd.sync()
}
}
}
true
} catch (e: Exception) {
Log.e("CopyRaw", "Error copying asset file: ${e.message}", e)
false
}
}
fun removeFile(targetDir: File, fileName: String): Boolean {
val file = File(targetDir, fileName)
return if (file.exists()) {
try {
if (file.delete()) {
Log.i("FileRemove", "Removed $fileName from ${file.absolutePath}")
true
} else {
Log.e("FileRemove", "Failed to remove $fileName from ${file.absolutePath}")
false
}
} catch (e: Exception) {
Log.e("FileRemove", "Error removing $fileName: ${e.message}")
false
}
} else {
Log.i("FileRemove", "$fileName does not exist in ${targetDir.absolutePath}")
false
}
}
@Composable
fun Modifier.bounceClick(
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
onClick: () -> Unit
): Modifier {
val isPressed by interactionSource.collectIsPressedAsState()
val scale by animateFloatAsState(
targetValue = if (isPressed) 0.93f else 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
),
label = "bounceScale"
)
return this
.scale(scale)
.clickable(
interactionSource = interactionSource,
indication = null,
onClick = onClick
)
}
@@ -0,0 +1,128 @@
package com.example.firefly_go_android.network
import android.content.Context
import android.util.Log
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class AuthManager(private val context: Context) {
private val sharedPrefs = context.getSharedPreferences("auth_prefs", Context.MODE_PRIVATE)
private val client = OkHttpClient()
companion object {
private const val BASE_URL = "https://api.punklorde.org"
private const val TOKEN_KEY = "access_token"
private const val EMAIL_KEY = "user_email"
private const val IS_PLAYER_KEY = "is_player"
}
var accessToken: String?
get() = sharedPrefs.getString(TOKEN_KEY, null)
set(value) = sharedPrefs.edit().putString(TOKEN_KEY, value).apply()
var userEmail: String?
get() = sharedPrefs.getString(EMAIL_KEY, null)
set(value) = sharedPrefs.edit().putString(EMAIL_KEY, value).apply()
var isPlayer: Boolean
get() = sharedPrefs.getBoolean(IS_PLAYER_KEY, false)
set(value) = sharedPrefs.edit().putBoolean(IS_PLAYER_KEY, value).apply()
val isLoggedIn: Boolean
get() = accessToken != null
suspend fun login(email: String, password: String): Result<Boolean> = withContext(Dispatchers.IO) {
val json = JSONObject().apply {
put("email", email)
put("password", password)
}
val body = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
val request = Request.Builder()
.url("$BASE_URL/auth/signin")
.post(body)
.build()
try {
client.newCall(request).execute().use { response ->
val bodyStr = response.body?.string() ?: ""
if (!response.isSuccessful) {
val errMsg = try {
JSONObject(bodyStr).getString("message")
} catch (e: Exception) {
"Login failed: ${response.code}"
}
return@withContext Result.failure(Exception(errMsg))
}
val jsonResponse = JSONObject(bodyStr)
if (!jsonResponse.getBoolean("status")) {
return@withContext Result.failure(Exception(jsonResponse.optString("message", "Login failed")))
}
val data = jsonResponse.getJSONObject("data")
val token = data.getString("access_token")
accessToken = token
userEmail = email
// Fetch user profile to verify role
return@withContext fetchProfileAndVerifyRole(token)
}
} catch (e: Exception) {
return@withContext Result.failure(e)
}
}
suspend fun fetchProfileAndVerifyRole(token: String): Result<Boolean> = withContext(Dispatchers.IO) {
val request = Request.Builder()
.url("$BASE_URL/users/current")
.header("Authorization", "Bearer $token")
.get()
.build()
try {
client.newCall(request).execute().use { response ->
val bodyStr = response.body?.string() ?: ""
if (!response.isSuccessful) {
// Token might have expired, clear local credentials
if (response.code == 401) {
logout()
}
return@withContext Result.failure(Exception("Failed to fetch profile: ${response.code}"))
}
val jsonResponse = JSONObject(bodyStr)
if (!jsonResponse.getBoolean("status")) {
return@withContext Result.failure(Exception(jsonResponse.optString("message", "Failed to fetch profile")))
}
val data = jsonResponse.getJSONObject("data")
val rolesArray = data.getJSONArray("roles")
var hasPlayerRole = false
for (i in 0 until rolesArray.length()) {
val roleObj = rolesArray.getJSONObject(i)
val roleName = roleObj.getString("name").uppercase()
if (roleName == "PLAYER" || roleName == "ADMIN" || roleName == "MOD") {
hasPlayerRole = true
break
}
}
isPlayer = hasPlayerRole
return@withContext Result.success(hasPlayerRole)
}
} catch (e: Exception) {
return@withContext Result.failure(e)
}
}
fun logout() {
accessToken = null
userEmail = null
isPlayer = false
}
}
@@ -0,0 +1,376 @@
package com.example.firefly_go_android
import AutoUpdaterManager
import android.content.Context
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.res.stringResource
import com.example.firefly_go_android.R
import com.example.autoupdater.UpdateFeatures
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
@Composable
fun AutoUpdateDialog(
onDismiss: () -> Unit,
appVersion: AppVersion,
dataDir: File,
isFirstOpen: Boolean = false
) {
val context = LocalContext.current
val autoUpdaterManager = AutoUpdaterManager(context)
var update by remember { mutableStateOf<UpdateFeatures?>(null) }
var progress by remember { mutableStateOf(0) }
var showDialog by remember { mutableStateOf(false) }
var isDownloading by remember { mutableStateOf(false) }
var downloadComplete by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val progressAnimation by animateFloatAsState(
targetValue = progress / 100f,
animationSpec = tween(300, easing = FastOutSlowInEasing),
label = "progress"
)
val scaleAnimation by animateFloatAsState(
targetValue = if (showDialog) 1f else 0.8f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "scale"
)
// Check for update
LaunchedEffect(Unit) {
val result = withContext(Dispatchers.IO) {
autoUpdaterManager.checkForUpdate(
JSONfileURL = "https://git.kain.io.vn/Firefly-Shelter/FireflyGo_Andoid/raw/branch/master/app/src/main/res/raw/app_version_json.json"
)
}
val hasUpdate = result != null && appVersion.latestVersion != result.latestversion
update = if (hasUpdate) result else null
showDialog = if (isFirstOpen) {
hasUpdate
} else {
result != null
}
}
// Download progress
LaunchedEffect(progress) {
if (progress >= 100 && isDownloading) {
downloadComplete = true
}
}
if (showDialog) {
Dialog(
onDismissRequest = {
if (!isDownloading) showDialog = false
onDismiss()
},
properties = DialogProperties(
dismissOnBackPress = !isDownloading,
dismissOnClickOutside = !isDownloading,
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.9f)
.scale(scaleAnimation)
.animateContentSize()
.background(Color.Black.copy(alpha = 0.85f), RoundedCornerShape(24.dp))
.border(1.5.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(24.dp))
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(72.dp)
.background(
Color.White.copy(alpha = 0.1f),
CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (update != null) Icons.Rounded.SystemUpdate
else Icons.Rounded.CheckCircle,
contentDescription = null,
modifier = Modifier.size(36.dp),
tint = if (update != null) Color(0xFF2196F3) else Color(0xFF4CAF50)
)
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = if (update != null) stringResource(id = R.string.update_available) else stringResource(id = R.string.no_update_available),
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
color = Color.White,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(12.dp))
if (update != null) {
VersionInfoSection(update!!)
ChangelogSection(update!!)
DownloadProgressSection(
isDownloading = isDownloading,
downloadComplete = downloadComplete,
progress = progressAnimation
)
ActionButtons(
isDownloading = isDownloading,
downloadComplete = downloadComplete,
onDownloadClick = {
isDownloading = true
coroutineScope.launch {
withContext(Dispatchers.IO) {
autoUpdaterManager.downloadapk(
context,
update!!.apk_url,
"FireflyGO_${update!!.latestversion}.apk"
) { prog -> progress = prog }
}
}
},
onDismiss = { showDialog = false; onDismiss() }
)
} else {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.app_up_to_date),
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(alpha = 0.7f),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { showDialog = false; onDismiss() },
modifier = Modifier.wrapContentWidth(),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color(0xCC0D47A1),
contentColor = Color.White
),
contentPadding = PaddingValues(horizontal = 32.dp, vertical = 12.dp)
) {
Text(
text = stringResource(id = R.string.ok),
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
}
}
}
}
@Composable
fun VersionInfoSection(update: UpdateFeatures) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = Color.White.copy(alpha = 0.08f)
),
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.latest_version),
style = MaterialTheme.typography.labelMedium,
color = Color.White.copy(alpha = 0.7f)
)
Surface(
shape = RoundedCornerShape(8.dp),
color = Color(0xCC0D47A1)
) {
Text(
text = "v${update.latestversion}",
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelSmall,
color = Color.White,
fontWeight = FontWeight.Medium
)
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
}
@Composable
fun ChangelogSection(update: UpdateFeatures) {
Column(modifier = Modifier.fillMaxWidth()) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Rounded.AutoAwesome,
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = Color(0xFF4CAF50)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(id = R.string.whats_new),
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Medium,
color = Color.White
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = update.changelog,
style = MaterialTheme.typography.bodyMedium,
color = Color.White.copy(alpha = 0.8f),
lineHeight = 20.sp
)
}
}
@Composable
fun DownloadProgressSection(
isDownloading: Boolean,
downloadComplete: Boolean,
progress: Float
) {
if (!isDownloading && !downloadComplete) return
Spacer(modifier = Modifier.height(16.dp))
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = if (downloadComplete) stringResource(id = R.string.installation_ready) else stringResource(id = R.string.downloading),
style = MaterialTheme.typography.labelMedium,
color = Color.White,
fontWeight = FontWeight.Medium
)
Text(
text = "${(progress * 100).toInt()}%",
style = MaterialTheme.typography.labelMedium,
color = Color.White.copy(alpha = 0.8f)
)
}
Spacer(modifier = Modifier.height(8.dp))
LinearProgressIndicator(
progress = { progress },
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.clip(RoundedCornerShape(4.dp)),
color = if (downloadComplete) Color(0xFF4CAF50) else Color(0xFF2196F3),
trackColor = Color.White.copy(alpha = 0.15f),
)
}
}
@Composable
fun ActionButtons(
isDownloading: Boolean,
downloadComplete: Boolean,
onDownloadClick: () -> Unit,
onDismiss: () -> Unit
) {
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = if (!isDownloading && !downloadComplete) Arrangement.spacedBy(12.dp) else Arrangement.Center
) {
if (!downloadComplete) {
OutlinedButton(
onClick = onDismiss,
modifier = Modifier.weight(1f),
enabled = !isDownloading,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White),
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.3f))
) {
Text(text = stringResource(id = R.string.later), style = MaterialTheme.typography.labelLarge)
}
}
Button(
onClick = onDownloadClick,
modifier = if (downloadComplete) Modifier.widthIn(min = 160.dp) else Modifier.weight(1f),
enabled = !isDownloading || downloadComplete,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(
containerColor = if (downloadComplete) Color(0xFF4CAF50) else Color(0xCC0D47A1)
)
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
when {
downloadComplete -> {
Icon(Icons.Rounded.InstallMobile, contentDescription = null, modifier = Modifier.size(18.dp), tint = Color.White)
Spacer(modifier = Modifier.width(8.dp))
Text(stringResource(id = R.string.install_now), style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Medium, color = Color.White)
}
isDownloading -> {
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = Color.White)
}
else -> {
Icon(
imageVector = Icons.Rounded.Download,
contentDescription = "Download",
modifier = Modifier.size(24.dp),
tint = Color.White
)
}
}
}
}
}
}
@@ -0,0 +1,254 @@
package com.example.firefly_go_android
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.res.stringResource
import com.example.firefly_go_android.R
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.foundation.clickable
import androidx.compose.foundation.shape.CircleShape
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import java.io.BufferedReader
import java.io.InputStreamReader
data class LogEntry(val id: Long, val text: String)
@Composable
fun LogPopup(
onDismiss: () -> Unit
) {
val logs = remember { mutableStateListOf<LogEntry>() }
var nextLogId by remember { mutableLongStateOf(0L) }
var autoScroll by remember { mutableStateOf(true) }
val listState = rememberLazyListState()
LaunchedEffect(Unit) {
withContext(Dispatchers.IO) {
var process: Process? = null
var reader: BufferedReader? = null
try {
process = Runtime.getRuntime().exec("logcat -T 300 -s GoLog")
reader = BufferedReader(InputStreamReader(process.inputStream))
// Safely destroy the logcat process and close the stream when the coroutine is cancelled
coroutineContext[Job]?.invokeOnCompletion {
try {
process?.destroy()
reader?.close()
} catch (e: Exception) {
// ignore
}
}
var line: String?
while (reader.readLine().also { line = it } != null) {
val clean = parseGoLogLine(line!!)
if (!clean.isNullOrBlank()) {
withContext(Dispatchers.Main) {
logs.add(LogEntry(nextLogId++, clean))
if (logs.size > 500) {
logs.removeAt(0)
}
}
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
logs.add(LogEntry(nextLogId++, "Error reading logcat: ${e.message}"))
}
} finally {
try {
process?.destroy()
reader?.close()
} catch (e: Exception) {
// ignore
}
Log.i("LogPopup", "Logcat process destroyed and stream closed successfully")
}
}
}
LaunchedEffect(logs.size) {
if (autoScroll && logs.isNotEmpty()) {
listState.scrollToItem(logs.size - 1)
}
}
val defaultTextColor = Color.White.copy(alpha = 0.9f)
Dialog(
onDismissRequest = { onDismiss() },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.9f)
.fillMaxHeight(0.7f)
.background(Color.Black.copy(alpha = 0.8f), RoundedCornerShape(16.dp))
.border(1.5.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(16.dp))
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(id = R.string.golog_output),
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold,
color = Color.White
)
// Auto-Scroll Toggle Button
val activeColor = Color(0xFF4CAF50)
val inactiveColor = Color.White.copy(alpha = 0.4f)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable { autoScroll = !autoScroll }
.padding(4.dp)
) {
Box(
modifier = Modifier
.size(8.dp)
.background(if (autoScroll) activeColor else inactiveColor, CircleShape)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = stringResource(id = R.string.auto_scroll),
fontSize = 11.sp,
color = if (autoScroll) Color.White else Color.White.copy(alpha = 0.5f),
fontWeight = FontWeight.Medium
)
}
}
Spacer(modifier = Modifier.height(8.dp))
LazyColumn(state = listState, modifier = Modifier.weight(1f)) {
items(
items = logs,
key = { log -> log.id }
) { log ->
Text(
text = parseAnsi(log.text, defaultTextColor),
fontSize = 12.sp,
fontFamily = FontFamily.Monospace,
lineHeight = 14.sp,
modifier = Modifier.padding(vertical = 2.dp)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
TextButton(
onClick = {
logs.clear()
try {
Runtime.getRuntime().exec("logcat -c")
} catch (e: Exception) {
// ignore
}
}
) {
Text(stringResource(id = R.string.clear), color = Color(0xFFE53935), fontWeight = FontWeight.SemiBold)
}
TextButton(
onClick = { onDismiss() }
) {
Text(stringResource(id = R.string.close), color = Color.White)
}
}
}
}
}
}
fun parseGoLogLine(line: String): String? {
val regex = Regex(""".*GoLog\s*:?\s*(.*)""")
val match = regex.find(line)
val content = match?.groupValues?.getOrNull(1)?.trim()
return if (content.isNullOrBlank()) null else content
}
fun parseAnsi(text: String, defaultColor: Color): AnnotatedString {
val regex = Regex("\u001B\\[(\\d+)(;\\d+)*m")
val builder = buildAnnotatedString {
var lastIndex = 0
var currentColor = defaultColor
for (match in regex.findAll(text)) {
val start = match.range.first
val before = text.substring(lastIndex, start)
if (before.isNotEmpty()) {
withStyle(SpanStyle(color = currentColor)) {
append(before)
}
}
val code = try {
match.groupValues[1].toInt()
} catch (e: NumberFormatException) {
0
}
currentColor = when (code) {
0 -> defaultColor
30 -> Color.Black
31 -> Color.Red
32 -> Color(0xFF00C853) // Green
33 -> Color(0xFFFFD600) // Yellow
34 -> Color(0xFF2962FF) // Blue
35 -> Color(0xFFD500F9) // Magenta
36 -> Color(0xFF00B8D4) // Cyan
37 -> Color.White
else -> currentColor
}
lastIndex = match.range.last + 1
}
if (lastIndex < text.length) {
val remain = text.substring(lastIndex)
if (remain.isNotEmpty()) {
withStyle(SpanStyle(color = currentColor)) {
append(remain)
}
}
}
}
return builder
}
@@ -0,0 +1,328 @@
package com.example.firefly_go_android
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.foundation.BorderStroke
import androidx.compose.ui.res.stringResource
import com.example.firefly_go_android.network.AuthManager
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginDialog(
authManager: AuthManager,
onDismiss: () -> Unit,
onLoginSuccess: () -> Unit
) {
val coroutineScope = rememberCoroutineScope()
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var passwordVisible by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) }
var errorMessage by remember { mutableStateOf<String?>(null) }
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
kotlinx.coroutines.delay(250)
try {
focusRequester.requestFocus()
keyboardController?.show()
} catch (e: Exception) {
// ignore
}
}
Dialog(
onDismissRequest = { if (!isLoading) { keyboardController?.hide(); onDismiss() } },
properties = DialogProperties(
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Color.Black.copy(alpha = 0.85f), RoundedCornerShape(20.dp))
.border(1.5.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(20.dp))
.padding(24.dp)
) {
if (authManager.isLoggedIn) {
// Profile & Logout view
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.account_profile),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.logged_in_as),
fontSize = 12.sp,
color = Color.White.copy(alpha = 0.6f)
)
Text(
text = authManager.userEmail ?: "Unknown User",
fontSize = 16.sp,
fontWeight = FontWeight.Medium,
color = Color.White
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.auto_update_status),
fontSize = 12.sp,
color = Color.White.copy(alpha = 0.6f)
)
val statusText = if (authManager.isPlayer) stringResource(id = R.string.unlocked_player) else stringResource(id = R.string.locked_no_permission)
val statusColor = if (authManager.isPlayer) Color(0xFF4CAF50) else Color(0xFFE53935)
Text(
text = statusText,
fontSize = 15.sp,
fontWeight = FontWeight.SemiBold,
color = statusColor
)
Spacer(modifier = Modifier.height(28.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedButton(
onClick = { keyboardController?.hide(); onDismiss() },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White),
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.3f))
) {
Text(stringResource(id = R.string.back))
}
Button(
onClick = {
keyboardController?.hide()
authManager.logout()
onLoginSuccess() // triggers UI refresh
onDismiss()
},
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFD32F2F))
) {
Text(stringResource(id = R.string.logout), color = Color.White)
}
}
}
} else {
// Sign In view
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.sign_in),
fontSize = 22.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
val pleaseEnterBothText = stringResource(id = R.string.please_enter_both)
TextField(
value = email,
onValueChange = { email = it; errorMessage = null },
placeholder = { Text(stringResource(id = R.string.email), color = Color.White.copy(alpha = 0.4f)) },
colors = TextFieldDefaults.colors(
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
focusedContainerColor = Color.White.copy(alpha = 0.08f),
unfocusedContainerColor = Color.White.copy(alpha = 0.04f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
cursorColor = Color.White
),
shape = RoundedCornerShape(12.dp),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color.White.copy(alpha = 0.15f), RoundedCornerShape(12.dp))
.focusRequester(focusRequester),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email)
)
Spacer(modifier = Modifier.height(12.dp))
TextField(
value = password,
onValueChange = { password = it; errorMessage = null },
placeholder = { Text(stringResource(id = R.string.password), color = Color.White.copy(alpha = 0.4f)) },
visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(),
trailingIcon = {
val image = if (passwordVisible) Icons.Filled.Visibility else Icons.Filled.VisibilityOff
IconButton(onClick = { passwordVisible = !passwordVisible }) {
Icon(imageVector = image, contentDescription = "Toggle password visibility", tint = Color.White.copy(alpha = 0.5f))
}
},
colors = TextFieldDefaults.colors(
focusedTextColor = Color.White,
unfocusedTextColor = Color.White,
focusedContainerColor = Color.White.copy(alpha = 0.08f),
unfocusedContainerColor = Color.White.copy(alpha = 0.04f),
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
cursorColor = Color.White
),
shape = RoundedCornerShape(12.dp),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.border(1.dp, Color.White.copy(alpha = 0.15f), RoundedCornerShape(12.dp)),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password)
)
if (errorMessage != null) {
Spacer(modifier = Modifier.height(8.dp))
Text(
text = errorMessage!!,
color = Color(0xFFE53935),
fontSize = 13.sp,
fontWeight = FontWeight.Medium,
modifier = Modifier.align(Alignment.Start)
)
}
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedButton(
onClick = { keyboardController?.hide(); onDismiss() },
modifier = Modifier.weight(1f),
enabled = !isLoading,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White),
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.3f))
) {
Text(stringResource(id = R.string.cancel))
}
Button(
onClick = {
if (email.isBlank() || password.isBlank()) {
errorMessage = pleaseEnterBothText
return@Button
}
keyboardController?.hide()
isLoading = true
coroutineScope.launch {
val result = authManager.login(email, password)
isLoading = false
if (result.isSuccess) {
onLoginSuccess()
onDismiss()
} else {
errorMessage = result.exceptionOrNull()?.message ?: "Login failed"
}
}
},
modifier = Modifier.weight(1f),
enabled = !isLoading,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xCC0D47A1))
) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.size(20.dp), color = Color.White, strokeWidth = 2.dp)
} else {
Text(stringResource(id = R.string.login), color = Color.White)
}
}
}
Spacer(modifier = Modifier.height(16.dp))
// Divider "or"
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
HorizontalDivider(
modifier = Modifier.weight(1f),
color = Color.White.copy(alpha = 0.2f)
)
Text(
text = stringResource(id = R.string.or),
fontSize = 11.sp,
color = Color.White.copy(alpha = 0.5f),
modifier = Modifier.padding(horizontal = 8.dp)
)
HorizontalDivider(
modifier = Modifier.weight(1f),
color = Color.White.copy(alpha = 0.2f)
)
}
Spacer(modifier = Modifier.height(16.dp))
val context = LocalContext.current
Button(
onClick = {
keyboardController?.hide()
val url = "https://api.punklorde.org/auth/discord/login?redirect=https://area999.punklorde.org/auth/launcher/discord&origin=firefly-launcher&is_return_token=true"
try {
val customTabsIntent = CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
customTabsIntent.launchUrl(context, Uri.parse(url))
} catch (e: Exception) {
// Fallback to system browser
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
context.startActivity(intent)
}
},
modifier = Modifier.fillMaxWidth().height(48.dp),
enabled = !isLoading,
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xCC5865F2))
) {
Text(stringResource(id = R.string.login_with_discord), color = Color.White, fontWeight = FontWeight.Medium)
}
}
}
}
}
}
@@ -0,0 +1,87 @@
package com.example.firefly_go_android.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import com.example.firefly_go_android.R
@Composable
fun ScamWarningDialog(
onDismiss: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(
dismissOnBackPress = false,
dismissOnClickOutside = false,
usePlatformDefaultWidth = false
)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Color.Black.copy(alpha = 0.85f), RoundedCornerShape(20.dp))
.border(1.5.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(20.dp))
.padding(24.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Icon(
imageVector = Icons.Default.Warning,
contentDescription = "Warning",
tint = Color(0xFFFFB300),
modifier = Modifier.size(48.dp)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.scam_warning_title),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(id = R.string.scam_warning_text),
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.85f),
textAlign = TextAlign.Center,
lineHeight = 20.sp
)
Spacer(modifier = Modifier.height(28.dp))
Button(
onClick = onDismiss,
modifier = Modifier.fillMaxWidth().height(48.dp),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF0D47A1))
) {
Text(
text = stringResource(id = R.string.i_understand),
color = Color.White,
fontWeight = FontWeight.SemiBold
)
}
}
}
}
}
@@ -0,0 +1,429 @@
package com.example.firefly_go_android
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.widget.Toast
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import com.example.firefly_go_android.network.AuthManager
import java.io.File
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.foundation.BorderStroke
@SuppressLint("ImplicitSamInstance")
@Composable
fun ServerControlScreen(
appDataPath: String,
dataDir: File,
appVersion: AppVersion,
authManager: AuthManager,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val isRunning = GolangServerService.isRunning
var showResetDialog by remember { mutableStateOf(false) }
var showUpdateDialog by remember { mutableStateOf(false) }
var showLoginDialog by remember { mutableStateOf(false) }
var showLogs by remember { mutableStateOf(false) }
var refreshTrigger by remember { mutableIntStateOf(0) } // Used to trigger UI refresh on login/logout
Box(
modifier = modifier.fillMaxSize()
) {
// Background image
Image(
painter = painterResource(id = R.drawable.background),
contentDescription = "Background",
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.3f))
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp, vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Header Row (Branding and Account Status Chip)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
// Brand Title
Column {
Text(
text = stringResource(id = R.string.app_name),
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(2f, 2f),
blurRadius = 4f
)
)
)
Text(
text = stringResource(id = R.string.android_edition),
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = Color.White.copy(alpha = 0.7f),
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(1f, 1f),
blurRadius = 2f
)
)
)
}
// Brand Logo / Icon (instead of Login Chip since login is disabled)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(42.dp)
.background(Color.White.copy(alpha = 0.08f), CircleShape)
.border(1.dp, Color.White.copy(alpha = 0.15f), CircleShape)
) {
Icon(
imageVector = Icons.Default.Android,
contentDescription = "Logo",
tint = Color.White,
modifier = Modifier.size(28.dp)
)
}
}
Spacer(modifier = Modifier.height(28.dp))
// Server status capsule
val statusColor = if (isRunning) Color(0xFF4CAF50) else Color(0xFF9E9E9E)
val statusText = if (isRunning) stringResource(id = R.string.server_running) else stringResource(id = R.string.server_stopped)
Box(
modifier = Modifier
.background(Color.Black.copy(alpha = 0.4f), RoundedCornerShape(20.dp))
.border(1.dp, Color.White.copy(alpha = 0.15f), RoundedCornerShape(20.dp))
.padding(horizontal = 20.dp, vertical = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Box(
modifier = Modifier
.size(10.dp)
.background(statusColor, CircleShape)
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = statusText,
fontSize = 18.sp,
color = Color.White,
fontWeight = FontWeight.Medium
)
}
}
Spacer(modifier = Modifier.weight(1.8f).heightIn(min = 24.dp))
// Toggle button
val buttonColor by animateColorAsState(
targetValue = if (isRunning) Color(0xCCB71C1C) else Color(0xCC0D47A1),
label = "buttonColor"
)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxWidth(0.75f)
.height(54.dp)
.bounceClick {
try {
if (!isRunning) {
val intent = Intent(context, GolangServerService::class.java)
intent.putExtra("appDataPath", appDataPath)
ContextCompat.startForegroundService(context, intent)
} else {
context.stopService(Intent(context, GolangServerService::class.java))
}
} catch (e: Exception) {
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
.background(buttonColor, RoundedCornerShape(14.dp))
.border(1.dp, Color.White.copy(alpha = 0.25f), RoundedCornerShape(14.dp))
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Icon(
imageVector = if (isRunning) Icons.Default.Stop else Icons.Default.PlayArrow,
contentDescription = null,
tint = Color.White,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = if (isRunning) stringResource(id = R.string.stop_server) else stringResource(id = R.string.start_server),
fontSize = 18.sp,
color = Color.White,
fontWeight = FontWeight.SemiBold
)
}
}
Spacer(modifier = Modifier.height(20.dp))
// Widget icons row
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Check Update widget (Now shown for all users since login is disabled)
if (true) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.height(96.dp)
.bounceClick { showUpdateDialog = true }
.background(
Color.White.copy(alpha = 0.15f),
RoundedCornerShape(16.dp)
)
.border(
1.dp,
Color.White.copy(alpha = 0.2f),
RoundedCornerShape(16.dp)
)
.padding(horizontal = 8.dp)
) {
Icon(
imageVector = Icons.Default.CloudDownload,
contentDescription = "Check Update",
tint = Color.White.copy(alpha = 0.85f),
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = stringResource(id = R.string.update),
fontSize = 12.sp,
color = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium,
maxLines = 2
)
}
}
// Reset Data widget
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.height(96.dp)
.bounceClick { showResetDialog = true }
.background(
Color.White.copy(alpha = 0.15f),
RoundedCornerShape(16.dp)
)
.border(
1.dp,
Color.White.copy(alpha = 0.2f),
RoundedCornerShape(16.dp)
)
.padding(horizontal = 8.dp)
) {
Icon(
imageVector = Icons.Default.RestartAlt,
contentDescription = "Reset Data",
tint = Color.White.copy(alpha = 0.85f),
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = stringResource(id = R.string.reset),
fontSize = 12.sp,
color = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium,
maxLines = 2
)
}
// Logcat (Lynx) widget
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.height(96.dp)
.bounceClick { showLogs = true }
.background(
Color.White.copy(alpha = 0.15f),
RoundedCornerShape(16.dp)
)
.border(
1.dp,
Color.White.copy(alpha = 0.2f),
RoundedCornerShape(16.dp)
)
.padding(horizontal = 8.dp)
) {
Icon(
imageVector = Icons.Default.BugReport,
contentDescription = "Open Logcat",
tint = Color.White.copy(alpha = 0.85f),
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.height(6.dp))
Text(
text = stringResource(id = R.string.logs),
fontSize = 12.sp,
color = Color.White,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium,
maxLines = 2
)
}
}
Spacer(modifier = Modifier.weight(0.4f).heightIn(min = 16.dp))
}
}
if (showLogs) {
LogPopup(onDismiss = { showLogs = false })
}
if (showLoginDialog) {
LoginDialog(
authManager = authManager,
onDismiss = { showLoginDialog = false },
onLoginSuccess = {
refreshTrigger++ // Force UI update
}
)
}
// Reset Data Confirmation Dialog
if (showResetDialog) {
Dialog(
onDismissRequest = { showResetDialog = false },
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Box(
modifier = Modifier
.fillMaxWidth(0.9f)
.background(Color.Black.copy(alpha = 0.85f), RoundedCornerShape(20.dp))
.border(1.5.dp, Color.White.copy(alpha = 0.2f), RoundedCornerShape(20.dp))
.padding(24.dp)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = stringResource(id = R.string.reset_data_title),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.White
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = stringResource(id = R.string.reset_data_confirm),
fontSize = 14.sp,
color = Color.White.copy(alpha = 0.8f),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(28.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedButton(
onClick = { showResetDialog = false },
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.outlinedButtonColors(contentColor = Color.White),
border = BorderStroke(1.dp, Color.White.copy(alpha = 0.3f))
) {
Text(stringResource(id = R.string.no))
}
Button(
onClick = {
showResetDialog = false
try {
copyRawToFile(context, dataDir, true)
Toast.makeText(context, "Data has been reset successfully", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(context, "Reset failed: ${e.message}", Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.weight(1f),
shape = RoundedCornerShape(12.dp),
colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFD32F2F))
) {
Text(stringResource(id = R.string.yes), color = Color.White)
}
}
}
}
}
}
// Auto Update Dialog
if (showUpdateDialog) {
AutoUpdateDialog(
onDismiss = { showUpdateDialog = false },
appVersion,
dataDir
)
}
}
@@ -1,4 +1,4 @@
package com.example.fireflypsandorid.ui.theme package com.example.firefly_go_android.ui.theme
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -1,6 +1,5 @@
package com.example.fireflypsandorid.ui.theme package com.example.firefly_go_android.ui.theme
import android.app.Activity
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@@ -1,4 +1,4 @@
package com.example.fireflypsandorid.ui.theme package com.example.firefly_go_android.ui.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -1,93 +0,0 @@
package com.example.fireflypsandorid
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import libandroid.Libandroid
class GolangServerService : Service() {
companion object {
const val CHANNEL_ID = "GolangServerChannel"
const val NOTIFICATION_ID = 1
private const val TAG = "GolangServerService"
}
override fun onCreate() {
super.onCreate()
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Golang Server")
.setContentText("Server đang chạy")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.build()
startForeground(NOTIFICATION_ID, notification)
// Chạy server trong thread riêng để tránh ANR
Thread {
try {
val appDataPath = intent?.getStringExtra("appDataPath")
if (appDataPath != null) {
Libandroid.setPathDataLocal(appDataPath)
Log.d(TAG, "✅ Set path data: $appDataPath")
} else {
Log.e(TAG, "❌ appDataPath not received in intent")
}
Libandroid.setServerRunning(true)
Log.d(TAG, "✅ Server started")
} catch (e: Exception) {
Log.e(TAG, "❌ Error when start server:", e)
}
}.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
try {
val result = Libandroid.setServerRunning(false)
Log.d(TAG, "Server shutdown result: $result")
} catch (e: Exception) {
Log.e(TAG, "Error shutting down server", e)
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Golang Server Channel",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Notify Golang backend runing"
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
}
@@ -1,156 +0,0 @@
package com.example.fireflypsandorid
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.fireflypsandorid.ui.theme.FireflyPsAndoridTheme
import java.io.*
class MainActivity : ComponentActivity() {
private val TAG = "AppInit"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appDataPath = filesDir.absolutePath
val dataDir = File("$appDataPath/data")
dataDir.mkdirs()
checkAndCreateFile(dataDir, "data-in-game.json", R.raw.data_in_game_json)
checkAndCreateFile(dataDir, "freesr-data.json", R.raw.freesr_data_json)
checkAndCreateFile(dataDir, "version.json", R.raw.version_json)
enableEdgeToEdge()
setContent {
FireflyPsAndoridTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
ServerControlScreen(appDataPath, Modifier.padding(innerPadding))
}
}
}
}
private fun checkAndCreateFile(targetDir: File, fileName: String, resId: Int) {
val outFile = File(targetDir, fileName)
if (!outFile.exists()) {
try {
resources.openRawResource(resId).use { input ->
FileOutputStream(outFile).use { output ->
input.copyTo(output)
}
}
Log.i(TAG, "✅ Copied $fileName to ${outFile.absolutePath}")
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to copy $fileName: ${e.message}")
}
} else {
Log.i(TAG, "$fileName already exists at ${outFile.absolutePath}")
}
}
}
@Composable
fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
val context = LocalContext.current
var isServerRunning by remember { mutableStateOf(false) }
val serverImage = if (isServerRunning)
painterResource(id = R.drawable.server_running)
else
painterResource(id = R.drawable.server_stopped)
Column(
modifier = modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally
) {
// Title
Text(
text = "Firefly Ps for Android",
fontSize = 26.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.padding(top = 24.dp),
color = Color(0xFF4CAF50).copy(alpha = 0.9f) // Lime Green
)
Spacer(modifier = Modifier.height(8.dp))
// Server status image
Image(
painter = serverImage,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(250.dp)
.padding(8.dp)
)
Spacer(modifier = Modifier.height(8.dp))
// Toggle button
Button(
onClick = {
try {
isServerRunning = !isServerRunning
if (isServerRunning) {
val intent = Intent(context, GolangServerService::class.java)
intent.putExtra("appDataPath", appDataPath)
context.startService(intent)
} else {
context.stopService(Intent(context, GolangServerService::class.java))
}
} catch (e: Exception) {
Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
},
colors = ButtonDefaults.buttonColors(
containerColor = if (isServerRunning) Color(0xFFB71C1C) else Color(0xFFFF5722),
contentColor = Color.White
),
shape = RoundedCornerShape(12.dp),
modifier = Modifier
.fillMaxWidth(0.7f)
.height(50.dp)
) {
Text(
text = if (isServerRunning) "Stop Server" else "Start Server",
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
// Server status text
Text(
text = if (isServerRunning) "Server is running" else "Server is stopped",
fontSize = 24.sp,
color = if (isServerRunning) Color(0xFF4CAF50) else Color.Gray
)
Spacer(modifier = Modifier.height(24.dp))
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

@@ -0,0 +1,5 @@
{
"latest_version": "4.3.5-01",
"changelog": "UPDATE: 4.3.5X",
"apk_url": "https://git.kain.io.vn/Firefly-Shelter/FireflyGo_Android/releases/download/4.3.5-01/firefly_go_android.apk"
}
@@ -1,62 +0,0 @@
{
"lineups": {
"0": 1310,
"1": 1410,
"2": 1409,
"3": 1407
},
"position": {
"x": -1115,
"z": -642542,
"y": -152077,
"rot_y": 195849
},
"scene": {
"plane_id": 10304,
"floor_id": 10304001,
"entry_id": 1030402
},
"char_path": {
"main": 8008,
"march_7": 1224
},
"char_enhanced": {
"1005": 1,
"1006": 1,
"1205": 1,
"1212": 1
},
"battle": {
"battle_id": 0,
"skip_half": 0,
"blessings": [],
"freesr": false,
"current_half": 0,
"path_resonance_id": 0,
"maze_buff": 0,
"first_lineup": [],
"second_lineup": []
},
"theory_craft": {
"hp": {
"1": 600000,
"2": 10000000
},
"cycle_count": 1,
"log": false,
"mode": false
},
"profile_data": {
"cur_chat_bubble_id": 220008,
"cur_phone_theme_id": 221011,
"cur_phone_case_id": 254001,
"cur_pam_skin_id": 252000,
"cur_pet_id": 1002,
"cur_avatar_player_icon": 202034,
"cur_player_personal_card": 253001
},
"skin_data": {
"1001": 1100101,
"1310": 1131001
}
}
-260
View File
@@ -1,260 +0,0 @@
{
"CNBETAAndroid3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"CNBETAAndroid3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"CNBETAAndroid3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAAndroid3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"CNBETAAndroid3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"CNBETAAndroid3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"CNBETAWin3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"CNBETAWin3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"CNBETAWin3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAWin3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"CNBETAWin3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"CNBETAWin3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"CNBETAiOS3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"CNBETAiOS3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"CNBETAiOS3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAiOS3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"CNBETAiOS3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"CNBETAiOS3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"CNPRODAndroid3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"CNPRODAndroid3.3.0": {
"asset_bundle_url": "prod_official_asia"
},
"CNPRODWin3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"CNPRODWin3.3.0": {
"asset_bundle_url": "prod_official_asia"
},
"CNPRODiOS3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"CNPRODiOS3.3.0": {
"asset_bundle_url": "prod_official_asia"
},
"OSBETAAndroid3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"OSBETAAndroid3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"OSBETAAndroid3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAAndroid3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"OSBETAAndroid3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"OSBETAAndroid3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"OSBETAWin3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"OSBETAWin3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"OSBETAWin3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAWin3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"OSBETAWin3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"OSBETAWin3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"OSBETAiOS3.3.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10451237_a3aa836fce75_f560b891c0d21e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10459782_ced8509d61c9_cdbde1049f2207",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10434495_6bff50432edd_1641e3e19f1244",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10429797_be4a832b1c47_f58faff155c2c4"
},
"OSBETAiOS3.3.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10478982_243ce40577bf_000895ae562404",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10494861_2ed49bac2846_b7f8d02fced269",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10479565_234d9d8dfe49_b0890465b5ae4f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10489293_ba258955cec6_d8347bc2994eab"
},
"OSBETAiOS3.3.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10553897_658616122c5e_1311a7ab7701f7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10564145_db8507c78423_dc92dd047d442d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10554126_56a3036b1f8c_6dab2038cb17c3",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAiOS3.3.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10614355_acdef3b37542_7c05a0c7169b39",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10643219_d0afbb4454ef_5ee89a3a8453cf",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10614562_0a9931c21f8d_d14b4994f719ef",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10620104_9313ee61e16d_574ea402695fa2"
},
"OSBETAiOS3.3.55": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10680207_39a321e6cd21_5498e87fbd0271",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10680207_86c7a513739e_b3e04f1f843fc8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10680696_4c8a486b535c_bec778e9be0475",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10699045_b68581c5e40a_b431fce3552eec"
},
"OSBETAiOS3.4.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_10897443_f25d08d935bf_770a3e243970a0",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_10898900_1b2e58b9a26e_0861bee3e89d40",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_10872485_88661d25b99c_8af3232d484baf",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_10894308_758b6d45fde6_ac9089c9b1e0a0"
},
"OSPRODAndroid3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"OSPRODAndroid3.3.0": {
"asset_bundle_url": "prod_official_asia"
},
"OSPRODWin3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"OSPRODWin3.3.0": {
"asset_bundle_url": "prod_official_asia"
},
"OSPRODiOS3.2.0": {
"asset_bundle_url": "prod_official_asia",
"lua_url": "client version not match"
},
"OSPRODiOS3.3.0": {
"asset_bundle_url": "prod_official_asia"
}
}
+57
View File
@@ -0,0 +1,57 @@
<resources>
<string name="app_name">Firefly Go</string>
<string name="android_edition">Android版</string>
<string name="sign_in">サインイン</string>
<string name="server_running">サーバー稼働中</string>
<string name="server_stopped">サーバー停止</string>
<string name="start_server">サーバー起動</string>
<string name="stop_server">サーバー停止</string>
<string name="update">アップデート</string>
<string name="reset">リセット</string>
<string name="logs">ログ</string>
<string name="reset_data_title">データリセット</string>
<string name="reset_data_confirm">すべてのデータをリセットしますか?この操作は取り消せません。</string>
<string name="no">いいえ</string>
<string name="yes">はい</string>
<!-- Scam Warning -->
<string name="scam_warning_title">セキュリティ警告</string>
<string name="scam_warning_text">これはオープンソースプロジェクトです。もし誰かからこのアプリを購入した場合、あなたは詐欺に遭っています。</string>
<string name="i_understand">了解しました</string>
<!-- LoginDialog -->
<string name="account_profile">アカウントプロフィール</string>
<string name="logged_in_as">ログイン中:</string>
<string name="auto_update_status">自動更新ステータス:</string>
<string name="unlocked_player">ロック解除 (PLAYER)</string>
<string name="locked_no_permission">ロック中 (権限なし)</string>
<string name="back">戻る</string>
<string name="logout">ログアウト</string>
<string name="email">メールアドレス</string>
<string name="password">パスワード</string>
<string name="cancel">キャンセル</string>
<string name="login">ログイン</string>
<string name="or">または</string>
<string name="login_with_discord">Discordでログイン</string>
<string name="please_enter_both">メールアドレスとパスワードを入力してください</string>
<string name="login_failed">ログイン失敗</string>
<string name="failed_to_fetch_profile">プロフィールの取得に失敗しました</string>
<!-- AutoUpdateDialog -->
<string name="update_available">アップデートがあります</string>
<string name="no_update_available">アップデートはありません</string>
<string name="app_up_to_date">アプリは最新状態です</string>
<string name="ok">確定</string>
<string name="latest_version">最新バージョン</string>
<string name="whats_new">更新内容</string>
<string name="downloading">ダウンロード中...</string>
<string name="installation_ready">インストールの準備完了</string>
<string name="later">後で</string>
<string name="install_now">今すぐインストール</string>
<!-- LogPopup -->
<string name="golog_output">GoLog出力</string>
<string name="auto_scroll">自動スクロール</string>
<string name="clear">消去</string>
<string name="close">閉じる</string>
</resources>
+57
View File
@@ -0,0 +1,57 @@
<resources>
<string name="app_name">Firefly Go</string>
<string name="android_edition">안드로이드 버전</string>
<string name="sign_in">로그인</string>
<string name="server_running">서버 실행 중</string>
<string name="server_stopped">서버 정지됨</string>
<string name="start_server">서버 시작</string>
<string name="stop_server">서버 정지</string>
<string name="update">업데이트</string>
<string name="reset">초기화</string>
<string name="logs">로그</string>
<string name="reset_data_title">데이터 초기화</string>
<string name="reset_data_confirm">모든 데이터를 초기화하시겠습니까? 이 작업은 취소할 수 없습니다.</string>
<string name="no">아니오</string>
<string name="yes"></string>
<!-- Scam Warning -->
<string name="scam_warning_title">보안 경고</string>
<string name="scam_warning_text">이것은 오픈 소스 프로젝트입니다. 누군가로부터 이 앱을 구매하셨다면, 귀하는 사기를 당한 것입니다.</string>
<string name="i_understand">확인했습니다</string>
<!-- LoginDialog -->
<string name="account_profile">계정 프로필</string>
<string name="logged_in_as">로그인 계정:</string>
<string name="auto_update_status">자동 업데이트 상태:</string>
<string name="unlocked_player">잠금 해제 (PLAYER)</string>
<string name="locked_no_permission">잠김 (권한 없음)</string>
<string name="back">뒤로</string>
<string name="logout">로그아웃</string>
<string name="email">이메일</string>
<string name="password">비밀번호</string>
<string name="cancel">취소</string>
<string name="login">로그인</string>
<string name="or">또는</string>
<string name="login_with_discord">Discord로 로그인</string>
<string name="please_enter_both">이메일과 비밀번호를 모두 입력하세요</string>
<string name="login_failed">로그인 실패</string>
<string name="failed_to_fetch_profile">프로필을 불러오지 못했습니다</string>
<!-- AutoUpdateDialog -->
<string name="update_available">업데이트 가능</string>
<string name="no_update_available">업데이트 없음</string>
<string name="app_up_to_date">앱이 최신 상태입니다</string>
<string name="ok">확인</string>
<string name="latest_version">최신 버전</string>
<string name="whats_new">업데이트 내역</string>
<string name="downloading">다운로드 중...</string>
<string name="installation_ready">설치 준비 완료</string>
<string name="later">나중에</string>
<string name="install_now">지금 설치</string>
<!-- LogPopup -->
<string name="golog_output">GoLog 출력</string>
<string name="auto_scroll">자동 스크롤</string>
<string name="clear">지우기</string>
<string name="close">닫기</string>
</resources>
+57
View File
@@ -0,0 +1,57 @@
<resources>
<string name="app_name">Firefly Go</string>
<string name="android_edition">Phiên bản Android</string>
<string name="sign_in">Đăng nhập</string>
<string name="server_running">Máy chủ đang chạy</string>
<string name="server_stopped">Máy chủ đã dừng</string>
<string name="start_server">Khởi động máy chủ</string>
<string name="stop_server">Dừng máy chủ</string>
<string name="update">Cập nhật</string>
<string name="reset">Đặt lại</string>
<string name="logs">Nhật ký</string>
<string name="reset_data_title">Đặt lại dữ liệu</string>
<string name="reset_data_confirm">Bạn có muốn đặt lại tất cả dữ liệu? Hành động này không thể hoàn tác.</string>
<string name="no">Không</string>
<string name="yes"></string>
<!-- Scam Warning -->
<string name="scam_warning_title">Cảnh báo bảo mật</string>
<string name="scam_warning_text">Đây là dự án mã nguồn mở. Nếu bạn mua ứng dụng này từ bất kỳ ai, bạn đã bị lừa đảo (scam).</string>
<string name="i_understand">Tôi đã hiểu</string>
<!-- LoginDialog -->
<string name="account_profile">Thông tin tài khoản</string>
<string name="logged_in_as">Đăng nhập bằng:</string>
<string name="auto_update_status">Trạng thái tự cập nhật:</string>
<string name="unlocked_player">Đã mở khóa (PLAYER)</string>
<string name="locked_no_permission">Bị khóa (Không có quyền)</string>
<string name="back">Quay lại</string>
<string name="logout">Đăng xuất</string>
<string name="email">Email</string>
<string name="password">Mật khẩu</string>
<string name="cancel">Hủy</string>
<string name="login">Đăng nhập</string>
<string name="or">HOẶC</string>
<string name="login_with_discord">Đăng nhập bằng Discord</string>
<string name="please_enter_both">Vui lòng nhập cả Email và Mật khẩu</string>
<string name="login_failed">Đăng nhập thất bại</string>
<string name="failed_to_fetch_profile">Không thể tải thông tin cá nhân</string>
<!-- AutoUpdateDialog -->
<string name="update_available">Có bản cập nhật mới</string>
<string name="no_update_available">Không có bản cập nhật</string>
<string name="app_up_to_date">Ứng dụng đã được cập nhật mới nhất</string>
<string name="ok">Đồng ý</string>
<string name="latest_version">Phiên bản mới nhất</string>
<string name="whats_new">Có gì mới</string>
<string name="downloading">Đang tải xuống...</string>
<string name="installation_ready">Sẵn sàng cài đặt</string>
<string name="later">Để sau</string>
<string name="install_now">Cài đặt ngay</string>
<!-- LogPopup -->
<string name="golog_output">Nhật ký GoLog</string>
<string name="auto_scroll">Tự động cuộn</string>
<string name="clear">Xóa</string>
<string name="close">Đóng</string>
</resources>
+57
View File
@@ -0,0 +1,57 @@
<resources>
<string name="app_name">Firefly Go</string>
<string name="android_edition">安卓版</string>
<string name="sign_in">登录</string>
<string name="server_running">服务器正在运行</string>
<string name="server_stopped">服务器已停止</string>
<string name="start_server">启动服务器</string>
<string name="stop_server">停止服务器</string>
<string name="update">更新</string>
<string name="reset">重置</string>
<string name="logs">日志</string>
<string name="reset_data_title">重置数据</string>
<string name="reset_data_confirm">您确定要重置所有数据吗?此操作无法撤销。</string>
<string name="no">取消</string>
<string name="yes">确定</string>
<!-- Scam Warning -->
<string name="scam_warning_title">安全警告</string>
<string name="scam_warning_text">这是一个开源项目。如果您是从别人那里购买的这个应用,您就被骗了(欺诈)。</string>
<string name="i_understand">我已知晓</string>
<!-- LoginDialog -->
<string name="account_profile">账户信息</string>
<string name="logged_in_as">已登录为:</string>
<string name="auto_update_status">自动更新状态:</string>
<string name="unlocked_player">已解锁 (PLAYER)</string>
<string name="locked_no_permission">已锁定 (无权限)</string>
<string name="back">返回</string>
<string name="logout">退出登录</string>
<string name="email">邮箱</string>
<string name="password">密码</string>
<string name="cancel">取消</string>
<string name="login">登录</string>
<string name="or"></string>
<string name="login_with_discord">使用 Discord 登录</string>
<string name="please_enter_both">请输入邮箱和密码</string>
<string name="login_failed">登录失败</string>
<string name="failed_to_fetch_profile">获取个人资料失败</string>
<!-- AutoUpdateDialog -->
<string name="update_available">有可用更新</string>
<string name="no_update_available">没有可用更新</string>
<string name="app_up_to_date">您的应用已是最新版本</string>
<string name="ok">确定</string>
<string name="latest_version">最新版本</string>
<string name="whats_new">更新日志</string>
<string name="downloading">正在下载...</string>
<string name="installation_ready">准备安装</string>
<string name="later">稍后</string>
<string name="install_now">立即安装</string>
<!-- LogPopup -->
<string name="golog_output">GoLog 输出</string>
<string name="auto_scroll">自动滚动</string>
<string name="clear">清除</string>
<string name="close">关闭</string>
</resources>
+55 -1
View File
@@ -1,3 +1,57 @@
<resources> <resources>
<string name="app_name">FireflyGo-3.4.5X</string> <string name="app_name">Firefly Go</string>
<string name="android_edition">Android Edition</string>
<string name="sign_in">Sign In</string>
<string name="server_running">Server is running</string>
<string name="server_stopped">Server is stopped</string>
<string name="start_server">Start Server</string>
<string name="stop_server">Stop Server</string>
<string name="update">Update</string>
<string name="reset">Reset</string>
<string name="logs">Logs</string>
<string name="reset_data_title">Reset Data</string>
<string name="reset_data_confirm">Do you want to reset all data? This action cannot be rolled back.</string>
<string name="no">No</string>
<string name="yes">Yes</string>
<!-- Scam Warning -->
<string name="scam_warning_title">Security Warning</string>
<string name="scam_warning_text">This is an open-source project. If you bought this application from someone, you have been scammed (fraud).</string>
<string name="i_understand">I Understand</string>
<!-- LoginDialog -->
<string name="account_profile">Account Profile</string>
<string name="logged_in_as">Logged in as:</string>
<string name="auto_update_status">Auto-Update Status:</string>
<string name="unlocked_player">Unlocked (PLAYER)</string>
<string name="locked_no_permission">Locked (No permission)</string>
<string name="back">Back</string>
<string name="logout">Logout</string>
<string name="email">Email</string>
<string name="password">Password</string>
<string name="cancel">Cancel</string>
<string name="login">Login</string>
<string name="or">OR</string>
<string name="login_with_discord">Login with Discord</string>
<string name="please_enter_both">Please enter both Email and Password</string>
<string name="login_failed">Login failed</string>
<string name="failed_to_fetch_profile">Failed to fetch profile</string>
<!-- AutoUpdateDialog -->
<string name="update_available">Update Available</string>
<string name="no_update_available">No Update Available</string>
<string name="app_up_to_date">Your app is up to date</string>
<string name="ok">OK</string>
<string name="latest_version">Latest Version</string>
<string name="whats_new">What\'s New</string>
<string name="downloading">Downloading...</string>
<string name="installation_ready">Installation Ready</string>
<string name="later">Later</string>
<string name="install_now">Install Now</string>
<!-- LogPopup -->
<string name="golog_output">GoLog Output</string>
<string name="auto_scroll">Auto-Scroll</string>
<string name="clear">Clear</string>
<string name="close">Close</string>
</resources> </resources>
+1 -1
View File
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.FireflyPsAndorid" parent="android:Theme.Material.Light.NoActionBar" /> <style name="Theme.FireflyGoAndroid" parent="android:Theme.Material.Light.NoActionBar" />
</resources> </resources>
+3
View File
@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="downloads" path="Download/"/>
</paths>
@@ -1,4 +1,4 @@
package com.example.fireflypsandorid package com.example.firefly_go_android
import org.junit.Test import org.junit.Test
+4
View File
@@ -14,6 +14,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# AndroidX package structure to make it clearer which packages are bundled with the # AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK # Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn # https://developer.android.com/topic/libraries/support-library/androidx-rn
org.gradle.parallel=true
android.useAndroidX=true android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
@@ -21,3 +22,6 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies, # resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library # thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
#org.gradle.configuration-cache=false
android.r8.optimizedResourceShrinking=true
org.gradle.java.installations.auto-download=true
+13
View File
@@ -0,0 +1,13 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/e99bae143b75f9a10ead10248f02055e/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/658299a896470fbb3103ba3a430ee227/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/536afcd1dff540251f85e5d2c80458cf/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/398ffe3949748bfb1d5636f023d228fd/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/248ffb1098f61659502d0c09aa348294/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/932015f6361ccaead0c6d9b8717ed96e/redirect
toolchainVendor=JETBRAINS
toolchainVersion=21
+39 -20
View File
@@ -1,29 +1,48 @@
[versions] [versions]
agp = "8.9.1" activityComposeVersion = "1.11.0"
kotlin = "2.0.21" agp = "8.13.0"
coreKtx = "1.10.1" androidxJunit = "1.3.0"
animationCore = "1.9.3"
autoupdater = "1.0.1"
espressoCoreVersion = "3.7.0"
foundation = "1.9.3"
kotlin = "2.2.20"
coreKtx = "1.17.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" lifecycleRuntimeKtxVersion = "2.9.4"
espressoCore = "3.5.1" material = "1.9.3"
lifecycleRuntimeKtx = "2.6.1" material3WindowSizeClass = "1.4.0"
activityCompose = "1.8.0" materialIconsExtended = "1.7.8"
composeBom = "2024.09.00" slf4jAndroidVersion = "1.7.36"
ui = "1.9.3"
uiGraphics = "1.9.3"
uiTestJunit4 = "1.9.3"
uiTestManifest = "1.9.3"
uiTooling = "1.9.3"
uiToolingPreview = "1.9.3"
[libraries] [libraries]
androidx-activity-compose-v1101 = { module = "androidx.activity:activity-compose", version.ref = "activityComposeVersion" }
androidx-animation = { module = "androidx.compose.animation:animation", version.ref = "animationCore" }
androidx-animation-core = { module = "androidx.compose.animation:animation-core", version.ref = "animationCore" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-espresso-core-v370 = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCoreVersion" }
androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
androidx-junit-v130 = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" }
androidx-lifecycle-runtime-ktx-v293 = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtxVersion" }
androidx-material = { module = "androidx.compose.material:material", version.ref = "material" }
androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
androidx-material3-window-size-class1 = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "material3WindowSizeClass" }
autoupdater = { module = "com.github.CSAbhiOnline:AutoUpdater", version.ref = "autoupdater" }
junit = { group = "junit", name = "junit", version.ref = "junit" } junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } material3 = { module = "androidx.compose.material3:material3", version.ref = "material3WindowSizeClass" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4jAndroidVersion" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } ui = { module = "androidx.compose.ui:ui", version.ref = "ui" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "uiGraphics" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiTestJunit4" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" } ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestManifest" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "uiToolingPreview" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
+2 -2
View File
@@ -1,6 +1,6 @@
#Mon Apr 28 17:05:34 ICT 2025 #Wed Oct 08 15:20:16 ICT 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+92
View File
@@ -1,3 +1,4 @@
<<<<<<< HEAD
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@@ -87,3 +88,94 @@ exit /b 1
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal
:omega :omega
=======
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
>>>>>>> 2459650 (feat: v4)
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a3910ac160281c9d9c4ff5467411f3f4caf21a34639ac565b5f90ee7c0c3d85b
size 72276820
+3
View File
@@ -0,0 +1,3 @@
# Changelog
## - UPDATE: Support 4.3.5X
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
{
"tag": "4.3.5-01",
"title": "PreBuild Version 4.3.55 - 01"
}
+12 -1
View File
@@ -1,5 +1,8 @@
pluginManagement { pluginManagement {
repositories { repositories {
maven {
setUrl("https://jitpack.io")
}
google { google {
content { content {
includeGroupByRegex("com\\.android.*") includeGroupByRegex("com\\.android.*")
@@ -9,15 +12,23 @@ pluginManagement {
} }
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
} }
} }
plugins {
id("org.gradle.toolchains.foojay-resolver-convention") version "0.10.0"
}
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
maven {
setUrl("https://jitpack.io")
}
google() google()
mavenCentral() mavenCentral()
} }
} }
rootProject.name = "FireflyPsAndorid" rootProject.name = "FireflyGoAndroid"
include(":app") include(":app")