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,10 +19,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Java JDK
|
- name: Setup Java JDK
|
||||||
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: 21
|
java-version: 21
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Upload release build
|
- name: Upload release build
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ matrix.config.lower == 'release' }}
|
if: ${{ matrix.config.lower == 'release' }}
|
||||||
with:
|
with:
|
||||||
name: geode-launcher-${{ matrix.config.lower }}
|
name: geode-launcher-${{ matrix.config.lower }}
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
${{ steps.rename_mappings.outputs.path }}
|
${{ steps.rename_mappings.outputs.path }}
|
||||||
|
|
||||||
- name: Upload debug build
|
- name: Upload debug build
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@v4
|
||||||
if: ${{ matrix.config.lower == 'debug' }}
|
if: ${{ matrix.config.lower == 'debug' }}
|
||||||
with:
|
with:
|
||||||
name: geode-launcher-${{ matrix.config.lower }}
|
name: geode-launcher-${{ matrix.config.lower }}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId = "com.geode.launcher"
|
applicationId = "com.geode.launcher"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 29
|
versionCode = 26
|
||||||
versionName = "1.8.0"
|
versionName = "1.7.3"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
@@ -78,7 +78,7 @@ android {
|
|||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
version = "3.21.0+"
|
version = "4.0.0+"
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,18 +88,18 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation (platform("androidx.compose:compose-bom:$composeBOM"))
|
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.ui:ui")
|
||||||
implementation ("androidx.compose.material3:material3")
|
implementation ("androidx.compose.material3:material3")
|
||||||
implementation ("androidx.compose.ui:ui-tooling-preview")
|
implementation ("androidx.compose.ui:ui-tooling-preview")
|
||||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
|
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")
|
||||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.10.0")
|
||||||
implementation ("androidx.activity:activity-compose:1.13.0")
|
implementation ("androidx.activity:activity-compose:1.12.4")
|
||||||
implementation ("androidx.activity:activity-ktx:1.13.0")
|
implementation ("androidx.activity:activity-ktx:1.12.4")
|
||||||
implementation ("androidx.appcompat:appcompat:1.7.1")
|
implementation ("androidx.appcompat:appcompat:1.7.1")
|
||||||
implementation ("androidx.documentfile:documentfile:1.1.0")
|
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:5.3.2")
|
||||||
implementation ("com.squareup.okhttp3:okhttp-coroutines: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-android:0.39.2")
|
||||||
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3: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")
|
debugImplementation ("androidx.compose.ui:ui-tooling")
|
||||||
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
|
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||||
}
|
}
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".preferences.TextViewActivity"
|
android:name=".preferences.GeodeLogViewActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher">
|
||||||
</activity>
|
</activity>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".GeometryDashActivity"
|
android:name=".GeometryDashActivity"
|
||||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard"
|
android:configChanges="orientation|screenSize|smallestScreenSize"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:hardwareAccelerated="true"
|
android:hardwareAccelerated="true"
|
||||||
android:screenOrientation="landscape"
|
android:screenOrientation="landscape"
|
||||||
@@ -195,26 +195,6 @@
|
|||||||
<data android:scheme="geode-launcher" android:host="main" />
|
<data android:scheme="geode-launcher" android:host="main" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity-alias>
|
</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
|
<activity-alias
|
||||||
android:name=".MainActivityGeode"
|
android:name=".MainActivityGeode"
|
||||||
android:enabled="false"
|
android:enabled="false"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 4.1.2)
|
||||||
|
|
||||||
project(launcherfix)
|
project(launcherfix)
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// this is every function that i thought would be relevant
|
// 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",
|
"__gxx_personality_v0",
|
||||||
"__cxa_throw",
|
"__cxa_throw",
|
||||||
"__cxa_rethrow",
|
"__cxa_rethrow",
|
||||||
@@ -25,10 +25,9 @@ constexpr std::array<const std::string_view, 10> remove_symbols{
|
|||||||
"__cxa_guard_abort",
|
"__cxa_guard_abort",
|
||||||
"__cxa_guard_acquire",
|
"__cxa_guard_acquire",
|
||||||
"__cxa_guard_release",
|
"__cxa_guard_release",
|
||||||
"__cxa_free_exception"
|
"__cxa_free_exception",
|
||||||
// conflicts with fix within geode on some devices
|
"__emutls_get_address", // added in 2.208
|
||||||
// "__emutls_get_address", // added in 2.208
|
"__cxa_thread_atexit"
|
||||||
// "__cxa_thread_atexit"
|
|
||||||
|
|
||||||
// android 10's libc doesn't have these symbols
|
// android 10's libc doesn't have these symbols
|
||||||
// and the ndk is forbidden from exporting them!! so keep it for old mods...
|
// 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.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.customRobTop.BaseRobTopActivity
|
import com.customRobTop.BaseRobTopActivity
|
||||||
import com.customRobTop.JniToCpp
|
import com.customRobTop.JniToCpp
|
||||||
|
import com.geode.launcher.main.FullScreenLoadingIndicator
|
||||||
import com.geode.launcher.main.LaunchNotification
|
import com.geode.launcher.main.LaunchNotification
|
||||||
import com.geode.launcher.main.determineDisplayedCards
|
import com.geode.launcher.main.determineDisplayedCards
|
||||||
import com.geode.launcher.utils.Constants
|
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.GeodeUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
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.Cocos2dxGLSurfaceView
|
||||||
import org.cocos2dx.lib.Cocos2dxHelper
|
import org.cocos2dx.lib.Cocos2dxHelper
|
||||||
import org.cocos2dx.lib.Cocos2dxRenderer
|
import org.cocos2dx.lib.Cocos2dxRenderer
|
||||||
import org.fmod.FMOD
|
import org.fmod.FMOD
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.microedition.khronos.egl.EGL10
|
import javax.microedition.khronos.egl.EGL10
|
||||||
import javax.microedition.khronos.egl.EGLConfig
|
import javax.microedition.khronos.egl.EGLConfig
|
||||||
@@ -91,97 +94,88 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
val rootLayout = setupContext()
|
||||||
createVersionFile()
|
val loadingView = ComposeView(this).apply {
|
||||||
tryLoadGame()
|
setContent {
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
FullScreenLoadingIndicator()
|
||||||
Log.e("GeodeLauncher", "Library linkage failure", e)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// generates helpful information for use in debugging library load failures
|
if (rootLayout != null) {
|
||||||
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
rootLayout.addView(loadingView)
|
||||||
val abiMismatch = GamePackageUtils.detectAbiMismatch(this, gdPackageInfo, e)
|
} else {
|
||||||
|
setContentView(loadingView)
|
||||||
|
}
|
||||||
|
|
||||||
val is64bit = LaunchUtils.is64bit
|
lifecycleScope.launch {
|
||||||
val errorMessage = when {
|
val error = preloadGame(rootLayout)
|
||||||
abiMismatch && is64bit -> LaunchUtils.LauncherError.LINKER_NEEDS_32BIT
|
if (error != null) {
|
||||||
abiMismatch -> LaunchUtils.LauncherError.LINKER_NEEDS_64BIT
|
returnToMain(error)
|
||||||
e.message?.contains("__gxx_personality_v0") == true ->
|
|
||||||
LaunchUtils.LauncherError.LINKER_FAILED_STL
|
|
||||||
else -> LaunchUtils.LauncherError.LINKER_FAILED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
returnToMain(errorMessage, e.message, e.stackTraceToString())
|
rootLayout?.removeView(loadingView)
|
||||||
|
|
||||||
return
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
|
||||||
|
|
||||||
returnToMain(
|
|
||||||
LaunchUtils.LauncherError.GENERIC,
|
|
||||||
e.message ?: "Unknown Exception",
|
|
||||||
e.stackTraceToString()
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun preloadGame(rootLayout: FrameLayout?): LaunchUtils.FullLauncherError? = try {
|
||||||
|
createVersionFile()
|
||||||
|
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)
|
||||||
|
|
||||||
|
// generates helpful information for use in debugging library load failures
|
||||||
|
val gdPackageInfo = packageManager.getPackageInfo(Constants.PACKAGE_NAME, 0)
|
||||||
|
val abiMismatch = GamePackageUtils.detectAbiMismatch(this, gdPackageInfo, e)
|
||||||
|
|
||||||
|
val is64bit = LaunchUtils.is64bit
|
||||||
|
val errorMessage = when {
|
||||||
|
abiMismatch && is64bit -> LaunchUtils.LauncherError.LINKER_NEEDS_32BIT
|
||||||
|
abiMismatch -> LaunchUtils.LauncherError.LINKER_NEEDS_64BIT
|
||||||
|
e.message?.contains("__gxx_personality_v0") == true ->
|
||||||
|
LaunchUtils.LauncherError.LINKER_FAILED_STL
|
||||||
|
else -> LaunchUtils.LauncherError.LINKER_FAILED
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchUtils.FullLauncherError(
|
||||||
|
errorMessage,
|
||||||
|
e.message ?: "Unknown exception",
|
||||||
|
e.stackTraceToString()
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
||||||
|
|
||||||
|
LaunchUtils.FullLauncherError(
|
||||||
|
LaunchUtils.LauncherError.GENERIC,
|
||||||
|
e.message ?: "Unknown Exception",
|
||||||
|
e.stackTraceToString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupContext(): FrameLayout? {
|
||||||
|
Cocos2dxHelper.init(this, this)
|
||||||
|
|
||||||
|
GeodeUtils.setContext(this)
|
||||||
|
GeodeUtils.setCapabilityListener(this)
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
mGLSurfaceView?.sendKeyBack()
|
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) {
|
if (!GeodeUtils.handleSafeArea && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
|
||||||
val frameLayoutParams = ViewGroup.LayoutParams(
|
val frameLayoutParams = ViewGroup.LayoutParams(
|
||||||
@@ -200,23 +194,65 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
right = displayInsets.safeInsetRight,
|
right = displayInsets.safeInsetRight,
|
||||||
bottom = displayInsets.safeInsetBottom
|
bottom = displayInsets.safeInsetBottom
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
v.updatePadding(
|
|
||||||
left = 0, top = 0, right = 0, bottom = 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
WindowInsets.CONSUMED
|
WindowInsets.CONSUMED
|
||||||
}
|
}
|
||||||
|
|
||||||
frameLayout.addView(baseView)
|
|
||||||
setContentView(frameLayout)
|
setContentView(frameLayout)
|
||||||
} else {
|
return frameLayout
|
||||||
setContentView(baseView)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
setupPostLibraryLoad(gdPackageInfo)
|
||||||
|
|
||||||
val geodeFailed = try {
|
return try {
|
||||||
loadGeodeLibrary()
|
loadGeodeLibrary()
|
||||||
false
|
false
|
||||||
} catch (e: UnsatisfiedLinkError) {
|
} catch (e: UnsatisfiedLinkError) {
|
||||||
@@ -226,8 +262,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
handleGeodeException(e)
|
handleGeodeException(e)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
setupNotificationView(baseView, geodeFailed)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
|
private fun setupNotificationView(baseView: FrameLayout, geodeFailed: Boolean) {
|
||||||
@@ -287,7 +321,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
private suspend fun tryLoadLibrary(packageInfo: PackageInfo, libraryName: String) {
|
||||||
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
|
val nativeDir = getNativeLibraryDirectory(packageInfo.applicationInfo!!)
|
||||||
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
|
val libraryPath = if (nativeDir.endsWith('/')) "${nativeDir}lib$libraryName.so" else "$nativeDir/lib$libraryName.so"
|
||||||
|
|
||||||
@@ -306,7 +340,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
private suspend fun loadLibraryCopy(libraryName: String, libraryPath: String) {
|
||||||
if (libraryPath.contains("!/")) {
|
if (libraryPath.contains("!/")) {
|
||||||
// library in apk can't be loaded directly
|
// library in apk can't be loaded directly
|
||||||
loadLibraryFromAssetsCopy(libraryName)
|
loadLibraryFromAssetsCopy(libraryName)
|
||||||
@@ -316,11 +350,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
val library = File(libraryPath)
|
val library = File(libraryPath)
|
||||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||||
|
|
||||||
libraryCopy.outputStream().use { libraryOutput ->
|
DownloadUtils.copyFile(library, libraryCopy)
|
||||||
library.inputStream().use { inputStream ->
|
|
||||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.load(libraryCopy.path)
|
System.load(libraryCopy.path)
|
||||||
|
|
||||||
@@ -328,7 +358,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun loadLibraryFromAssetsCopy(libraryName: String) {
|
private suspend fun loadLibraryFromAssetsCopy(libraryName: String) {
|
||||||
// loads a library loaded in assets
|
// loads a library loaded in assets
|
||||||
// this copies the library to a non-compressed directory
|
// this copies the library to a non-compressed directory
|
||||||
|
|
||||||
@@ -343,9 +373,11 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
// there doesn't seem to be a way to load a library from a file descriptor
|
// there doesn't seem to be a way to load a library from a file descriptor
|
||||||
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
val libraryCopy = File(cacheDir, "lib$libraryName.so")
|
||||||
|
|
||||||
libraryCopy.outputStream().use { libraryOutput ->
|
withContext(Dispatchers.IO) {
|
||||||
libraryFd.createInputStream().use { inputStream ->
|
libraryCopy.outputStream().use { libraryOutput ->
|
||||||
DownloadUtils.copyFile(inputStream, libraryOutput)
|
libraryFd.createInputStream().use { inputStream ->
|
||||||
|
DownloadUtils.copyFile(inputStream, libraryOutput)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,7 +412,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||||
private fun loadGeodeLibrary() {
|
private suspend fun loadGeodeLibrary() {
|
||||||
// Load Geode if exists
|
// Load Geode if exists
|
||||||
// bundling the object with the application allows for nicer backtraces
|
// bundling the object with the application allows for nicer backtraces
|
||||||
try {
|
try {
|
||||||
@@ -401,22 +433,23 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
// also i get 20 million permission denied errors
|
// also i get 20 million permission denied errors
|
||||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
||||||
|
|
||||||
val copiedPath = File(filesDir.path, "copied")
|
val copiedPath = File(filesDir, "copied")
|
||||||
if (copiedPath.exists()) {
|
if (copiedPath.exists()) {
|
||||||
copiedPath.deleteRecursively()
|
copiedPath.deleteRecursively()
|
||||||
}
|
}
|
||||||
copiedPath.mkdir()
|
copiedPath.mkdir()
|
||||||
|
|
||||||
val copiedGeodePath = File(copiedPath.path, "Geode.so")
|
val copiedGeodePath = File(copiedPath, "Geode.so")
|
||||||
|
|
||||||
if (externalGeodePath.exists()) {
|
if (externalGeodePath.exists()) {
|
||||||
DownloadUtils.copyFile(
|
DownloadUtils.copyFile(
|
||||||
FileInputStream(externalGeodePath),
|
externalGeodePath,
|
||||||
FileOutputStream(copiedGeodePath)
|
copiedGeodePath
|
||||||
)
|
)
|
||||||
|
|
||||||
if (copiedGeodePath.exists()) {
|
if (copiedGeodePath.exists()) {
|
||||||
println("Loading Geode from ${externalGeodePath.name}")
|
println("Loading Geode from ${externalGeodePath.name}")
|
||||||
|
|
||||||
System.load(copiedGeodePath.path)
|
System.load(copiedGeodePath.path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -623,10 +656,6 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
mGLSurfaceView?.enableControllerEvents()
|
mGLSurfaceView?.enableControllerEvents()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
GeodeUtils.CAPABILITY_RESIZE_CALLBACK -> {
|
|
||||||
mGLSurfaceView?.supportsResizeEvents = true
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ import android.text.format.Formatter
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.LinearEasing
|
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.platform.LocalResources
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -85,9 +82,7 @@ import com.geode.launcher.ui.theme.Theme
|
|||||||
import com.geode.launcher.ui.theme.Theme.DARK
|
import com.geode.launcher.ui.theme.Theme.DARK
|
||||||
import com.geode.launcher.ui.theme.Theme.LIGHT
|
import com.geode.launcher.ui.theme.Theme.LIGHT
|
||||||
import com.geode.launcher.ui.theme.launcherTitleStyle
|
import com.geode.launcher.ui.theme.launcherTitleStyle
|
||||||
import com.geode.launcher.ui.theme.sapphireTitleStyle
|
|
||||||
import com.geode.launcher.updater.ReleaseManager
|
import com.geode.launcher.updater.ReleaseManager
|
||||||
import com.geode.launcher.utils.ApplicationIcon
|
|
||||||
import com.geode.launcher.utils.Constants
|
import com.geode.launcher.utils.Constants
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
@@ -457,75 +452,35 @@ fun ExtraOptions(onSettings: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ColorPalette(
|
val lightOuterColorGradient = arrayOf(
|
||||||
val outerColors: List<Pair<Float, Color>>,
|
0.0f to Color(0xfff5b11d),
|
||||||
val innerColors: List<Pair<Float, Color>>,
|
0.10f to Color(0xfff0834d),
|
||||||
|
0.24f to Color(0xffe85f6b),
|
||||||
|
0.49f to Color(0xffc9659e),
|
||||||
|
1.0f to Color(0xffb9588f)
|
||||||
)
|
)
|
||||||
|
|
||||||
data class AnimatedIconSpec(
|
val lightInnerColorGradient = arrayOf(
|
||||||
val lightPalette: ColorPalette,
|
0.48f to Color(0xfff2ce00),
|
||||||
val darkPalette: ColorPalette,
|
0.63f to Color(0xfff4b97e),
|
||||||
|
0.73f to Color(0xfff7b66a),
|
||||||
@param:DrawableRes val innerId: Int,
|
1.0f to Color(0xffeb8fac)
|
||||||
@param:DrawableRes val outerId: Int,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
data class BrandPalette(
|
val darkOuterColorGradient = arrayOf(
|
||||||
@param:DrawableRes val darkLogo: Int,
|
0.0f to Color(0xfffffdff),
|
||||||
@param:DrawableRes val lightLogo: Int,
|
0.08f to Color(0xffF2EAF5),
|
||||||
|
0.12f to Color(0xffEDE5EF),
|
||||||
@param:StringRes val title: Int,
|
0.31f to Color(0xffCDB5CD),
|
||||||
val titleFont: TextStyle,
|
0.49f to Color(0xffBA9BBC),
|
||||||
val animatedIcon: AnimatedIconSpec? = null,
|
1.0f to Color(0xff8D7ACF),
|
||||||
)
|
)
|
||||||
|
|
||||||
val geodeColorPalette = BrandPalette(
|
val darkInnerColorGradient = arrayOf(
|
||||||
animatedIcon = AnimatedIconSpec(
|
0.48f to Color(0xffffffff),
|
||||||
lightPalette = ColorPalette(
|
0.63f to Color(0xfff5e4c2),
|
||||||
outerColors = listOf(
|
0.73f to Color(0xffe5c7ad),
|
||||||
0.0f to Color(0xfff5b11d),
|
1.0f to Color(0xffb790a9),
|
||||||
0.10f to Color(0xfff0834d),
|
|
||||||
0.24f to Color(0xffe85f6b),
|
|
||||||
0.49f to Color(0xffc9659e),
|
|
||||||
1.0f to Color(0xffb9588f)
|
|
||||||
),
|
|
||||||
innerColors = listOf(
|
|
||||||
0.48f to Color(0xfff2ce00),
|
|
||||||
0.63f to Color(0xfff4b97e),
|
|
||||||
0.73f to Color(0xfff7b66a),
|
|
||||||
1.0f to Color(0xffeb8fac)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
darkPalette = ColorPalette(
|
|
||||||
outerColors = listOf(
|
|
||||||
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(
|
|
||||||
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, light")
|
||||||
@@ -550,49 +505,22 @@ fun GeodeLogoAnimatedPreview() {
|
|||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
GeodeLauncherTheme(theme = theme) {
|
GeodeLauncherTheme(theme = theme) {
|
||||||
Surface {
|
Surface {
|
||||||
GeodeLogo(shouldAnimate = true)
|
GeodeLogo(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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
fun AnimatedLogo(modifier: Modifier = Modifier) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
|
val innerColorPalette = remember(theme) {
|
||||||
|
if (theme == LIGHT) lightInnerColorGradient else darkInnerColorGradient
|
||||||
|
}
|
||||||
|
|
||||||
val colorPalette = if (theme == LIGHT) spec.lightPalette
|
val outerColorPalette = remember(theme) {
|
||||||
else spec.darkPalette
|
if (theme == LIGHT) lightOuterColorGradient else darkOuterColorGradient
|
||||||
|
}
|
||||||
val innerColorPalette = colorPalette.innerColors
|
|
||||||
val outerColorPalette = colorPalette.outerColors
|
|
||||||
|
|
||||||
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
|
val infiniteTransition = rememberInfiniteTransition(label = "infinite transition")
|
||||||
val offset by infiniteTransition.animateFloat(
|
val offset by infiniteTransition.animateFloat(
|
||||||
@@ -606,14 +534,14 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val innerBrush = Brush.linearGradient(
|
val innerBrush = Brush.linearGradient(
|
||||||
colorStops = innerColorPalette.toTypedArray(),
|
colorStops = innerColorPalette,
|
||||||
start = Offset(0.0f, 0.0f + (165.06f * offset)),
|
start = Offset(0.0f, 0.0f + (165.06f * offset)),
|
||||||
end = Offset(0.0f, 165.06f + (165.06f * offset)),
|
end = Offset(0.0f, 165.06f + (165.06f * offset)),
|
||||||
tileMode = TileMode.Mirror
|
tileMode = TileMode.Mirror
|
||||||
)
|
)
|
||||||
|
|
||||||
val outerBrush = Brush.linearGradient(
|
val outerBrush = Brush.linearGradient(
|
||||||
colorStops = outerColorPalette.toTypedArray(),
|
colorStops = outerColorPalette,
|
||||||
start = Offset(0.0f + (84.69f * offset), 0.0f + (165.06f * offset)),
|
start = Offset(0.0f + (84.69f * offset), 0.0f + (165.06f * offset)),
|
||||||
end = Offset(84.69f + (84.69f * offset), 169.61f + (165.06f * offset)),
|
end = Offset(84.69f + (84.69f * offset), 169.61f + (165.06f * offset)),
|
||||||
tileMode = TileMode.Mirror
|
tileMode = TileMode.Mirror
|
||||||
@@ -621,7 +549,7 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(spec.innerId),
|
painter = painterResource(id = R.drawable.geode_monochrome_inner),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -632,10 +560,9 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.size(64.dp, 64.dp)
|
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(spec.outerId),
|
painter = painterResource(id = R.drawable.geode_monochrome_outer),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -646,50 +573,39 @@ fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.size(64.dp, 64.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GeodeLogo(modifier: Modifier = Modifier, shouldAnimate: Boolean = false, basePalette: BrandPalette = geodeColorPalette) {
|
fun GeodeLogo(shouldAnimate: Boolean = false, modifier: Modifier = Modifier) {
|
||||||
FlowRow(
|
Row(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
itemVerticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
|
|
||||||
if (basePalette.animatedIcon != null) {
|
Crossfade(
|
||||||
Crossfade(
|
targetState = shouldAnimate,
|
||||||
targetState = shouldAnimate,
|
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
||||||
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
label="logo fade"
|
||||||
label="logo fade"
|
) { screen ->
|
||||||
) { screen ->
|
when (screen) {
|
||||||
when (screen) {
|
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp))
|
||||||
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp), spec = basePalette.animatedIcon)
|
false -> Image(
|
||||||
false -> Image(
|
painterResource(if (theme == LIGHT)
|
||||||
painterResource(if (theme == LIGHT)
|
R.drawable.geode_base_light else R.drawable.geode_base
|
||||||
basePalette.lightLogo else basePalette.darkLogo
|
),
|
||||||
),
|
contentDescription = null,
|
||||||
contentDescription = null,
|
modifier = Modifier.size(64.dp, 64.dp)
|
||||||
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(
|
Text(
|
||||||
stringResource(basePalette.title),
|
stringResource(R.string.launcher_title),
|
||||||
style = basePalette.titleFont,
|
style = launcherTitleStyle,
|
||||||
fontSize = 64.sp,
|
fontSize = 64.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
@@ -777,14 +693,7 @@ fun AltMainScreen(
|
|||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
val sapphireLogo = remember {
|
GeodeLogo(shouldAnimate = launchUIState.isInProgress())
|
||||||
PreferenceUtils.get(context)
|
|
||||||
.getString(PreferenceUtils.Key.SELECTED_ICON) == ApplicationIcon.SAPPHIRE.toId()
|
|
||||||
}
|
|
||||||
|
|
||||||
val basePalette = if (sapphireLogo) sapphireColorPalette
|
|
||||||
else geodeColorPalette
|
|
||||||
GeodeLogo(shouldAnimate = launchUIState.isInProgress(), basePalette = basePalette)
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import kotlin.time.Instant
|
|||||||
|
|
||||||
data class CrashDump(
|
data class CrashDump(
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant,
|
val lastModified: Instant
|
||||||
val fullPath: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(crashDirectory) {
|
||||||
@@ -42,19 +41,16 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> !LaunchUtils.CRASH_INDICATOR_NAMES.contains(name)
|
_, name -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
||||||
} ?: return emptyList()
|
} ?: return emptyList()
|
||||||
|
|
||||||
hasIndicator = LaunchUtils.CRASH_INDICATOR_NAMES.any {
|
hasIndicator = File(crashDirectory, LaunchUtils.CRASH_INDICATOR_NAME).exists()
|
||||||
File(crashDirectory, it).exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
return children
|
return children
|
||||||
.map {
|
.map {
|
||||||
CrashDump(
|
CrashDump(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified())
|
||||||
fullPath = it.path,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
@@ -62,12 +58,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun clearIndicator() {
|
fun clearIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
LaunchUtils.CRASH_INDICATOR_NAMES.forEach {
|
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).delete()
|
||||||
val crashFile = File(baseDirectory, it)
|
|
||||||
if (crashFile.exists()) {
|
|
||||||
crashFile.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hasIndicator = false
|
hasIndicator = false
|
||||||
}
|
}
|
||||||
@@ -75,8 +66,7 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun createIndicator() {
|
fun createIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val crashFile = File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAMES.first())
|
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).createNewFile()
|
||||||
crashFile.createNewFile()
|
|
||||||
|
|
||||||
hasIndicator = true
|
hasIndicator = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ data class GeodeLog(
|
|||||||
val filename: String,
|
val filename: String,
|
||||||
val lastModified: Instant,
|
val lastModified: Instant,
|
||||||
val fileSize: Long,
|
val fileSize: Long,
|
||||||
val fullPath: String,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog>(crashDirectory) {
|
||||||
@@ -44,8 +43,7 @@ class GeodeLogsViewModel(crashDirectory: File) : BaseDirectoryViewModel<GeodeLog
|
|||||||
GeodeLog(
|
GeodeLog(
|
||||||
filename = it.name,
|
filename = it.name,
|
||||||
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified()),
|
||||||
fileSize = it.length(),
|
fileSize = it.length()
|
||||||
fullPath = it.path,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedByDescending { it.lastModified }
|
.sortedByDescending { it.lastModified }
|
||||||
|
|||||||
@@ -55,14 +55,11 @@ import com.geode.launcher.preferences.ApplicationLogsActivity
|
|||||||
import com.geode.launcher.BuildConfig
|
import com.geode.launcher.BuildConfig
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
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.ui.theme.Typography
|
||||||
import com.geode.launcher.utils.GamePackageUtils
|
import com.geode.launcher.utils.GamePackageUtils
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
data class LoadFailureInfo(
|
data class LoadFailureInfo(
|
||||||
val title: LaunchUtils.LauncherError,
|
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) {
|
fun onShareCrash(context: Context) {
|
||||||
val lastCrash = findSomethingForCrash(context)
|
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||||
if (lastCrash == null) {
|
if (lastCrash == null) {
|
||||||
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
|
|
||||||
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
||||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
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) {
|
fun onShowLogs(context: Context) {
|
||||||
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
val launchIntent = Intent(context, ApplicationLogsActivity::class.java)
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
@@ -285,34 +255,20 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
|||||||
Text(stringResource(R.string.launcher_error_copy))
|
Text(stringResource(R.string.launcher_error_copy))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val crashFile = remember { findSomethingForCrash(context) }
|
Button(onClick = { onShareCrash(context) }) {
|
||||||
val readableCrash = remember { crashFile?.extension == "log" }
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_share),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
||||||
|
Text(stringResource(R.string.launcher_error_export_crash))
|
||||||
|
}
|
||||||
|
|
||||||
if (crashFile != null) {
|
val developerSettingsEnabled = remember {
|
||||||
Button(onClick = { onShareCrash(context) }) {
|
PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.DEVELOPER_MODE)
|
||||||
Icon(
|
}
|
||||||
painterResource(R.drawable.icon_share),
|
if (developerSettingsEnabled) {
|
||||||
contentDescription = null
|
FilledTonalButton(onClick = { onShowLogs(context) }) {
|
||||||
)
|
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Button(onClick = {
|
|
||||||
val launchIntent = Intent(context, GeodeLogsListingActivity::class.java)
|
|
||||||
context.startActivity(launchIntent)
|
|
||||||
}) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.icon_description),
|
painterResource(R.drawable.icon_description),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ import androidx.compose.animation.shrinkHorizontally
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.IntrinsicSize
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.displayCutoutPadding
|
import androidx.compose.foundation.layout.displayCutoutPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedCard
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
@@ -120,9 +123,10 @@ fun LaunchNotification(cards: List<LaunchNotificationType>) {
|
|||||||
val theme = Theme.fromInt(themeOption)
|
val theme = Theme.fromInt(themeOption)
|
||||||
|
|
||||||
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
|
||||||
|
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||||
|
|
||||||
CompositionLocalProvider(LocalTheme provides 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
|
// surface is not in use, so this is unfortunately not provided
|
||||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
Column(
|
Column(
|
||||||
@@ -279,4 +283,27 @@ fun ForwardCompatibilityContent(modifier: Modifier = Modifier) {
|
|||||||
},
|
},
|
||||||
modifier = modifier.width(IntrinsicSize.Max)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -182,6 +182,17 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// last launch crashed, so cancel
|
||||||
|
if (loadFailure != null) {
|
||||||
|
// show a different message if this is the first thing users see
|
||||||
|
val cancelType = if (_uiState.value == LaunchUIState.Initial) {
|
||||||
|
LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC
|
||||||
|
} else LaunchCancelReason.LAST_LAUNCH_CRASHED
|
||||||
|
|
||||||
|
_uiState.tryEmit(LaunchUIState.Cancelled(cancelType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE_MIN) {
|
if (GamePackageUtils.getGameVersionCode(packageManager) < Constants.SUPPORTED_VERSION_CODE_MIN) {
|
||||||
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
_uiState.tryEmit(LaunchUIState.Cancelled(LaunchCancelReason.GAME_OUTDATED))
|
||||||
return
|
return
|
||||||
@@ -194,18 +205,6 @@ class LaunchViewModel(private val application: Application): ViewModel() {
|
|||||||
|
|
||||||
// if forcing immediate launch, then act as if it's manually started (skips timers)
|
// if forcing immediate launch, then act as if it's manually started (skips timers)
|
||||||
val forceImmediate = launchArguments?.forceLaunch == true
|
val forceImmediate = launchArguments?.forceLaunch == true
|
||||||
|
|
||||||
// last launch crashed, so cancel
|
|
||||||
if (!forceImmediate && loadFailure != null) {
|
|
||||||
// show a different message if this is the first thing users see
|
|
||||||
val cancelType = if (_uiState.value == LaunchUIState.Initial) {
|
|
||||||
LaunchCancelReason.LAST_LAUNCH_CRASHED_AUTOMATIC
|
|
||||||
} else LaunchCancelReason.LAST_LAUNCH_CRASHED
|
|
||||||
|
|
||||||
_uiState.tryEmit(LaunchUIState.Cancelled(cancelType))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceImmediate) {
|
if (forceImmediate) {
|
||||||
hasManuallyStarted = true
|
hasManuallyStarted = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,6 +237,7 @@ fun ApplicationIconScreen(
|
|||||||
) {
|
) {
|
||||||
ApplicationIcon.entries.forEach {
|
ApplicationIcon.entries.forEach {
|
||||||
val iconData = IconUtils.getIconDetails(it)
|
val iconData = IconUtils.getIconDetails(it)
|
||||||
|
|
||||||
IconCard(iconData, selectedIcon == iconData.id)
|
IconCard(iconData, selectedIcon == iconData.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import androidx.activity.OnBackPressedDispatcher
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -84,7 +83,7 @@ class CrashLogsActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareCrash(context: Context, filename: String) {
|
fun shareCrash(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
||||||
|
|
||||||
if (!crashPath.exists()) {
|
if (!crashPath.exists()) {
|
||||||
@@ -111,20 +110,11 @@ fun shareCrash(context: Context, filename: String) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
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
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 1.dp, horizontal = 8.dp)
|
.padding(vertical = 1.dp, horizontal = 8.dp)
|
||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.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)
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
.padding(vertical = 8.dp, horizontal = 8.dp)
|
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -197,6 +197,20 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
onClick = { onOpenLogs(context) }
|
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(
|
OptionsButton(
|
||||||
title = stringResource(R.string.preferences_copy_external_button),
|
title = stringResource(R.string.preferences_copy_external_button),
|
||||||
description = LaunchUtils.getBaseDirectory(context).path,
|
description = LaunchUtils.getBaseDirectory(context).path,
|
||||||
|
|||||||
+7
-52
@@ -1,11 +1,7 @@
|
|||||||
package com.geode.launcher.preferences
|
package com.geode.launcher.preferences
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.DocumentsContract
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.OnBackPressedDispatcher
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
import androidx.activity.compose.setContent
|
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.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
@@ -38,27 +35,23 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.geode.launcher.R
|
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.GeodeLauncherTheme
|
||||||
import com.geode.launcher.ui.theme.LocalTheme
|
import com.geode.launcher.ui.theme.LocalTheme
|
||||||
import com.geode.launcher.ui.theme.Theme
|
import com.geode.launcher.ui.theme.Theme
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.ui.theme.robotoMonoFamily
|
import com.geode.launcher.ui.theme.robotoMonoFamily
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
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 {
|
companion object {
|
||||||
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
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)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun GeodeLogView(
|
fun GeodeLogView(
|
||||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
geodeLogsViewModel: GeodeLogsViewModel = viewModel(factory = GeodeLogsViewModel.Factory)
|
||||||
) {
|
) {
|
||||||
var loadingFile by remember { mutableStateOf(true) }
|
var loadingFile by remember { mutableStateOf(true) }
|
||||||
var description by remember { mutableStateOf(emptyList<String>()) }
|
var description by remember { mutableStateOf(emptyList<String>()) }
|
||||||
|
|
||||||
val toRead = File(filename)
|
|
||||||
|
|
||||||
LaunchedEffect(filename) {
|
LaunchedEffect(filename) {
|
||||||
loadingFile = true
|
loadingFile = true
|
||||||
|
description = geodeLogsViewModel.getFileText(filename)
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
if (!toRead.isFile) {
|
|
||||||
description = listOf("File does not exist!")
|
|
||||||
} else {
|
|
||||||
description = toRead.readLines()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingFile = false
|
loadingFile = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,21 +123,12 @@ fun GeodeLogView(
|
|||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
toRead.name,
|
filename,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior,
|
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) {
|
if (loadingFile) {
|
||||||
@@ -80,7 +80,7 @@ class GeodeLogsListingActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareLog(context: Context, filename: String) {
|
fun shareLog(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
val crashPath = File(LaunchUtils.getGeodeLogsDirectory(context), filename)
|
val crashPath = File(LaunchUtils.getGeodeLogsDirectory(context), filename)
|
||||||
|
|
||||||
if (!crashPath.exists()) {
|
if (!crashPath.exists()) {
|
||||||
@@ -117,8 +117,8 @@ fun GeodeLogCard(geodeLog: GeodeLog, geodeLogsViewModel: GeodeLogsViewModel, mod
|
|||||||
.height(64.dp)
|
.height(64.dp)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
.clickable {
|
.clickable {
|
||||||
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
val launchIntent = Intent(context, GeodeLogViewActivity::class.java).run {
|
||||||
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.fullPath)
|
putExtra(GeodeLogViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.filename)
|
||||||
}
|
}
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ fun GameplaySettingsGroup() {
|
|||||||
SafeModeDialog(onDismiss = {
|
SafeModeDialog(onDismiss = {
|
||||||
showSafeModeDialog = false
|
showSafeModeDialog = false
|
||||||
}) {
|
}) {
|
||||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!.apply {
|
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
||||||
// hardcode it, why not
|
// hardcode it, why not
|
||||||
action = Intent.ACTION_VIEW
|
action = Intent.ACTION_VIEW
|
||||||
data = "geode-launcher://main/launch?safe-mode=true".toUri()
|
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(
|
OptionsButton(
|
||||||
title = stringResource(R.string.preferences_view_crashes),
|
title = stringResource(R.string.preferences_view_crashes),
|
||||||
icon = {
|
icon = {
|
||||||
|
|||||||
@@ -16,20 +16,9 @@ val quicksandFamily = FontFamily(
|
|||||||
Font(R.font.quicksand_semibold, FontWeight.SemiBold)
|
Font(R.font.quicksand_semibold, FontWeight.SemiBold)
|
||||||
)
|
)
|
||||||
|
|
||||||
val googleSansFamily = FontFamily(
|
|
||||||
Font(R.font.googlesans_medium, FontWeight.Medium)
|
|
||||||
)
|
|
||||||
|
|
||||||
val launcherTitleStyle = TextStyle(
|
val launcherTitleStyle = TextStyle(
|
||||||
fontFamily = quicksandFamily,
|
fontFamily = quicksandFamily,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
fontSize = 32.sp,
|
fontSize = 32.sp,
|
||||||
letterSpacing = 0.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
|
* Release download link for the current platform
|
||||||
*/
|
*/
|
||||||
abstract fun getDownload(): DownloadableAsset?
|
abstract fun getDownload(): DownloadableAsset?
|
||||||
|
|
||||||
/**
|
|
||||||
* Resources download link for current platform.
|
|
||||||
*/
|
|
||||||
abstract fun getResourcesDownload(): DownloadableAsset?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadable() {
|
||||||
@@ -106,18 +101,6 @@ class DownloadableGitHubLoaderRelease(private val release: Release) : Downloadab
|
|||||||
size = download.size.toLong()
|
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() {
|
class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
||||||
@@ -143,7 +126,8 @@ class DownloadableLauncherRelease(val release: Release) : Downloadable() {
|
|||||||
* 0 | 0 | 1
|
* 0 | 0 | 1
|
||||||
* surprise! it's an xnor
|
* 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()
|
size = download.size.toLong()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getResourcesDownload(): DownloadableAsset? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadable() {
|
||||||
@@ -171,19 +151,11 @@ class DownloadableLoaderRelease(private val version: LoaderVersion) : Downloadab
|
|||||||
return version.createdAt.epochSeconds
|
return version.createdAt.epochSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDownload(): DownloadableAsset {
|
override fun getDownload(): DownloadableAsset? {
|
||||||
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
val filename = "geode-${version.tag}-${LaunchUtils.platformName}.zip"
|
||||||
return DownloadableAsset(
|
return DownloadableAsset(
|
||||||
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
url = "https://github.com/geode-sdk/geode/releases/download/${version.tag}/$filename",
|
||||||
filename = 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 kotlinx.coroutines.launch
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okio.HashingSink
|
|
||||||
import okio.blackholeSink
|
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import okio.source
|
import okio.source
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InterruptedIOException
|
import java.io.InterruptedIOException
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.time.Clock
|
import kotlin.time.Clock
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@@ -98,10 +94,7 @@ class ReleaseManager private constructor(
|
|||||||
|
|
||||||
var dismissedLauncherUpdate = false
|
var dismissedLauncherUpdate = false
|
||||||
|
|
||||||
var skipStateUpdate = false
|
|
||||||
|
|
||||||
private fun sendError(e: Exception) {
|
private fun sendError(e: Exception) {
|
||||||
skipStateUpdate = true
|
|
||||||
_uiState.value = ReleaseManagerState.Failure(e)
|
_uiState.value = ReleaseManagerState.Failure(e)
|
||||||
|
|
||||||
// ignore cancellation, it's good actually
|
// ignore cancellation, it's good actually
|
||||||
@@ -173,60 +166,7 @@ class ReleaseManager private constructor(
|
|||||||
return Result.success(outputFile)
|
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)) {
|
|
||||||
println("Failed to rename temporary directory!")
|
|
||||||
DownloadUtils.copyDirectory(outputDir, finalDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} 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) {
|
private suspend fun performUpdate(release: Downloadable) {
|
||||||
skipStateUpdate = false
|
|
||||||
|
|
||||||
val releaseAsset = release.getDownload()
|
val releaseAsset = release.getDownload()
|
||||||
if (releaseAsset == null) {
|
if (releaseAsset == null) {
|
||||||
val noAssetException = Exception("missing Android download")
|
val noAssetException = Exception("missing Android download")
|
||||||
@@ -235,14 +175,8 @@ class ReleaseManager private constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val resourcesAsset = release.getResourcesDownload()
|
|
||||||
|
|
||||||
var releaseSize = releaseAsset.size ?: 0L
|
|
||||||
val resourcesSize = resourcesAsset?.size ?: 0L
|
|
||||||
|
|
||||||
// set an initial download size
|
// set an initial download size
|
||||||
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
|
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
|
||||||
_uiState.value = ReleaseManagerState.InDownload(0, initialSize)
|
|
||||||
|
|
||||||
val outputFile = getTempFile()
|
val outputFile = getTempFile()
|
||||||
// clone the file instance as renameTo may move the original file
|
// clone the file instance as renameTo may move the original file
|
||||||
@@ -261,10 +195,7 @@ class ReleaseManager private constructor(
|
|||||||
httpClient,
|
httpClient,
|
||||||
releaseAsset.url,
|
releaseAsset.url,
|
||||||
onProgress = { progress, outOf ->
|
onProgress = { progress, outOf ->
|
||||||
if (!skipStateUpdate)
|
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
||||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
|
||||||
|
|
||||||
releaseSize = max(outOf, releaseSize)
|
|
||||||
},
|
},
|
||||||
onResponse = { body ->
|
onResponse = { body ->
|
||||||
DownloadUtils.extractFileFromZipStream(
|
DownloadUtils.extractFileFromZipStream(
|
||||||
@@ -298,12 +229,6 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourcesAsset != null) {
|
|
||||||
if (!performResourceDownload(resourcesAsset, releaseSize)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extraction performed
|
// extraction performed
|
||||||
updatePreferences(release)
|
updatePreferences(release)
|
||||||
|
|
||||||
@@ -324,9 +249,10 @@ class ReleaseManager private constructor(
|
|||||||
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
val sharedPreferences = PreferenceUtils.get(applicationContext)
|
||||||
|
|
||||||
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
|
||||||
?: return true
|
?: return false
|
||||||
|
|
||||||
val currentFileHash = computeFileHash(geodeFile)
|
val currentFileHash = computeFileHash(geodeFile)
|
||||||
|
|
||||||
return originalFileHash != currentFileHash
|
return originalFileHash != currentFileHash
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,14 +384,8 @@ class ReleaseManager private constructor(
|
|||||||
sharedPreferences.setString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED, fileHash)
|
sharedPreferences.setString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED, fileHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeFileHash(file: File): String {
|
private fun computeFileHash(file: File): String =
|
||||||
val sink = HashingSink.md5(blackholeSink())
|
file.source().buffer().use { it.readByteString().md5().hex() }
|
||||||
file.source().buffer().use {
|
|
||||||
it.readAll(sink)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sink.hash.hex()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getGeodeOutputPath(): File {
|
private fun getGeodeOutputPath(): File {
|
||||||
val geodeName = LaunchUtils.geodeFilename
|
val geodeName = LaunchUtils.geodeFilename
|
||||||
@@ -474,13 +394,6 @@ class ReleaseManager private constructor(
|
|||||||
return File(geodeDirectory, geodeName)
|
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 {
|
private fun getTempFile(): File {
|
||||||
val geodeName = LaunchUtils.geodeFilename
|
val geodeName = LaunchUtils.geodeFilename
|
||||||
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
|
||||||
@@ -488,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
|
// 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)
|
// 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 tmpName = "tmp-$suffix.$geodeName"
|
||||||
|
|
||||||
val tempFile = File(geodeDirectory, tmpName)
|
val tempFile = File(geodeDirectory, tmpName)
|
||||||
@@ -496,19 +413,6 @@ class ReleaseManager private constructor(
|
|||||||
return tempFile
|
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.
|
* Cancels the current update job.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ object Constants {
|
|||||||
const val SUPPORTED_VERSION_CODE_MIN = 37L
|
const val SUPPORTED_VERSION_CODE_MIN = 37L
|
||||||
|
|
||||||
// anything below this version code shows a warning on the main menu
|
// 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
|
// anything above this version code will show a warning when starting the game
|
||||||
const val SUPPORTED_VERSION_CODE = 41L
|
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
|
* Extracts a file named by zipPath from inputStream and copies it to outputStream
|
||||||
*
|
*
|
||||||
@@ -160,13 +131,6 @@ object DownloadUtils {
|
|||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun copyDirectory(sourceFile: File, outputFile: File) = withContext(Dispatchers.IO) {
|
|
||||||
sourceFile.copyRecursively(
|
|
||||||
outputFile,
|
|
||||||
overwrite = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// based on <https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java>
|
// based on <https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/Progress.java>
|
||||||
|
|||||||
@@ -557,7 +557,6 @@ object GeodeUtils {
|
|||||||
const val CAPABILITY_EXTENDED_INPUT = "extended_input"
|
const val CAPABILITY_EXTENDED_INPUT = "extended_input"
|
||||||
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
|
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
|
||||||
const val CAPABILITY_RICH_INPUT = "internal_callbacks_v2"
|
const val CAPABILITY_RICH_INPUT = "internal_callbacks_v2"
|
||||||
const val CAPABILITY_RESIZE_CALLBACK = "resize_callbacks"
|
|
||||||
|
|
||||||
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
|
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ import androidx.compose.ui.res.painterResource
|
|||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import com.geode.launcher.R
|
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
|
// https://gist.github.com/tkuenneth/ddf598663f041dc79960cda503d14448?permalink_comment_id=4660486#gistcomment-4660486
|
||||||
@Composable
|
@Composable
|
||||||
@@ -47,26 +43,23 @@ enum class ApplicationIcon {
|
|||||||
DEFAULT,
|
DEFAULT,
|
||||||
GEODE,
|
GEODE,
|
||||||
PRIDE,
|
PRIDE,
|
||||||
TRANS,
|
TRANS;
|
||||||
SAPPHIRE;
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromId(id: String) = when (id) {
|
fun fromId(id: String) = when (id) {
|
||||||
"default" -> DEFAULT
|
"default" -> ApplicationIcon.DEFAULT
|
||||||
"geode" -> GEODE
|
"geode" -> ApplicationIcon.GEODE
|
||||||
"pride" -> PRIDE
|
"pride" -> ApplicationIcon.PRIDE
|
||||||
"trans" -> TRANS
|
"trans" -> ApplicationIcon.TRANS
|
||||||
"sapphire" -> SAPPHIRE
|
else -> ApplicationIcon.DEFAULT
|
||||||
else -> DEFAULT
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toId() = when (this) {
|
fun toId() = when (this) {
|
||||||
PRIDE -> "pride"
|
ApplicationIcon.PRIDE -> "pride"
|
||||||
GEODE -> "geode"
|
ApplicationIcon.GEODE -> "geode"
|
||||||
DEFAULT -> "default"
|
ApplicationIcon.DEFAULT -> "default"
|
||||||
TRANS -> "trans"
|
ApplicationIcon.TRANS -> "trans"
|
||||||
SAPPHIRE -> "sapphire"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,11 +96,5 @@ object IconUtils {
|
|||||||
iconId = R.mipmap.ic_launcher_trans,
|
iconId = R.mipmap.ic_launcher_trans,
|
||||||
nameId = R.string.application_icon_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,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ object LaunchUtils {
|
|||||||
return File(getBaseDirectory(context), "save")
|
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 {
|
fun getCrashDirectory(context: Context): File {
|
||||||
val base = getBaseDirectory(context)
|
val base = getBaseDirectory(context)
|
||||||
@@ -85,11 +85,6 @@ object LaunchUtils {
|
|||||||
return File(base, "game/geode/logs/")
|
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? {
|
fun getLastCrash(context: Context): File? {
|
||||||
val crashDirectory = getCrashDirectory(context)
|
val crashDirectory = getCrashDirectory(context)
|
||||||
if (!crashDirectory.exists()) {
|
if (!crashDirectory.exists()) {
|
||||||
@@ -98,7 +93,7 @@ object LaunchUtils {
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// 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() }
|
return children?.maxByOrNull { it.lastModified() }
|
||||||
@@ -106,9 +101,9 @@ object LaunchUtils {
|
|||||||
|
|
||||||
fun lastSessionCrashed(context: Context): Boolean {
|
fun lastSessionCrashed(context: Context): Boolean {
|
||||||
val base = getCrashDirectory(context)
|
val base = getCrashDirectory(context)
|
||||||
return CRASH_INDICATOR_NAMES.any {
|
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
||||||
File(base, it).exists()
|
|
||||||
}
|
return crashIndicatorFile.exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LauncherError : Serializable {
|
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_ERROR = "return_error"
|
||||||
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
|
const val LAUNCHER_KEY_RETURN_MESSAGE = "return_message"
|
||||||
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"
|
const val LAUNCHER_KEY_RETURN_EXTENDED_MESSAGE = "return_extended_message"
|
||||||
|
|||||||
@@ -63,7 +63,6 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
|||||||
var manualBackEvents = false
|
var manualBackEvents = false
|
||||||
var controllerEventsEnabled = false
|
var controllerEventsEnabled = false
|
||||||
private set
|
private set
|
||||||
var supportsResizeEvents = false
|
|
||||||
|
|
||||||
var cocos2dxEditText: AppCompatEditText? = null
|
var cocos2dxEditText: AppCompatEditText? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -289,12 +288,6 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
|||||||
if (!isInEditMode) {
|
if (!isInEditMode) {
|
||||||
cocos2dxRenderer.setScreenWidthAndHeight(newSurfaceWidth, newSurfaceHeight)
|
cocos2dxRenderer.setScreenWidthAndHeight(newSurfaceWidth, newSurfaceHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportsResizeEvents) {
|
|
||||||
queueEvent {
|
|
||||||
GeodeUtils.resizeSurface(newSurfaceWidth, newSurfaceHeight)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -1,18 +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,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,6 +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"/>
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_sapphire_monochrome" />
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -53,7 +53,6 @@
|
|||||||
<string name="launcher_error_view_logs">View logs</string>
|
<string name="launcher_error_view_logs">View logs</string>
|
||||||
<string name="launcher_error_copy_error_alt">Copy</string>
|
<string name="launcher_error_copy_error_alt">Copy</string>
|
||||||
<string name="launcher_error_copy">Copy details</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_crash">Share crash</string>
|
||||||
<string name="launcher_error_export_missing">No crash dump was found!</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>
|
<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_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_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_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_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_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="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>
|
<string name="message_box_cancel">Cancel</string>
|
||||||
@@ -336,7 +335,6 @@
|
|||||||
<string name="application_icon_geode">Geode</string>
|
<string name="application_icon_geode">Geode</string>
|
||||||
<string name="application_icon_pride">Pride</string>
|
<string name="application_icon_pride">Pride</string>
|
||||||
<string name="application_icon_trans">Transgender</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">Change icon to %1$s?</string>
|
||||||
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
|
<string name="application_icon_confirm_description">Geode Launcher will close itself to change the application icon.</string>
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
buildscript {
|
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.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") version "9.1.0" apply false
|
id("com.android.application") version "9.0.1" apply false
|
||||||
id("com.android.library") version "9.1.0" 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.serialization") version "2.3.10" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" apply false
|
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
#Sun Apr 05 23:29:16 MST 2026
|
#Wed Jan 28 04:12:33 MST 2026
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in new issue
Block a user