mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-24 00:59:21 +00:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
269f2fbd02 | ||
|
|
21c3374248 | ||
|
|
120f412986 | ||
|
|
633543f363 | ||
|
|
d6b4157cdc | ||
|
|
482e3bc5d0 | ||
|
|
15e24b566e | ||
|
|
0de077ec1d | ||
|
|
655587e1ee | ||
|
|
d3ad272ffc | ||
|
|
79632c9844 | ||
|
|
8f5b1b4218 | ||
|
|
23ed997640 | ||
|
|
acaff15d4d | ||
|
|
669ea87be0 | ||
|
|
b668e5032b | ||
|
|
4508abc155 | ||
|
|
38a994436f | ||
|
|
5ab71d6c62 | ||
|
|
3b88a32e8f | ||
|
|
0ceecbbc72 | ||
|
|
714f750f77 |
No files matched your search
@@ -19,8 +19,8 @@ android {
|
|||||||
applicationId = "com.geode.launcher"
|
applicationId = "com.geode.launcher"
|
||||||
minSdk = 23
|
minSdk = 23
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 26
|
versionCode = 28
|
||||||
versionName = "1.7.3"
|
versionName = "1.7.5"
|
||||||
|
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
useSupportLibrary = true
|
useSupportLibrary = true
|
||||||
@@ -78,7 +78,7 @@ android {
|
|||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
version = "4.0.0+"
|
version = "3.21.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.17.0")
|
implementation ("androidx.core:core-ktx:1.18.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.12.4")
|
implementation ("androidx.activity:activity-compose:1.13.0")
|
||||||
implementation ("androidx.activity:activity-ktx:1.12.4")
|
implementation ("androidx.activity:activity-ktx:1.13.0")
|
||||||
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.16.4")
|
implementation ("com.squareup.okio:okio:3.17.0")
|
||||||
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.9.0")
|
implementation ("androidx.browser:browser:1.10.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.GeodeLogViewActivity"
|
android:name=".preferences.TextViewActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:theme="@style/Theme.GeodeLauncher">
|
android:theme="@style/Theme.GeodeLauncher">
|
||||||
</activity>
|
</activity>
|
||||||
@@ -195,6 +195,26 @@
|
|||||||
<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 4.1.2)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
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, 12> remove_symbols{
|
constexpr std::array<const std::string_view, 10> remove_symbols{
|
||||||
"__gxx_personality_v0",
|
"__gxx_personality_v0",
|
||||||
"__cxa_throw",
|
"__cxa_throw",
|
||||||
"__cxa_rethrow",
|
"__cxa_rethrow",
|
||||||
@@ -25,9 +25,10 @@ constexpr std::array<const std::string_view, 12> 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"
|
||||||
"__emutls_get_address", // added in 2.208
|
// conflicts with fix within geode on some devices
|
||||||
"__cxa_thread_atexit"
|
// "__emutls_get_address", // added in 2.208
|
||||||
|
// "__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...
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
|||||||
returnMessage: String? = null,
|
returnMessage: String? = null,
|
||||||
returnExtendedMessage: String? = null
|
returnExtendedMessage: String? = null
|
||||||
) {
|
) {
|
||||||
val launchIntent = Intent(this, MainActivity::class.java).apply {
|
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)!!.apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
|
||||||
if (error != null && !returnMessage.isNullOrEmpty()) {
|
if (error != null && !returnMessage.isNullOrEmpty()) {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ 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
|
||||||
@@ -70,6 +72,7 @@ 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
|
||||||
@@ -82,11 +85,14 @@ 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
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import com.geode.launcher.utils.checkIconDate
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.net.ConnectException
|
import java.net.ConnectException
|
||||||
@@ -452,35 +458,75 @@ fun ExtraOptions(onSettings: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val lightOuterColorGradient = arrayOf(
|
data class ColorPalette(
|
||||||
0.0f to Color(0xfff5b11d),
|
val outerColors: List<Pair<Float, Color>>,
|
||||||
0.10f to Color(0xfff0834d),
|
val innerColors: List<Pair<Float, Color>>,
|
||||||
0.24f to Color(0xffe85f6b),
|
|
||||||
0.49f to Color(0xffc9659e),
|
|
||||||
1.0f to Color(0xffb9588f)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val lightInnerColorGradient = arrayOf(
|
data class AnimatedIconSpec(
|
||||||
0.48f to Color(0xfff2ce00),
|
val lightPalette: ColorPalette,
|
||||||
0.63f to Color(0xfff4b97e),
|
val darkPalette: ColorPalette,
|
||||||
0.73f to Color(0xfff7b66a),
|
|
||||||
1.0f to Color(0xffeb8fac)
|
@param:DrawableRes val innerId: Int,
|
||||||
|
@param:DrawableRes val outerId: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
val darkOuterColorGradient = arrayOf(
|
data class BrandPalette(
|
||||||
0.0f to Color(0xfffffdff),
|
@param:DrawableRes val darkLogo: Int,
|
||||||
0.08f to Color(0xffF2EAF5),
|
@param:DrawableRes val lightLogo: Int,
|
||||||
0.12f to Color(0xffEDE5EF),
|
|
||||||
0.31f to Color(0xffCDB5CD),
|
@param:StringRes val title: Int,
|
||||||
0.49f to Color(0xffBA9BBC),
|
val titleFont: TextStyle,
|
||||||
1.0f to Color(0xff8D7ACF),
|
val animatedIcon: AnimatedIconSpec? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
val darkInnerColorGradient = arrayOf(
|
val geodeColorPalette = BrandPalette(
|
||||||
0.48f to Color(0xffffffff),
|
animatedIcon = AnimatedIconSpec(
|
||||||
0.63f to Color(0xfff5e4c2),
|
lightPalette = ColorPalette(
|
||||||
0.73f to Color(0xffe5c7ad),
|
outerColors = listOf(
|
||||||
1.0f to Color(0xffb790a9),
|
0.0f to Color(0xfff5b11d),
|
||||||
|
0.10f to Color(0xfff0834d),
|
||||||
|
0.24f to Color(0xffe85f6b),
|
||||||
|
0.49f to Color(0xffc9659e),
|
||||||
|
1.0f to Color(0xffb9588f)
|
||||||
|
),
|
||||||
|
innerColors = listOf(
|
||||||
|
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")
|
||||||
@@ -505,22 +551,49 @@ fun GeodeLogoAnimatedPreview() {
|
|||||||
CompositionLocalProvider(LocalTheme provides theme) {
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
GeodeLauncherTheme(theme = theme) {
|
GeodeLauncherTheme(theme = theme) {
|
||||||
Surface {
|
Surface {
|
||||||
GeodeLogo(true)
|
GeodeLogo(shouldAnimate = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "sapphire, non-animated, light")
|
||||||
|
@Preview(name = "sapphire, non-animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun SapphireLogoPreview() {
|
||||||
|
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
||||||
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
|
GeodeLauncherTheme(theme = theme) {
|
||||||
|
Surface {
|
||||||
|
GeodeLogo(shouldAnimate = false, basePalette = sapphireColorPalette)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "sapphire, animated, light")
|
||||||
|
@Preview(name = "sapphire, animated, dark", uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
fun SapphireLogoAnimatedPreview() {
|
||||||
|
val theme = if (isSystemInDarkTheme()) DARK else LIGHT
|
||||||
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
|
GeodeLauncherTheme(theme = theme) {
|
||||||
|
Surface {
|
||||||
|
GeodeLogo(shouldAnimate = true, basePalette = sapphireColorPalette)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AnimatedLogo(modifier: Modifier = Modifier) {
|
fun AnimatedLogo(modifier: Modifier = Modifier, spec: AnimatedIconSpec) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
val innerColorPalette = remember(theme) {
|
|
||||||
if (theme == LIGHT) lightInnerColorGradient else darkInnerColorGradient
|
|
||||||
}
|
|
||||||
|
|
||||||
val outerColorPalette = remember(theme) {
|
val colorPalette = if (theme == LIGHT) spec.lightPalette
|
||||||
if (theme == LIGHT) lightOuterColorGradient else darkOuterColorGradient
|
else spec.darkPalette
|
||||||
}
|
|
||||||
|
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(
|
||||||
@@ -534,14 +607,14 @@ fun AnimatedLogo(modifier: Modifier = Modifier) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val innerBrush = Brush.linearGradient(
|
val innerBrush = Brush.linearGradient(
|
||||||
colorStops = innerColorPalette,
|
colorStops = innerColorPalette.toTypedArray(),
|
||||||
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,
|
colorStops = outerColorPalette.toTypedArray(),
|
||||||
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
|
||||||
@@ -549,7 +622,7 @@ fun AnimatedLogo(modifier: Modifier = Modifier) {
|
|||||||
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.geode_monochrome_inner),
|
painter = painterResource(spec.innerId),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -560,9 +633,10 @@ fun AnimatedLogo(modifier: Modifier = Modifier) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.size(64.dp, 64.dp)
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.geode_monochrome_outer),
|
painter = painterResource(spec.outerId),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
.graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
|
||||||
@@ -573,39 +647,50 @@ fun AnimatedLogo(modifier: Modifier = Modifier) {
|
|||||||
blendMode = BlendMode.SrcIn
|
blendMode = BlendMode.SrcIn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
.size(64.dp, 64.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GeodeLogo(shouldAnimate: Boolean = false, modifier: Modifier = Modifier) {
|
fun GeodeLogo(modifier: Modifier = Modifier, shouldAnimate: Boolean = false, basePalette: BrandPalette = geodeColorPalette) {
|
||||||
Row(
|
FlowRow(
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
itemVerticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
) {
|
) {
|
||||||
val theme = LocalTheme.current
|
val theme = LocalTheme.current
|
||||||
|
|
||||||
Crossfade(
|
if (basePalette.animatedIcon != null) {
|
||||||
targetState = shouldAnimate,
|
Crossfade(
|
||||||
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
targetState = shouldAnimate,
|
||||||
label="logo fade"
|
animationSpec = tween(durationMillis = 1000, easing = FastOutSlowInEasing),
|
||||||
) { screen ->
|
label="logo fade"
|
||||||
when (screen) {
|
) { screen ->
|
||||||
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp))
|
when (screen) {
|
||||||
false -> Image(
|
true -> AnimatedLogo(modifier = Modifier.size(64.dp, 64.dp), spec = basePalette.animatedIcon)
|
||||||
painterResource(if (theme == LIGHT)
|
false -> Image(
|
||||||
R.drawable.geode_base_light else R.drawable.geode_base
|
painterResource(if (theme == LIGHT)
|
||||||
),
|
basePalette.lightLogo else basePalette.darkLogo
|
||||||
contentDescription = null,
|
),
|
||||||
modifier = Modifier.size(64.dp, 64.dp)
|
contentDescription = null,
|
||||||
)
|
modifier = Modifier.size(64.dp, 64.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Image(
|
||||||
|
painterResource(if (theme == LIGHT)
|
||||||
|
basePalette.lightLogo else basePalette.darkLogo
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(84.dp, 84.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.launcher_title),
|
stringResource(basePalette.title),
|
||||||
style = launcherTitleStyle,
|
style = basePalette.titleFont,
|
||||||
fontSize = 64.sp,
|
fontSize = 64.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(12.dp)
|
.padding(12.dp)
|
||||||
@@ -693,7 +778,14 @@ fun AltMainScreen(
|
|||||||
.padding(8.dp)
|
.padding(8.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
GeodeLogo(shouldAnimate = launchUIState.isInProgress())
|
val sapphireLogo = remember {
|
||||||
|
checkIconDate() || PreferenceUtils.get(context)
|
||||||
|
.getString(PreferenceUtils.Key.SELECTED_ICON) == ApplicationIcon.SAPPHIRE.toId()
|
||||||
|
}
|
||||||
|
|
||||||
|
val basePalette = if (sapphireLogo) sapphireColorPalette
|
||||||
|
else geodeColorPalette
|
||||||
|
GeodeLogo(shouldAnimate = launchUIState.isInProgress(), basePalette = basePalette)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ 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) {
|
||||||
@@ -41,16 +42,19 @@ 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 -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
_, name -> !LaunchUtils.CRASH_INDICATOR_NAMES.contains(name)
|
||||||
} ?: return emptyList()
|
} ?: return emptyList()
|
||||||
|
|
||||||
hasIndicator = File(crashDirectory, LaunchUtils.CRASH_INDICATOR_NAME).exists()
|
hasIndicator = LaunchUtils.CRASH_INDICATOR_NAMES.any {
|
||||||
|
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 }
|
||||||
@@ -58,7 +62,12 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun clearIndicator() {
|
fun clearIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).delete()
|
LaunchUtils.CRASH_INDICATOR_NAMES.forEach {
|
||||||
|
val crashFile = File(baseDirectory, it)
|
||||||
|
if (crashFile.exists()) {
|
||||||
|
crashFile.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
hasIndicator = false
|
hasIndicator = false
|
||||||
}
|
}
|
||||||
@@ -66,7 +75,8 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
fun createIndicator() {
|
fun createIndicator() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAME).createNewFile()
|
val crashFile = File(baseDirectory, LaunchUtils.CRASH_INDICATOR_NAMES.first())
|
||||||
|
crashFile.createNewFile()
|
||||||
|
|
||||||
hasIndicator = true
|
hasIndicator = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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) {
|
||||||
@@ -43,7 +44,8 @@ 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,11 +55,14 @@ 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,
|
||||||
@@ -137,14 +140,28 @@ fun ErrorInfoTitle(failureReason: LaunchUtils.LauncherError) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onShareCrash(context: Context) {
|
fun findSomethingForCrash(context: Context): File? {
|
||||||
val lastCrash = LaunchUtils.getLastCrash(context)
|
val lastCrash = LaunchUtils.getLastCrash(context)
|
||||||
|
if (lastCrash != null) {
|
||||||
|
return lastCrash
|
||||||
|
}
|
||||||
|
|
||||||
|
val logsDirectory = LaunchUtils.getGeodeLogsDirectory(context)
|
||||||
|
if (!logsDirectory.exists()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return logsDirectory.listFiles()?.maxByOrNull { it.lastModified() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onShareCrash(context: Context) {
|
||||||
|
val lastCrash = findSomethingForCrash(context)
|
||||||
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)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||||
|
|
||||||
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
val providerLocation = lastCrash.toRelativeString(baseDirectory)
|
||||||
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||||
@@ -163,6 +180,19 @@ 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)
|
||||||
@@ -255,20 +285,34 @@ fun ErrorInfoActions(extraDetails: String?, modifier: Modifier = Modifier) {
|
|||||||
Text(stringResource(R.string.launcher_error_copy))
|
Text(stringResource(R.string.launcher_error_copy))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Button(onClick = { onShareCrash(context) }) {
|
val crashFile = remember { findSomethingForCrash(context) }
|
||||||
Icon(
|
val readableCrash = remember { crashFile?.extension == "log" }
|
||||||
painterResource(R.drawable.icon_share),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
Spacer(Modifier.size(ButtonDefaults.IconSpacing))
|
|
||||||
Text(stringResource(R.string.launcher_error_export_crash))
|
|
||||||
}
|
|
||||||
|
|
||||||
val developerSettingsEnabled = remember {
|
if (crashFile != null) {
|
||||||
PreferenceUtils.get(context).getBoolean(PreferenceUtils.Key.DEVELOPER_MODE)
|
Button(onClick = { onShareCrash(context) }) {
|
||||||
}
|
Icon(
|
||||||
if (developerSettingsEnabled) {
|
painterResource(R.drawable.icon_share),
|
||||||
FilledTonalButton(onClick = { onShowLogs(context) }) {
|
contentDescription = null
|
||||||
|
)
|
||||||
|
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
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import com.geode.launcher.utils.ApplicationIcon
|
|||||||
import com.geode.launcher.utils.ApplicationIconDetails
|
import com.geode.launcher.utils.ApplicationIconDetails
|
||||||
import com.geode.launcher.utils.IconUtils
|
import com.geode.launcher.utils.IconUtils
|
||||||
import com.geode.launcher.utils.adaptiveIconPainterResource
|
import com.geode.launcher.utils.adaptiveIconPainterResource
|
||||||
|
import com.geode.launcher.utils.checkIconDate
|
||||||
|
|
||||||
class ApplicationIconActivity : ComponentActivity() {
|
class ApplicationIconActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -231,14 +232,17 @@ fun ApplicationIconScreen(
|
|||||||
) {
|
) {
|
||||||
val selectedIcon by PreferenceUtils.useStringPreference(PreferenceUtils.Key.SELECTED_ICON)
|
val selectedIcon by PreferenceUtils.useStringPreference(PreferenceUtils.Key.SELECTED_ICON)
|
||||||
|
|
||||||
|
val aprilFools = remember { checkIconDate() }
|
||||||
|
|
||||||
Column(Modifier
|
Column(Modifier
|
||||||
.padding(horizontal = 12.dp)
|
.padding(horizontal = 12.dp)
|
||||||
.clip(RoundedCornerShape(16.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
) {
|
) {
|
||||||
ApplicationIcon.entries.forEach {
|
ApplicationIcon.entries.forEach {
|
||||||
val iconData = IconUtils.getIconDetails(it)
|
val iconData = IconUtils.getIconDetails(it)
|
||||||
|
if (!iconData.checkDate || aprilFools) {
|
||||||
IconCard(iconData, selectedIcon == iconData.id)
|
IconCard(iconData, selectedIcon == iconData.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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
|
||||||
@@ -83,7 +84,7 @@ class CrashLogsActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareCrash(context: Context, filename: String) {
|
fun shareCrash(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||||
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
||||||
|
|
||||||
if (!crashPath.exists()) {
|
if (!crashPath.exists()) {
|
||||||
@@ -110,11 +111,20 @@ 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,20 +197,6 @@ 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,
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class GeodeLogsListingActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun shareLog(context: Context, filename: String) {
|
fun shareLog(context: Context, filename: String) {
|
||||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
val baseDirectory = LaunchUtils.getBaseDirectory(context, true)
|
||||||
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, GeodeLogViewActivity::class.java).run {
|
val launchIntent = Intent(context, TextViewActivity::class.java).run {
|
||||||
putExtra(GeodeLogViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.filename)
|
putExtra(TextViewActivity.EXTRA_LOG_VIEW_FILENAME, geodeLog.fullPath)
|
||||||
}
|
}
|
||||||
context.startActivity(launchIntent)
|
context.startActivity(launchIntent)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -497,7 +497,7 @@ fun GameplaySettingsGroup() {
|
|||||||
SafeModeDialog(onDismiss = {
|
SafeModeDialog(onDismiss = {
|
||||||
showSafeModeDialog = false
|
showSafeModeDialog = false
|
||||||
}) {
|
}) {
|
||||||
val launchIntent = Intent(context, MainActivity::class.java).apply {
|
val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)!!.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,6 +581,20 @@ 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 = {
|
||||||
|
|||||||
+52
-7
@@ -1,7 +1,11 @@
|
|||||||
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
|
||||||
@@ -12,7 +16,6 @@ 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
|
||||||
@@ -35,23 +38,27 @@ 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.log.GeodeLogsViewModel
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
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 GeodeLogViewActivity : ComponentActivity() {
|
class TextViewActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
const val EXTRA_LOG_VIEW_FILENAME = "EXTRA_LOG_FILENAME"
|
||||||
}
|
}
|
||||||
@@ -90,19 +97,48 @@ class GeodeLogViewActivity : 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +159,21 @@ fun GeodeLogView(
|
|||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
filename,
|
toRead.name,
|
||||||
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) {
|
||||||
@@ -16,9 +16,20 @@ 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,6 +62,11 @@ 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() {
|
||||||
@@ -101,6 +106,18 @@ 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() {
|
||||||
@@ -126,8 +143,7 @@ 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +155,10 @@ 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() {
|
||||||
@@ -151,11 +171,19 @@ 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,12 +18,16 @@ 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
|
||||||
|
|
||||||
@@ -94,7 +98,10 @@ 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
|
||||||
@@ -166,7 +173,59 @@ 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)) {
|
||||||
|
throw IOException("failed to rename resources output directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
sendError(e)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
val tempPathClone = File(outputPath)
|
||||||
|
runCatching {
|
||||||
|
if (tempPathClone.exists()) tempPathClone.deleteRecursively()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun performUpdate(release: Downloadable) {
|
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")
|
||||||
@@ -175,8 +234,14 @@ 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
|
||||||
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
|
val initialSize = if (releaseAsset.size == null && resourcesAsset?.size == null) null else releaseSize + resourcesSize
|
||||||
|
_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
|
||||||
@@ -195,7 +260,10 @@ class ReleaseManager private constructor(
|
|||||||
httpClient,
|
httpClient,
|
||||||
releaseAsset.url,
|
releaseAsset.url,
|
||||||
onProgress = { progress, outOf ->
|
onProgress = { progress, outOf ->
|
||||||
_uiState.value = ReleaseManagerState.InDownload(progress, outOf)
|
if (!skipStateUpdate)
|
||||||
|
_uiState.value = ReleaseManagerState.InDownload(progress, outOf + resourcesSize)
|
||||||
|
|
||||||
|
releaseSize = max(outOf, releaseSize)
|
||||||
},
|
},
|
||||||
onResponse = { body ->
|
onResponse = { body ->
|
||||||
DownloadUtils.extractFileFromZipStream(
|
DownloadUtils.extractFileFromZipStream(
|
||||||
@@ -229,6 +297,12 @@ class ReleaseManager private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resourcesAsset != null) {
|
||||||
|
if (!performResourceDownload(resourcesAsset, releaseSize)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// extraction performed
|
// extraction performed
|
||||||
updatePreferences(release)
|
updatePreferences(release)
|
||||||
|
|
||||||
@@ -249,10 +323,9 @@ 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 false
|
?: return true
|
||||||
|
|
||||||
val currentFileHash = computeFileHash(geodeFile)
|
val currentFileHash = computeFileHash(geodeFile)
|
||||||
|
|
||||||
return originalFileHash != currentFileHash
|
return originalFileHash != currentFileHash
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,8 +457,14 @@ 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 {
|
||||||
file.source().buffer().use { it.readByteString().md5().hex() }
|
val sink = HashingSink.md5(blackholeSink())
|
||||||
|
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
|
||||||
@@ -394,6 +473,13 @@ 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)
|
||||||
@@ -401,11 +487,7 @@ 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 alphabet = ('A'..'Z') + ('a'..'z') + ('0'..'9')
|
val suffix = createRandomString()
|
||||||
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)
|
||||||
@@ -413,6 +495,19 @@ 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 = 40L
|
const val SUPPORTED_VERSION_CODE_MIN_WARNING = 41L
|
||||||
|
|
||||||
// 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,6 +72,35 @@ 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
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ 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
|
||||||
@@ -43,31 +47,40 @@ 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" -> ApplicationIcon.DEFAULT
|
"default" -> DEFAULT
|
||||||
"geode" -> ApplicationIcon.GEODE
|
"geode" -> GEODE
|
||||||
"pride" -> ApplicationIcon.PRIDE
|
"pride" -> PRIDE
|
||||||
"trans" -> ApplicationIcon.TRANS
|
"trans" -> TRANS
|
||||||
else -> ApplicationIcon.DEFAULT
|
"sapphire" -> SAPPHIRE
|
||||||
|
else -> DEFAULT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toId() = when (this) {
|
fun toId() = when (this) {
|
||||||
ApplicationIcon.PRIDE -> "pride"
|
PRIDE -> "pride"
|
||||||
ApplicationIcon.GEODE -> "geode"
|
GEODE -> "geode"
|
||||||
ApplicationIcon.DEFAULT -> "default"
|
DEFAULT -> "default"
|
||||||
ApplicationIcon.TRANS -> "trans"
|
TRANS -> "trans"
|
||||||
|
SAPPHIRE -> "sapphire"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkIconDate(): Boolean {
|
||||||
|
val currentDate = Clock.System.todayIn(TimeZone.currentSystemDefault())
|
||||||
|
return currentDate.month == Month.APRIL && currentDate.day <= 7
|
||||||
|
}
|
||||||
|
|
||||||
data class ApplicationIconDetails(
|
data class ApplicationIconDetails(
|
||||||
val id: String,
|
val id: String,
|
||||||
val component: String,
|
val component: String,
|
||||||
@param:DrawableRes val iconId: Int,
|
@param:DrawableRes val iconId: Int,
|
||||||
@param:StringRes val nameId: Int,
|
@param:StringRes val nameId: Int,
|
||||||
|
val checkDate: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
object IconUtils {
|
object IconUtils {
|
||||||
@@ -96,5 +109,12 @@ 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,
|
||||||
|
checkDate = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ object LaunchUtils {
|
|||||||
return File(getBaseDirectory(context), "save")
|
return File(getBaseDirectory(context), "save")
|
||||||
}
|
}
|
||||||
|
|
||||||
const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
val CRASH_INDICATOR_NAMES = setOf("lastSessionDidCrash", "last-pid", "last-crashed")
|
||||||
|
|
||||||
fun getCrashDirectory(context: Context): File {
|
fun getCrashDirectory(context: Context): File {
|
||||||
val base = getBaseDirectory(context)
|
val base = getBaseDirectory(context)
|
||||||
@@ -85,6 +85,11 @@ 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()) {
|
||||||
@@ -93,7 +98,7 @@ object LaunchUtils {
|
|||||||
|
|
||||||
val children = crashDirectory.listFiles {
|
val children = crashDirectory.listFiles {
|
||||||
// ignore indicator files (including old file)
|
// ignore indicator files (including old file)
|
||||||
_, name -> name != CRASH_INDICATOR_NAME && name != "last-pid"
|
_, name -> !CRASH_INDICATOR_NAMES.contains(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return children?.maxByOrNull { it.lastModified() }
|
return children?.maxByOrNull { it.lastModified() }
|
||||||
@@ -101,9 +106,9 @@ object LaunchUtils {
|
|||||||
|
|
||||||
fun lastSessionCrashed(context: Context): Boolean {
|
fun lastSessionCrashed(context: Context): Boolean {
|
||||||
val base = getCrashDirectory(context)
|
val base = getCrashDirectory(context)
|
||||||
val crashIndicatorFile = File(base, CRASH_INDICATOR_NAME)
|
return CRASH_INDICATOR_NAMES.any {
|
||||||
|
File(base, it).exists()
|
||||||
return crashIndicatorFile.exists()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LauncherError : Serializable {
|
enum class LauncherError : Serializable {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<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>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="643"
|
||||||
|
android:viewportHeight="766">
|
||||||
|
<group android:scaleX="0.41038585"
|
||||||
|
android:scaleY="0.4888889"
|
||||||
|
android:translateX="191.0163"
|
||||||
|
android:translateY="195.75555">
|
||||||
|
<path
|
||||||
|
android:pathData="M299.94,33.78C313.01,25.85 329.41,25.85 342.48,33.78L370.9,51.03L268.17,116.49L268.23,116.51L92.68,223.61C84.64,228.51 79.74,237.25 79.74,246.66V509.84C79.74,519.26 84.64,527.99 92.68,532.89L306.22,663.17C314.85,668.44 325.71,668.44 334.34,663.17L547.89,532.89C555.92,527.99 560.82,519.26 560.82,509.85V246.66C560.82,246.2 560.81,245.74 560.78,245.28V166.26L599.75,189.9C612,197.34 619.48,210.63 619.48,224.96V540.81C619.48,555.14 612,568.42 599.75,575.86L342.48,731.98C329.41,739.91 313.01,739.91 299.94,731.98L42.66,575.86C30.41,568.42 22.93,555.14 22.93,540.81V224.96C22.93,210.63 30.41,197.34 42.66,189.9L299.94,33.78Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.15"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M311.75,173.17C318.78,168.88 327.62,168.88 334.65,173.17L481.88,262.87C488.43,266.86 492.43,273.98 492.43,281.66V463.16C492.43,470.84 488.43,477.96 481.88,481.95L334.65,571.65C327.62,575.94 318.78,575.94 311.75,571.65L164.52,481.95C157.97,477.96 153.97,470.84 153.97,463.16V414.95L208.24,448.54C208.52,448.75 208.8,448.95 209.1,449.13L315.66,515.36C319.22,517.57 323.72,517.57 327.28,515.36L433.84,449.13C437.07,447.13 439.03,443.59 439.03,439.79V305.9C439.03,302.09 437.07,298.56 433.84,296.55L327.28,230.33C323.72,228.12 319.22,228.12 315.66,230.33L209.1,296.55C208.62,296.85 208.17,297.18 207.75,297.55L153.97,331.28V281.66C153.97,273.98 157.97,266.86 164.52,262.87L311.75,173.17Z"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.15"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M299.94,5.95C313.01,-1.98 329.41,-1.98 342.48,5.95L374.73,25.52L264.09,96.01L264.15,96.03L74.11,211.97C66.07,216.88 61.17,225.61 61.17,235.02V520.77C61.17,530.18 66.07,538.92 74.11,543.82L306.15,685.38C314.78,690.65 325.64,690.65 334.27,685.38L566.31,543.82C574.34,538.92 579.25,530.18 579.25,520.77V235.02C579.25,234.53 579.23,234.05 579.2,233.56V149.6L622.68,175.99C634.93,183.42 642.41,196.71 642.41,211.04V554.72C642.41,569.05 634.93,582.34 622.68,589.78L342.48,759.82C329.41,767.75 313.01,767.75 299.94,759.82L19.73,589.78C7.48,582.34 0,569.05 0,554.72V211.04C0,196.71 7.48,183.42 19.73,175.99L299.94,5.95Z"
|
||||||
|
android:fillColor="#ffffff"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M311.75,142.59C318.78,138.31 327.62,138.31 334.65,142.59L507.79,248.08C514.34,252.08 518.34,259.2 518.34,266.87V479.95C518.34,487.62 514.34,494.74 507.79,498.74L334.65,604.23C327.62,608.51 318.78,608.51 311.75,604.23L138.61,498.74C132.06,494.74 128.06,487.62 128.06,479.95V422.46L189.93,460.76C190.22,460.98 190.52,461.19 190.84,461.39L315.4,538.8C318.95,541.01 323.46,541.01 327.01,538.8L451.58,461.39C454.8,459.38 456.77,455.85 456.77,452.04V295.77C456.77,291.97 454.8,288.44 451.58,286.43L327.01,209.02C323.46,206.81 318.95,206.81 315.4,209.02L190.84,286.43C190.32,286.76 189.83,287.12 189.37,287.52L128.06,325.98V266.87C128.06,259.2 132.06,252.08 138.61,248.08L311.75,142.59Z"
|
||||||
|
android:fillColor="#ffffff"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</group>
|
||||||
|
</vector>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_sapphire_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_sapphire_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
<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>
|
||||||
@@ -118,10 +119,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">Enable the safe mode button in Settings.</string>
|
<string name="load_failed_recommendation_safe_mode">Use 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>
|
||||||
@@ -335,6 +336,7 @@
|
|||||||
<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>
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,13 +1,13 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
val composeBOM by extra("2026.02.00")
|
val composeBOM by extra("2026.03.01")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.0.1" apply false
|
id("com.android.application") version "9.0.1" apply false
|
||||||
id("com.android.library") version "9.0.1" apply false
|
id("com.android.library") version "9.0.1" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.10" apply false
|
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.20" apply false
|
||||||
id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" apply false
|
id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Delete>("clean") {
|
tasks.register<Delete>("clean") {
|
||||||
|
|||||||
Reference in new issue
Block a user