19 Commits

Author SHA1 Message Date
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
ed411fc284 UPDATE: update for 3.5.1 2025-08-19 11:18:08 +07:00
b7b0457685 FIX: app runing state bug 2025-08-06 19:09:41 +07:00
bdd458a64f UPDATE: Add wakelock 2025-08-06 18:32:18 +07:00
93d86df411 update for v5 2025-07-25 12:53:42 +07:00
a0cef76ae6 update for v4 2025-07-22 14:22:23 +07:00
b40252d958 fix env 2025-07-15 21:59:45 +07:00
01b311fb24 update for v3 2025-07-15 21:35:35 +07:00
d21a84ee47 update for 3.4.5x 2025-07-08 15:04:02 +07:00
30 changed files with 9643 additions and 53621 deletions

Binary file not shown.

View File

@@ -1,3 +1,5 @@
@file:Suppress("UnstableApiUsage")
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -5,12 +7,13 @@ plugins {
}
android {
namespace = "com.example.fireflypsandorid"
compileSdk = 35
namespace = "com.example.firefly_go_android"
compileSdk = 36
defaultConfig {
applicationId = "com.example.fireflypsandorid"
applicationId = "com.example.firefly_go_android"
minSdk = 24
//noinspection OldTargetApi
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -27,34 +30,63 @@ android {
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
buildToolsVersion = "36.0.0"
ndkVersion = "27.2.12479018"
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.lifecycle.runtime.ktx.v293)
implementation(libs.androidx.activity.compose.v1101)
// Compose UI
implementation(libs.ui)
implementation(libs.ui.graphics)
implementation(libs.ui.tooling.preview)
// 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)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
// Android Instrumentation Test
androidTestImplementation(libs.androidx.junit.v130)
androidTestImplementation(libs.androidx.espresso.core.v370)
androidTestImplementation(libs.ui.test.junit4)
// Debug
debugImplementation(libs.ui.tooling)
debugImplementation(libs.ui.test.manifest)
// Local AAR library
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.ext.junit.runners.AndroidJUnit4

View File

@@ -3,13 +3,11 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<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.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<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
android:allowBackup="true"
@@ -25,18 +23,28 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.FireflyPsAndorid">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".GolangServerService"
android:foregroundServiceType="dataSync" />
android:foregroundServiceType="dataSync"
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>
</manifest>

View File

@@ -1,4 +1,4 @@
package com.example.fireflypsandorid
package com.example.firefly_go_android
import android.app.NotificationChannel
import android.app.NotificationManager
@@ -8,73 +8,119 @@ import android.content.Context
import android.content.Intent
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 libandroid.Libandroid
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()
}
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_IMMUTABLE
this,
0,
notificationIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Golang Server")
.setContentText("Server đang chạy")
.setContentTitle("FireflyGO Server")
.setContentText("FireflyGO is running...")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.build()
startForeground(NOTIFICATION_ID, notification)
// Chạy server trong thread riêng để tránh ANR
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, "❌ WakeLock failed", e)
}
Thread {
try {
val appDataPath = intent?.getStringExtra("appDataPath")
if (appDataPath != null) {
Libandroid.setPathDataLocal(appDataPath)
Log.d(TAG, "✅ Set path data: $appDataPath")
} else {
isRunning = false
Log.e(TAG, "❌ appDataPath not received in intent")
stopSelf()
return@Thread
}
Libandroid.setServerRunning(true)
isRunning = true
Log.d(TAG, "✅ Server started")
} catch (e: Exception) {
Log.e(TAG, "❌ Error when start server:", e)
isRunning = false
Log.e(TAG, "❌ Error starting server", e)
stopSelf()
}
}.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "onDestroy called")
// 1. Tắt server
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)
}
// 2. Giải phóng WakeLock nếu còn giữ
try {
wakeLock?.let {
if (it.isHeld) {
it.release()
Log.d(TAG, "✅ WakeLock released")
}
}
} catch (e: Exception) {
Log.e(TAG, "❌ Failed to release WakeLock", e)
}
isRunning = false
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onBind(intent: Intent?): IBinder? = null
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -83,11 +129,12 @@ class GolangServerService : Service() {
"Golang Server Channel",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Notify Golang backend runing"
description = "Channel for running Golang backend in foreground"
}
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
Log.d(TAG, "✅ Notification channel created")
}
}
}

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

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 androidx.compose.foundation.isSystemInDarkTheme
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.ui.text.TextStyle

