mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-24 00:59:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
642fdc36ee | ||
|
|
c6155443a2 | ||
|
|
b96561bf33 | ||
|
|
61c018ee0d | ||
|
|
41e3e0a133 | ||
|
|
3853a469cd | ||
|
|
62b31b8924 | ||
|
|
ab1d0c96f4 | ||
|
|
96354096e2 | ||
|
|
25b686f183 | ||
|
|
7694236434 | ||
|
|
7c0f1da312 | ||
|
|
019d9747b5 | ||
|
|
e806ec223a | ||
|
|
9f58e76f97 | ||
|
|
371750a184 | ||
|
|
2494bda4e8 | ||
|
|
b67be09cd3 | ||
|
|
09b1c9bc3a | ||
|
|
483fb56e25 | ||
|
|
7857fae68f | ||
|
|
61b84cd223 | ||
|
|
bd67332adc | ||
|
|
84657fd6b3 | ||
|
|
f197b45f9e | ||
|
|
d0dc8bec5d | ||
|
|
c9e0408eb2 | ||
|
|
b50a5b2b54 | ||
|
|
c8e3d7404c | ||
|
|
c4c41e324e | ||
|
|
4d324913df | ||
|
|
19bd549b1f | ||
|
|
9dc501c9ab | ||
|
|
5521869ac9 |
No files matched your search
@@ -61,7 +61,7 @@ jobs:
|
||||
echo "path=$OUT_PATH" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload development build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: geode-launcher-${{ matrix.config.lower }}
|
||||
path: |
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# Geode Android Launcher
|
||||
|
||||
Launches a vanilla copy of Geometry Dash with the Geode loader added.
|
||||
|
||||
## TODO:
|
||||
|
||||
* Figure out better way of packaging libgeode
|
||||
@@ -1,98 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.geode.launcher"
|
||||
minSdk 23
|
||||
targetSdk 34
|
||||
versionCode 9
|
||||
versionName "1.1.1"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments "-DUSE_TULIPHOOK:BOOL=OFF"
|
||||
}
|
||||
}
|
||||
|
||||
ndkConfig.abiFilters "arm64-v8a", "armeabi-v7a"
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
enable true
|
||||
reset()
|
||||
|
||||
//noinspection ChromeOsAbiSupport (not my fault)
|
||||
include "arm64-v8a", "armeabi-v7a"
|
||||
|
||||
universalApk true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
// enables a polyfill for java Instant on api levels < 26 (used for updater)
|
||||
coreLibraryDesugaringEnabled true
|
||||
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
buildConfig true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion compose_compiler
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path file('src/main/cpp/CMakeLists.txt')
|
||||
}
|
||||
}
|
||||
namespace 'com.geode.launcher'
|
||||
ndkVersion '26.1.10909125'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation platform('androidx.compose:compose-bom:2023.10.01')
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
implementation "androidx.compose.ui:ui"
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation "androidx.compose.ui:ui-tooling-preview"
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0"
|
||||
implementation 'androidx.activity:activity-compose:1.8.2'
|
||||
implementation 'androidx.activity:activity-ktx:1.8.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation "androidx.documentfile:documentfile:1.0.1"
|
||||
implementation "com.squareup.okio:okio:3.7.0"
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-datetime:0.5.0'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.0"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling"
|
||||
debugImplementation "androidx.compose.ui:ui-test-manifest"
|
||||
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
val composeCompiler: String by rootProject.extra
|
||||
val composeBOM: String by rootProject.extra
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.geode.launcher"
|
||||
minSdk = 23
|
||||
targetSdk = 34
|
||||
versionCode = 11
|
||||
versionName = "1.2.1"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments("-DUSE_TULIPHOOK:BOOL=OFF")
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection ChromeOsAbiSupport (not my fault)
|
||||
ndk.abiFilters += listOf("arm64-v8a", "armeabi-v7a")
|
||||
}
|
||||
|
||||
splits {
|
||||
abi {
|
||||
isEnable = true
|
||||
reset()
|
||||
|
||||
//noinspection ChromeOsAbiSupport. i'm sorry!
|
||||
include("arm64-v8a", "armeabi-v7a")
|
||||
|
||||
isUniversalApk = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
// enables a polyfill for java Instant on api levels < 26 (used for updater)
|
||||
isCoreLibraryDesugaringEnabled = true
|
||||
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = composeCompiler
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/cpp/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
namespace = "com.geode.launcher"
|
||||
ndkVersion = "26.1.10909125"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation (platform("androidx.compose:compose-bom:$composeBOM"))
|
||||
implementation ("androidx.core:core-ktx:1.12.0")
|
||||
implementation ("androidx.compose.ui:ui")
|
||||
implementation ("androidx.compose.material3:material3")
|
||||
implementation ("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
|
||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
|
||||
implementation ("androidx.activity:activity-compose:1.8.2")
|
||||
implementation ("androidx.activity:activity-ktx:1.8.2")
|
||||
implementation ("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation ("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation ("com.squareup.okio:okio:3.7.0")
|
||||
implementation ("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
||||
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.6.2")
|
||||
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
|
||||
debugImplementation ("androidx.compose.ui:ui-tooling")
|
||||
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||
}
|
||||
@@ -23,14 +23,14 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
@@ -51,9 +51,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
@@ -104,16 +106,20 @@ fun getLogColor(priority: LogPriority): Color {
|
||||
// i just copied these from my ide tbh
|
||||
return if (isDark) {
|
||||
when (priority) {
|
||||
LogPriority.ERROR -> Color(0xffdf5151)
|
||||
LogPriority.WARN -> Color(0xffbcb500)
|
||||
LogPriority.FATAL -> Color(0xffb8b6fc)
|
||||
LogPriority.ERROR -> Color(0xff_df_51_51)
|
||||
LogPriority.WARN -> Color(0xff_bc_b5_00)
|
||||
LogPriority.FATAL -> Color(0xff_b8_b6_fc)
|
||||
LogPriority.DEBUG -> Color(0xff_aa_aa_aa)
|
||||
LogPriority.VERBOSE -> Color(0xff_77_77_77)
|
||||
else -> Color.Unspecified
|
||||
}
|
||||
} else {
|
||||
when (priority) {
|
||||
LogPriority.ERROR -> Color(0xffdf5151)
|
||||
LogPriority.WARN -> Color(0xffeab700)
|
||||
LogPriority.FATAL -> Color(0xffa142f4)
|
||||
LogPriority.ERROR -> Color(0xff_df_51_51)
|
||||
LogPriority.WARN -> Color(0xff_ea_b7_00)
|
||||
LogPriority.FATAL -> Color(0xff_a1_42_f4)
|
||||
LogPriority.DEBUG -> Color(0xff_77_77_77)
|
||||
LogPriority.VERBOSE -> Color(0xff_aa_aa_aa)
|
||||
else -> Color.Unspecified
|
||||
}
|
||||
}
|
||||
@@ -138,16 +144,39 @@ fun copyLogText(context: Context, logMessage: String) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectLogLevelDialog(
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (LogPriority) -> Unit,
|
||||
initialValue: LogPriority
|
||||
) {
|
||||
SelectDialog(
|
||||
title = stringResource(R.string.application_logs_level_dialog_name),
|
||||
onDismissRequest = onDismissRequest,
|
||||
onSelect = { onSelect(it) },
|
||||
initialValue = initialValue,
|
||||
toLabel = {
|
||||
// i'm lazy
|
||||
it.name.lowercase().replaceFirstChar { c -> c.uppercaseChar() }
|
||||
},
|
||||
optionsCount = LogPriority.FATAL downTo LogPriority.VERBOSE
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LogCard(logLine: LogLine, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val haptics = LocalHapticFeedback.current
|
||||
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(8.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
copyLogText(context, logLine.asSimpleString)
|
||||
}
|
||||
)
|
||||
@@ -205,6 +234,18 @@ fun ApplicationLogsScreen(
|
||||
listState.scrollToItem(logLines.size)
|
||||
}
|
||||
|
||||
var showLogLevelSelect by remember { mutableStateOf(false) }
|
||||
if (showLogLevelSelect) {
|
||||
SelectLogLevelDialog(
|
||||
onDismissRequest = { showLogLevelSelect = false },
|
||||
onSelect = {
|
||||
logViewModel.logLevel = it
|
||||
showLogLevelSelect = false
|
||||
},
|
||||
initialValue = logViewModel.logLevel
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
@@ -213,7 +254,7 @@ fun ApplicationLogsScreen(
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back_icon_alt)
|
||||
)
|
||||
}
|
||||
@@ -238,7 +279,7 @@ fun ApplicationLogsScreen(
|
||||
contentDescription = stringResource(R.string.application_logs_share)
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { showMoreOptions = !showMoreOptions },) {
|
||||
IconButton(onClick = { showMoreOptions = !showMoreOptions }) {
|
||||
Icon(
|
||||
Icons.Filled.MoreVert,
|
||||
contentDescription = stringResource(R.string.application_logs_more)
|
||||
@@ -252,7 +293,7 @@ fun ApplicationLogsScreen(
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_filter_list),
|
||||
painterResource(R.drawable.icon_bug_report),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
@@ -272,6 +313,22 @@ fun ApplicationLogsScreen(
|
||||
}
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.drawable.icon_filter_list),
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(stringResource(R.string.application_logs_level))
|
||||
},
|
||||
onClick = {
|
||||
showLogLevelSelect = true
|
||||
showMoreOptions = false
|
||||
}
|
||||
)
|
||||
|
||||
DropdownMenuItem(
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
@@ -345,7 +402,7 @@ fun ApplicationLogsScreen(
|
||||
logLines
|
||||
) { line ->
|
||||
LogCard(line)
|
||||
Divider()
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.geode.launcher
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Intent
|
||||
@@ -8,7 +7,6 @@ import android.content.IntentFilter
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
@@ -37,7 +35,7 @@ import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
|
||||
|
||||
class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener {
|
||||
class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperListener, GeodeUtils.CapabilityListener {
|
||||
private var mGLSurfaceView: Cocos2dxGLSurfaceView? = null
|
||||
private val sTag = GeometryDashActivity::class.simpleName
|
||||
private var mIsRunning = false
|
||||
@@ -59,7 +57,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
try {
|
||||
tryLoadGame()
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
e.printStackTrace()
|
||||
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)
|
||||
@@ -79,7 +77,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Log.e("GeodeLauncher", "Uncaught error during game load", e)
|
||||
|
||||
returnToMain(
|
||||
getString(R.string.load_failed_generic_error),
|
||||
@@ -108,22 +106,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
|
||||
setupRedirection(gdPackageInfo)
|
||||
|
||||
// request read and write permissions
|
||||
val hasStoragePerms = (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) and
|
||||
(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
|
||||
|
||||
if (!hasStoragePerms) {
|
||||
requestPermissions(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
),
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
Cocos2dxHelper.init(this, this)
|
||||
|
||||
GeodeUtils.setContext(this)
|
||||
GeodeUtils.setCapabilityListener(this)
|
||||
|
||||
tryLoadLibrary(gdPackageInfo, Constants.FMOD_LIB_NAME)
|
||||
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
|
||||
@@ -136,8 +122,25 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
|
||||
setupPostLibraryLoad(gdPackageInfo)
|
||||
|
||||
if (!loadGeodeLibrary()) {
|
||||
try {
|
||||
loadGeodeLibrary()
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
handleGeodeException(e)
|
||||
} catch (e: Exception) {
|
||||
handleGeodeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleGeodeException(e: Throwable) {
|
||||
e.printStackTrace()
|
||||
|
||||
val ignoreFailure = PreferenceUtils.get(this)
|
||||
.getBoolean(PreferenceUtils.Key.IGNORE_LOAD_FAILURE)
|
||||
|
||||
if (ignoreFailure) {
|
||||
Log.w("GeodeLauncher", "could not load Geode object!")
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +148,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
try {
|
||||
LaunchUtils.addAssetsFromPackage(assets, packageInfo)
|
||||
} catch (e: NoSuchMethodException) {
|
||||
e.printStackTrace()
|
||||
Log.e("GeodeLauncher", "Failed to add asset redirection", e)
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -157,7 +160,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
LauncherFix.setOriginalDataPath(Constants.GJ_DATA_DIR)
|
||||
LauncherFix.setDataPath(saveDir.path)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
e.printStackTrace()
|
||||
Log.e("GeodeLauncher", "Failed to load LauncherFix", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,26 +212,26 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
private fun loadGeodeLibrary(): Boolean {
|
||||
private fun loadGeodeLibrary() {
|
||||
// Load Geode if exists
|
||||
// bundling the object with the application allows for nicer backtraces
|
||||
try {
|
||||
// put libgeode.so in jniLibs/armeabi-v7a to get this
|
||||
System.loadLibrary("geode")
|
||||
return true
|
||||
return
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
// but users may prefer it stored with data
|
||||
val geodeFilename = LaunchUtils.geodeFilename
|
||||
val geodePath = File(filesDir.path, "launcher/$geodeFilename")
|
||||
if (geodePath.exists()) {
|
||||
System.load(geodePath.path)
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
// you know zmx i have 0 clue what this does so im
|
||||
// just gonna like copy the binary from external
|
||||
// also i get 20 million permission denied errors
|
||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this) ?: return false
|
||||
val externalGeodePath = LaunchUtils.getInstalledGeodePath(this)!!
|
||||
|
||||
val copiedPath = File(filesDir.path, "copied")
|
||||
if (copiedPath.exists()) {
|
||||
@@ -245,17 +248,14 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
)
|
||||
|
||||
if (copiedGeodePath.exists()) {
|
||||
try {
|
||||
println("Loading Geode from ${externalGeodePath.name}")
|
||||
System.load(copiedGeodePath.path)
|
||||
} catch (e: UnsatisfiedLinkError) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
println("Loading Geode from ${externalGeodePath.name}")
|
||||
System.load(copiedGeodePath.path)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
private fun createView(): FrameLayout {
|
||||
@@ -450,4 +450,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCapabilityAdded(capability: String): Boolean {
|
||||
if (capability == GeodeUtils.CAPABILITY_EXTENDED_INPUT) {
|
||||
mGLSurfaceView?.useKeyboardEvents = true
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
@@ -27,8 +29,11 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -46,7 +51,10 @@ import com.geode.launcher.utils.Constants
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
import com.geode.launcher.updater.ReleaseManager
|
||||
import com.geode.launcher.utils.GeodeUtils
|
||||
import com.geode.launcher.utils.useCountdownTimer
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import java.net.ConnectException
|
||||
import java.net.UnknownHostException
|
||||
|
||||
@@ -122,7 +130,7 @@ fun LauncherUpdateIndicator(modifier: Modifier = Modifier, openTo: String, onDis
|
||||
|
||||
Spacer(Modifier.size(4.dp))
|
||||
|
||||
TextButton(onClick = { uriHandler.openUri(openTo) },) {
|
||||
TextButton(onClick = { uriHandler.openUri(openTo) }) {
|
||||
Text(stringResource(R.string.launcher_download))
|
||||
}
|
||||
}
|
||||
@@ -137,7 +145,7 @@ fun UpdateProgressIndicator(
|
||||
message: String,
|
||||
releaseViewModel: ReleaseViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
progress: Float? = null
|
||||
progress: (() -> Float)? = null
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
@@ -151,7 +159,7 @@ fun UpdateProgressIndicator(
|
||||
if (progress == null) {
|
||||
LinearProgressIndicator()
|
||||
} else {
|
||||
LinearProgressIndicator(progress)
|
||||
LinearProgressIndicator(progress = progress)
|
||||
}
|
||||
|
||||
TextButton(
|
||||
@@ -226,67 +234,91 @@ fun LoadFailedDialog(returnTitle: String, returnMessage: String) {
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateWarning() {
|
||||
fun UpdateWarning(inSafeMode: Boolean = false, onDismiss: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val packageManager = context.packageManager
|
||||
|
||||
val gdVersionCode = remember { LaunchUtils.getGeometryDashVersionCode(packageManager) }
|
||||
val gdVersionString = remember { LaunchUtils.getGeometryDashVersionString(packageManager) }
|
||||
|
||||
var showUpdateWarning by remember { mutableStateOf(true) }
|
||||
|
||||
var lastDismissedVersion by PreferenceUtils.useLongPreference(
|
||||
preferenceKey = PreferenceUtils.Key.DISMISSED_GJ_UPDATE
|
||||
)
|
||||
|
||||
if (gdVersionCode != Constants.SUPPORTED_VERSION_CODE && gdVersionCode != lastDismissedVersion) {
|
||||
if (showUpdateWarning) {
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Warning,
|
||||
contentDescription = stringResource(R.string.launcher_warning_icon_alt)
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(R.string.launcher_unsupported_version_title))
|
||||
},
|
||||
text = {
|
||||
val message = if (gdVersionCode >= Constants.SUPPORTED_VERSION_CODE) {
|
||||
R.string.launcher_unsupported_version_description
|
||||
} else {
|
||||
R.string.launcher_outdated_version_description
|
||||
}
|
||||
val canDismissRelease = gdVersionCode >= Constants.SUPPORTED_VERSION_CODE
|
||||
val shouldDismiss = canDismissRelease && gdVersionCode == lastDismissedVersion
|
||||
|
||||
Text(stringResource(
|
||||
message, gdVersionString, Constants.SUPPORTED_VERSION_STRING
|
||||
))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showUpdateWarning = false
|
||||
lastDismissedVersion = gdVersionCode
|
||||
if (gdVersionCode != Constants.SUPPORTED_VERSION_CODE && !shouldDismiss) {
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Warning,
|
||||
contentDescription = stringResource(R.string.launcher_warning_icon_alt)
|
||||
)
|
||||
},
|
||||
title = {
|
||||
Text(stringResource(R.string.launcher_unsupported_version_title))
|
||||
},
|
||||
text = {
|
||||
val message = if (gdVersionCode >= Constants.SUPPORTED_VERSION_CODE) {
|
||||
R.string.launcher_unsupported_version_description
|
||||
} else {
|
||||
R.string.launcher_outdated_version_description
|
||||
}
|
||||
|
||||
onLaunch(context)
|
||||
}) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showUpdateWarning = false }) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { showUpdateWarning = false }
|
||||
)
|
||||
}
|
||||
Text(stringResource(
|
||||
message, gdVersionString, Constants.SUPPORTED_VERSION_STRING
|
||||
))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onDismiss()
|
||||
lastDismissedVersion = gdVersionCode
|
||||
|
||||
onLaunch(context, inSafeMode)
|
||||
}) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { onDismiss() }) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { onDismiss() }
|
||||
)
|
||||
} else {
|
||||
LaunchedEffect(gdVersionCode) {
|
||||
onLaunch(context)
|
||||
onLaunch(context, inSafeMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SafeModeDialog(onDismiss: () -> Unit, onLaunch: () -> Unit) {
|
||||
AlertDialog(
|
||||
icon = {
|
||||
Icon(
|
||||
Icons.Filled.Warning,
|
||||
contentDescription = stringResource(R.string.launcher_warning_icon_alt)
|
||||
)
|
||||
},
|
||||
title = { Text(stringResource(R.string.safe_mode_enable_title)) },
|
||||
text = { Text(stringResource(R.string.safe_mode_enable_description)) },
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onLaunch) {
|
||||
Text(stringResource(R.string.message_box_continue))
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismiss
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
@@ -317,8 +349,6 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
|
||||
)
|
||||
}
|
||||
is ReleaseViewModel.ReleaseUIState.InDownload -> {
|
||||
val progress = state.downloaded / state.outOf.toDouble()
|
||||
|
||||
val downloaded = remember(state.downloaded) {
|
||||
formatShortFileSize(context, state.downloaded)
|
||||
}
|
||||
@@ -335,7 +365,10 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
|
||||
),
|
||||
modifier = modifier,
|
||||
releaseViewModel = releaseViewModel,
|
||||
progress = progress.toFloat()
|
||||
progress = {
|
||||
val progress = state.downloaded / state.outOf.toDouble()
|
||||
progress.toFloat()
|
||||
}
|
||||
)
|
||||
}
|
||||
is ReleaseViewModel.ReleaseUIState.InUpdateCheck -> {
|
||||
@@ -389,7 +422,13 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
|
||||
}
|
||||
}
|
||||
|
||||
fun onLaunch(context: Context) {
|
||||
fun onLaunch(context: Context, safeMode: Boolean = false) {
|
||||
if (safeMode) {
|
||||
GeodeUtils.setAdditionalLaunchArguments(GeodeUtils.ARGUMENT_SAFE_MODE)
|
||||
} else {
|
||||
GeodeUtils.clearLaunchArguments()
|
||||
}
|
||||
|
||||
val launchIntent = Intent(context, GeometryDashActivity::class.java)
|
||||
launchIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
|
||||
@@ -410,7 +449,7 @@ fun MainScreen(
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val shouldAutomaticallyLaunch = PreferenceUtils.useBooleanPreference(
|
||||
val shouldAutomaticallyLaunch by PreferenceUtils.useBooleanPreference(
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY
|
||||
)
|
||||
|
||||
@@ -423,6 +462,8 @@ fun MainScreen(
|
||||
val geodeInstalled = geodePreinstalled || geodeJustInstalled
|
||||
|
||||
var beginLaunch by remember { mutableStateOf(false) }
|
||||
var showSafeModeDialog by remember { mutableStateOf(false) }
|
||||
var launchInSafeMode by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(shouldUpdate) {
|
||||
if (shouldUpdate && !releaseViewModel.hasPerformedCheck) {
|
||||
@@ -451,15 +492,11 @@ fun MainScreen(
|
||||
)
|
||||
|
||||
if (gdInstalled && geodeInstalled) {
|
||||
if (shouldAutomaticallyLaunch.value && !releaseViewModel.isInUpdate && !disableAutomaticLaunch) {
|
||||
val stopLaunch = releaseViewModel.isInUpdate || disableAutomaticLaunch || showSafeModeDialog
|
||||
if (shouldAutomaticallyLaunch && !stopLaunch) {
|
||||
val countdownTimer = useCountdownTimer(
|
||||
time = 3000,
|
||||
onCountdownFinish = {
|
||||
// just in case this changed async
|
||||
if (shouldAutomaticallyLaunch.value) {
|
||||
beginLaunch = true
|
||||
}
|
||||
}
|
||||
onCountdownFinish = { beginLaunch = true }
|
||||
)
|
||||
|
||||
if (countdownTimer != 0L) {
|
||||
@@ -476,10 +513,42 @@ fun MainScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// compose apis don't provide a good way of adding long press to a button
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val viewConfiguration = LocalViewConfiguration.current
|
||||
val haptics = LocalHapticFeedback.current
|
||||
|
||||
LaunchedEffect(interactionSource) {
|
||||
interactionSource.interactions.collectLatest { interaction ->
|
||||
when (interaction) {
|
||||
is PressInteraction.Press -> {
|
||||
launchInSafeMode = false
|
||||
|
||||
delay(viewConfiguration.longPressTimeoutMillis)
|
||||
|
||||
// perform a second delay to make the action more obvious
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
delay(viewConfiguration.longPressTimeoutMillis)
|
||||
|
||||
haptics.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
|
||||
showSafeModeDialog = true
|
||||
}
|
||||
|
||||
is PressInteraction.Release -> {
|
||||
if (!showSafeModeDialog) {
|
||||
beginLaunch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
Button(
|
||||
onClick = { beginLaunch = true },
|
||||
enabled = !releaseViewModel.isInUpdate
|
||||
onClick = { },
|
||||
enabled = !releaseViewModel.isInUpdate,
|
||||
interactionSource = interactionSource
|
||||
) {
|
||||
Icon(
|
||||
Icons.Filled.PlayArrow,
|
||||
@@ -545,7 +614,24 @@ fun MainScreen(
|
||||
}
|
||||
|
||||
if (beginLaunch) {
|
||||
UpdateWarning()
|
||||
UpdateWarning(launchInSafeMode) {
|
||||
beginLaunch = false
|
||||
launchInSafeMode = false
|
||||
}
|
||||
}
|
||||
|
||||
if (showSafeModeDialog) {
|
||||
SafeModeDialog(
|
||||
onDismiss = {
|
||||
showSafeModeDialog = false
|
||||
},
|
||||
onLaunch = {
|
||||
launchInSafeMode = true
|
||||
beginLaunch = true
|
||||
|
||||
showSafeModeDialog = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,22 +21,18 @@ import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -45,7 +41,6 @@ import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geode.launcher.updater.ReleaseViewModel
|
||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
@@ -84,16 +79,6 @@ class SettingsActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun onExportSaveData(context: Context) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"This function is not implemented yet!",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
*/
|
||||
|
||||
fun onOpenFolder(context: Context) {
|
||||
val file = LaunchUtils.getBaseDirectory(context)
|
||||
val clipboardManager =
|
||||
@@ -160,7 +145,9 @@ fun UpdateIndicator(
|
||||
enablePopup = true
|
||||
val progress = updateStatus.downloaded / updateStatus.outOf.toDouble()
|
||||
|
||||
CircularProgressIndicator(progress.toFloat())
|
||||
CircularProgressIndicator(
|
||||
progress = { progress.toFloat() },
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -274,7 +261,7 @@ fun SettingsScreen(
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = context.getString(R.string.back_icon_alt)
|
||||
)
|
||||
}
|
||||
@@ -315,19 +302,10 @@ fun SettingsScreen(
|
||||
description = context.getString(R.string.preference_load_automatically_description),
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY,
|
||||
)
|
||||
OptionsButton(
|
||||
title = context.getString(R.string.preferences_copy_external_button),
|
||||
description = LaunchUtils.getBaseDirectory(context).path,
|
||||
onClick = { onOpenFolder(context) }
|
||||
)
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_open_file_manager),
|
||||
onClick = { onOpenFileManager(context) }
|
||||
)
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_view_logs),
|
||||
onClick = { onOpenLogs(context) }
|
||||
)
|
||||
}
|
||||
|
||||
OptionsGroup(context.getString(R.string.preference_category_updater)) {
|
||||
@@ -365,6 +343,27 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
OptionsGroup(stringResource(R.string.preference_category_developer)) {
|
||||
SettingsStringCard(
|
||||
title = stringResource(R.string.preference_launch_arguments_name),
|
||||
dialogTitle = stringResource(R.string.preference_launch_arguments_set_title),
|
||||
preferenceKey = PreferenceUtils.Key.LAUNCH_ARGUMENTS,
|
||||
filterInput = { it.filter { c ->
|
||||
// if only there was a better way to define this!
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}<>[]?:;'\"~`-_+=\\| ".contains(c)
|
||||
}}
|
||||
)
|
||||
OptionsButton(
|
||||
title = context.getString(R.string.preferences_copy_external_button),
|
||||
description = LaunchUtils.getBaseDirectory(context).path,
|
||||
onClick = { onOpenFolder(context) }
|
||||
)
|
||||
OptionsButton(
|
||||
title = stringResource(R.string.preferences_view_logs),
|
||||
onClick = { onOpenLogs(context) }
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
stringResource(R.string.preference_launcher_version, BuildConfig.VERSION_NAME),
|
||||
style = Typography.labelSmall,
|
||||
@@ -375,240 +374,6 @@ fun SettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
return preferences.toggleBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
fun getSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
return preferences.getBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsGroup(title: String, content: @Composable () -> Unit) {
|
||||
Column {
|
||||
Text(
|
||||
title,
|
||||
style = Typography.titleMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsSelectCard(
|
||||
title: String,
|
||||
dialogTitle: String,
|
||||
maxVal: Int,
|
||||
preferenceKey: PreferenceUtils.Key,
|
||||
toLabel: @Composable (Int) -> String,
|
||||
extraSelectBehavior: ((Int) -> Unit)? = null
|
||||
) {
|
||||
val preferenceValue by PreferenceUtils.useIntPreference(preferenceKey)
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
OptionsCard(
|
||||
title = { OptionsTitle(title = title) },
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
role = Role.Button
|
||||
)
|
||||
) {
|
||||
Text(toLabel(preferenceValue))
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
val context = LocalContext.current
|
||||
|
||||
SelectDialog(
|
||||
title = dialogTitle,
|
||||
onDismissRequest = {
|
||||
showDialog = false
|
||||
},
|
||||
onSelect = { selected ->
|
||||
showDialog = false
|
||||
PreferenceUtils.get(context)
|
||||
.setInt(preferenceKey, selected)
|
||||
extraSelectBehavior?.invoke(selected)
|
||||
},
|
||||
initialValue = preferenceValue,
|
||||
toLabel = toLabel,
|
||||
optionsCount = maxVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SelectDialog(
|
||||
title: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (Int) -> Unit,
|
||||
initialValue: Int,
|
||||
toLabel: @Composable (Int) -> String,
|
||||
optionsCount: Int,
|
||||
) {
|
||||
var selectedValue by remember { mutableIntStateOf(initialValue) }
|
||||
|
||||
Dialog(onDismissRequest = onDismissRequest) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
// styling a dialog is actually a little hard if you're doing what i'm doing
|
||||
// maybe there's a better way to make these padding values...
|
||||
Column {
|
||||
Text(
|
||||
title,
|
||||
style = Typography.titleLarge,
|
||||
modifier = Modifier.padding(
|
||||
start = 28.dp,
|
||||
top = 24.dp,
|
||||
bottom = 12.dp
|
||||
)
|
||||
)
|
||||
|
||||
// do not give the row or column padding!! it messes up the selection effect
|
||||
(0..optionsCount).forEach { id ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = { selectedValue = id },
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(horizontal = 12.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedValue == id,
|
||||
onClick = { selectedValue = id }
|
||||
)
|
||||
Text(
|
||||
toLabel(id),
|
||||
style = Typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
bottom = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 4.dp
|
||||
)
|
||||
) {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
|
||||
TextButton(onClick = { onSelect(selectedValue) }) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsButton(title: String, description: String? = null, onClick: () -> Unit) {
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(
|
||||
title = title,
|
||||
description = description
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick, role = Role.Button)
|
||||
) { }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCard(title: String, description: String? = null, preferenceKey: PreferenceUtils.Key) {
|
||||
val context = LocalContext.current
|
||||
val settingEnabled = remember {
|
||||
mutableStateOf(getSetting(context, preferenceKey))
|
||||
}
|
||||
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(
|
||||
Modifier.fillMaxWidth(0.75f),
|
||||
title = title,
|
||||
description = description
|
||||
)
|
||||
},
|
||||
modifier = Modifier.toggleable(
|
||||
value = settingEnabled.value,
|
||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
||||
role = Role.Switch,
|
||||
)
|
||||
) {
|
||||
Switch(checked = settingEnabled.value, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsTitle(modifier: Modifier = Modifier, title: String, description: String? = null) {
|
||||
Column(
|
||||
modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(title)
|
||||
if (!description.isNullOrEmpty()) {
|
||||
Text(
|
||||
description,
|
||||
style = Typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsCard(modifier: Modifier = Modifier, title: @Composable () -> Unit, content: @Composable () -> Unit) {
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
Arrangement.SpaceBetween,
|
||||
Alignment.CenterVertically,
|
||||
) {
|
||||
title()
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun OptionsCardPreview() {
|
||||
GeodeLauncherTheme {
|
||||
OptionsGroup(title = "Preview Group") {
|
||||
SettingsCard(
|
||||
title = "Load files from /test",
|
||||
description = "Very long testing description goes here. It is incredibly long, it should wrap onto a new line.",
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_TESTING
|
||||
)
|
||||
SettingsCard(
|
||||
title = "Testing option 2",
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Composable
|
||||
fun DefaultPreview() {
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.geode.launcher
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.selection.toggleable
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Clear
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||
import com.geode.launcher.ui.theme.Typography
|
||||
import com.geode.launcher.utils.PreferenceUtils
|
||||
|
||||
|
||||
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
return preferences.toggleBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
fun getSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
|
||||
val preferences = PreferenceUtils.get(context)
|
||||
|
||||
return preferences.getBoolean(preferenceKey)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsGroup(title: String, content: @Composable () -> Unit) {
|
||||
Column {
|
||||
Text(
|
||||
title,
|
||||
style = Typography.titleMedium,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
)
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsSelectCard(
|
||||
title: String,
|
||||
dialogTitle: String,
|
||||
maxVal: Int,
|
||||
preferenceKey: PreferenceUtils.Key,
|
||||
toLabel: @Composable (Int) -> String,
|
||||
extraSelectBehavior: ((Int) -> Unit)? = null
|
||||
) {
|
||||
val preferenceValue by PreferenceUtils.useIntPreference(preferenceKey)
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
OptionsCard(
|
||||
title = { OptionsTitle(title = title) },
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
role = Role.Button
|
||||
)
|
||||
) {
|
||||
Text(toLabel(preferenceValue))
|
||||
}
|
||||
|
||||
if (showDialog) {
|
||||
val context = LocalContext.current
|
||||
|
||||
SelectDialog(
|
||||
title = dialogTitle,
|
||||
onDismissRequest = {
|
||||
showDialog = false
|
||||
},
|
||||
onSelect = { selected ->
|
||||
showDialog = false
|
||||
PreferenceUtils.get(context)
|
||||
.setInt(preferenceKey, selected)
|
||||
extraSelectBehavior?.invoke(selected)
|
||||
},
|
||||
initialValue = preferenceValue,
|
||||
toLabel = toLabel,
|
||||
optionsCount = 0..maxVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsStringCard(
|
||||
title: String,
|
||||
dialogTitle: String,
|
||||
preferenceKey: PreferenceUtils.Key,
|
||||
filterInput: ((String) -> String)? = null
|
||||
) {
|
||||
var preferenceValue by PreferenceUtils.useStringPreference(preferenceKey)
|
||||
|
||||
var showDialog by remember { mutableStateOf(false) }
|
||||
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(title = title, description = preferenceValue)
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = {
|
||||
showDialog = true
|
||||
},
|
||||
role = Role.Button
|
||||
)
|
||||
) { }
|
||||
|
||||
if (showDialog) {
|
||||
StringDialog(
|
||||
title = dialogTitle,
|
||||
onDismissRequest = { showDialog = false },
|
||||
onSelect = {
|
||||
preferenceValue = it
|
||||
showDialog = false
|
||||
},
|
||||
initialValue = preferenceValue ?: "",
|
||||
filterInput = filterInput
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StringDialog(
|
||||
title: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (String) -> Unit,
|
||||
initialValue: String,
|
||||
filterInput: ((String) -> String)? = null
|
||||
) {
|
||||
var enteredValue by remember { mutableStateOf(initialValue) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDismissRequest() },
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
OutlinedTextField(
|
||||
value = enteredValue,
|
||||
onValueChange = {
|
||||
enteredValue = filterInput?.invoke(it) ?: it
|
||||
},
|
||||
singleLine = true,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Ascii,
|
||||
autoCorrect = false
|
||||
),
|
||||
trailingIcon = {
|
||||
if (enteredValue.isNotEmpty()) {
|
||||
IconButton(onClick = { enteredValue = "" }) {
|
||||
Icon(
|
||||
Icons.Filled.Clear,
|
||||
contentDescription = stringResource(R.string.preference_text_clear)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = { onSelect(enteredValue) }) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> SelectDialog(
|
||||
title: String,
|
||||
onDismissRequest: () -> Unit,
|
||||
onSelect: (T) -> Unit,
|
||||
initialValue: T,
|
||||
toLabel: @Composable (T) -> String,
|
||||
optionsCount: Iterable<T>,
|
||||
) {
|
||||
var selectedValue by remember { mutableStateOf(initialValue) }
|
||||
|
||||
Dialog(onDismissRequest = onDismissRequest) {
|
||||
Card(
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
) {
|
||||
// styling a dialog is actually a little hard if you're doing what i'm doing
|
||||
// maybe there's a better way to make these padding values...
|
||||
Column {
|
||||
Text(
|
||||
title,
|
||||
style = Typography.titleLarge,
|
||||
modifier = Modifier.padding(
|
||||
start = 28.dp,
|
||||
top = 24.dp,
|
||||
bottom = 12.dp
|
||||
)
|
||||
)
|
||||
|
||||
// do not give the row or column padding!! it messes up the selection effect
|
||||
optionsCount.forEach { id ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClick = { selectedValue = id },
|
||||
role = Role.RadioButton
|
||||
)
|
||||
.padding(horizontal = 12.dp)
|
||||
) {
|
||||
RadioButton(
|
||||
selected = selectedValue == id,
|
||||
onClick = { selectedValue = id }
|
||||
)
|
||||
Text(
|
||||
toLabel(id),
|
||||
style = Typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.End,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
bottom = 16.dp,
|
||||
end = 16.dp,
|
||||
top = 4.dp
|
||||
)
|
||||
) {
|
||||
TextButton(onClick = onDismissRequest) {
|
||||
Text(stringResource(R.string.message_box_cancel))
|
||||
}
|
||||
|
||||
TextButton(onClick = { onSelect(selectedValue) }) {
|
||||
Text(stringResource(R.string.message_box_accept))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsButton(title: String, description: String? = null, onClick: () -> Unit) {
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(
|
||||
title = title,
|
||||
description = description
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.clickable(onClick = onClick, role = Role.Button)
|
||||
) { }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCard(title: String, description: String? = null, preferenceKey: PreferenceUtils.Key) {
|
||||
val context = LocalContext.current
|
||||
val settingEnabled = remember {
|
||||
mutableStateOf(getSetting(context, preferenceKey))
|
||||
}
|
||||
|
||||
OptionsCard(
|
||||
title = {
|
||||
OptionsTitle(
|
||||
Modifier.fillMaxWidth(0.75f),
|
||||
title = title,
|
||||
description = description
|
||||
)
|
||||
},
|
||||
modifier = Modifier.toggleable(
|
||||
value = settingEnabled.value,
|
||||
onValueChange = { settingEnabled.value = toggleSetting(context, preferenceKey) },
|
||||
role = Role.Switch,
|
||||
)
|
||||
) {
|
||||
Switch(checked = settingEnabled.value, onCheckedChange = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsTitle(modifier: Modifier = Modifier, title: String, description: String? = null) {
|
||||
Column(
|
||||
modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(title)
|
||||
if (!description.isNullOrEmpty()) {
|
||||
Text(
|
||||
description,
|
||||
style = Typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun OptionsCard(modifier: Modifier = Modifier, title: @Composable () -> Unit, content: @Composable () -> Unit) {
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.height(64.dp)
|
||||
.padding(horizontal = 16.dp),
|
||||
Arrangement.SpaceBetween,
|
||||
Alignment.CenterVertically,
|
||||
) {
|
||||
title()
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun OptionsCardPreview() {
|
||||
GeodeLauncherTheme {
|
||||
OptionsGroup(title = "Preview Group") {
|
||||
SettingsCard(
|
||||
title = "Load files from /test",
|
||||
description = "Very long testing description goes here. It is incredibly long, it should wrap onto a new line.",
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_TESTING
|
||||
)
|
||||
SettingsCard(
|
||||
title = "Testing option 2",
|
||||
preferenceKey = PreferenceUtils.Key.LOAD_AUTOMATICALLY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@ package com.geode.launcher
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.CancellationSignal
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.DocumentsProvider
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.geode.launcher.utils.LaunchUtils
|
||||
import java.io.File
|
||||
|
||||
@@ -150,7 +152,7 @@ class UserDirectoryProvider : DocumentsProvider() {
|
||||
notifyFileChange(file)
|
||||
}
|
||||
|
||||
override fun renameDocument(documentId: String, displayName: String): String? {
|
||||
override fun renameDocument(documentId: String, displayName: String): String {
|
||||
val file = getFileForDocumentId(documentId)
|
||||
val dest = findFileNameForNewFile(File(file.parentFile, displayName))
|
||||
file.renameTo(dest)
|
||||
@@ -203,4 +205,19 @@ class UserDirectoryProvider : DocumentsProvider() {
|
||||
val uri = getDocumentUri(parentDocumentId)
|
||||
context!!.contentResolver.notifyChange(uri, null)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun findDocumentPath(
|
||||
parentDocumentId: String?,
|
||||
childDocumentId: String
|
||||
): DocumentsContract.Path {
|
||||
if (!parentDocumentId.isNullOrEmpty()) {
|
||||
// not implementing this for now...
|
||||
return super.findDocumentPath(parentDocumentId, childDocumentId)
|
||||
}
|
||||
|
||||
val nonRootPath = listOf(ROOT) + childDocumentId.removePrefix(ROOT).split("/")
|
||||
|
||||
return DocumentsContract.Path(ROOT, nonRootPath)
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,50 @@ data class ProcessInformation(val processId: Int, val threadId: Int, val process
|
||||
enum class LogPriority {
|
||||
UNKNOWN, DEFAULT, VERBOSE, DEBUG, INFO, WARN, ERROR, FATAL, SILENT;
|
||||
|
||||
class LogPriorityIterator(
|
||||
start: LogPriority,
|
||||
private val end: LogPriority,
|
||||
private val step: Int
|
||||
) : Iterator<LogPriority> {
|
||||
private var hasNext: Boolean = if (step > 0) start <= end else start >= end
|
||||
private var next = if (hasNext) start else end
|
||||
|
||||
override fun hasNext() = hasNext
|
||||
|
||||
override fun next(): LogPriority {
|
||||
val current = next
|
||||
if (current == end) {
|
||||
if (!hasNext) {
|
||||
throw NoSuchElementException()
|
||||
}
|
||||
|
||||
hasNext = false
|
||||
} else {
|
||||
next = LogPriority.fromInt(next.toInt() + step)
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
}
|
||||
|
||||
class LogPriorityProgression(
|
||||
override val start: LogPriority,
|
||||
override val endInclusive: LogPriority,
|
||||
private val step: Int = 1
|
||||
) : Iterable<LogPriority>, ClosedRange<LogPriority> {
|
||||
override fun iterator(): Iterator<LogPriority> {
|
||||
return LogPriorityIterator(start, endInclusive, step)
|
||||
}
|
||||
}
|
||||
|
||||
infix fun downTo(to: LogPriority) = LogPriorityProgression(this, to, -1)
|
||||
|
||||
operator fun rangeTo(to: LogPriority) = LogPriorityProgression(this, to)
|
||||
|
||||
companion object {
|
||||
fun fromByte(byte: Byte): LogPriority {
|
||||
return when (byte.toInt()) {
|
||||
fun fromByte(byte: Byte): LogPriority = fromInt(byte.toInt())
|
||||
|
||||
fun fromInt(int: Int) = when (int) {
|
||||
0x1 -> DEFAULT
|
||||
0x2 -> VERBOSE
|
||||
0x3 -> DEBUG
|
||||
@@ -41,10 +82,21 @@ enum class LogPriority {
|
||||
0x7 -> FATAL
|
||||
0x8 -> SILENT
|
||||
else -> UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toInt() = when (this) {
|
||||
DEFAULT -> 0x1
|
||||
VERBOSE -> 0x2
|
||||
DEBUG -> 0x3
|
||||
INFO -> 0x4
|
||||
WARN -> 0x5
|
||||
ERROR -> 0x6
|
||||
FATAL -> 0x7
|
||||
SILENT -> 0x8
|
||||
else -> 0x0
|
||||
}
|
||||
|
||||
fun toChar(): Char {
|
||||
return when (this) {
|
||||
VERBOSE -> 'V'
|
||||
|
||||
@@ -37,6 +37,15 @@ class LogViewModel: ViewModel() {
|
||||
var filterCrashes = false
|
||||
private set
|
||||
|
||||
// skip debug/verbose lines by default
|
||||
var logLevel = LogPriority.INFO
|
||||
set(value) {
|
||||
field = value
|
||||
|
||||
_lineState.clear()
|
||||
loadLogs()
|
||||
}
|
||||
|
||||
fun toggleCrashBuffer() {
|
||||
filterCrashes = !filterCrashes
|
||||
|
||||
@@ -70,8 +79,7 @@ class LogViewModel: ViewModel() {
|
||||
while (true) {
|
||||
val line = LogLine.fromBufferedSource(logSource)
|
||||
|
||||
// skip debug/verbose lines
|
||||
if (line.priority >= LogPriority.INFO) {
|
||||
if (line.priority >= logLevel) {
|
||||
logLines += line
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ class ReleaseManager private constructor(
|
||||
val geodeFile = getGeodeOutputPath()
|
||||
|
||||
// check if an update is needed
|
||||
if (latestVersion <= currentVersion && geodeFile.exists()) {
|
||||
if (latestVersion == currentVersion && geodeFile.exists()) {
|
||||
_uiState.value = ReleaseManagerState.Finished()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.geode.launcher.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
@@ -7,13 +8,20 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.geode.launcher.BuildConfig
|
||||
import com.geode.launcher.R
|
||||
import com.geode.launcher.UserDirectoryProvider
|
||||
import com.geode.launcher.activityresult.GeodeOpenFileActivityResult
|
||||
import com.geode.launcher.activityresult.GeodeOpenFilesActivityResult
|
||||
import com.geode.launcher.activityresult.GeodeSaveFileActivityResult
|
||||
@@ -21,7 +29,7 @@ import java.io.File
|
||||
import java.lang.ref.WeakReference
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
@Suppress("unused")
|
||||
@Suppress("unused", "KotlinJniMissingFunction")
|
||||
object GeodeUtils {
|
||||
private lateinit var activity: WeakReference<AppCompatActivity>
|
||||
private lateinit var openFileResultLauncher: ActivityResultLauncher<GeodeOpenFileActivityResult.OpenFileParams>
|
||||
@@ -29,6 +37,11 @@ object GeodeUtils {
|
||||
private lateinit var openFilesResultLauncher: ActivityResultLauncher<GeodeOpenFilesActivityResult.OpenFileParams>
|
||||
private lateinit var saveFileResultLauncher: ActivityResultLauncher<GeodeSaveFileActivityResult.SaveFileParams>
|
||||
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
|
||||
private lateinit var internalRequestPermissionsLauncher: ActivityResultLauncher<Array<String>>
|
||||
private lateinit var internalRequestAllFilesLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private var afterRequestPermissions: (() -> Unit)? = null
|
||||
private var afterRequestPermissionsFailure: (() -> Unit)? = null
|
||||
|
||||
fun setContext(activity: AppCompatActivity) {
|
||||
this.activity = WeakReference(activity)
|
||||
@@ -79,6 +92,32 @@ object GeodeUtils {
|
||||
requestPermissionLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
|
||||
permissionCallback(isGranted)
|
||||
}
|
||||
|
||||
internalRequestPermissionsLauncher = activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||
val isGranted = permissions.values.all { it }
|
||||
if (isGranted) {
|
||||
afterRequestPermissions?.invoke()
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.missing_permissions, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
|
||||
afterRequestPermissionsFailure?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
// only necessary on newer android versions
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
|
||||
internalRequestAllFilesLauncher = activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
afterRequestPermissions?.invoke()
|
||||
} else {
|
||||
Toast.makeText(activity, R.string.missing_permissions, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
|
||||
afterRequestPermissionsFailure?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -129,99 +168,175 @@ object GeodeUtils {
|
||||
// TODO As of now this is unused
|
||||
@JvmStatic
|
||||
fun openFolder(path: String): Boolean {
|
||||
activity.get()?.run {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, it.uri)
|
||||
}
|
||||
intent.setType("*/*")
|
||||
val context = activity.get()!!
|
||||
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
startActivity(intent)
|
||||
return true
|
||||
val pathFile = File(path)
|
||||
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||
val isInternalPath = pathFile.startsWith(baseDirectory)
|
||||
|
||||
val intent = if (isInternalPath) {
|
||||
// TODO: figure out how to get this to point to the path it should be pointing at
|
||||
// (the best i got was pointing at a file)
|
||||
// val relativePath = pathFile.relativeTo(baseDirectory)
|
||||
|
||||
Intent(Intent.ACTION_VIEW).apply {
|
||||
data = DocumentsContract.buildRootUri(
|
||||
"${context.packageName}.user", UserDirectoryProvider.ROOT
|
||||
)
|
||||
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PREFIX_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val documentFile = DocumentFile.fromFile(File(path))
|
||||
putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentFile.uri)
|
||||
}
|
||||
|
||||
type = "*/*"
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
return try {
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("KotlinJniMissingFunction")
|
||||
external fun selectFileCallback(path: String)
|
||||
private external fun selectFileCallback(path: String)
|
||||
|
||||
@Suppress("KotlinJniMissingFunction")
|
||||
external fun selectFilesCallback(paths: Array<String>)
|
||||
private external fun selectFilesCallback(paths: Array<String>)
|
||||
|
||||
@Suppress("KotlinJniMissingFunction")
|
||||
external fun failedCallback()
|
||||
private external fun failedCallback()
|
||||
|
||||
private fun checkForFilePermissions(onSuccess: () -> Unit, onFailure: () -> Unit) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
|
||||
if (Environment.isExternalStorageManager()) {
|
||||
onSuccess()
|
||||
} else {
|
||||
val intent = Intent(
|
||||
ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
|
||||
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
|
||||
)
|
||||
|
||||
internalRequestAllFilesLauncher.launch(intent)
|
||||
afterRequestPermissions = onSuccess
|
||||
afterRequestPermissionsFailure = onFailure
|
||||
}
|
||||
} else {
|
||||
val permissions = listOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
val context = activity.get()!!
|
||||
|
||||
val needsPermissions = permissions.filter {
|
||||
ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
if (needsPermissions.isNotEmpty()) {
|
||||
internalRequestPermissionsLauncher.launch(needsPermissions.toTypedArray())
|
||||
afterRequestPermissions = onSuccess
|
||||
afterRequestPermissionsFailure = onFailure
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun selectFile(path: String): Boolean {
|
||||
activity.get()?.run {
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
try {
|
||||
openFileResultLauncher.launch(GeodeOpenFileActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
return false
|
||||
}
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
|
||||
return try {
|
||||
checkForFilePermissions(
|
||||
onSuccess = {
|
||||
openFileResultLauncher.launch(GeodeOpenFileActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||
},
|
||||
onFailure = { failedCallback() }
|
||||
)
|
||||
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun selectFiles(path: String): Boolean {
|
||||
activity.get()?.run {
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
try {
|
||||
openFilesResultLauncher.launch(GeodeOpenFilesActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
return false
|
||||
}
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
|
||||
return try {
|
||||
checkForFilePermissions(
|
||||
onSuccess = {
|
||||
openFilesResultLauncher.launch(GeodeOpenFilesActivityResult.OpenFileParams(arrayOf("*/*"), uri))
|
||||
},
|
||||
onFailure = { failedCallback() }
|
||||
)
|
||||
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun selectFolder(path: String): Boolean {
|
||||
activity.get()?.run {
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
try {
|
||||
openDirectoryResultLauncher.launch(uri)
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
return false
|
||||
}
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
return false
|
||||
|
||||
return try {
|
||||
checkForFilePermissions(
|
||||
onSuccess = {
|
||||
openDirectoryResultLauncher.launch(uri)
|
||||
},
|
||||
onFailure = { failedCallback() }
|
||||
)
|
||||
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createFile(path: String): Boolean {
|
||||
activity.get()?.run {
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
|
||||
try {
|
||||
saveFileResultLauncher.launch(GeodeSaveFileActivityResult.SaveFileParams(null, uri))
|
||||
return true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
return false
|
||||
}
|
||||
var uri: Uri?
|
||||
DocumentFile.fromFile(File(path)).also {
|
||||
uri = it.uri
|
||||
}
|
||||
|
||||
return try {
|
||||
checkForFilePermissions(
|
||||
onSuccess = {
|
||||
saveFileResultLauncher.launch(GeodeSaveFileActivityResult.SaveFileParams(null, uri))
|
||||
},
|
||||
onFailure = { failedCallback() }
|
||||
)
|
||||
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -256,34 +371,105 @@ object GeodeUtils {
|
||||
return "com.geode.launcher.user" == uri.authority
|
||||
}
|
||||
|
||||
private const val INTERNAL_PERMISSION_PREFIX = "geode.permission_internal"
|
||||
private const val MANAGE_ALL_FILES = "${INTERNAL_PERMISSION_PREFIX}.MANAGE_ALL_FILES"
|
||||
|
||||
@JvmStatic
|
||||
fun getPermissionStatus(permission: String): Boolean {
|
||||
return ContextCompat.checkSelfPermission(
|
||||
activity.get()?.applicationContext ?: return false,
|
||||
permission
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
val context = activity.get() ?: return false
|
||||
|
||||
return when (permission) {
|
||||
MANAGE_ALL_FILES -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S_V2) {
|
||||
Environment.isExternalStorageManager()
|
||||
} else {
|
||||
val permissions = listOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
return permissions.all {
|
||||
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
else -> ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun requestPermission(permission: String) {
|
||||
if (permission == MANAGE_ALL_FILES) {
|
||||
// this function handles already having perms for us
|
||||
checkForFilePermissions(onSuccess = {
|
||||
permissionCallback(true)
|
||||
}, onFailure = {
|
||||
permissionCallback(false)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (getPermissionStatus(permission)) {
|
||||
permissionCallback(true)
|
||||
return
|
||||
}
|
||||
|
||||
activity.get()?.run {
|
||||
try {
|
||||
requestPermissionLauncher.launch(permission)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
permissionCallback(false)
|
||||
}
|
||||
|
||||
return
|
||||
try {
|
||||
requestPermissionLauncher.launch(permission)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
permissionCallback(false)
|
||||
}
|
||||
|
||||
permissionCallback(false)
|
||||
}
|
||||
|
||||
@Suppress("KotlinJniMissingFunction")
|
||||
external fun permissionCallback(granted: Boolean)
|
||||
private external fun permissionCallback(granted: Boolean)
|
||||
|
||||
const val ARGUMENT_SAFE_MODE = "--geode:safe-mode"
|
||||
|
||||
private var additionalLaunchArguments = arrayListOf<String>()
|
||||
fun setAdditionalLaunchArguments(vararg args: String) {
|
||||
additionalLaunchArguments.addAll(args)
|
||||
}
|
||||
|
||||
fun clearLaunchArguments() = setAdditionalLaunchArguments()
|
||||
|
||||
@JvmStatic
|
||||
fun getLaunchArguments(): String? {
|
||||
activity.get()?.apply {
|
||||
val preferences = PreferenceUtils.get(this)
|
||||
|
||||
val userArgs = preferences.getString(PreferenceUtils.Key.LAUNCH_ARGUMENTS)
|
||||
val args = if (!userArgs.isNullOrEmpty()) {
|
||||
listOf(userArgs) + additionalLaunchArguments
|
||||
} else additionalLaunchArguments
|
||||
|
||||
return args.joinToString(" ")
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
interface CapabilityListener {
|
||||
fun onCapabilityAdded(capability: String): Boolean
|
||||
}
|
||||
|
||||
const val CAPABILITY_EXTENDED_INPUT = "extended_input"
|
||||
|
||||
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
|
||||
|
||||
fun setCapabilityListener(listener: CapabilityListener) {
|
||||
capabilityListener = WeakReference(listener)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun reportPlatformCapability(capability: String?): Boolean {
|
||||
if (capability.isNullOrEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return capabilityListener.get()?.onCapabilityAdded(capability) ?: false
|
||||
}
|
||||
|
||||
external fun nativeKeyUp(keyCode: Int, modifiers: Int)
|
||||
external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean)
|
||||
external fun nativeActionScroll(scrollX: Float, scrollY: Float)
|
||||
external fun resizeSurface(width: Int, height: Int)
|
||||
}
|
||||
@@ -133,7 +133,9 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
BLACK_BACKGROUND,
|
||||
CURRENT_RELEASE_MODIFIED,
|
||||
LAST_DISMISSED_UPDATE,
|
||||
DISMISSED_GJ_UPDATE
|
||||
DISMISSED_GJ_UPDATE,
|
||||
LAUNCH_ARGUMENTS,
|
||||
IGNORE_LOAD_FAILURE
|
||||
}
|
||||
|
||||
private fun defaultValueForBooleanKey(key: Key): Boolean {
|
||||
@@ -157,6 +159,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
|
||||
Key.CURRENT_RELEASE_MODIFIED -> "PreferenceReleaseModifiedHash"
|
||||
Key.LAST_DISMISSED_UPDATE -> "PreferenceLastDismissedUpdate"
|
||||
Key.DISMISSED_GJ_UPDATE -> "PreferenceDismissedGJUpdate"
|
||||
Key.LAUNCH_ARGUMENTS -> "PreferenceLaunchArguments"
|
||||
Key.IGNORE_LOAD_FAILURE -> "PreferenceIgnoreLoadFailure"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import com.customRobTop.BaseRobTopActivity
|
||||
import com.geode.launcher.utils.GeodeUtils
|
||||
|
||||
private const val HANDLER_OPEN_IME_KEYBOARD = 2
|
||||
private const val HANDLER_CLOSE_IME_KEYBOARD = 3
|
||||
@@ -37,6 +38,11 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
||||
}
|
||||
|
||||
private lateinit var cocos2dxRenderer: Cocos2dxRenderer
|
||||
var useKeyboardEvents = false
|
||||
set(value) {
|
||||
field = value
|
||||
cocos2dxRenderer.sendResizeEvents = value
|
||||
}
|
||||
|
||||
var cocos2dxEditText: Cocos2dxEditText? = null
|
||||
set(value) {
|
||||
@@ -207,7 +213,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||
private fun legacyKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_MENU -> {
|
||||
if (keyEvent.repeatCount != 0 || BaseRobTopActivity.blockBackButton) {
|
||||
@@ -220,6 +226,71 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (!useKeyboardEvents) {
|
||||
return legacyKeyDown(keyCode, event)
|
||||
}
|
||||
|
||||
return when (keyCode) {
|
||||
// ignore system keys
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_MUTE -> {
|
||||
super.onKeyDown(keyCode, event)
|
||||
}
|
||||
else -> {
|
||||
if (BaseRobTopActivity.blockBackButton) {
|
||||
return true
|
||||
}
|
||||
|
||||
queueEvent {
|
||||
GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
||||
if (!useKeyboardEvents) {
|
||||
return super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
return when (keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_MUTE -> {
|
||||
super.onKeyUp(keyCode, event)
|
||||
}
|
||||
else -> {
|
||||
if (event.repeatCount != 0 || BaseRobTopActivity.blockBackButton) {
|
||||
return true
|
||||
}
|
||||
|
||||
queueEvent {
|
||||
GeodeUtils.nativeKeyUp(keyCode, event.modifiers)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGenericMotionEvent(event: MotionEvent?): Boolean {
|
||||
if (!useKeyboardEvents) {
|
||||
return super.onGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
if (event?.action == MotionEvent.ACTION_SCROLL) {
|
||||
val scrollX = event.getAxisValue(MotionEvent.AXIS_HSCROLL)
|
||||
val scrollY = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
|
||||
|
||||
queueEvent {
|
||||
GeodeUtils.nativeActionScroll(scrollX, scrollY)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return super.onGenericMotionEvent(event)
|
||||
}
|
||||
|
||||
fun setCocos2dxRenderer(renderer: Cocos2dxRenderer) {
|
||||
this.cocos2dxRenderer = renderer
|
||||
setRenderer(this.cocos2dxRenderer)
|
||||
|
||||
@@ -59,6 +59,7 @@ class Cocos2dxRenderer : GLSurfaceView.Renderer {
|
||||
private var lastTickInNanoSeconds: Long = 0
|
||||
private var screenWidth = 0
|
||||
private var screenHeight = 0
|
||||
var sendResizeEvents = false
|
||||
|
||||
fun setScreenWidthAndHeight(surfaceWidth: Int, surfaceHeight: Int) {
|
||||
screenWidth = surfaceWidth
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M480,760Q546,760 593,713Q640,666 640,600L640,440Q640,374 593,327Q546,280 480,280Q414,280 367,327Q320,374 320,440L320,600Q320,666 367,713Q414,760 480,760ZM400,640L560,640L560,560L400,560L400,640ZM400,480L560,480L560,400L400,400L400,480ZM480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520Q480,520 480,520L480,520Q480,520 480,520Q480,520 480,520ZM480,840Q415,840 359.5,808Q304,776 272,720L160,720L160,640L244,640Q241,620 240.5,600Q240,580 240,560L160,560L160,480L240,480Q240,460 240.5,440Q241,420 244,400L160,400L160,320L272,320Q286,297 303.5,277Q321,257 344,242L280,176L336,120L422,206Q450,197 479,197Q508,197 536,206L624,120L680,176L614,242Q637,257 655.5,276.5Q674,296 688,320L800,320L800,400L716,400Q719,420 719.5,440Q720,460 720,480L800,480L800,560L720,560Q720,580 719.5,600Q719,620 716,640L800,640L800,720L688,720Q656,776 600.5,808Q545,840 480,840Z"/>
|
||||
</vector>
|
||||
@@ -30,6 +30,8 @@
|
||||
</string>
|
||||
<string name="launcher_update_available">A new launcher update is available!</string>
|
||||
<string name="launcher_update_dismiss">Dismiss</string>
|
||||
<string name="safe_mode_enable_title">Enable safe mode?</string>
|
||||
<string name="safe_mode_enable_description">Safe mode disables loading of all mods, allowing one to remove problematic mods or diagnose crashes. If you\'re not experiencing issues with Geode, you should probably not be using safe mode.</string>
|
||||
|
||||
<!-- Updater strings -->
|
||||
<string name="release_fetch_failed">Update failed.\n%1$s</string>
|
||||
@@ -46,13 +48,15 @@
|
||||
|
||||
<!-- GeometryDashActivity strings -->
|
||||
<string name="load_failed_link_error">Failed to link libraries</string>
|
||||
<string name="load_failed_link_error_description">Game libraries could not be found. This may occur if the architecture of the launcher and Geometry Dash do not match, or from a system bug.\nCheck application logs for more details.</string>
|
||||
<string name="load_failed_link_error_description">Game libraries could not be found. This may occur if the architecture or version of the launcher and Geometry Dash do not match, or from a system bug.\nCheck application logs for more details.</string>
|
||||
<string name="load_failed_abi_error_need_64bit_description">The architecture of the launcher and Geometry Dash do not match. Please reinstall Geometry Dash from the Play Store or use the 64-bit version of the launcher to resolve this issue.\nCheck application logs for more details.</string>
|
||||
<string name="load_failed_abi_error_need_32bit_description">The architecture of the launcher and Geometry Dash do not match. Please reinstall Geometry Dash from the Play Store or use the 32-bit version of the launcher to resolve this issue.\nCheck application logs for more details.</string>
|
||||
<string name="load_failed_generic_error">Load failed</string>
|
||||
<string name="load_failed_generic_error_description">Uncaught error during game load: `%1$s`.\nCheck application logs for more details. Please report this issue!</string>
|
||||
<string name="load_failed_generic_error_description">Uncaught error during game load: `%1$s`.\nCheck application logs for more details.</string>
|
||||
<string name="message_box_cancel">Cancel</string>
|
||||
<string name="message_box_accept">OK</string>
|
||||
<string name="message_box_continue">Continue</string>
|
||||
<string name="missing_permissions">Missing required permissions for this action!</string>
|
||||
|
||||
<!-- SettingsActivity strings -->
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
@@ -90,6 +94,12 @@
|
||||
<string name="preference_check_for_updates_no_internet">No internet found.</string>
|
||||
<string name="preference_check_for_updates_already_updating">An update is already in progress!</string>
|
||||
|
||||
<string name="preference_category_developer">Developer</string>
|
||||
<string name="preference_launch_arguments_name">Launch arguments</string>
|
||||
<string name="preference_launch_arguments_set_title">Set launch arguments</string>
|
||||
<string name="preference_ignore_load_failure_name">Ignore loader errors</string>
|
||||
<string name="preference_text_clear">Clear</string>
|
||||
|
||||
<!-- ApplicationLogsActivity strings -->
|
||||
<string name="title_activity_application_logs">Application Logs</string>
|
||||
<string name="application_logs_no_logs">No logs available.</string>
|
||||
@@ -98,6 +108,8 @@
|
||||
<string name="application_log_copy_label">Log Message</string>
|
||||
<string name="application_logs_more">More</string>
|
||||
<string name="application_logs_export">Save To</string>
|
||||
<string name="application_logs_level">Set Log Level</string>
|
||||
<string name="application_logs_level_dialog_name">Set minimum log level</string>
|
||||
<string name="application_logs_export_completed">Log export complete.</string>
|
||||
<string name="application_logs_default_filename" translatable="false">launcher_logcat.txt</string>
|
||||
<string name="application_logs_crash_only">Show Crash Buffer</string>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
buildscript {
|
||||
ext {
|
||||
compose_version = '1.5.4'
|
||||
compose_compiler = '1.5.7'
|
||||
}
|
||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.2.1' apply false
|
||||
id 'com.android.library' version '8.2.1' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.21' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.21' apply false
|
||||
}
|
||||
|
||||
tasks.register('clean', Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
buildscript {
|
||||
val composeBOM by extra("2024.02.00")
|
||||
val composeCompiler by extra("1.5.9")
|
||||
}
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.2" apply false
|
||||
id("com.android.library") version "8.2.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" apply false
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
+3
-1
@@ -20,4 +20,6 @@ kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonTransitiveRClass=true
|
||||
# nice optimization
|
||||
org.gradle.configuration-cache=true
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -202,11 +202,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
@@ -5,6 +5,8 @@ pluginManagement {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
@@ -12,5 +14,6 @@ dependencyResolutionManagement {
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "launcher"
|
||||
include ':app'
|
||||
include(":app")
|
||||
Reference in new issue
Block a user