mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 16:42:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd122631c |
No files matched your search
@@ -19,8 +19,8 @@ android {
|
||||
applicationId = "com.geode.launcher"
|
||||
minSdk = 23
|
||||
targetSdk = 36
|
||||
versionCode = 28
|
||||
versionName = "1.7.5"
|
||||
versionCode = 26
|
||||
versionName = "1.7.3"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
@@ -78,7 +78,7 @@ android {
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
version = "3.21.0+"
|
||||
version = "4.0.0+"
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
@@ -88,18 +88,18 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation (platform("androidx.compose:compose-bom:$composeBOM"))
|
||||
implementation ("androidx.core:core-ktx:1.18.0")
|
||||
implementation ("androidx.core:core-ktx:1.17.0")
|
||||
implementation ("androidx.compose.ui:ui")
|
||||
implementation ("androidx.compose.material3:material3")
|
||||
implementation ("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
|
||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
||||
implementation ("androidx.activity:activity-compose:1.13.0")
|
||||
implementation ("androidx.activity:activity-ktx:1.13.0")
|
||||
implementation ("androidx.activity:activity-compose:1.12.4")
|
||||
implementation ("androidx.activity:activity-ktx:1.12.4")
|
||||
implementation ("androidx.appcompat:appcompat:1.7.1")
|
||||
implementation ("androidx.documentfile:documentfile:1.1.0")
|
||||
|
||||
implementation ("com.squareup.okio:okio:3.17.0")
|
||||
implementation ("com.squareup.okio:okio:3.16.4")
|
||||
implementation ("com.squareup.okhttp3:okhttp:5.3.2")
|
||||
implementation ("com.squareup.okhttp3:okhttp-coroutines:5.3.2")
|
||||
|
||||
@@ -109,7 +109,7 @@ dependencies {
|
||||
|
||||
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.39.2")
|
||||
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.39.2")
|
||||
implementation ("androidx.browser:browser:1.10.0")
|
||||
implementation ("androidx.browser:browser:1.9.0")
|
||||
debugImplementation ("androidx.compose.ui:ui-tooling")
|
||||
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
}
|
||||
@@ -103,7 +103,7 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".preferences.TextViewActivity"
|
||||
android:name=".preferences.GeodeLogViewActivity"
|
||||
android:exported="false"
|
||||
android:theme="@style/Theme.GeodeLauncher">
|
||||
</activity>
|
||||
@@ -195,26 +195,6 @@
|
||||
<data android:scheme="geode-launcher" android:host="main" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias
|
||||
android:name=".MainActivitySapphire"
|
||||
android:enabled="false"
|
||||
android:exported="true"
|
||||
android:icon="@mipmap/ic_launcher_sapphire"
|
||||
android:label="@string/application_icon_sapphire"
|
||||
android:targetActivity=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="geode-launcher" android:host="main" />
|
||||
</intent-filter>
|
||||
</activity-alias>
|
||||
<activity-alias
|
||||
android:name=".MainActivityGeode"
|
||||
android:enabled="false"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
cmake_minimum_required(VERSION 4.1.2)
|
||||
|
||||
project(launcherfix)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
};
|
||||
|
||||
// this is every function that i thought would be relevant
|
||||
constexpr std::array<const std::string_view, 10> remove_symbols{
|
||||
constexpr std::array<const std::string_view, 12> remove_symbols{
|
||||
"__gxx_personality_v0",
|
||||
"__cxa_throw",
|
||||
"__cxa_rethrow",
|
||||
@@ -25,10 +25,9 @@ constexpr std::array<const std::string_view, 10> remove_symbols{
|
||||
"__cxa_guard_abort",
|
||||
"__cxa_guard_acquire",
|
||||
"__cxa_guard_release",
|
||||
"__cxa_free_exception"
|
||||
// conflicts with fix within geode on some devices
|
||||
// "__emutls_get_address", // added in 2.208
|
||||
// "__cxa_thread_atexit"
|
||||
"__cxa_free_exception",
|
||||
"__emutls_get_address", // added in 2.208
|
||||
"__cxa_thread_atexit"
|
||||
|
||||
// android 10's libc doesn't have these symbols
|
||||
// and the ndk is forbidden from exporting them!! so keep it for old mods...
|
||||
|
||||
@@ -24,8 +24,10 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.customRobTop.BaseRobTopActivity
|
||||
import com.customRobTop.JniToCpp
|
||||
import com.geode.launcher.main.FullScreenLoadingIndicator
|
||||
import com.geode.launcher.main.LaunchNotification
|
||||
import com.geode.launcher.main.determineDisplayedCards
|
||||
import com.geode.launcher.utils.Constants
|
||||
@@ -35,13 +37,14 @@ import com.geode.launcher.utils.GamePackageUtils
|
||||
import com.geode.launcher.utils.GeodeUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
|
||||
import org.cocos2dx.lib.Cocos2dxHelper
|
||||
import org.cocos2dx.lib.Cocos2dxRenderer
|
||||
import org.fmod.FMOD
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import javax.microedition.khronos.egl.EGL10
|
||||
import javax.microedition.khronos.egl.EGLConfig
|
||||
@@ -91,9 +94,46 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val rootLayout = setupContext()
|
||||
val loadingView = ComposeView(this).apply {
|
||||
setContent {
|
||||
FullScreenLoadingIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
if (rootLayout != null) {
|
||||
rootLayout.addView(loadingView)
|
||||
} else {
|
||||
setContentView(loadingView)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val error = preloadGame(rootLayout)
|
||||
if (error != null) {
|
||||
returnToMain(error)
|
||||
}
|
||||
|
||||
rootLayout?.removeView(loadingView)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun preloadGame(rootLayout: FrameLayout?): LaunchUtils.FullLauncherError? = try {
|
||||
createVersionFile()
|
||||
tryLoadGame()
|
||||
val loadFailed = tryLoadGame()
|
||||
|
||||
val baseView = createView()
|
||||
|
||||
if (rootLayout != null) {
|
||||
rootLayout.addView(baseView)
|
||||
} else {
|
||||
setContentView(baseView)
|
||||
}
|
||||
|
||||
mGLSurfaceView?.manualBackEvents = true
|
||||
|
||||
setupNotificationView(baseView, loadFailed)
|
||||
|
||||
null
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
Log.e("GeodeLauncher", "Library linkage failure", e)
|
||||
|
||||
@@ -110,78 +150,32 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
else -> LaunchUtils.LauncherError.LINKER_FAILED
|
||||
}
|
||||
|
||||
returnToMain(errorMessage, e.message, e.stackTraceToString())
|
||||
|
||||
return
|
||||
LaunchUtils.FullLauncherError(
|
||||
errorMessage,
|
||||
e.message ?: "Unknown exception",
|
||||
e.stackTraceToString()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
||||
|
||||
returnToMain(
|
||||
LaunchUtils.FullLauncherError(
|
||||
LaunchUtils.LauncherError.GENERIC,
|
||||
e.message ?: "Unknown Exception",
|
||||
e.stackTraceToString()
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
private fun setupContext(): FrameLayout? {
|
||||
Cocos2dxHelper.init(this, this)
|
||||
|
||||
GeodeUtils.setContext(this)
|
||||
GeodeUtils.setCapabilityListener(this)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
mGLSurfaceView?.sendKeyBack()
|
||||
}
|
||||
})
|
||||
mGLSurfaceView?.manualBackEvents = true
|
||||
}
|
||||
|
||||
private fun createVersionFile() {
|
||||
val versionPath = File(filesDir, "game_version.txt")
|
||||
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
|
||||
|
||||
versionPath.writeText("$gameVersion")
|
||||
}
|
||||
|
||||
private fun returnToMain(
|
||||
error: LaunchUtils.LauncherError? = null,
|
||||
returnMessage: String? = null,
|
||||
returnExtendedMessage: String? = null
|
||||
) {
|
||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)!!.apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
if (error != null && !returnMessage.isNullOrEmpty()) {
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, error)
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, returnMessage)
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, returnExtendedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(launchIntent)
|
||||
}
|
||||
|
||||
private fun tryLoadGame() {
|
||||
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
||||
|
||||
setupRedirection(gdPackageInfo)
|
||||
|
||||
Cocos2dxHelper.init(this, this)
|
||||
|
||||
GeodeUtils.setContext(this)
|
||||
GeodeUtils.setCapabilityListener(this)
|
||||
|
||||
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
|
||||
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
|
||||
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
|
||||
// this fix requires geode v3, which is 2.206+
|
||||
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
|
||||
LauncherFix.enableExceptionsRenaming()
|
||||
}
|
||||
|
||||
LauncherFix.performPatches()
|
||||
|
||||
loadInternalMods()
|
||||
|
||||
val baseView = createView()
|
||||
|
||||
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||
val frameLayoutParams = ViewGroup.LayoutParams(
|
||||
@@ -204,15 +198,61 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
WindowInsets.CONSUMED
|
||||
}
|
||||
|
||||
frameLayout.addView(baseView)
|
||||
setContentView(frameLayout)
|
||||
} else {
|
||||
setContentView(baseView)
|
||||
return frameLayout
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun createVersionFile() = runCatching {
|
||||
val gameVersion = GamePackageUtils.getGameVersionCode(packageManager)
|
||||
|
||||
val versionPath = File(filesDir, "game_version.txt")
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
if (versionPath.exists()) {
|
||||
versionPath.delete()
|
||||
}
|
||||
|
||||
versionPath.writeText("$gameVersion")
|
||||
}
|
||||
}
|
||||
|
||||
private fun returnToMain(message: LaunchUtils.FullLauncherError? = null, ) {
|
||||
val launchIntent = Intent(this, MainActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
if (message != null) {
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_ERROR, message.error)
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_MESSAGE, message.returnMessage)
|
||||
putExtra(LaunchUtils.LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE, message.extendedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
startActivity(launchIntent)
|
||||
}
|
||||
|
||||
private suspend fun tryLoadGame(): Boolean {
|
||||
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
||||
setupRedirection(gdPackageInfo)
|
||||
|
||||
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
|
||||
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
|
||||
|
||||
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
|
||||
// this fix requires geode v3, which is 2.206+
|
||||
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
|
||||
LauncherFix.enableExceptionsRenaming()
|
||||
}
|
||||
|
||||
LauncherFix.performPatches()
|
||||
|
||||
loadInternalMods()
|
||||
|
||||
setupPostLibraryLoad(gdPackageInfo)
|
||||
|
||||
val geodeFailed = try {
|
||||
return try {
|
||||
loadGeodeLibrary()
|
||||
false
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
@@ -222,8 +262,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
handleGeodeException(e)
|
||||
true
|
||||
}
|
||||
|
||||
setupNotificationView(baseView, geodeFailed)
|
||||
}
|
||||
|
||||
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
|
||||
@@ -283,7 +321,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
||||
private suspend fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
||||
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
|
||||
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
|
||||
|
||||
@@ -302,7 +340,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
||||
private suspend fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
||||
if (libraryPath.contains("!/")) {
|
||||
// library in apk can't be loaded directly
|
||||
loadLibraryFromAssetsCopy(libraryName)
|
||||
@@ -312,11 +350,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
val library = File(libraryPath)
|
||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||
|
||||
libraryCopy.outputStream().use { libraryOutput ->
|
||||
library.inputStream().use { inputStream ->
|
||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
||||
}
|
||||
}
|
||||
DownloadUtils.copyFile(library, libraryCopy)
|
||||
|
||||
System.load(libraryCopy.path)
|
||||
|
||||
@@ -324,7 +358,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadLibraryFromAssetsCopy(libraryName: String) {
|
||||
private suspend fun loadLibraryFromAssetsCopy(libraryName: String) {
|
||||
// loads a library loaded in assets
|
||||
// this copies the library to a non-compressed directory
|
||||
|
||||
@@ -339,11 +373,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
// there doesn't seem to be a way to load a library from a file descriptor
|
||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
libraryCopy.outputStream().use { libraryOutput ->
|
||||
libraryFd.createInputStream().use { inputStream ->
|
||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.load(libraryCopy.path)
|
||||
|
||||
@@ -376,7 +412,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadGeodeLibrary() {
|
||||
private suspend fun loadGeodeLibrary() {
|
||||
// Load Geode if exists
|
||||
// bundling the object with the application allows for nicer backtraces
|
||||
try {
|
||||
@@ -397,22 +433,23 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
// also i get 20 million permission denied errors
|
||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
||||
|
||||
val copiedPath = File(filesDir.path, "copied")
|
||||
val copiedPath = File(filesDir, "copied")
|
||||
if (copiedPath.exists()) {
|
||||
copiedPath.deleteRecursively()
|
||||
}
|
||||
copiedPath.mkdir()
|
||||
|
||||
val copiedGeodePath = File(copiedPath.path, "Geode.so")
|
||||
val copiedGeodePath = File(copiedPath, "Geode.so")
|
||||
|
||||
if (externalGeodePath.exists()) {
|
||||
DownloadUtils.copyFile(
|
||||
FileInputStream(externalGeodePath),
|
||||
FileOutputStream(copiedGeodePath)
|
||||
externalGeodePath,
|
||||
copiedGeodePath
|
||||
)
|
||||
|
||||
if (copiedGeodePath.exists()) {
|
||||
println("Loading Geode from ${externalGeodePath.name}")
|
||||
|
||||
System.load(copiedGeodePath.path)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import android.text.format.Formatter
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
@@ -72,7 +70,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalResources
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -85,14 +82,11 @@ import com.geode.launcher.ui.theme.Theme
|
||||
import com.geode.launcher.ui.theme.Theme.DARK
|
||||
import com.geode.launcher.ui.theme.Theme.LIGHT
|
||||
import com.geode.launcher.ui.theme.launcherTitleStyle
|
||||
import com.geode.launcher.ui.theme.sapphireTitleStyle
|
||||
import com.geode.launcher.updater.ReleaseManager
|
||||
import com.geode.launcher.utils.ApplicationIcon
|
||||
import com.geode.launcher.utils.Constants
|
||||
import com.geode.launcher.utils.GamePackageUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import com.geode.launcher.utils.checkIconDate
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.ConnectException
|
||||
@@ -458,76 +452,36 @@ fun ExtraOptions(onSettings: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
data class ColorPalette(
|
||||
val outerColors: List<Pair<Float, Color>>,
|
||||
val innerColors: List<Pair<Float, Color>>,
|
||||
)
|
||||
|
||||
data class AnimatedIconSpec(
|
||||
val lightPalette: ColorPalette,
|
||||
val darkPalette: ColorPalette,
|
||||
|
||||
@param:DrawableRes val innerId: Int,
|
||||
@param:DrawableRes val outerId: Int,
|
||||
)
|
||||
|
||||
data class BrandPalette(
|
||||
@param:DrawableRes val darkLogo: Int,
|
||||
@param:DrawableRes val lightLogo: Int,
|
||||
|
||||
@param:StringRes val title: Int,
|
||||
val titleFont: TextStyle,
|
||||
val animatedIcon: AnimatedIconSpec? = null,
|
||||
)
|
||||
|
||||
val geodeColorPalette = BrandPalette(
|
||||
animatedIcon = AnimatedIconSpec(
|
||||
lightPalette = ColorPalette(
|
||||
outerColors = listOf(
|
||||
val lightOuterColorGradient = arrayOf(
|
||||
0.0f to Color(0xfff5b11d),
|
||||
0.10f to Color(0xfff0834d),
|
||||
0.24f to Color(0xffe85f6b),
|
||||
0.49f to Color(0xffc9659e),
|
||||
1.0f to Color(0xffb9588f)
|
||||
),
|
||||
innerColors = listOf(
|
||||
)
|
||||
|
||||
val lightInnerColorGradient = arrayOf(
|
||||
0.48f to Color(0xfff2ce00),
|
||||
0.63f to Color(0xfff4b97e),
|
||||
0.73f to Color(0xfff7b66a),
|
||||
1.0f to Color(0xffeb8fac)
|
||||
)
|
||||
),
|
||||
darkPalette = ColorPalette(
|
||||
outerColors = listOf(
|
||||
|
||||
val darkOuterColorGradient = arrayOf(
|
||||
0.0f to Color(0xfffffdff),
|
||||
0.08f to Color(0xffF2EAF5),
|
||||
0.12f to Color(0xffEDE5EF),
|
||||
0.31f to Color(0xffCDB5CD),
|
||||
0.49f to Color(0xffBA9BBC),
|
||||
1.0f to Color(0xff8D7ACF),
|
||||
),
|
||||
innerColors = listOf(
|
||||
)
|
||||
|
||||
val darkInnerColorGradient = arrayOf(
|
||||
0.48f to Color(0xffffffff),
|
||||
0.63f to Color(0xfff5e4c2),
|
||||
0.73f to Color(0xffe5c7ad),
|
||||
1.0f to Color(0xffb790a9),
|
||||
)
|
||||
),
|
||||
innerId = R.drawable.geode_monochrome_inner,
|
||||
outerId = R.drawable.geode_monochrome_outer,
|
||||
),
|
||||
darkLogo = R.drawable.geode_base,
|
||||
lightLogo = R.drawable.geode_base_light,
|
||||
title = R.string.launcher_title,
|
||||
titleFont = launcherTitleStyle
|
||||
)
|
||||
|
||||
val sapphireColorPalette = BrandPalette(
|
||||
darkLogo = R.drawable.sapphire_full,
|
||||
lightLogo = R.drawable.sapphire_full,
|
||||
title = R.string.application_icon_sapphire,
|
||||
titleFont = sapphireTitleStyle
|
||||
)
|
||||
|
||||
@Preview(name = "non-animated, light")
|
||||
@Preview(name = "non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||
@@ -551,49 +505,22 @@ fun GeodeLogoAnimatedPreview() {
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme) {
|
||||
Surface {
|
||||
GeodeLogo(shouldAnimate = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "sapphire, non-animated, light")
|
||||
@Preview(name = "sapphire, non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun SapphireLogoPreview() {
|
||||
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme) {
|
||||
Surface {
|
||||
GeodeLogo(shouldAnimate = false, basePalette = sapphireColorPalette)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "sapphire, animated, light")
|
||||
@Preview(name = "sapphire, animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun SapphireLogoAnimatedPreview() {
|
||||
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme) {
|
||||
Surface {
|
||||
GeodeLogo(shouldAnimate = true, basePalette = sapphireColorPalette)
|
||||
GeodeLogo(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||
fun AnimatedLogo(modifier: Modifier = Modifier) {
|
||||
val theme = LocalTheme.current
|
||||
val innerColorPalette = remember(theme) {
|
||||
if (theme == LIGHT) lightInnerColorGradient else darkInnerColorGradient
|
||||
}
|
||||
|
||||
val colorPalette = if (theme == LIGHT) spec.lightPalette
|
||||
else spec.darkPalette
|
||||
|
||||
val innerColorPalette = colorPalette.innerColors
|
||||
val outerColorPalette = colorPalette.outerColors
|
||||
val outerColorPalette = remember(theme) {
|
||||
if (theme == LIGHT) lightOuterColorGradient else darkOuterColorGradient
|
||||
}
|
||||
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
|
||||
val offset by infiniteTransition.animateFloat(
|
||||
@@ -607,14 +534,14 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||
)
|
||||
|
||||
val innerBrush = Brush.linearGradient(
|
||||
colorStops = innerColorPalette.toTypedArray(),
|
||||
colorStops = innerColorPalette,
|
||||
start = Offset(0.0f, 0.0f + (165.06f * offset)),
|
||||
end = Offset(0.0f, 165.06f + (165.06f * offset)),
|
||||
tileMode = TileMode.Mirror
|
||||
)
|
||||
|
||||
val outerBrush = Brush.linearGradient(
|
||||
colorStops = outerColorPalette.toTypedArray(),
|
||||
colorStops = outerColorPalette,
|
||||
start = Offset(0.0f + (84.69f * offset), 0.0f + (165.06f * offset)),
|
||||
end = Offset(84.69f + (84.69f * offset), 169.61f + (165.06f * offset)),
|
||||
tileMode = TileMode.Mirror
|
||||
@@ -622,7 +549,7 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||
|
||||
Box(modifier = modifier) {
|
||||
Icon(
|
||||
painter = painterResource(spec.innerId),
|
||||
painter = painterResource(id = R.drawable.geode_monochrome_inner),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||
@@ -633,10 +560,9 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||
blendMode = BlendMode.SrcIn
|
||||
)
|
||||
}
|
||||
.size(64.dp, 64.dp)
|
||||
)
|
||||
Icon(
|
||||
painter = painterResource(spec.outerId),
|
||||
painter = painterResource(id = R.drawable.geode_monochrome_outer),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||
@@ -647,50 +573,39 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||
blendMode = BlendMode.SrcIn
|
||||
)
|
||||
}
|
||||
.size(64.dp, 64.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun GeodeLogo(modifier: Modifier = Modifier, shouldAnimate: Boolean = false, basePalette: BrandPalette = geodeColorPalette) {
|
||||
FlowRow(
|
||||
fun GeodeLogo(shouldAnimate: Boolean = false, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
itemVerticalAlignment = Alignment.CenterVertically,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
) {
|
||||
val theme = LocalTheme.current
|
||||
|
||||
if (basePalette.animatedIcon != null) {
|
||||
Crossfade(
|
||||
targetState = shouldAnimate,
|
||||
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
||||
label="logo fade"
|
||||
) { screen ->
|
||||
when (screen) {
|
||||
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp), spec = basePalette.animatedIcon)
|
||||
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp))
|
||||
false -> Image(
|
||||
painterResource(if (theme == LIGHT)
|
||||
basePalette.lightLogo else basePalette.darkLogo
|
||||
R.drawable.geode_base_light else R.drawable.geode_base
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(64.dp, 64.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image(
|
||||
painterResource(if (theme == LIGHT)
|
||||
basePalette.lightLogo else basePalette.darkLogo
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(84.dp, 84.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(basePalette.title),
|
||||
style = basePalette.titleFont,
|
||||
stringResource(R.string.launcher_title),
|
||||
style = launcherTitleStyle,
|
||||
fontSize = 64.sp,
|
||||
modifier = Modifier
|
||||
.padding(12.dp)
|
||||
@@ -778,14 +693,7 @@ fun AltMainScreen(
|
||||
.padding(8.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
val sapphireLogo = remember {
|
||||
checkIconDate() || PreferenceUtils.get(context)
|
||||
.getString(PreferenceUtils.Key.SELECTED_ICON) == ApplicationIcon.SAPPHIRE.toId()
|
||||
}
|
||||
|
||||
val basePalette = if (sapphireLogo) sapphireColorPalette
|
||||
else geodeColorPalette
|
||||
GeodeLogo(shouldAnimate = launchUIState.isInProgress(), basePalette = basePalette)
|
||||
GeodeLogo(shouldAnimate = launchUIState.isInProgress())
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
|
||||
@@ -13,8 +13,7 @@ import kotlin.time.Instant
|
||||
|
||||
data class CrashDump(
|
||||
val filename: String,
|
||||
val lastModified: Instant,
|
||||
val fullPath: String,
|
||||
val lastModified: Instant
|
||||
)
|
||||
|
||||
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
||||
@@ -42,19 +41,16 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
||||
|
||||
val children = crashDirectory.listFiles {
|
||||
// ignore indicator files (including old file)
|
||||
_, name -> !LaunchUtils.CRASH_INDICATOR_NAMES.contains(name)
|
||||
_, name -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
||||
} ?: return emptyList()
|
||||
|
||||
hasIndicator = LaunchUtils.CRASH_INDICATOR_NAMES.any {
|
||||
File(crashDirectory, it).exists()
|
||||
}
|
||||
hasIndicator = File(crashDirectory, LaunchUtils.CRASH_INDICATOR_NAME).exists()
|
||||
|
||||
return children
|
||||
.map {
|
||||
CrashDump(
|
||||
filename = it.name,
|
||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||
fullPath = it.path,
|
||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified())
|
||||
)
|
||||
}
|
||||
.sortedByDescending { it.lastModified }
|
||||
@@ -62,12 +58,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
||||
|
||||
fun clearIndicator() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
LaunchUtils.CRASH_INDICATOR_NAMES.forEach {
|
||||
val crashFile = File(baseDirectory, it)
|
||||
if (crashFile.exists()) {
|
||||
crashFile.delete()
|
||||
}
|
||||
}
|
||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).delete()
|
||||
|
||||
hasIndicator = false
|
||||
}
|
||||
@@ -75,8 +66,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
||||
|
||||
fun createIndicator() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val crashFile = File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAMES.first())
|
||||
crashFile.createNewFile()
|
||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).createNewFile()
|
||||
|
||||
hasIndicator = true
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ data class GeodeLog(
|
||||
val filename: String,
|
||||
val lastModified: Instant,
|
||||
val fileSize: Long,
|
||||
val fullPath: String,
|
||||
)
|
||||
|
||||
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
||||
@@ -44,8 +43,7 @@ class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog
|
||||
GeodeLog(
|
||||
filename = it.name,
|
||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||
fileSize = it.length(),
|
||||
fullPath = it.path,
|
||||
fileSize = it.length()
|
||||
)
|
||||
}
|
||||
.sortedByDescending { it.lastModified }
|
||||
|
||||
@@ -55,14 +55,11 @@ import com.geode.launcher.preferences.ApplicationLogsActivity
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.UserDirectoryProvider
|
||||
import com.geode.launcher.preferences.GeodeLogsListingActivity
|
||||
import com.geode.launcher.preferences.TextViewActivity
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.utils.GamePackageUtils
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
|
||||
data class LoadFailureInfo(
|
||||
val title: LaunchUtils.LauncherError,
|
||||
@@ -140,28 +137,14 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
|
||||
|
||||
}
|
||||
|
||||
fun findSomethingForCrash(context: Context): File? {
|
||||
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||
if (lastCrash != null) {
|
||||
return lastCrash
|
||||
}
|
||||
|
||||
val logsDirectory = LaunchUtils.getGeodeLogsDirectory(context)
|
||||
if (!logsDirectory.exists()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return logsDirectory.listFiles()?.maxByOrNull { it.lastModified() }
|
||||
}
|
||||
|
||||
fun onShareCrash(context: Context) {
|
||||
val lastCrash = findSomethingForCrash(context)
|
||||
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||
if (lastCrash == null) {
|
||||
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||
|
||||
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||
@@ -180,19 +163,6 @@ fun onShareCrash(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
fun openLastCrash(context: Context) {
|
||||
val lastCrash = findSomethingForCrash(context)
|
||||
if (lastCrash == null) {
|
||||
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||
return
|
||||
}
|
||||
|
||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, lastCrash.path)
|
||||
}
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
|
||||
fun onShowLogs(context: Context) {
|
||||
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
||||
context.startActivity(launchIntent)
|
||||
@@ -285,10 +255,6 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
||||
Text(stringResource(R.string.launcher_error_copy))
|
||||
}
|
||||
} else {
|
||||
val crashFile = remember { findSomethingForCrash(context) }
|
||||
val readableCrash = remember { crashFile?.extension == "log" }
|
||||
|
||||
if (crashFile != null) {
|
||||
Button(onClick = { onShareCrash(context) }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_share),
|
||||
@@ -298,21 +264,11 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
||||
Text(stringResource(R.string.launcher_error_export_crash))
|
||||
}
|
||||
|
||||
if (readableCrash) {
|
||||
FilledTonalButton(onClick = { openLastCrash(context) }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_description),
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||
Text(stringResource(R.string.launcher_error_view_details))
|
||||
val developerSettingsEnabled = remember {
|
||||
PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.DEVELOPER_MODE)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Button(onClick = {
|
||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
||||
context.startActivity(launchIntent)
|
||||
}) {
|
||||
if (developerSettingsEnabled) {
|
||||
FilledTonalButton(onClick = { onShowLogs(context) }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_description),
|
||||
contentDescription = null
|
||||
|
||||
@@ -10,13 +10,16 @@ import androidx.compose.animation.shrinkHorizontally
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
@@ -120,9 +123,10 @@ fun LaunchNotification(cards: List<LaunchNotificationType>) {
|
||||
val theme = Theme.fromInt(themeOption)
|
||||
|
||||
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
||||
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
|
||||
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
|
||||
// surface is not in use, so this is unfortunately not provided
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column(
|
||||
@@ -280,3 +284,26 @@ fun ForwardCompatibilityContent(modifier: Modifier = Modifier) {
|
||||
modifier = modifier.width(IntrinsicSize.Max)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FullScreenLoadingIndicator() {
|
||||
val theme = Theme.DARK
|
||||
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||
|
||||
CompositionLocalProvider(LocalTheme provides theme) {
|
||||
GeodeLauncherTheme(theme = theme, blackBackground = true, dynamicColor = !dynamicColorOption) {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
var showProgress by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
delay(1000L)
|
||||
showProgress = true
|
||||
}
|
||||
|
||||
AnimatedVisibility(showProgress) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,6 @@ import com.geode.launcher.utils.ApplicationIcon
|
||||
import com.geode.launcher.utils.ApplicationIconDetails
|
||||
import com.geode.launcher.utils.IconUtils
|
||||
import com.geode.launcher.utils.adaptiveIconPainterResource
|
||||
import com.geode.launcher.utils.checkIconDate
|
||||
|
||||
class ApplicationIconActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -232,18 +231,15 @@ fun ApplicationIconScreen(
|
||||
) {
|
||||
val selectedIcon by PreferenceUtils.useStringPreference(PreferenceUtils.Key.SELECTED_ICON)
|
||||
|
||||
val aprilFools = remember { checkIconDate() }
|
||||
|
||||
Column(Modifier
|
||||
.padding(horizontal = 12.dp)
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
) {
|
||||
ApplicationIcon.entries.forEach {
|
||||
val iconData = IconUtils.getIconDetails(it)
|
||||
if (!iconData.checkDate || aprilFools) {
|
||||
|
||||
IconCard(iconData, selectedIcon == iconData.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -84,7 +83,7 @@ class CrashLogsActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
fun shareCrash(context: Context, filename: String) {
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
||||
|
||||
if (!crashPath.exists()) {
|
||||
@@ -111,20 +110,11 @@ fun shareCrash(context: Context, filename: String) {
|
||||
|
||||
@Composable
|
||||
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
||||
val isReadable = crashDump.filename.endsWith(".log")
|
||||
val context = LocalContext.current
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 1.dp, horizontal = 8.dp)
|
||||
.height(64.dp)
|
||||
.then(modifier)
|
||||
.clickable(isReadable) {
|
||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, crashDump.fullPath)
|
||||
}
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||
) {
|
||||
|
||||
@@ -197,6 +197,20 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
||||
onClick = { onOpenLogs(context) }
|
||||
)
|
||||
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_view_geode_logs),
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_description),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
)
|
||||
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_copy_external_button),
|
||||
description = LaunchUtils.getBaseDirectory(context).path,
|
||||
|
||||
+7
-52
@@ -1,11 +1,7 @@
|
||||
package com.geode.launcher.preferences
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.DocumentsContract
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.OnBackPressedDispatcher
|
||||
import androidx.activity.compose.setContent
|
||||
@@ -16,6 +12,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
@@ -38,27 +35,23 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.UserDirectoryProvider
|
||||
import com.geode.launcher.log.GeodeLogsViewModel
|
||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
import com.geode.launcher.ui.theme.LocalTheme
|
||||
import com.geode.launcher.ui.theme.Theme
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.ui.theme.robotoMonoFamily
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
|
||||
class TextViewActivity : ComponentActivity() {
|
||||
class GeodeLogViewActivity : ComponentActivity() {
|
||||
companion object {
|
||||
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
||||
}
|
||||
@@ -97,48 +90,19 @@ class TextViewActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun onShareFile(context: Context, filename: File) {
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||
|
||||
val providerLocation = filename.toRelativeString(baseDirectory)
|
||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||
|
||||
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", documentsPath)
|
||||
|
||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
type = "application/octet-stream"
|
||||
}
|
||||
|
||||
try {
|
||||
context.startActivity(shareIntent)
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.no_activity_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun GeodeLogView(
|
||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||
filename: String,
|
||||
geodeLogsViewModel: GeodeLogsViewModel = viewModel(factory = GeodeLogsViewModel.Factory)
|
||||
) {
|
||||
var loadingFile by remember { mutableStateOf(true) }
|
||||
var description by remember { mutableStateOf(emptyList<String>()) }
|
||||
|
||||
val toRead = File(filename)
|
||||
|
||||
LaunchedEffect(filename) {
|
||||
loadingFile = true
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
if (!toRead.isFile) {
|
||||
description = listOf("File does not exist!")
|
||||
} else {
|
||||
description = toRead.readLines()
|
||||
}
|
||||
}
|
||||
|
||||
description = geodeLogsViewModel.getFileText(filename)
|
||||
loadingFile = false
|
||||
}
|
||||
|
||||
@@ -159,21 +123,12 @@ fun GeodeLogView(
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
toRead.name,
|
||||
filename,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
},
|
||||
scrollBehavior = scrollBehavior,
|
||||
actions = {
|
||||
val context = LocalContext.current
|
||||
IconButton(onClick = { onShareFile(context, toRead) }) {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_share),
|
||||
contentDescription = stringResource(R.string.application_logs_share)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (loadingFile) {
|
||||
@@ -80,7 +80,7 @@ class GeodeLogsListingActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
fun shareLog(context: Context, filename: String) {
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||
val crashPath = File(LaunchUtils.getGeodeLogsDirectory(context), filename)
|
||||
|
||||
if (!crashPath.exists()) {
|
||||
@@ -117,8 +117,8 @@ fun GeodeLogCard(geodeLog: GeodeLog, geodeLogsViewModel: GeodeLogsViewModel, mod
|
||||
.height(64.dp)
|
||||
.then(modifier)
|
||||
.clickable {
|
||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.fullPath)
|
||||
val launchIntent = Intent(context, GeodeLogViewActivity::class.java).run {
|
||||
putExtra(GeodeLogViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.filename)
|
||||
}
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ fun GameplaySettingsGroup() {
|
||||
SafeModeDialog(onDismiss = {
|
||||
showSafeModeDialog = false
|
||||
}) {
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!.apply {
|
||||
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||
// hardcode it, why not
|
||||
action = Intent.ACTION_VIEW
|
||||
data = "geode-launcher://main/launch?safe-mode=true".toUri()
|
||||
@@ -581,20 +581,6 @@ fun AdvancedSettingsGroup(developerModeEnabled: Boolean, snackbarHostState: Snac
|
||||
)
|
||||
}
|
||||
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_view_geode_logs),
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_description),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
onClick = {
|
||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
||||
context.startActivity(launchIntent)
|
||||
}
|
||||
)
|
||||
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_view_crashes),
|
||||
icon = {
|
||||
|
||||
@@ -16,20 +16,9 @@ val quicksandFamily = FontFamily(
|
||||
Font(R.font.quicksand_semibold, FontWeight.SemiBold)
|
||||
)
|
||||
|
||||
val googleSansFamily = FontFamily(
|
||||
Font(R.font.googlesans_medium, FontWeight.Medium)
|
||||
)
|
||||
|
||||
val launcherTitleStyle = TextStyle(
|
||||
fontFamily = quicksandFamily,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 32.sp,
|
||||
letterSpacing = 0.sp
|
||||
)
|
||||
|
||||
val sapphireTitleStyle = TextStyle(
|
||||
fontFamily = googleSansFamily,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 32.sp,
|
||||
letterSpacing = 0.sp
|
||||
)
|
||||
@@ -62,11 +62,6 @@ abstract class Downloadable {
|
||||
* Release download link for the current platform
|
||||
*/
|
||||
abstract fun getDownload(): DownloadableAsset?
|
||||
|
||||
/**
|
||||
* Resources download link for current platform.
|
||||
*/
|
||||
abstract fun getResourcesDownload(): DownloadableAsset?
|
||||
}
|
||||
|
||||
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
||||
@@ -106,18 +101,6 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset? {
|
||||
val download = release.assets.find {
|
||||
it.name == "resources.zip"
|
||||
} ?: return null
|
||||
|
||||
return DownloadableAsset(
|
||||
url = download.browserDownloadUrl,
|
||||
filename = download.name,
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
@@ -143,7 +126,8 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
* 0 | 0 | 1
|
||||
* surprise! it's an xnor
|
||||
*/
|
||||
asset.name.endsWith("apk") && asset.name.contains("android32") == use32BitPlatform
|
||||
|
||||
(asset.name.contains("android32")) == use32BitPlatform
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +139,6 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||
size = download.size.toLong()
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
||||
@@ -171,19 +151,11 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
|
||||
return version.createdAt.epochSeconds
|
||||
}
|
||||
|
||||
override fun getDownload(): DownloadableAsset {
|
||||
override fun getDownload(): DownloadableAsset? {
|
||||
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
||||
return DownloadableAsset(
|
||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
||||
filename = filename
|
||||
)
|
||||
}
|
||||
|
||||
override fun getResourcesDownload(): DownloadableAsset {
|
||||
val filename = "resources.zip"
|
||||
return DownloadableAsset(
|
||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/resources.zip",
|
||||
filename = filename
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -18,16 +18,12 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import okio.HashingSink
|
||||
import okio.blackholeSink
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.InterruptedIOException
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@@ -98,10 +94,7 @@ class ReleaseManager private constructor(
|
||||
|
||||
var dismissedLauncherUpdate = false
|
||||
|
||||
var skipStateUpdate = false
|
||||
|
||||
private fun sendError(e: Exception) {
|
||||
skipStateUpdate = true
|
||||
_uiState.value = ReleaseManagerState.Failure(e)
|
||||
|
||||
// ignore cancellation, it's good actually
|
||||
@@ -173,59 +166,7 @@ class ReleaseManager private constructor(
|
||||
return Result.success(outputFile)
|
||||
}
|
||||
|
||||
private suspend fun performResourceDownload(resourceAsset: DownloadableAsset, initialSize: Long): Boolean {
|
||||
val guessSize = if (resourceAsset.size == null) null else initialSize + resourceAsset.size
|
||||
val guessInitial = if (guessSize == null) 0 else initialSize
|
||||
_uiState.value = ReleaseManagerState.InDownload(guessInitial, guessSize)
|
||||
|
||||
val outputDir = getTempResourcesDirectory()
|
||||
val outputPath = outputDir.path
|
||||
|
||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
||||
|
||||
try {
|
||||
if (!outputDir.exists()) {
|
||||
outputDir.mkdirs()
|
||||
}
|
||||
|
||||
DownloadUtils.downloadStream(
|
||||
httpClient,
|
||||
resourceAsset.url,
|
||||
onProgress = { progress, outOf ->
|
||||
if (!skipStateUpdate)
|
||||
_uiState.value = ReleaseManagerState.InDownload(initialSize + progress, outOf + initialSize)
|
||||
},
|
||||
onResponse = { body ->
|
||||
DownloadUtils.copyZipStreamToDirectory(
|
||||
body.byteStream(),
|
||||
outputDir
|
||||
)
|
||||
|
||||
if (finalDir.exists()) {
|
||||
finalDir.deleteRecursively()
|
||||
}
|
||||
|
||||
if (!outputDir.renameTo(finalDir)) {
|
||||
throw IOException("failed to rename resources output directory")
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
sendError(e)
|
||||
return false
|
||||
} finally {
|
||||
val tempPathClone = File(outputPath)
|
||||
runCatching {
|
||||
if (tempPathClone.exists()) tempPathClone.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private suspend fun performUpdate(release: Downloadable) {
|
||||
skipStateUpdate = false
|
||||
|
||||
val releaseAsset = release.getDownload()
|
||||
if (releaseAsset == null) {
|
||||
val noAssetException = Exception("missing Android download")
|
||||
@@ -234,14 +175,8 @@ class ReleaseManager private constructor(
|
||||
return
|
||||
}
|
||||
|
||||
val resourcesAsset = release.getResourcesDownload()
|
||||
|
||||
var releaseSize = releaseAsset.size ?: 0L
|
||||
val resourcesSize = resourcesAsset?.size ?: 0L
|
||||
|
||||
// set an initial download size
|
||||
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
|
||||
_uiState.value = ReleaseManagerState.InDownload(0, initialSize)
|
||||
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
|
||||
|
||||
val outputFile = getTempFile()
|
||||
// clone the file instance as renameTo may move the original file
|
||||
@@ -260,10 +195,7 @@ class ReleaseManager private constructor(
|
||||
httpClient,
|
||||
releaseAsset.url,
|
||||
onProgress = { progress, outOf ->
|
||||
if (!skipStateUpdate)
|
||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
||||
|
||||
releaseSize = max(outOf, releaseSize)
|
||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
||||
},
|
||||
onResponse = { body ->
|
||||
DownloadUtils.extractFileFromZipStream(
|
||||
@@ -297,12 +229,6 @@ class ReleaseManager private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
if (resourcesAsset != null) {
|
||||
if (!performResourceDownload(resourcesAsset, releaseSize)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// extraction performed
|
||||
updatePreferences(release)
|
||||
|
||||
@@ -323,9 +249,10 @@ class ReleaseManager private constructor(
|
||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||
|
||||
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
||||
?: return true
|
||||
?: return false
|
||||
|
||||
val currentFileHash = computeFileHash(geodeFile)
|
||||
|
||||
return originalFileHash != currentFileHash
|
||||
}
|
||||
|
||||
@@ -457,14 +384,8 @@ class ReleaseManager private constructor(
|
||||
sharedPreferences.setString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED, fileHash)
|
||||
}
|
||||
|
||||
private fun computeFileHash(file: File): String {
|
||||
val sink = HashingSink.md5(blackholeSink())
|
||||
file.source().buffer().use {
|
||||
it.readAll(sink)
|
||||
}
|
||||
|
||||
return sink.hash.hex()
|
||||
}
|
||||
private fun computeFileHash(file: File): String =
|
||||
file.source().buffer().use { it.readByteString().md5().hex() }
|
||||
|
||||
private fun getGeodeOutputPath(): File {
|
||||
val geodeName = LaunchUtils.geodeFilename
|
||||
@@ -473,13 +394,6 @@ class ReleaseManager private constructor(
|
||||
return File(geodeDirectory, geodeName)
|
||||
}
|
||||
|
||||
private fun createRandomString(): String {
|
||||
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
return buildString(8) {
|
||||
repeat(8) { append(alphabet.random()) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTempFile(): File {
|
||||
val geodeName = LaunchUtils.geodeFilename
|
||||
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
||||
@@ -487,7 +401,11 @@ class ReleaseManager private constructor(
|
||||
// warning!! while File::createTempFile may look tempting, a certain brand of phones has a messed up implementation of it
|
||||
// so we're making a temp file manually (as long as it doesn't collide with the geode download, it's okay)
|
||||
|
||||
val suffix = createRandomString()
|
||||
val alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
||||
val suffix = buildString(8) {
|
||||
repeat(8) { append(alphabet.random()) }
|
||||
}
|
||||
|
||||
val tmpName = "tmp-$suffix.$geodeName"
|
||||
|
||||
val tempFile = File(geodeDirectory, tmpName)
|
||||
@@ -495,19 +413,6 @@ class ReleaseManager private constructor(
|
||||
return tempFile
|
||||
}
|
||||
|
||||
private fun getTempResourcesDirectory(): File {
|
||||
val finalDir = LaunchUtils.getGeodeResourcesDirectory(applicationContext)
|
||||
val suffix = createRandomString()
|
||||
val tempDirName = "tmp-$suffix-geode.loader"
|
||||
|
||||
val parentDir = finalDir.parentFile
|
||||
return if (parentDir != null) {
|
||||
File(finalDir.parentFile, tempDirName)
|
||||
} else {
|
||||
File(tempDirName)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the current update job.
|
||||
*/
|
||||
|
||||
@@ -17,7 +17,7 @@ object Constants {
|
||||
const val SUPPORTED_VERSION_CODE_MIN = 37L
|
||||
|
||||
// anything below this version code shows a warning on the main menu
|
||||
const val SUPPORTED_VERSION_CODE_MIN_WARNING = 41L
|
||||
const val SUPPORTED_VERSION_CODE_MIN_WARNING = 40L
|
||||
|
||||
// anything above this version code will show a warning when starting the game
|
||||
const val SUPPORTED_VERSION_CODE = 41L
|
||||
|
||||
@@ -72,35 +72,6 @@ object DownloadUtils {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun copyZipStreamToDirectory(inputStream: InputStream, output: File) = runInterruptible {
|
||||
ZipInputStream(inputStream).use { zipStream ->
|
||||
if (output.exists()) output.deleteRecursively()
|
||||
output.mkdirs()
|
||||
|
||||
val canonicalOutput = output.canonicalPath
|
||||
|
||||
var entry = zipStream.nextEntry
|
||||
while (entry != null) {
|
||||
val destination = File(output, entry.name)
|
||||
if (!destination.canonicalPath.startsWith(canonicalOutput)) {
|
||||
throw IOException("attempted copy to outside of output directory: ${entry.name}")
|
||||
}
|
||||
|
||||
if (destination.isDirectory) {
|
||||
destination.mkdirs()
|
||||
} else {
|
||||
destination.parentFile?.mkdirs()
|
||||
|
||||
destination.outputStream().use { destinationStream ->
|
||||
zipStream.copyTo(destinationStream)
|
||||
}
|
||||
}
|
||||
|
||||
entry = zipStream.nextEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a file named by zipPath from inputStream and copies it to outputStream
|
||||
*
|
||||
|
||||
@@ -15,10 +15,6 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import com.geode.launcher.R
|
||||
import kotlinx.datetime.Month
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.datetime.todayIn
|
||||
import kotlin.time.Clock
|
||||
|
||||
// https://gist.github.com/tkuenneth/ddf598663f041dc79960cda503d14448?permalink_comment_id=4660486#gistcomment-4660486
|
||||
@Composable
|
||||
@@ -47,40 +43,31 @@ enum class ApplicationIcon {
|
||||
DEFAULT,
|
||||
GEODE,
|
||||
PRIDE,
|
||||
TRANS,
|
||||
SAPPHIRE;
|
||||
TRANS;
|
||||
|
||||
companion object {
|
||||
fun fromId(id: String) = when (id) {
|
||||
"default" -> DEFAULT
|
||||
"geode" -> GEODE
|
||||
"pride" -> PRIDE
|
||||
"trans" -> TRANS
|
||||
"sapphire" -> SAPPHIRE
|
||||
else -> DEFAULT
|
||||
"default" -> ApplicationIcon.DEFAULT
|
||||
"geode" -> ApplicationIcon.GEODE
|
||||
"pride" -> ApplicationIcon.PRIDE
|
||||
"trans" -> ApplicationIcon.TRANS
|
||||
else -> ApplicationIcon.DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
fun toId() = when (this) {
|
||||
PRIDE -> "pride"
|
||||
GEODE -> "geode"
|
||||
DEFAULT -> "default"
|
||||
TRANS -> "trans"
|
||||
SAPPHIRE -> "sapphire"
|
||||
ApplicationIcon.PRIDE -> "pride"
|
||||
ApplicationIcon.GEODE -> "geode"
|
||||
ApplicationIcon.DEFAULT -> "default"
|
||||
ApplicationIcon.TRANS -> "trans"
|
||||
}
|
||||
}
|
||||
|
||||
fun checkIconDate(): Boolean {
|
||||
val currentDate = Clock.System.todayIn(TimeZone.currentSystemDefault())
|
||||
return currentDate.month == Month.APRIL && currentDate.day <= 7
|
||||
}
|
||||
|
||||
data class ApplicationIconDetails(
|
||||
val id: String,
|
||||
val component: String,
|
||||
@param:DrawableRes val iconId: Int,
|
||||
@param:StringRes val nameId: Int,
|
||||
val checkDate: Boolean = false,
|
||||
)
|
||||
|
||||
object IconUtils {
|
||||
@@ -109,12 +96,5 @@ object IconUtils {
|
||||
iconId = R.mipmap.ic_launcher_trans,
|
||||
nameId = R.string.application_icon_trans
|
||||
)
|
||||
ApplicationIcon.SAPPHIRE -> ApplicationIconDetails(
|
||||
id = id.toId(),
|
||||
component = "MainActivitySapphire",
|
||||
iconId = R.mipmap.ic_launcher_sapphire,
|
||||
nameId = R.string.application_icon_sapphire,
|
||||
checkDate = true
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ object LaunchUtils {
|
||||
return File(getBaseDirectory(context), "save")
|
||||
}
|
||||
|
||||
val CRASH_INDICATOR_NAMES = setOf("lastSessionDidCrash", "last-pid", "last-crashed")
|
||||
const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
||||
|
||||
fun getCrashDirectory(context: Context): File {
|
||||
val base = getBaseDirectory(context)
|
||||
@@ -85,11 +85,6 @@ object LaunchUtils {
|
||||
return File(base, "game/geode/logs/")
|
||||
}
|
||||
|
||||
fun getGeodeResourcesDirectory(context: Context): File {
|
||||
val base = getBaseDirectory(context)
|
||||
return File(base, "game/geode/resources/geode.loader/")
|
||||
}
|
||||
|
||||
fun getLastCrash(context: Context): File? {
|
||||
val crashDirectory = getCrashDirectory(context)
|
||||
if (!crashDirectory.exists()) {
|
||||
@@ -98,7 +93,7 @@ object LaunchUtils {
|
||||
|
||||
val children = crashDirectory.listFiles {
|
||||
// ignore indicator files (including old file)
|
||||
_, name -> !CRASH_INDICATOR_NAMES.contains(name)
|
||||
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
|
||||
}
|
||||
|
||||
return children?.maxByOrNull { it.lastModified() }
|
||||
@@ -106,9 +101,9 @@ object LaunchUtils {
|
||||
|
||||
fun lastSessionCrashed(context: Context): Boolean {
|
||||
val base = getCrashDirectory(context)
|
||||
return CRASH_INDICATOR_NAMES.any {
|
||||
File(base, it).exists()
|
||||
}
|
||||
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
||||
|
||||
return crashIndicatorFile.exists()
|
||||
}
|
||||
|
||||
enum class LauncherError : Serializable {
|
||||
@@ -124,6 +119,12 @@ object LaunchUtils {
|
||||
}
|
||||
}
|
||||
|
||||
data class FullLauncherError(
|
||||
val error: LauncherError,
|
||||
val returnMessage: String,
|
||||
val extendedMessage: String? = null,
|
||||
) : Serializable
|
||||
|
||||
const val LAUNCHER_KEY_RETURN_ERROR = "return_error"
|
||||
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
|
||||
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024">
|
||||
<path
|
||||
android:pathData="M0,0h1024v1024h-1024z"
|
||||
android:fillColor="#070066"/>
|
||||
<path
|
||||
android:pathData="M512.5,521.1L915.2,638.7L1082,922.6L915.2,1206.4L512.5,1324L109.8,1206.4L-57,922.6L109.8,638.7L512.5,521.1Z"
|
||||
android:fillColor="#6694C8"/>
|
||||
<path
|
||||
android:pathData="M915.2,638.7L1082,922.6V638.7H915.2Z"
|
||||
android:fillColor="#4268B8"/>
|
||||
<path
|
||||
android:pathData="M109.8,638.7L512.5,521.1L109.8,397.2V638.7Z"
|
||||
android:fillColor="#2D5EC7"/>
|
||||
<path
|
||||
android:pathData="M-57,922.6L109.8,638.7H-57V922.6Z"
|
||||
android:fillColor="#4268B8"/>
|
||||
<path
|
||||
android:pathData="M512.5,521.1L915.2,638.7V397.2L512.5,521.1Z"
|
||||
android:fillColor="#2D5EC7"/>
|
||||
<path
|
||||
android:pathData="M-57,638.7H109.8V397.2L-57,638.7Z"
|
||||
android:fillColor="#2248AA"/>
|
||||
<path
|
||||
android:pathData="M109.8,397.2L512.5,521.1V194L109.8,397.2Z"
|
||||
android:fillColor="#020F9D"/>
|
||||
<path
|
||||
android:pathData="M512.5,521.1L915.2,397.2L512.5,194V521.1Z"
|
||||
android:fillColor="#020F9D"/>
|
||||
<path
|
||||
android:pathData="M915.2,638.7H1082L915.2,397.2V638.7Z"
|
||||
android:fillColor="#2248AA"/>
|
||||
</vector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="643"
|
||||
android:viewportHeight="766">
|
||||
<group android:scaleX="0.41038585"
|
||||
android:scaleY="0.4888889"
|
||||
android:translateX="191.0163"
|
||||
android:translateY="195.75555">
|
||||
<path
|
||||
android:pathData="M299.94,33.78C313.01,25.85 329.41,25.85 342.48,33.78L370.9,51.03L268.17,116.49L268.23,116.51L92.68,223.61C84.64,228.51 79.74,237.25 79.74,246.66V509.84C79.74,519.26 84.64,527.99 92.68,532.89L306.22,663.17C314.85,668.44 325.71,668.44 334.34,663.17L547.89,532.89C555.92,527.99 560.82,519.26 560.82,509.85V246.66C560.82,246.2 560.81,245.74 560.78,245.28V166.26L599.75,189.9C612,197.34 619.48,210.63 619.48,224.96V540.81C619.48,555.14 612,568.42 599.75,575.86L342.48,731.98C329.41,739.91 313.01,739.91 299.94,731.98L42.66,575.86C30.41,568.42 22.93,555.14 22.93,540.81V224.96C22.93,210.63 30.41,197.34 42.66,189.9L299.94,33.78Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillAlpha="0.15"/>
|
||||
<path
|
||||
android:pathData="M311.75,173.17C318.78,168.88 327.62,168.88 334.65,173.17L481.88,262.87C488.43,266.86 492.43,273.98 492.43,281.66V463.16C492.43,470.84 488.43,477.96 481.88,481.95L334.65,571.65C327.62,575.94 318.78,575.94 311.75,571.65L164.52,481.95C157.97,477.96 153.97,470.84 153.97,463.16V414.95L208.24,448.54C208.52,448.75 208.8,448.95 209.1,449.13L315.66,515.36C319.22,517.57 323.72,517.57 327.28,515.36L433.84,449.13C437.07,447.13 439.03,443.59 439.03,439.79V305.9C439.03,302.09 437.07,298.56 433.84,296.55L327.28,230.33C323.72,228.12 319.22,228.12 315.66,230.33L209.1,296.55C208.62,296.85 208.17,297.18 207.75,297.55L153.97,331.28V281.66C153.97,273.98 157.97,266.86 164.52,262.87L311.75,173.17Z"
|
||||
android:fillColor="#000000"
|
||||
android:fillAlpha="0.15"
|
||||
android:fillType="evenOdd"/>
|
||||
<path
|
||||
android:pathData="M299.94,5.95C313.01,-1.98 329.41,-1.98 342.48,5.95L374.73,25.52L264.09,96.01L264.15,96.03L74.11,211.97C66.07,216.88 61.17,225.61 61.17,235.02V520.77C61.17,530.18 66.07,538.92 74.11,543.82L306.15,685.38C314.78,690.65 325.64,690.65 334.27,685.38L566.31,543.82C574.34,538.92 579.25,530.18 579.25,520.77V235.02C579.25,234.53 579.23,234.05 579.2,233.56V149.6L622.68,175.99C634.93,183.42 642.41,196.71 642.41,211.04V554.72C642.41,569.05 634.93,582.34 622.68,589.78L342.48,759.82C329.41,767.75 313.01,767.75 299.94,759.82L19.73,589.78C7.48,582.34 0,569.05 0,554.72V211.04C0,196.71 7.48,183.42 19.73,175.99L299.94,5.95Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M311.75,142.59C318.78,138.31 327.62,138.31 334.65,142.59L507.79,248.08C514.34,252.08 518.34,259.2 518.34,266.87V479.95C518.34,487.62 514.34,494.74 507.79,498.74L334.65,604.23C327.62,608.51 318.78,608.51 311.75,604.23L138.61,498.74C132.06,494.74 128.06,487.62 128.06,479.95V422.46L189.93,460.76C190.22,460.98 190.52,461.19 190.84,461.39L315.4,538.8C318.95,541.01 323.46,541.01 327.01,538.8L451.58,461.39C454.8,459.38 456.77,455.85 456.77,452.04V295.77C456.77,291.97 454.8,288.44 451.58,286.43L327.01,209.02C323.46,206.81 318.95,206.81 315.4,209.02L190.84,286.43C190.32,286.76 189.83,287.12 189.37,287.52L128.06,325.98V266.87C128.06,259.2 132.06,252.08 138.61,248.08L311.75,142.59Z"
|
||||
android:fillColor="#ffffff"
|
||||
android:fillType="evenOdd"/>
|
||||
</group>
|
||||
</vector>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_sapphire_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_sapphire_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -53,7 +53,6 @@
|
||||
<string name="launcher_error_view_logs">View logs</string>
|
||||
<string name="launcher_error_copy_error_alt">Copy</string>
|
||||
<string name="launcher_error_copy">Copy details</string>
|
||||
<string name="launcher_error_view_details">View details</string>
|
||||
<string name="launcher_error_export_crash">Share crash</string>
|
||||
<string name="launcher_error_export_missing">No crash dump was found!</string>
|
||||
<string name="launcher_error_device_info">Android %2$s, %1$s (%5$s)\nGD %4$s, Geode %3$s + v%6$s</string>
|
||||
@@ -119,10 +118,10 @@
|
||||
<string name="load_failed_crashed_description">A crash was reported during the last session. Try to:</string>
|
||||
<string name="load_failed_recommendation_reinstall">Reinstall Geometry Dash.</string>
|
||||
<string name="load_failed_recommendation_switch_32bit_abi">Install the 32-bit version of the launcher.</string>
|
||||
<string name="load_failed_recommendation_switch_64bit_abi">Install the non-32-bit version of the launcher.</string>
|
||||
<string name="load_failed_recommendation_switch_64bit_abi">Install the non 32-bit version of the launcher.</string>
|
||||
<string name="load_failed_recommendation_update">Verify that the latest versions of the launcher and Geometry Dash are installed.</string>
|
||||
<string name="load_failed_recommendation_report">Report this issue to the developers.</string>
|
||||
<string name="load_failed_recommendation_safe_mode">Use the safe mode button in Settings.</string>
|
||||
<string name="load_failed_recommendation_safe_mode">Enable the safe mode button in Settings.</string>
|
||||
<string name="load_failed_recommendation_dev_stl">Devs: If this is a locally built Geode version, please use the c++_shared ANDROID_STL!</string>
|
||||
|
||||
<string name="message_box_cancel">Cancel</string>
|
||||
@@ -336,7 +335,6 @@
|
||||
<string name="application_icon_geode">Geode</string>
|
||||
<string name="application_icon_pride">Pride</string>
|
||||
<string name="application_icon_trans">Transgender</string>
|
||||
<string name="application_icon_sapphire">Sapphire</string>
|
||||
<string name="application_icon_confirm">Change icon to %1$s?</string>
|
||||
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
|
||||
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||
buildscript {
|
||||
val composeBOM by extra("2026.03.01")
|
||||
val composeBOM by extra("2026.02.00")
|
||||
}
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "9.0.1" apply false
|
||||
id("com.android.library") version "9.0.1" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.20" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.10" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
|
||||
Reference in new issue
Block a user