Compare commits

..
22 Commits
Author SHA1 Message Date
Chloe 7d2b5e0091 add multiline input util 2025-06-26 16:56:58 -07:00
Chloe 0b92f4a318 implement internal callbacks 2025-06-22 14:45:04 -07:00
Chloe 7d75825d01 this breaks things and might fix others 2025-06-07 19:38:54 -07:00
Chloe c77271f45c some text input changes 2025-06-07 19:38:01 -07:00
Chloe 09bb28d66f remove unused import 2025-05-28 17:14:33 -07:00
Chloe 157952b6f7 download to temp file first 2025-05-28 17:11:22 -07:00
Chloe 352ff63ab0 also send timestamp for back 2025-05-03 16:33:19 -07:00
Chloe 91fc804dee bump version 2025-05-03 16:24:55 -07:00
Chloe 87808b56b3 remove premature bug fix 2025-05-03 16:22:21 -07:00
Chloe 2c2884c3b8 use indeterminate state instead of ellipses 2025-05-03 16:14:12 -07:00
Chloe 74e91e7dc1 document some unlisted methods 2025-05-03 14:29:40 -07:00
Chloe e3ffbf311e try it 2025-05-03 14:13:39 -07:00
Chloe 5ad77bca57 i forgot i never actually committed that 2025-05-03 03:27:25 -07:00
Chloe 3be0127e04 unfortunate 2025-05-03 03:24:39 -07:00
Chloe 458c837aba add index release api opt in 2025-05-03 03:16:29 -07:00
Chloe 67ca7707d8 support predictive back gestures 2025-04-24 11:59:31 -07:00
Chloe 776a4fa468 obligatory update commit 2025-04-24 11:00:12 -07:00
Chloe 9528eef313 bump tulip 2025-04-23 23:56:55 -07:00
Chloe 9827fe36c1 add screen insets utils 2025-04-23 23:56:54 -07:00
Oleksandr Nemesh ce44270b7a add webview util (#37) 2025-04-22 13:01:53 -07:00
Chloe 34e324c54d revert all refresh rate changes 2025-01-23 20:14:52 -07:00
Chloe 986e33ce03 this is dumb 2025-01-23 19:15:08 -07:00
18 changed files with 290 additions and 126 deletions

No files matched your search

+17 -15
View File
@@ -14,14 +14,14 @@ plugins {
} }
android { android {
compileSdk = 35 compileSdk = 36
defaultConfig { defaultConfig {
applicationId = "com.geode.launcher" applicationId = "com.geode.launcher"
minSdk = 23 minSdk = 23
targetSdk = 35 targetSdk = 35
versionCode = 20 versionCode = 22
versionName = "1.5.0" versionName = "1.6.1"
vectorDrawables { vectorDrawables {
useSupportLibrary = true useSupportLibrary = true
@@ -30,7 +30,7 @@ android {
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
externalNativeBuild { externalNativeBuild {
cmake { cmake {
arguments("-DUSE_TULIPHOOK:BOOL=OFF", "-DANDROID_STL=c++_shared") arguments("-DUSE_TULIPHOOK:BOOL=OFF", "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES:BOOL=OFF")
} }
} }
@@ -82,32 +82,34 @@ android {
} }
externalNativeBuild { externalNativeBuild {
cmake { cmake {
version = "3.25.0+"
path = file("src/main/cpp/CMakeLists.txt") path = file("src/main/cpp/CMakeLists.txt")
} }
} }
namespace = "com.geode.launcher" namespace = "com.geode.launcher"
ndkVersion = "27.2.12479018" ndkVersion = "28.1.13356709"
} }
dependencies { dependencies {
implementation (platform("androidx.compose:compose-bom:$composeBOM")) implementation (platform("androidx.compose:compose-bom:$composeBOM"))
implementation ("androidx.core:core-ktx:1.15.0") implementation ("androidx.core:core-ktx:1.16.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.8.7") implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7") implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation ("androidx.activity:activity-compose:1.10.0") implementation ("androidx.activity:activity-compose:1.10.1")
implementation ("androidx.activity:activity-ktx:1.10.0") implementation ("androidx.activity:activity-ktx:1.10.1")
implementation ("androidx.appcompat:appcompat:1.7.0") implementation ("androidx.appcompat:appcompat:1.7.0")
implementation ("androidx.documentfile:documentfile:1.0.1") implementation ("androidx.documentfile:documentfile:1.0.1")
implementation ("com.squareup.okio:okio:3.10.2") implementation ("com.squareup.okio:okio:3.11.0")
implementation ("com.squareup.okhttp3:okhttp:4.12.0") implementation ("com.squareup.okhttp3:okhttp:4.12.0")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0") implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.8.0") implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json-okio:1.8.1")
implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") implementation ("org.jetbrains.kotlinx:kotlinx-datetime:0.6.2")
implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.30.0") implementation ("com.mikepenz:multiplatform-markdown-renderer-android:0.33.0")
implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.30.0") implementation ("com.mikepenz:multiplatform-markdown-renderer-m3:0.33.0")
implementation ("androidx.browser:browser:1.8.0")
debugImplementation ("androidx.compose.ui:ui-tooling") debugImplementation ("androidx.compose.ui:ui-tooling")
coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.4") coreLibraryDesugaring ("com.android.tools:desugar_jdk_libs:2.1.5")
} }
+2 -1
View File
@@ -39,8 +39,9 @@
android:label="@string/app_name" android:label="@string/app_name"
android:hasFragileUserData="true" android:hasFragileUserData="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.GeodeLauncher" android:theme="@style/Theme.GeodeLauncher"
tools:targetApi="31"> tools:targetApi="35">
<activity <activity
android:name=".AltMainActivity" android:name=".AltMainActivity"
android:exported="false" android:exported="false"
+3 -2
View File
@@ -13,7 +13,7 @@ option(USE_TULIPHOOK "enables choice of hooking library" OFF)
if (USE_TULIPHOOK) if (USE_TULIPHOOK)
FetchContent_Declare(TulipHook FetchContent_Declare(TulipHook
GIT_REPOSITORY https://github.com/geode-sdk/TulipHook.git GIT_REPOSITORY https://github.com/geode-sdk/TulipHook.git
GIT_TAG 0021301fcea265416fa8eb5372694931c1d410cb GIT_TAG 9aa1f95091ebac18c657d57b755f59d00fe6f1f8
) )
set(TULIP_LINK_SOURCE ON CACHE INTERNAL "") set(TULIP_LINK_SOURCE ON CACHE INTERNAL "")
@@ -24,8 +24,9 @@ if (USE_TULIPHOOK)
target_link_libraries(launcherfix PRIVATE TulipHook TulipHookInclude) target_link_libraries(launcherfix PRIVATE TulipHook TulipHookInclude)
else() else()
FetchContent_Declare(dobby FetchContent_Declare(dobby
# this is the last version that builds on android at all
GIT_REPOSITORY https://github.com/jmpews/Dobby GIT_REPOSITORY https://github.com/jmpews/Dobby
GIT_TAG 6a80a61ea062424d1595613058a174c64454fa57 GIT_TAG 0932d69c320e786672361ab53825ba8f4245e9d3
) )
FetchContent_GetProperties(dobby) FetchContent_GetProperties(dobby)
+1 -1
View File
@@ -109,7 +109,7 @@ bool patch_symbol(std::uint32_t* hash_table, char* str_table, ElfW(Sym)* sym_tab
// we found it! now go rename the symbol // we found it! now go rename the symbol
#ifdef USE_TULIPHOOK #ifdef USE_TULIPHOOK
auto res = tulip::hook::writeMemory(sym_name, new_symbol.data(), new_symbol.size()); auto res = tulip::hook::writeMemory(sym_name, SYMBOL_PATCH_VALUE.data(), SYMBOL_PATCH_VALUE.size());
if (!res) { if (!res) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "failed to patch symbol: %s", res.unwrapErr().c_str()); __android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "failed to patch symbol: %s", res.unwrapErr().c_str());
return false; return false;
@@ -14,8 +14,10 @@ import android.util.Log
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatEditText
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@@ -31,7 +33,6 @@ import com.geode.launcher.utils.GamePackageUtils
import com.geode.launcher.utils.GeodeUtils import com.geode.launcher.utils.GeodeUtils
import com.geode.launcher.utils.LaunchUtils import com.geode.launcher.utils.LaunchUtils
import com.geode.launcher.utils.PreferenceUtils import com.geode.launcher.utils.PreferenceUtils
import org.cocos2dx.lib.Cocos2dxEditText
import org.cocos2dx.lib.Cocos2dxGLSurfaceView import org.cocos2dx.lib.Cocos2dxGLSurfaceView
import org.cocos2dx.lib.Cocos2dxHelper import org.cocos2dx.lib.Cocos2dxHelper
import org.cocos2dx.lib.Cocos2dxRenderer import org.cocos2dx.lib.Cocos2dxRenderer
@@ -70,7 +71,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
private var mHasWindowFocus = false private var mHasWindowFocus = false
private var displayMode = DisplayMode.DEFAULT private var displayMode = DisplayMode.DEFAULT
private var mChosenDisplayRate: Float? = null private var mForceRefreshRate = false
private var mLimitedRefreshRate: Int? = null private var mLimitedRefreshRate: Int? = null
private var mAspectRatio = 0.0f private var mAspectRatio = 0.0f
private var mScreenZoom = 1.0f private var mScreenZoom = 1.0f
@@ -121,6 +122,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
return return
} }
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
mGLSurfaceView?.sendKeyBack()
}
})
mGLSurfaceView?.manualBackEvents = true
} }
private fun createVersionFile() { private fun createVersionFile() {
@@ -398,7 +406,7 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT ViewGroup.LayoutParams.WRAP_CONTENT
) )
val editText = Cocos2dxEditText(this) val editText = AppCompatEditText(this)
editText.layoutParams = editTextLayoutParams editText.layoutParams = editTextLayoutParams
frameLayout.addView(editText) frameLayout.addView(editText)
@@ -428,14 +436,14 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val renderer = Cocos2dxRenderer(glSurfaceView) val renderer = Cocos2dxRenderer(glSurfaceView)
glSurfaceView.setCocos2dxRenderer(renderer) glSurfaceView.setCocos2dxRenderer(renderer)
renderer.setFrameRate = mChosenDisplayRate renderer.setFrameRate = mForceRefreshRate
val frameRate = mLimitedRefreshRate val frameRate = mLimitedRefreshRate
if (frameRate != null) { if (frameRate != null) {
renderer.limitFrameRate(frameRate) renderer.limitFrameRate(frameRate)
} }
editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
glSurfaceView.cocos2dxEditText = editText glSurfaceView.cocos2dxEditText = editText
return frameLayout return frameLayout
@@ -457,20 +465,24 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
val limitedRefreshRate = preferenceUtils.getInt(PreferenceUtils.Key.LIMIT_FRAME_RATE).takeIf { it != 0 } val limitedRefreshRate = preferenceUtils.getInt(PreferenceUtils.Key.LIMIT_FRAME_RATE).takeIf { it != 0 }
mLimitedRefreshRate = limitedRefreshRate mLimitedRefreshRate = limitedRefreshRate
val forceRefreshRate = preferenceUtils.getBoolean(PreferenceUtils.Key.FORCE_HRR) mForceRefreshRate = preferenceUtils.getBoolean(PreferenceUtils.Key.FORCE_HRR)
if (forceRefreshRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (mForceRefreshRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
var chosenDisplay = display.supportedModes?.maxByOrNull { it.refreshRate } val chosenDisplay = display.supportedModes?.maxByOrNull { it.refreshRate }
if (chosenDisplay != null) { if (chosenDisplay != null) {
var chosenRefreshRate = chosenDisplay.refreshRate var chosenRefreshRate = chosenDisplay.refreshRate
println("Forcing a refresh rate of $chosenRefreshRate (display ${chosenDisplay.modeId})") println("setupUIState: Forcing a refresh rate of $chosenRefreshRate (display ${chosenDisplay.modeId})")
window.attributes.preferredRefreshRate = chosenRefreshRate window.attributes.preferredRefreshRate = chosenRefreshRate
mChosenDisplayRate = chosenRefreshRate } else {
println("setupUIState could not find a display to select refresh rate of...")
} }
} }
if (displayMode == DisplayMode.FULLSCREEN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (displayMode == DisplayMode.FULLSCREEN && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
GeodeUtils.handleSafeArea = true
} else {
GeodeUtils.handleSafeArea = false
} }
hideSystemUi() hideSystemUi()
@@ -576,6 +588,10 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
mGLSurfaceView?.sendTimestampEvents = true mGLSurfaceView?.sendTimestampEvents = true
true true
} }
GeodeUtils.CAPABILITY_INTERNAL_CALLBACKS -> {
mGLSurfaceView?.sendInternalTimestampEvents = true
true
}
else -> false else -> false
} }
} }
@@ -58,9 +58,9 @@ import com.geode.launcher.utils.LaunchUtils
import com.geode.launcher.utils.PreferenceUtils import com.geode.launcher.utils.PreferenceUtils
import com.mikepenz.markdown.compose.LocalBulletListHandler import com.mikepenz.markdown.compose.LocalBulletListHandler
import com.mikepenz.markdown.m3.Markdown import com.mikepenz.markdown.m3.Markdown
import com.mikepenz.markdown.m3.markdownColor
import com.mikepenz.markdown.m3.markdownTypography import com.mikepenz.markdown.m3.markdownTypography
import kotlinx.datetime.toJavaInstant import kotlinx.datetime.toJavaInstant
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
import java.io.File import java.io.File
import java.net.ConnectException import java.net.ConnectException
import java.net.UnknownHostException import java.net.UnknownHostException
@@ -191,17 +191,17 @@ fun LauncherUpdateInformation(onDismiss: () -> Unit) {
style = Typography.titleLarge style = Typography.titleLarge
) )
CompositionLocalProvider(LocalBulletListHandler provides { _, _, _ -> "•  " }) { // every new release of the markdown library adds another arg to it
CompositionLocalProvider(LocalBulletListHandler provides { _, _, _, _, _ -> "•  " }) {
Markdown( Markdown(
content = nextRelease.release.body.replace("\r", ""), content = nextRelease.release.body.replace("\r", ""),
colors = markdownColor(
linkText = MaterialTheme.colorScheme.primary
),
typography = markdownTypography( typography = markdownTypography(
link = MaterialTheme.typography.bodyLarge.copy( link = MaterialTheme.typography.bodyLarge.copy(
textDecoration = TextDecoration.Underline textDecoration = TextDecoration.Underline,
) color = MaterialTheme.colorScheme.primary,
),
), ),
flavour = GFMFlavourDescriptor(),
) )
} }
} }
@@ -370,10 +370,13 @@ fun UpdateCard(releaseViewModel: ReleaseViewModel, modifier: Modifier = Modifier
} }
UpdateProgressIndicator( UpdateProgressIndicator(
stringResource( if (outOf != null) stringResource(
R.string.release_fetch_downloading, R.string.release_fetch_downloading,
downloaded,
outOf
) else stringResource(
R.string.release_fetch_downloading_indeterminate,
downloaded, downloaded,
outOf ?: "…"
), ),
modifier = modifier, modifier = modifier,
releaseViewModel = releaseViewModel, releaseViewModel = releaseViewModel,
@@ -146,6 +146,11 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
} }
OptionsGroup(title = stringResource(R.string.preference_category_updater)) { OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
SettingsCard(
title = stringResource(R.string.preference_use_index_update_api),
preferenceKey = PreferenceUtils.Key.USE_INDEX_API
)
SettingsSelectCard( SettingsSelectCard(
title = stringResource(R.string.preference_release_channel_tag_name), title = stringResource(R.string.preference_release_channel_tag_name),
dialogTitle = stringResource(R.string.preference_release_channel_select), dialogTitle = stringResource(R.string.preference_release_channel_select),
@@ -120,12 +120,24 @@ class ReleaseManager private constructor(
} }
val gameVersion = GamePackageUtils.getGameVersionCode(applicationContext.packageManager) val gameVersion = GamePackageUtils.getGameVersionCode(applicationContext.packageManager)
val targetTag = getBestReleaseForGameVersion(gameVersion) ?: return null val preferences = PreferenceUtils.get(applicationContext)
return when (targetTag) { if (preferences.getBoolean(PreferenceUtils.Key.USE_INDEX_API)) {
TAG_LATEST -> releaseRepository.getLatestGeodeRelease() val targetTag = preferences.getInt(PreferenceUtils.Key.RELEASE_CHANNEL_TAG)
TAG_BETA -> releaseRepository.getLatestGeodePreRelease()
else -> releaseRepository.getReleaseByTag(targetTag) return when (targetTag) {
2 -> releaseRepository.getReleaseByTag("nightly")
1 -> releaseRepository.getLatestIndexRelease(gameVersion, true)
else -> releaseRepository.getLatestIndexRelease(gameVersion, false)
}
} else {
val targetTag = getBestReleaseForGameVersion(gameVersion) ?: return null
return when (targetTag) {
TAG_LATEST -> releaseRepository.getLatestGeodeRelease()
TAG_BETA -> releaseRepository.getLatestGeodePreRelease()
else -> releaseRepository.getReleaseByTag(targetTag)
}
} }
} }
@@ -169,6 +181,10 @@ class ReleaseManager private constructor(
// set an initial download size // set an initial download size
_uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size) _uiState.value = ReleaseManagerState.InDownload(0, releaseAsset.size)
val outputFile = getTempFile()
// clone the file instance as renameTo may move the original file
val tempFilePath = outputFile.path
try { try {
val fileStream = DownloadUtils.downloadStream( val fileStream = DownloadUtils.downloadStream(
httpClient, httpClient,
@@ -177,23 +193,30 @@ class ReleaseManager private constructor(
_uiState.value = ReleaseManagerState.InDownload(progress, outOf) _uiState.value = ReleaseManagerState.InDownload(progress, outOf)
} }
outputFile.parentFile?.mkdirs()
val geodeFile = getGeodeOutputPath() val geodeFile = getGeodeOutputPath()
DownloadUtils.extractFileFromZipStream(
fileStream,
outputFile.outputStream(),
geodeFile.name
)
// work around a permission issue from adb push // work around a permission issue from adb push
if (geodeFile.exists()) { if (geodeFile.exists()) {
geodeFile.delete() geodeFile.delete()
} }
geodeFile.parentFile?.mkdirs() outputFile.renameTo(geodeFile)
DownloadUtils.extractFileFromZipStream(
fileStream,
geodeFile.outputStream(),
geodeFile.name
)
} catch (e: Exception) { } catch (e: Exception) {
sendError(e) sendError(e)
return return
} finally {
val tempFileClone = File(tempFilePath)
try {
if (tempFileClone.exists()) tempFileClone.delete()
} catch (_: Exception) { }
} }
downloadLauncherUpdateIfNecessary() downloadLauncherUpdateIfNecessary()
@@ -313,6 +336,15 @@ class ReleaseManager private constructor(
return File(geodeDirectory, geodeName) return File(geodeDirectory, geodeName)
} }
private fun getTempFile(): File {
val geodeName = LaunchUtils.geodeFilename
val geodeDirectory = LaunchUtils.getBaseDirectory(applicationContext)
val tempFile = File.createTempFile(geodeName, null, geodeDirectory)
return tempFile
}
/** /**
* Cancels the current update job. * Cancels the current update job.
*/ */
@@ -165,10 +165,10 @@ class ReleaseRepository(private val httpClient: OkHttpClient) {
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
suspend fun getLatestIndexRelease(gd: String, prerelease: Boolean = false): DownloadableLoaderRelease? { suspend fun getLatestIndexRelease(gd: Long, prerelease: Boolean = false): DownloadableLoaderRelease? {
val url = "$GEODE_API_BASE/loader/versions/latest".toHttpUrlOrNull()!! val url = "$GEODE_API_BASE/loader/versions/latest".toHttpUrlOrNull()!!
.newBuilder() .newBuilder()
.addQueryParameter("gd", gd) .addQueryParameter("gd", gd.toString())
.addQueryParameter("platform", "android") .addQueryParameter("platform", "android")
.addQueryParameter("prerelease", prerelease.toString()) .addQueryParameter("prerelease", prerelease.toString())
.build() .build()
@@ -21,7 +21,9 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import com.geode.launcher.BuildConfig import com.geode.launcher.BuildConfig
import com.geode.launcher.R import com.geode.launcher.R
@@ -29,6 +31,7 @@ import com.geode.launcher.UserDirectoryProvider
import com.geode.launcher.activityresult.GeodeOpenFileActivityResult import com.geode.launcher.activityresult.GeodeOpenFileActivityResult
import com.geode.launcher.activityresult.GeodeOpenFilesActivityResult import com.geode.launcher.activityresult.GeodeOpenFilesActivityResult
import com.geode.launcher.activityresult.GeodeSaveFileActivityResult import com.geode.launcher.activityresult.GeodeSaveFileActivityResult
import org.cocos2dx.lib.Cocos2dxGLSurfaceView
import java.io.File import java.io.File
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.system.exitProcess import kotlin.system.exitProcess
@@ -37,6 +40,8 @@ import kotlin.system.exitProcess
@Suppress("unused", "KotlinJniMissingFunction") @Suppress("unused", "KotlinJniMissingFunction")
object GeodeUtils { object GeodeUtils {
private lateinit var activity: WeakReference<AppCompatActivity> private lateinit var activity: WeakReference<AppCompatActivity>
var handleSafeArea: Boolean = false
private lateinit var openFileResultLauncher: ActivityResultLauncher<GeodeOpenFileActivityResult.OpenFileParams> private lateinit var openFileResultLauncher: ActivityResultLauncher<GeodeOpenFileActivityResult.OpenFileParams>
private lateinit var openDirectoryResultLauncher: ActivityResultLauncher<Uri?> private lateinit var openDirectoryResultLauncher: ActivityResultLauncher<Uri?>
private lateinit var openFilesResultLauncher: ActivityResultLauncher<GeodeOpenFilesActivityResult.OpenFileParams> private lateinit var openFilesResultLauncher: ActivityResultLauncher<GeodeOpenFilesActivityResult.OpenFileParams>
@@ -386,6 +391,9 @@ object GeodeUtils {
return "" return ""
} }
/**
* Returns the current version code of the launcher.
*/
@JvmStatic @JvmStatic
fun getLauncherVersion(): Int { fun getLauncherVersion(): Int {
return BuildConfig.VERSION_CODE return BuildConfig.VERSION_CODE
@@ -457,9 +465,16 @@ object GeodeUtils {
} }
// vibration // vibration
/**
* If vibration is supported on the device.
*/
@JvmStatic @JvmStatic
fun vibrateSupported(): Boolean = getVibrator()?.hasVibrator() == true fun vibrateSupported(): Boolean = getVibrator()?.hasVibrator() == true
/**
* Vibrates for the given amount of time.
*/
@JvmStatic @JvmStatic
fun vibrate(ms: Long) { fun vibrate(ms: Long) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -470,6 +485,10 @@ object GeodeUtils {
} }
} }
/**
* Vibrates with the given pattern.
* @see android.os.VibrationEffect.createWaveform
*/
@JvmStatic @JvmStatic
fun vibratePattern(pattern: LongArray, repeat: Int) { fun vibratePattern(pattern: LongArray, repeat: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -480,6 +499,19 @@ object GeodeUtils {
} }
} }
@JvmStatic
fun getScreenInsets(): IntArray? {
if (!handleSafeArea || Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return null
}
return activity.get()?.run {
val displayCutout = window.decorView.rootWindowInsets?.displayCutout ?: return null
intArrayOf(displayCutout.safeInsetLeft, displayCutout.safeInsetBottom, displayCutout.safeInsetRight, displayCutout.safeInsetTop)
}
}
const val ARGUMENT_SAFE_MODE = "--geode:safe-mode" const val ARGUMENT_SAFE_MODE = "--geode:safe-mode"
private var additionalLaunchArguments = arrayListOf<String>() private var additionalLaunchArguments = arrayListOf<String>()
@@ -511,6 +543,7 @@ object GeodeUtils {
const val CAPABILITY_EXTENDED_INPUT = "extended_input" const val CAPABILITY_EXTENDED_INPUT = "extended_input"
const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs" const val CAPABILITY_TIMESTAMP_INPUT = "timestamp_inputs"
const val CAPABILITY_INTERNAL_CALLBACKS = "internal_callbacks_v1"
private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null) private var capabilityListener: WeakReference<CapabilityListener?> = WeakReference(null)
@@ -518,6 +551,10 @@ object GeodeUtils {
capabilityListener = WeakReference(listener) capabilityListener = WeakReference(listener)
} }
/**
* Reports that the game client supports a feature.
* These features typically require callbacks into the game, so ensure that the required methods are registered first.
*/
@JvmStatic @JvmStatic
fun reportPlatformCapability(capability: String?): Boolean { fun reportPlatformCapability(capability: String?): Boolean {
if (capability.isNullOrEmpty()) { if (capability.isNullOrEmpty()) {
@@ -527,11 +564,30 @@ object GeodeUtils {
return capabilityListener.get()?.onCapabilityAdded(capability) == true return capabilityListener.get()?.onCapabilityAdded(capability) == true
} }
@JvmStatic
fun openWebview(url: String) {
val activity = activity.get() ?: return
CustomTabsIntent.Builder()
.setShowTitle(true)
.build()
.launchUrl(activity, url.toUri())
}
@JvmStatic
fun openMultilineIMEKeyboard() {
Cocos2dxGLSurfaceView.openMultilineIMEKeyboard()
}
external fun nativeKeyUp(keyCode: Int, modifiers: Int) external fun nativeKeyUp(keyCode: Int, modifiers: Int)
external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean) external fun nativeKeyDown(keyCode: Int, modifiers: Int, isRepeating: Boolean)
external fun nativeActionScroll(scrollX: Float, scrollY: Float) external fun nativeActionScroll(scrollX: Float, scrollY: Float)
external fun resizeSurface(width: Int, height: Int) external fun resizeSurface(width: Int, height: Int)
// represents the timestamp of the next input callback, in nanoseconds (most events don't send it, but it's there) /**
* Gives the timestamp of the next input callback, in nanoseconds.
* Enable this by sending the "timestamp_inputs" platform capability.
* @see reportPlatformCapability
*/
external fun setNextInputTimestamp(timestamp: Long) external fun setNextInputTimestamp(timestamp: Long)
external fun setNextInputTimestampInternal(timestamp: Long)
} }
@@ -147,13 +147,15 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
CUSTOM_ASPECT_RATIO, CUSTOM_ASPECT_RATIO,
SCREEN_ZOOM, SCREEN_ZOOM,
SCREEN_ZOOM_FIT, SCREEN_ZOOM_FIT,
LIMIT_FRAME_RATE LIMIT_FRAME_RATE,
USE_INDEX_API,
} }
private fun defaultValueForBooleanKey(key: Key): Boolean { private fun defaultValueForBooleanKey(key: Key): Boolean {
return when (key) { return when (key) {
Key.UPDATE_AUTOMATICALLY, Key.FORCE_HRR -> true Key.UPDATE_AUTOMATICALLY, Key.FORCE_HRR -> true
Key.DEVELOPER_MODE -> BuildConfig.DEBUG Key.DEVELOPER_MODE -> BuildConfig.DEBUG
Key.USE_INDEX_API -> true
else -> false else -> false
} }
} }
@@ -195,6 +197,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
Key.SCREEN_ZOOM -> "PreferenceScreenZoom" Key.SCREEN_ZOOM -> "PreferenceScreenZoom"
Key.SCREEN_ZOOM_FIT -> "PreferenceScreenZoomFit" Key.SCREEN_ZOOM_FIT -> "PreferenceScreenZoomFit"
Key.LIMIT_FRAME_RATE -> "PreferenceLimitFrameRate" Key.LIMIT_FRAME_RATE -> "PreferenceLimitFrameRate"
Key.USE_INDEX_API -> "PreferenceUseIndexApi"
} }
} }
@@ -1,18 +0,0 @@
package org.cocos2dx.lib
import android.content.Context
import android.view.KeyEvent
import androidx.appcompat.widget.AppCompatEditText
class Cocos2dxEditText(context: Context) : AppCompatEditText(context) {
var cocos2dxGLSurfaceView: Cocos2dxGLSurfaceView? = null
override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean {
super.onKeyDown(keyCode, keyEvent)
if (keyCode != KeyEvent.KEYCODE_BACK) {
return true
}
cocos2dxGLSurfaceView?.requestFocus()
return true
}
}
@@ -1,22 +1,27 @@
package org.cocos2dx.lib package org.cocos2dx.lib
import android.app.Activity
import android.content.Context import android.content.Context
import android.opengl.GLSurfaceView import android.opengl.GLSurfaceView
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.text.InputType
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.Surface import android.view.Surface
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.AppCompatEditText
import com.customRobTop.BaseRobTopActivity import com.customRobTop.BaseRobTopActivity
import com.geode.launcher.utils.GeodeUtils import com.geode.launcher.utils.GeodeUtils
private const val HANDLER_OPEN_IME_KEYBOARD = 2 private const val HANDLER_OPEN_IME_KEYBOARD = 2
private const val HANDLER_CLOSE_IME_KEYBOARD = 3 private const val HANDLER_CLOSE_IME_KEYBOARD = 3
private const val HANDLER_ARG_MULTILINE = 1
private const val HANDLER_ARG_SINGLELINE = 0
private const val MS_TO_NS = 1_000_000 private const val MS_TO_NS = 1_000_000
class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) { class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
@@ -25,11 +30,22 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
private lateinit var handler: Handler private lateinit var handler: Handler
private lateinit var cocos2dxTextInputWrapper: Cocos2dxTextInputWrapper private lateinit var cocos2dxTextInputWrapper: Cocos2dxTextInputWrapper
fun openMultilineIMEKeyboard() {
val msg = Message().apply {
what = HANDLER_OPEN_IME_KEYBOARD
obj = cocos2dxGLSurfaceView.getContentText()
arg1 = HANDLER_ARG_MULTILINE
}
handler.sendMessage(msg)
}
@JvmStatic @JvmStatic
fun openIMEKeyboard() { fun openIMEKeyboard() {
val msg = Message() val msg = Message().apply {
msg.what = HANDLER_OPEN_IME_KEYBOARD what = HANDLER_OPEN_IME_KEYBOARD
msg.obj = cocos2dxGLSurfaceView.getContentText() obj = cocos2dxGLSurfaceView.getContentText()
arg1 = HANDLER_ARG_SINGLELINE
}
handler.sendMessage(msg) handler.sendMessage(msg)
} }
@@ -54,16 +70,29 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
var sendTimestampEvents = false var sendTimestampEvents = false
var sendInternalTimestampEvents = false
var manualBackEvents = false
var cocos2dxEditText: Cocos2dxEditText? = null var cocos2dxEditText: AppCompatEditText? = null
set(value) { set(value) {
field = value field = value
field?.setOnEditorActionListener(cocos2dxTextInputWrapper) field?.setOnEditorActionListener(cocos2dxTextInputWrapper)
field?.cocos2dxGLSurfaceView = this
requestFocus() requestFocus()
} }
fun setKeyboardMultiline() {
val editText = cocos2dxEditText ?: return
editText.inputType = editText.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
editText.maxLines = Int.MAX_VALUE
}
fun setKeyboardSingleLine() {
val editText = cocos2dxEditText ?: return
editText.inputType = editText.inputType and InputType.TYPE_TEXT_FLAG_MULTI_LINE.inv()
editText.maxLines = 1
}
fun initView() { fun initView() {
setEGLContextClientVersion(2) setEGLContextClientVersion(2)
isFocusableInTouchMode = true isFocusableInTouchMode = true
@@ -75,6 +104,12 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
when (msg.what) { when (msg.what) {
HANDLER_OPEN_IME_KEYBOARD -> { HANDLER_OPEN_IME_KEYBOARD -> {
if (cocos2dxEditText?.requestFocus() == true) { if (cocos2dxEditText?.requestFocus() == true) {
if (msg.arg1 == HANDLER_ARG_MULTILINE) {
setKeyboardMultiline()
} else {
setKeyboardSingleLine()
}
cocos2dxEditText?.apply { cocos2dxEditText?.apply {
removeTextChangedListener( removeTextChangedListener(
cocos2dxTextInputWrapper cocos2dxTextInputWrapper
@@ -119,8 +154,21 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
@RequiresApi(Build.VERSION_CODES.R) @RequiresApi(Build.VERSION_CODES.R)
fun updateRefreshRate(refreshRate: Float) { fun updateRefreshRate() {
holder.surface.setFrameRate(refreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT) val chosenDisplay = display.supportedModes?.maxByOrNull { it.refreshRate }
if (chosenDisplay == null) {
println("updateRefreshRate failed to find a display to maximize refresh rate...")
return
}
val chosenRefreshRate = chosenDisplay.refreshRate
println("updateRefreshRate: selecting refresh rate of ${chosenDisplay.refreshRate} (display ${chosenDisplay.modeId})")
holder.surface.setFrameRate(chosenRefreshRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
if (isAttachedToWindow) {
(context as Activity).window.attributes.preferredRefreshRate = chosenRefreshRate
}
} }
override fun onPause() { override fun onPause() {
@@ -135,6 +183,14 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
queueEvent { cocos2dxRenderer.handleOnResume() } queueEvent { cocos2dxRenderer.handleOnResume() }
} }
private fun sendNextEventTimestamp(timestamp: Long) {
if (sendTimestampEvents)
GeodeUtils.setNextInputTimestamp(timestamp)
if (sendInternalTimestampEvents)
GeodeUtils.setNextInputTimestampInternal(timestamp)
}
override fun onTouchEvent(motionEvent: MotionEvent): Boolean { override fun onTouchEvent(motionEvent: MotionEvent): Boolean {
val pointerNumber = motionEvent.pointerCount val pointerNumber = motionEvent.pointerCount
val ids = IntArray(pointerNumber) val ids = IntArray(pointerNumber)
@@ -154,9 +210,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xDown = xs[0] val xDown = xs[0]
val f = ys[0] val f = ys[0]
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionDown(idDown, xDown, f) cocos2dxRenderer.handleActionDown(idDown, xDown, f)
} }
true true
@@ -166,27 +220,21 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val f2 = xs[0] val f2 = xs[0]
val f3 = ys[0] val f3 = ys[0]
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionUp(idUp, f2, f3) cocos2dxRenderer.handleActionUp(idUp, f2, f3)
} }
true true
} }
MotionEvent.ACTION_MOVE -> { MotionEvent.ACTION_MOVE -> {
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionMove(ids, xs, ys) cocos2dxRenderer.handleActionMove(ids, xs, ys)
} }
true true
} }
MotionEvent.ACTION_CANCEL -> { MotionEvent.ACTION_CANCEL -> {
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionCancel(ids, xs, ys) cocos2dxRenderer.handleActionCancel(ids, xs, ys)
} }
true true
@@ -197,9 +245,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xPointerDown = motionEvent.getX(indexPointerDown) val xPointerDown = motionEvent.getX(indexPointerDown)
val y = motionEvent.getY(indexPointerDown) val y = motionEvent.getY(indexPointerDown)
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, y) cocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, y)
} }
true true
@@ -210,9 +256,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
val xPointerUp = motionEvent.getX(indexPointUp) val xPointerUp = motionEvent.getX(indexPointUp)
val y2 = motionEvent.getY(indexPointUp) val y2 = motionEvent.getY(indexPointUp)
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
cocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, y2) cocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, y2)
} }
true true
@@ -245,6 +289,28 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
} }
fun sendKeyBack() {
if (cocos2dxEditText?.isFocused == true) {
requestFocus()
}
if (BaseRobTopActivity.blockBackButton) {
return
}
if (useKeyboardEvents) {
queueEvent {
cocos2dxRenderer.handleKeyDown(KeyEvent.KEYCODE_BACK)
}
} else {
val currentTime = System.nanoTime()
queueEvent {
sendNextEventTimestamp(currentTime)
GeodeUtils.nativeKeyDown(KeyEvent.KEYCODE_BACK, 0, false)
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (!useKeyboardEvents) { if (!useKeyboardEvents) {
return legacyKeyDown(keyCode, event) return legacyKeyDown(keyCode, event)
@@ -261,9 +327,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.setNextInputTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0) GeodeUtils.nativeKeyDown(keyCode, event.modifiers, event.repeatCount != 0)
} }
true true
@@ -286,9 +350,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
} }
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.setNextInputTimestamp(event.eventTime * MS_TO_NS)
GeodeUtils.nativeKeyUp(keyCode, event.modifiers) GeodeUtils.nativeKeyUp(keyCode, event.modifiers)
} }
@@ -309,9 +371,7 @@ class Cocos2dxGLSurfaceView(context: Context) : GLSurfaceView(context) {
event.eventTimeNanos else event.eventTime * MS_TO_NS event.eventTimeNanos else event.eventTime * MS_TO_NS
queueEvent { queueEvent {
if (sendTimestampEvents) sendNextEventTimestamp(timestamp)
GeodeUtils.setNextInputTimestamp(timestamp)
GeodeUtils.nativeActionScroll(scrollX, scrollY) GeodeUtils.nativeActionScroll(scrollX, scrollY)
} }
@@ -64,7 +64,7 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
private var screenWidth = 0 private var screenWidth = 0
private var screenHeight = 0 private var screenHeight = 0
var sendResizeEvents = false var sendResizeEvents = false
var setFrameRate: Float? = null var setFrameRate = false
private var mAnimationInterval: Long? = null private var mAnimationInterval: Long? = null
fun limitFrameRate(rate: Int) { fun limitFrameRate(rate: Int) {
@@ -82,9 +82,8 @@ class Cocos2dxRenderer(private var handler: Cocos2dxGLSurfaceView) : GLSurfaceVi
} }
override fun onSurfaceChanged(gl10: GL10?, width: Int, height: Int) { override fun onSurfaceChanged(gl10: GL10?, width: Int, height: Int) {
val chosenRefreshRate = setFrameRate if (setFrameRate && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (chosenRefreshRate != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { handler.updateRefreshRate()
handler.updateRefreshRate(chosenRefreshRate)
} }
println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps") println("renderer surfaceChanged: ${width}x${height}@${handler.display.refreshRate}fps")
@@ -5,6 +5,7 @@ import android.text.TextWatcher
import android.widget.TextView.OnEditorActionListener import android.widget.TextView.OnEditorActionListener
import android.text.Editable import android.text.Editable
import android.view.KeyEvent import android.view.KeyEvent
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.TextView import android.widget.TextView
@@ -47,22 +48,22 @@ class Cocos2dxTextInputWrapper(private val cocos2dxGLSurfaceView: Cocos2dxGLSurf
override fun onTextChanged(pCharSequence: CharSequence, start: Int, before: Int, count: Int) {} override fun onTextChanged(pCharSequence: CharSequence, start: Int, before: Int, count: Int) {}
override fun onEditorAction(textView: TextView, actionID: Int, keyEvent: KeyEvent?): Boolean { override fun onEditorAction(textView: TextView, actionID: Int, keyEvent: KeyEvent?): Boolean {
if (actionID != EditorInfo.IME_ACTION_DONE) {
return false
}
if (cocos2dxGLSurfaceView.cocos2dxEditText == textView && isFullScreenEdit) { if (cocos2dxGLSurfaceView.cocos2dxEditText == textView && isFullScreenEdit) {
for (i in originText!!.length downTo 1) { for (i in originText!!.length downTo 1) {
cocos2dxGLSurfaceView.deleteBackward() cocos2dxGLSurfaceView.deleteBackward()
} }
var text = textView.text.toString() var text = textView.text.toString()
if (text.compareTo("") == 0) { if (!text.endsWith('\n')) {
text = "\n" text += '\n'
}
if (10 != text[text.length - 1].code) {
text += 10.toChar()
} }
cocos2dxGLSurfaceView.insertText(text) cocos2dxGLSurfaceView.insertText(text)
} }
if (actionID != 6) {
return false
}
cocos2dxGLSurfaceView.requestFocus() cocos2dxGLSurfaceView.requestFocus()
Cocos2dxGLSurfaceView.closeIMEKeyboard() Cocos2dxGLSurfaceView.closeIMEKeyboard()
return false return false
+2
View File
@@ -84,6 +84,7 @@
<string name="release_fetch_in_progress">Checking for new releases…</string> <string name="release_fetch_in_progress">Checking for new releases…</string>
<string name="release_fetch_success">Geode updated!</string> <string name="release_fetch_success">Geode updated!</string>
<string name="release_fetch_downloading">Downloading update… (%1$s/%2$s)</string> <string name="release_fetch_downloading">Downloading update… (%1$s/%2$s)</string>
<string name="release_fetch_downloading_indeterminate">Downloading update… (%1$s)</string>
<string name="release_fetch_cancelling">Cancelling update…</string> <string name="release_fetch_cancelling">Cancelling update…</string>
<string name="release_fetch_no_internet">No internet found.</string> <string name="release_fetch_no_internet">No internet found.</string>
<string name="release_fetch_cancelled">Update cancelled.</string> <string name="release_fetch_cancelled">Update cancelled.</string>
@@ -254,4 +255,5 @@
<string name="preference_override_exceptions_name">Custom exceptions fix symbols</string> <string name="preference_override_exceptions_name">Custom exceptions fix symbols</string>
<string name="preference_override_exceptions_description">Uses exception_symbols.txt to patch</string> <string name="preference_override_exceptions_description">Uses exception_symbols.txt to patch</string>
<string name="preference_use_index_update_api">Use index release API</string>
</resources> </resources>
+6 -6
View File
@@ -1,14 +1,14 @@
buildscript { buildscript {
val composeBOM by extra("2025.01.00") val composeBOM by extra("2025.04.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 "8.8.0" apply false id("com.android.application") version "8.9.2" apply false
id("com.android.library") version "8.8.0" apply false id("com.android.library") version "8.9.2" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "2.1.20" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.0" apply false id("org.jetbrains.kotlin.plugin.serialization") version "2.1.20" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.1.20" apply false
} }
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
+2 -1
View File
@@ -1,6 +1,7 @@
#Thu Apr 24 10:54:01 MST 2025
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME