14 Commits

Author SHA1 Message Date
dafb6aba1b UPDATE: New res 2025-10-03 22:03:27 +07:00
3e967d7bed UPDATE: New res 2025-10-03 22:02:57 +07:00
c75207f8e1 UPDATE: Extra setting 2025-10-02 23:37:14 +07:00
3871dc8677 FIX: Fix env 2025-09-30 12:06:51 +07:00
72dc9b238f UPDATE: Update new link 2025-09-30 11:15:49 +07:00
edf158028e UPDATE: apk link 2025-09-30 10:30:40 +07:00
e21b59b9b1 UPDATE: Update to 3.6.52 2025-09-30 10:10:06 +07:00
4c10a53229 UPDATE: Fix bug, update libs 2025-09-02 20:00:36 +07:00
e61bb39fc5 UPDATE: Update for v4 2025-09-02 14:18:06 +07:00
8f86f3ea61 UPDATE: Add self update, reset data, logs 2025-08-25 09:56:01 +07:00
d57f7c024b UPDATE: Add self update, reset data, logs 2025-08-25 09:54:12 +07:00
aec3601f2a UPDATE: Add self update, reset data, logs 2025-08-25 09:52:03 +07:00
b54d8bd0c5 FIX: add asset bundle url b 2025-08-19 16:32:14 +07:00
89a772152b FIX: add new resdata 2025-08-19 12:23:08 +07:00
30 changed files with 7505 additions and 6940 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
.history/

View File

@@ -1,3 +1,5 @@
@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)
@@ -5,12 +7,13 @@ plugins {
} }
android { android {
namespace = "com.example.fireflypsandorid" namespace = "com.example.firefly_go_android"
compileSdk = 35 compileSdk = 36
defaultConfig { defaultConfig {
applicationId = "com.example.fireflypsandorid" applicationId = "com.example.firefly_go_android"
minSdk = 24 minSdk = 24
//noinspection OldTargetApi
targetSdk = 35 targetSdk = 35
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@@ -27,34 +30,63 @@ android {
) )
} }
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
buildFeatures { buildFeatures {
compose = true compose = true
} }
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
buildToolsVersion = "36.0.0"
ndkVersion = "27.2.12479018"
} }
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)
// 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)
// Debug
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
// Local AAR library
implementation(files("../library/firefly-go.aar")) implementation(files("../library/firefly-go.aar"))
} }

View File

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

View File

@@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_LOGS"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
@@ -32,6 +34,17 @@
android:name=".GolangServerService" android:name=".GolangServerService"
android:foregroundServiceType="dataSync" android:foregroundServiceType="dataSync"
android:exported="false" /> android:exported="false" />
<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>

View File

@@ -1,4 +1,4 @@
package com.example.fireflypsandorid package com.example.firefly_go_android
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager

View File

