UPDATE: Add self update, reset data, logs
This commit is contained in:
@@ -27,34 +27,61 @@ 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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
|
||||||
|
implementation("androidx.activity:activity-compose:1.8.2")
|
||||||
|
// Compose UI
|
||||||
|
implementation("androidx.compose.ui:ui:1.5.3")
|
||||||
|
implementation("androidx.compose.ui:ui-graphics:1.5.3")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview:1.5.3")
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
// Foundation & Animation
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation("androidx.compose.foundation:foundation:1.5.3")
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation("androidx.compose.animation:animation:1.5.3")
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation("androidx.compose.animation:animation-core:1.5.3")
|
||||||
implementation(libs.androidx.ui)
|
|
||||||
implementation(libs.androidx.ui.graphics)
|
// Material & Material3
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation("androidx.compose.material:material:1.5.3")
|
||||||
implementation(libs.androidx.material3)
|
implementation("androidx.compose.material:material-icons-extended:1.5.3")
|
||||||
testImplementation(libs.junit)
|
implementation("androidx.compose.material3:material3:1.2.0")
|
||||||
androidTestImplementation(libs.androidx.junit)
|
implementation("androidx.compose.material3:material3-window-size-class:1.2.0")
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
// Auto updater library
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
implementation("com.github.CSAbhiOnline:AutoUpdater:1.0.1")
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
// Unit Test
|
||||||
|
testImplementation("junit:junit:4.13.2")
|
||||||
|
|
||||||
|
// Android Instrumentation Test
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.5.3")
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
debugImplementation("androidx.compose.ui:ui-tooling:1.5.3")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-test-manifest:1.5.3")
|
||||||
|
|
||||||
|
// Local AAR library
|
||||||
implementation(files("../library/firefly-go.aar"))
|
implementation(files("../library/firefly-go.aar"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package com.example.fireflypsandorid
|
package com.example.fireflypsandorid
|
||||||
|
|
||||||
|
import AutoUpdaterManager
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
@@ -13,7 +16,6 @@ import androidx.compose.foundation.layout.*
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.*
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -21,12 +23,54 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.example.autoupdater.UpdateFeatures
|
||||||
import com.example.fireflypsandorid.ui.theme.FireflyPsAndoridTheme
|
import com.example.fireflypsandorid.ui.theme.FireflyPsAndoridTheme
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.*
|
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.items
|
||||||
|
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() {
|
class MainActivity : ComponentActivity() {
|
||||||
private val tag = "AppInit"
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
@@ -34,121 +78,736 @@ class MainActivity : ComponentActivity() {
|
|||||||
val dataDir = File("$appDataPath/data")
|
val dataDir = File("$appDataPath/data")
|
||||||
dataDir.mkdirs()
|
dataDir.mkdirs()
|
||||||
|
|
||||||
checkAndCreateFile(dataDir, "data-in-game.json", R.raw.data_in_game_json)
|
copyRawToFile(this, dataDir, "data-in-game.json", R.raw.data_in_game_json)
|
||||||
checkAndCreateFile(dataDir, "freesr-data.json", R.raw.freesr_data_json)
|
copyRawToFile(this, dataDir, "freesr-data.json", R.raw.freesr_data_json)
|
||||||
checkAndCreateFile(dataDir, "version.json", R.raw.version_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()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
FireflyPsAndoridTheme {
|
FireflyPsAndoridTheme {
|
||||||
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
|
||||||
ServerControlScreen(appDataPath, Modifier.padding(innerPadding))
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
}
|
ServerControlScreen(appDataPath, dataDir, appVersion, Modifier.padding(innerPadding))
|
||||||
}
|
AutoUpdateDialog(onDismiss = {}, appVersion, true)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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")
|
@SuppressLint("ImplicitSamInstance")
|
||||||
@Composable
|
@Composable
|
||||||
fun ServerControlScreen(appDataPath: String, modifier: Modifier = Modifier) {
|
fun ServerControlScreen(appDataPath: String, dataDir: File, appVersion: AppVersion, modifier: Modifier = Modifier) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val isRunning = GolangServerService.isRunning
|
val isRunning = GolangServerService.isRunning
|
||||||
val serverImage = if (isRunning)
|
|
||||||
painterResource(id = R.drawable.server_running)
|
|
||||||
else
|
|
||||||
painterResource(id = R.drawable.server_stopped)
|
|
||||||
|
|
||||||
Column(
|
var showResetDialog by remember { mutableStateOf(false) }
|
||||||
modifier = modifier
|
var showUpdateDialog by remember { mutableStateOf(false) }
|
||||||
.fillMaxSize()
|
var showLogs by remember { mutableStateOf(false) }
|
||||||
.padding(24.dp),
|
Box(
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
modifier = modifier.fillMaxSize()
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
) {
|
||||||
// Title
|
// Background image
|
||||||
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(
|
Image(
|
||||||
painter = serverImage,
|
painter = painterResource(id = R.drawable.background),
|
||||||
contentDescription = null,
|
contentDescription = "Background",
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize()
|
||||||
.fillMaxWidth()
|
|
||||||
.height(250.dp)
|
|
||||||
.padding(8.dp)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black.copy(alpha = 0.3f))
|
||||||
|
)
|
||||||
|
|
||||||
// Toggle button
|
Column(
|
||||||
Button(
|
modifier = Modifier
|
||||||
onClick = {
|
.fillMaxSize()
|
||||||
try {
|
.padding(24.dp),
|
||||||
if (!isRunning) {
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
val intent = Intent(context, GolangServerService::class.java)
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
intent.putExtra("appDataPath", appDataPath)
|
) {
|
||||||
context.startService(intent)
|
// Title
|
||||||
} else {
|
Text(
|
||||||
context.stopService(Intent(context, GolangServerService::class.java))
|
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()
|
||||||
}
|
}
|
||||||
} 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))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = ButtonDefaults.buttonColors(
|
dismissButton = {
|
||||||
containerColor = if (isRunning) Color(0xFFB71C1C) else Color(0xFF2196F3),
|
TextButton(
|
||||||
contentColor = Color.White
|
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),
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
tonalElevation = 8.dp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.7f)
|
.fillMaxWidth()
|
||||||
.height(50.dp)
|
.fillMaxHeight(0.7f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
text = if (isRunning) "Stop Server" else "Start Server",
|
Text(
|
||||||
fontSize = 20.sp
|
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"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
val hasUpdate = result != null && appVersion.latestVersion != result.latestversion
|
||||||
|
|
||||||
// Server status text
|
update = if (hasUpdate) result else null
|
||||||
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))
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
app/src/main/res/drawable/background.jpg
Normal file
BIN
app/src/main/res/drawable/background.jpg
Normal file
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 |
5
app/src/main/res/raw/app_version_json.json
Normal file
5
app/src/main/res/raw/app_version_json.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"latest_version": "3.5.2-4",
|
||||||
|
"changelog": "UPDATE: Add self update, reset data, logs",
|
||||||
|
"apk_url": "https://git.kain.io.vn/Firefly-Shelter/FireflyGo_Andoid/releases/download/3.5.2-02/firefly_go_android.apk"
|
||||||
|
}
|
||||||
3
app/src/main/res/xml/file_paths.xml
Normal file
3
app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<external-files-path name="downloads" path="Download/"/>
|
||||||
|
</paths>
|
||||||
@@ -15,6 +15,7 @@ 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=true
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# 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
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:abad4850602cbc65eba293b5d11527f6412275c1c7e5fd4e146c59fe3cf6258d
|
oid sha256:de74022be6eac83c759d9e55cd5a9a97575ce8dbb8e5e7ae77efce6e11988c7e
|
||||||
size 73423352
|
size 86853555
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven{
|
||||||
|
url=uri("https://jitpack.io")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user