View File

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

View File

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

View File

@@ -1,21 +1,25 @@
{
"leader": 0,
"lineups": {
"0": 1015,
"1": 1306,
"2": 1403,
"3": 1006
"0": 1413,
"1": 1403,
"2": 1409,
"3": 1407
},
"position": {
"x": -4030,
"z": -13006,
"y": 0,
"rot_y": 270000
"x": -30,
"z": -22750,
"y": -15000,
"rot_y": 234288
},
"scene": {
"plane_id": 10000,
"floor_id": 10000000,
"entry_id": 100000104
"floor_id": 10000003,
"entry_id": 100000352
},
"player_outfit": [
1001
],
"char_path": {
"main": 8008,
"march_7": 1224
@@ -26,17 +30,119 @@
"1205": 1,
"1212": 1
},
"battle": {
"battle_id": 0,
"challenge": {
"challenge_id": 0,
"skip_half": 0,
"blessings": [],
"freesr": false,
"current_half": 0,
"is_in_challenge": false,
"current_stage_id": 30118121,
"path_resonance_id": 0,
"maze_buff": 0,
"first_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": {
"hp": {
"1": 600000,
@@ -48,9 +154,24 @@
},
"profile_data": {
"cur_chat_bubble_id": 220008,
"cur_phone_theme_id": 221011,
"cur_phone_theme_id": 221012,
"cur_phone_case_id": 254001,
"cur_pam_skin_id": 252000,
"cur_pet_id": 1002
"cur_pet_id": 1003,
"cur_avatar_player_icon": 202034,
"cur_player_personal_card": 253001,
"cur_signature": "Firefly GO By Kain",
"cur_display_avatar": [
1310,
1309,
1407,
1413,
1412
],
"cur_is_display_avatar": true
},
"skin_data": {
"1001": 1100101,
"1310": 1131001
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,188 +1,44 @@
{
"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.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
},
"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"
"CNBETAWin3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
},
"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"
"CNBETAiOS3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
},
"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"
"OSBETAAndroid3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"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.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.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"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.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"
},
"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"
},
"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"
},
"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"
},
"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"
},
"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"
"OSBETAiOS3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"asset_bundle_url_b": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12056690_16bfd67c199f_f3c0367d7b051e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"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>
<string name="app_name">FireflyGo-3.3.5X</string>
<string name="app_name">Firefly Go</string>
</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

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
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.enableJetifier=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# 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,
# 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]
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"
coreKtx = "1.10.1"
coreKtx = "1.17.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.09.00"
lifecycleRuntimeKtxVersion = "2.9.3"
material = "1.9.0"
material3WindowSizeClass = "1.3.2"
materialIconsExtended = "1.7.8"
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]
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-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" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
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" }
material3 = { module = "androidx.compose.material3:material3", version.ref = "material3WindowSizeClass" }
ui = { module = "androidx.compose.ui:ui", version.ref = "ui" }
ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "uiGraphics" }
ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "uiTestJunit4" }
ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestManifest" }
ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "uiTooling" }
ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "uiToolingPreview" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:34b7602e8ecc18b4a4a209357b1c358535226389ec88727ca7cae93f96ae74f7
size 71508675
oid sha256:2e71e934cd45ec0cea8c45d9ca8fb64c375f7059691dc51b9c05f3834d2ae2e9
size 89535013

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: Fix env

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-02",
"title": "PreBuild Version 3.6.52 - 02"
}

View File

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