@@ -0,0 +1,812 @@
package com.example.firefly_go_android
import AutoUpdaterManager
import android.annotation.SuppressLint
import android.content.Context
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.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.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.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
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.TextStyle
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
data class AppVersion(
val latestVersion: String,
val changelog: String,
val apkUrl: String
)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val appDataPath = filesDir.absolutePath
val dataDir = File("$appDataPath/data")
dataDir.mkdirs()
copyRawToFile(this, dataDir, "data-in-game.json", R.raw.data_in_game_json)
copyRawToFile(this, dataDir, "freesr-data.json", R.raw.freesr_data_json)
copyRawToFile(this, dataDir, "version.json", R.raw.version_json)
val jsonString = resources.openRawResource(R.raw.app_version_json).use { input ->
input.bufferedReader().use { it.readText() }
}
val jsonObject = JSONObject(jsonString)
val latestVersion = jsonObject.getString("latest_version")
val changelog = jsonObject.getString("changelog")
val apkUrl = jsonObject.getString("apk_url")
val appVersion = AppVersion(latestVersion, changelog, apkUrl)
enableEdgeToEdge()
setContent {
FireflyPsAndoridTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Box(modifier = Modifier.fillMaxSize()) {
ServerControlScreen(appDataPath, dataDir, appVersion, Modifier.padding(innerPadding))
AutoUpdateDialog(onDismiss = {}, appVersion, true)
}
}
}
}
}
}
fun copyRawToFile(context: Context, targetDir: File, fileName: String, resId: Int, override: Boolean = false) {
val outFile = File(targetDir, fileName)
if (!outFile.exists() || override) {
try {
context.resources.openRawResource(resId).use { input ->
FileOutputStream(outFile).use { output ->
input.copyTo(output)
}
}
Log.i("FileCopy", "${if (override) "✅ Overridden" else "✅ Copied"} $fileName to ${outFile.absolutePath}")
} catch (e: Exception) {
Log.e("FileCopy", "❌ Failed to copy $fileName: ${e.message}")
}
} else {
Log.i("FileCopy", " $fileName already exists at ${outFile.absolutePath}")
}
}
@SuppressLint("ImplicitSamInstance")
@Composable
fun ServerControlScreen(appDataPath: String, dataDir: File, appVersion: AppVersion, modifier: Modifier = Modifier) {
val context = LocalContext.current
val isRunning = GolangServerService.isRunning
var showResetDialog by remember { mutableStateOf(false) }
var showUpdateDialog by remember { mutableStateOf(false) }
var showLogs by remember { mutableStateOf(false) }
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(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.White,
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(2f, 2f),
blurRadius = 4f
)
)
)
// Server status with icon
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier.padding(8.dp)
) {
Icon(
imageVector = if (isRunning) Icons.Default.PlayCircleFilled else Icons.Default.StopCircle,
contentDescription = null,
tint = if (isRunning) Color(0xFF4CAF50) else Color.Gray,
modifier = Modifier.size(40.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = if (isRunning) "Server is running" else "Server is stopped",
fontSize = 30.sp,
color = Color.White,
fontWeight = FontWeight.Medium,
style = TextStyle(
shadow = Shadow(
color = Color.Black,
offset = Offset(1f, 1f),
blurRadius = 2f
)
)
)
}
Spacer(modifier = Modifier.height(200.dp))
// Toggle button
Button(
onClick = {
try {
if (!isRunning) {
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 (isRunning) Color(0xFFB71C1C) else Color(0xFF2196F3),
contentColor = Color.White
),
shape = RoundedCornerShape(12.dp),
modifier = Modifier
.fillMaxWidth(0.8f)
.height(50.dp)
) {
Icon(
imageVector = if (isRunning) Icons.Default.Stop else Icons.Default.PlayArrow,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = if (isRunning) "Stop Server" else "Start Server",
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
// Widget icons row
Row(
horizontalArrangement = Arrangement.spacedBy(32.dp),
verticalAlignment = Alignment.CenterVertically
) {
val context = LocalContext.current
// Check Update widget
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clickable { showUpdateDialog = true }
.background(
Color.White.copy(alpha = 0.8f),
RoundedCornerShape(8.dp)
)
.padding(12.dp)
) {
Icon(
imageVector = Icons.Default.CloudDownload,
contentDescription = "Check Update",
tint = Color.Gray,
modifier = Modifier.size(32.dp)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Update",
fontSize = 12.sp,
color = Color.Black,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium
)
}
// Reset Data widget
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clickable { showResetDialog = true }
.background(
Color.White.copy(alpha = 0.8f),
RoundedCornerShape(8.dp)
)
.padding(12.dp)
) {
Icon(
imageVector = Icons.Default.RestartAlt,
contentDescription = "Reset Data",
tint = Color.Gray,
modifier = Modifier.size(32.dp)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Reset",
fontSize = 12.sp,
color = Color.Black,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium
)
}
// Logcat (Lynx) widget
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clickable {
showLogs = true // mở popup log
}
.background(
Color.White.copy(alpha = 0.8f),
RoundedCornerShape(8.dp)
)
.padding(12.dp)
) {
Icon(
imageVector = Icons.Default.BugReport,
contentDescription = "Open Logcat",
tint = Color.Gray,
modifier = Modifier.size(32.dp)
)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = "Logs",
fontSize = 12.sp,
color = Color.Black,
textAlign = TextAlign.Center,
fontWeight = FontWeight.Medium
)
}
}
Spacer(modifier = Modifier.height(75.dp))
}
}
if (showLogs) {
LogPopup(onDismiss = { showLogs = false })
}
// Reset Data Confirmation Dialog
if (showResetDialog) {
AlertDialog(
onDismissRequest = { showResetDialog = false },
title = {
Text(text = "Reset Data")
},
text = {
Text(text = "Do you want reset all data? This action can not rollback.")
},
confirmButton = {
TextButton(
onClick = {
showResetDialog = false
try {
copyRawToFile(context, dataDir, "data-in-game.json", R.raw.data_in_game_json, true)
copyRawToFile(context, dataDir, "freesr-data.json", R.raw.freesr_data_json, true)
copyRawToFile(context, dataDir, "version.json", R.raw.version_json, 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()
}
}
) {
Text("Yes", color = Color(0xFFFF0606))
}
},
dismissButton = {
TextButton(
onClick = { showResetDialog = false }
) {
Text("No")
}
}
)
}
// Auto Update Dialog
if (showUpdateDialog) {
AutoUpdateDialog(
onDismiss = { showUpdateDialog = false }, appVersion
)
}
}
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
}
@Composable
fun LogPopup(
onDismiss: () -> Unit
) {
var logs by remember { mutableStateOf(listOf<String>()) }
val scope = rememberCoroutineScope()
LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) {
try {
val process = Runtime.getRuntime().exec("logcat -s GoLog")
val reader = BufferedReader(InputStreamReader(process.inputStream))
var line: String?
while (reader.readLine().also { line = it } != null) {
val clean = parseGoLogLine(line!!)
if (!clean.isNullOrBlank()) {
logs = (logs + clean).takeLast(200)
}
}
} catch (e: Exception) {
logs = logs + "Error reading logcat: ${e.message}"
}
}
}
val listState = rememberLazyListState()
LaunchedEffect(logs.size) {
if (logs.isNotEmpty()) {
listState.animateScrollToItem(logs.size - 1)
}
}
Dialog(onDismissRequest = { onDismiss() }) {
Surface(
shape = RoundedCornerShape(12.dp),
tonalElevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.7f)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "GoLog Output",
style = MaterialTheme.typography.titleMedium,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(8.dp))
LazyColumn(
state = listState,
modifier = Modifier.weight(1f)
) {
items(logs.size) { index ->
Text(
text = logs[index],
fontSize = 12.sp,
color = Color.Black,
modifier = Modifier.padding(vertical = 2.dp)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = { onDismiss() },
modifier = Modifier.align(Alignment.End)
) {
Text("Close")
}
}
}
}
}
@Composable
fun AutoUpdateDialog(
onDismiss: () -> Unit,
appVersion: AppVersion,
isFirstOpen: Boolean = false
) {
val context = LocalContext.current
val autoUpdaterManager = AutoUpdaterManager(context)
var update by remember { mutableStateOf<UpdateFeatures?>(null) }
var progress by remember { mutableIntStateOf(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
delay(500)
}
}
if (showDialog) {
Dialog(
onDismissRequest = {
if (!isDownloading) showDialog = false
onDismiss()
},
properties = DialogProperties(
dismissOnBackPress = !isDownloading,
dismissOnClickOutside = !isDownloading
)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.scale(scaleAnimation)
.animateContentSize(),
shape = RoundedCornerShape(24.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp)
) {
Column(
modifier = Modifier.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Header icon
Box(
modifier = Modifier
.size(72.dp)
.background(
MaterialTheme.colorScheme.primaryContainer,
CircleShape
),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = if (update != null) Icons.Rounded.SystemUpdate
else Icons.Rounded.CheckCircle,
contentDescription = null,
modifier = Modifier.size(36.dp),
tint = MaterialTheme.colorScheme.onPrimaryContainer
)
}
Spacer(modifier = Modifier.height(16.dp))
// Title
Text(
text = if (update != null) "Update Available" else "No Update Available",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface,
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,
"MyApp_${update!!.latestversion}.apk"
) { prog -> progress = prog }
}
}
},
onDismiss = { showDialog = false; onDismiss() }
)
} else {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Your app is up to date",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.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 = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
contentPadding = PaddingValues(horizontal = 32.dp, vertical = 12.dp)
) {
Text(
text = "OK",
style = MaterialTheme.typography.labelMedium
)
}
}
}
}
}
}
}
}
// Version info card
@Composable
fun VersionInfoSection(update: UpdateFeatures) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f)
),
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Latest Version",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Surface(
shape = RoundedCornerShape(8.dp),
color = MaterialTheme.colorScheme.primary
) {
Text(
text = "v${update.latestversion}",
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onPrimary,
fontWeight = FontWeight.Medium
)
}
}
}
}
Spacer(modifier = Modifier.height(12.dp))
}
// Changelog section
@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 = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "What's New",
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSurface
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = update.changelog,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
lineHeight = 20.sp
)
}
}
// Progress section
@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) "Installation Ready" else "Downloading...",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium
)
Text(
text = "${(progress*100).toInt()}%",
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
Spacer(modifier = Modifier.height(8.dp))
LinearProgressIndicator(
progress = { progress },
modifier = Modifier
.fillMaxWidth()
.height(8.dp)
.clip(RoundedCornerShape(4.dp)),
color = if (downloadComplete) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
}
}
// Action buttons
@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)
) {
Text(text = "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) MaterialTheme.colorScheme.tertiary else MaterialTheme.colorScheme.primary
)
) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
when {
downloadComplete -> {
Icon(Icons.Rounded.InstallMobile, contentDescription = null, modifier = Modifier.size(18.dp))
Spacer(modifier = Modifier.width(8.dp))
Text("Install Now", style = MaterialTheme.typography.labelLarge, fontWeight = FontWeight.Medium)
}
isDownloading -> {
CircularProgressIndicator(modifier = Modifier.size(24.dp), color = MaterialTheme.colorScheme.onPrimary)
}
else -> {
Icon(
imageVector = Icons.Rounded.Download,
contentDescription = "Download",
modifier = Modifier.size(24.dp)
)
}
}
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,154 +0,0 @@
package com.example.fireflypsandorid
import android.annotation.SuppressLint
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.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.*
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}")
}
}
}
@SuppressLint("ImplicitSamInstance")
@Composable
fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
val context = LocalContext.current
val isRunning = GolangServerService.isRunning
val serverImage = if (isRunning)
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 {
if (!isRunning) {
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 (isRunning) Color(0xFFB71C1C) else Color(0xFF2196F3),
contentColor = Color.White
),
shape = RoundedCornerShape(12.dp),
modifier = Modifier
.fillMaxWidth(0.7f)
.height(50.dp)
) {
Text(
text = if (isRunning) "Stop Server" else "Start Server",
fontSize = 20.sp
)
}
Spacer(modifier = Modifier.height(4.dp))
// Server status text
Text(
text = if (isRunning) "Server is running" else "Server is stopped",
fontSize = 24.sp,
color = if (isRunning) 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

View File

@@ -0,0 +1,5 @@
{
"latest_version": "3.6.2-04",
"changelog": "UPDATE: New res",
"apk_url": "https://git.kain.io.vn/Firefly-Shelter/FireflyGo_Android/releases/download/3.6.2-04/firefly_go_android.apk"
}

View File

@@ -1,22 +1,25 @@
{ {
"leader": 1, "leader": 0,
"lineups": { "lineups": {
"0": 1310, "0": 1413,
"1": 1410, "1": 1415,
"2": 1409, "2": 1409,
"3": 1407 "3": 1407
}, },
"position": { "position": {
"x": -4030, "x": 218004,
"z": -13006, "z": 259263,
"y": 0, "y": 53915,
"rot_y": 270000 "rot_y": 79863
}, },
"scene": { "scene": {
"plane_id": 10000, "plane_id": 20423,
"floor_id": 10000000, "floor_id": 20423001,
"entry_id": 100000104 "entry_id": 2042301
}, },
"player_outfit": [
1001
],
"char_path": { "char_path": {
"main": 8008, "main": 8008,
"march_7": 1224 "march_7": 1224
@@ -27,32 +30,141 @@
"1205": 1, "1205": 1,
"1212": 1 "1212": 1
}, },
"battle": { "challenge": {
"battle_id": 0, "challenge_id": 0,
"skip_half": 0, "skip_half": 0,
"blessings": [], "blessings": [],
"freesr": false, "is_in_challenge": false,
"current_half": 0, "current_stage_id": 30118121,
"path_resonance_id": 0, "path_resonance_id": 0,
"maze_buff": 0, "maze_buff": 0,
"first_lineup": [], "first_lineup": [],
"second_lineup": [] "second_lineup": []
}, },
"challenge_peak": {
"current_mode": "Knight",
"group_id": 2,
"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
}
]
}
}
}
},
"theory_craft": { "theory_craft": {
"hp": { "hp": {
"1": 600000, "1": [
"2": 10000000 200000,
1000000,
200000
],
"2": [
500000,
10000000,
500000
]
}, },
"cycle_count": 1, "cycle_count": 1,
"log": false, "mode": true
"mode": false
}, },
"profile_data": { "profile_data": {
"cur_chat_bubble_id": 220008, "cur_chat_bubble_id": 220008,
"cur_phone_theme_id": 221011, "cur_phone_theme_id": 221012,
"cur_phone_case_id": 254001, "cur_phone_case_id": 254001,
"cur_pam_skin_id": 252000, "cur_pam_skin_id": 252000,
"cur_pet_id": 1002, "cur_pet_id": 1003,
"cur_avatar_player_icon": 202034, "cur_avatar_player_icon": 202034,
"cur_player_personal_card": 253001, "cur_player_personal_card": 253001,
"cur_signature": "Firefly GO By Kain", "cur_signature": "Firefly GO By Kain",
@@ -60,8 +172,8 @@
1310, 1310,
1309, 1309,
1407, 1407,
1412, 1413,
1001 1412
], ],
"cur_is_display_avatar": true "cur_is_display_avatar": true
}, },

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +1,44 @@
{ {
"CNBETAAndroid3.4.55": { "CNBETAAndroid3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
}, },
"CNBETAWin3.4.55": { "CNBETAWin3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
}, },
"CNBETAiOS3.4.55": { "CNBETAiOS3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
}, },
"OSBETAAndroid3.4.55": { "OSBETAAndroid3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
}, },
"OSBETAWin3.4.55": { "OSBETAWin3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
}, },
"OSBETAiOS3.4.55": { "OSBETAiOS3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11217430_23c5cfd2cafc_071126dd600bae", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11218061_68ee992558e3_fe33df3598d887", "asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11218130_15360461a27a_91a0fd1d2b4db3", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885" "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
} }
} }

View File

@@ -1,3 +1,3 @@
<resources> <resources>
<string name="app_name">FireflyGo-3.5.5X</string> <string name="app_name">Firefly Go</string>
</resources> </resources>

View File

@@ -0,0 +1,3 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="downloads" path="Download/"/>
</paths>

View File

@@ -1,4 +1,4 @@
package com.example.fireflypsandorid package com.example.firefly_go_android
import org.junit.Test import org.junit.Test

View File

@@ -15,9 +15,11 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# 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
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=false
# 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
# Enables namespacing of each library's R class so that its R class includes only the # Enables namespacing of each library's R class so that its R class includes only the
# 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=true

View File

@@ -1,29 +1,46 @@
[versions] [versions]
agp = "8.9.1" activityComposeVersion = "1.10.1"
agp = "8.9.3"
androidxJunit = "1.3.0"
animationCore = "1.9.0"
autoupdater = "1.0.1"
espressoCoreVersion = "3.7.0"
foundation = "1.9.0"
kotlin = "2.0.21" kotlin = "2.0.21"
coreKtx = "1.10.1" coreKtx = "1.17.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.1.5" lifecycleRuntimeKtxVersion = "2.9.3"
espressoCore = "3.5.1" material = "1.9.0"
lifecycleRuntimeKtx = "2.6.1" material3WindowSizeClass = "1.3.2"
activityCompose = "1.8.0" materialIconsExtended = "1.7.8"
composeBom = "2024.09.00" ui = "1.9.0"
uiGraphics = "1.9.0"
uiTestJunit4 = "1.9.0"
uiTestManifest = "1.9.0"
uiTooling = "1.9.0"
uiToolingPreview = "1.9.0"
[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" } ui = { module = "androidx.compose.ui:ui", version.ref = "ui" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "uiGraphics" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiTestJunit4" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestManifest" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" } ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "uiToolingPreview" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
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" }

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:26eaf2a6981aed15c5c7f6edc465505406f05567739054e71f2d3af60f111ef2 oid sha256:87408dc51a2862b36217129b2a675518705b4b69bbf33b8a6c876b0bdefb91b9
size 73415308 size 89628224

2
script/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
*.exe

2
script/README_Note.md Normal file
View File

@@ -0,0 +1,2 @@
# Changelog
## - UPDATE: New res

5
script/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module release
go 1.25.0
require github.com/joho/godotenv v1.5.1

2
script/go.sum Normal file
View File

@@ -0,0 +1,2 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=

154
script/main.go Normal file
View File

@@ -0,0 +1,154 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"path/filepath"
"github.com/joho/godotenv"
)
const (
repoOwner = "Firefly-Shelter"
repoName = "FireflyGo_Android"
giteaURL = "https://git.kain.io.vn"
)
type ReleaseInput struct {
TagName string `json:"tag_name"`
Name string `json:"name"`
Body string `json:"body"`
Draft bool `json:"draft"`
Prerelease bool `json:"prerelease"`
}
type ReleaseResponse struct {
ID int `json:"id"`
HTMLURL string `json:"html_url"`
URL string `json:"url"`
}
func readFile(path string) string {
data, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("Failed to read %s: %v", path, err))
}
return string(data)
}
func main() {
err := godotenv.Load("script/.env")
if err != nil {
fmt.Println("Error loading .env file")
}
token := os.Getenv("TOKEN")
if token == "" {
fmt.Println("TOKEN not found in .env")
}
releaseJSON := readFile("script/release.json")
var meta map[string]string
if err := json.Unmarshal([]byte(releaseJSON), &meta); err != nil {
panic("Invalid release.json")
}
tag := meta["tag"]
title := meta["title"]
body := readFile("script/README_Note.md")
// Step 1: Create release
releaseInput := ReleaseInput{
TagName: tag,
Name: title,
Body: body,
Draft: false,
Prerelease: false,
}
payload, _ := json.Marshal(releaseInput)
req, _ := http.NewRequest("POST",
fmt.Sprintf("%s/api/v1/repos/%s/%s/releases", giteaURL, repoOwner, repoName),
bytes.NewReader(payload),
)
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusCreated {
bodyBytes, _ := io.ReadAll(resp.Body)
panic(fmt.Sprintf("Failed to create release: %s", bodyBytes))
}
var releaseResp ReleaseResponse
if err := json.NewDecoder(resp.Body).Decode(&releaseResp); err != nil {
panic("Failed to decode release response")
}
fmt.Printf("Release created:\n- ID: %d\n- HTML: %s\n", releaseResp.ID, releaseResp.HTMLURL)
uploadURL := releaseResp.URL
if uploadURL == "" {
panic("url missing in release response")
}
files, err := os.ReadDir("app/release")
if err != nil {
panic("Cannot read prebuild folder")
}
for _, file := range files {
if filepath.Ext(file.Name()) != ".apk" {
continue
}
uploadPath := filepath.Join("app/release", file.Name())
fmt.Println("Uploading:", uploadPath)
buf := new(bytes.Buffer)
writer := multipart.NewWriter(buf)
part, err := writer.CreateFormFile("attachment", filepath.Base(uploadPath))
if err != nil {
fmt.Printf("Failed to create form: %v\n", err)
continue
}
src, err := os.Open(uploadPath)
if err != nil {
fmt.Printf("Cannot open file %s: %v\n", file.Name(), err)
continue
}
io.Copy(part, src)
src.Close()
writer.Close()
req, err := http.NewRequest("POST", uploadURL+"/assets", buf)
if err != nil {
fmt.Printf("NewRequest error: %v\n", err)
continue
}
req.Header.Set("Authorization", "token "+token)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Printf("Upload failed: %v\n", err)
continue
}
if resp.StatusCode != http.StatusCreated {
bodyBytes, _ := io.ReadAll(resp.Body)
fmt.Printf("Upload failed: %s\n", bodyBytes)
} else {
fmt.Println("Uploaded:", file.Name())
}
resp.Body.Close()
}
}

5
script/release.json Normal file
View File

@@ -0,0 +1,5 @@
{
"tag": "3.6.2-04",
"title": "PreBuild Version 3.6.52 - 04"
}

View File

@@ -16,6 +16,9 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven{
url=uri("https://jitpack.io")
}
} }
} }